From b2a4d70ee22ce42cf51057b1d1d72f8038292a0a Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Thu, 16 Apr 2026 12:39:13 +0400 Subject: [PATCH 1/3] Deprecate direct AI connector step types in favor of ai.prompt Extend the step deprecation mechanism to support prefix-based matching, deprecating all inference.*, bedrock.*, gen-ai.*, and gemini.* step types. Users are confused by the availability of raw AI connector steps alongside the purpose-built ai.* steps; this hides them from autocomplete, the action menu, and AI agent discovery while keeping them functional for existing workflows. Closes elastic/security-team#16816 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../spec/deprecated_step_metadata.test.ts | 118 ++++++++++++++++++ .../spec/deprecated_step_metadata.ts | 29 ++++- .../workflows_management/common/schema.ts | 12 +- .../lib/get_action_options.test.ts | 12 +- .../lib/get_action_options.ts | 5 +- .../validate_deprecated_step_types.test.ts | 20 +++ .../lib/validate_deprecated_step_types.ts | 5 +- .../get_completion_item_provider.test.ts | 14 ++- .../get_completion_item_provider.ts | 13 +- .../tools/get_step_definitions_tool.test.ts | 1 + .../tools/get_step_definitions_tool.ts | 4 +- 11 files changed, 205 insertions(+), 28 deletions(-) create mode 100644 src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.test.ts diff --git a/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.test.ts b/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.test.ts new file mode 100644 index 0000000000000..078abe194667a --- /dev/null +++ b/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + DEPRECATED_STEP_METADATA, + DEPRECATED_STEP_PREFIX_METADATA, + getDeprecatedStepMessage, + getStepDeprecationInfo, + isDeprecatedStepType, +} from './deprecated_step_metadata'; + +describe('deprecated_step_metadata', () => { + describe('getStepDeprecationInfo', () => { + it('returns deprecation info for exact-match step types', () => { + const info = getStepDeprecationInfo('kibana.createCase'); + expect(info).toEqual({ replacementStepType: 'cases.createCase' }); + }); + + it('returns undefined for non-deprecated step types', () => { + expect(getStepDeprecationInfo('cases.createCase')).toBeUndefined(); + }); + + it('returns deprecation info for prefix-matched step types', () => { + expect(getStepDeprecationInfo('inference.completion')).toEqual({ + replacementStepType: 'ai.prompt', + }); + expect(getStepDeprecationInfo('bedrock.invokeAI')).toEqual({ + replacementStepType: 'ai.prompt', + }); + expect(getStepDeprecationInfo('gen-ai.run')).toEqual({ + replacementStepType: 'ai.prompt', + }); + expect(getStepDeprecationInfo('gemini.invokeStream')).toEqual({ + replacementStepType: 'ai.prompt', + }); + }); + + it('does not match prefixes without the trailing dot', () => { + // 'inference' alone should not match the 'inference.' prefix + expect(getStepDeprecationInfo('inference')).toBeUndefined(); + expect(getStepDeprecationInfo('bedrock')).toBeUndefined(); + }); + + it('prioritizes exact match over prefix match', () => { + // Verify all exact-match entries are still returned correctly + for (const [stepType, info] of Object.entries(DEPRECATED_STEP_METADATA)) { + expect(getStepDeprecationInfo(stepType)).toEqual(info); + } + }); + + it('does not deprecate unrelated step types', () => { + expect(getStepDeprecationInfo('http')).toBeUndefined(); + expect(getStepDeprecationInfo('ai.prompt')).toBeUndefined(); + expect(getStepDeprecationInfo('elasticsearch.search')).toBeUndefined(); + }); + }); + + describe('isDeprecatedStepType', () => { + it('returns true for exact-match deprecated step types', () => { + expect(isDeprecatedStepType('kibana.createCase')).toBe(true); + }); + + it('returns true for prefix-matched deprecated step types', () => { + expect(isDeprecatedStepType('inference.completion')).toBe(true); + expect(isDeprecatedStepType('gen-ai.invokeAI')).toBe(true); + }); + + it('returns false for non-deprecated step types', () => { + expect(isDeprecatedStepType('ai.prompt')).toBe(false); + expect(isDeprecatedStepType('http')).toBe(false); + }); + }); + + describe('getDeprecatedStepMessage', () => { + it('formats message with replacement step type', () => { + const message = getDeprecatedStepMessage('inference.completion', { + replacementStepType: 'ai.prompt', + }); + expect(message).toBe( + 'Step type "inference.completion" is deprecated. Use "ai.prompt" instead.' + ); + }); + + it('uses custom message when provided', () => { + const message = getDeprecatedStepMessage('old.step', { + message: 'Custom deprecation notice.', + }); + expect(message).toBe('Custom deprecation notice.'); + }); + + it('falls back to generic message when no replacement', () => { + const message = getDeprecatedStepMessage('old.step', {}); + expect(message).toBe('Step type "old.step" is deprecated.'); + }); + }); + + describe('DEPRECATED_STEP_PREFIX_METADATA', () => { + it('includes all AI connector prefixes', () => { + const prefixes = DEPRECATED_STEP_PREFIX_METADATA.map((entry) => entry.prefix); + expect(prefixes).toContain('inference.'); + expect(prefixes).toContain('bedrock.'); + expect(prefixes).toContain('gen-ai.'); + expect(prefixes).toContain('gemini.'); + }); + + it('all prefix entries suggest ai.prompt as replacement', () => { + for (const entry of DEPRECATED_STEP_PREFIX_METADATA) { + expect(entry.deprecation.replacementStepType).toBe('ai.prompt'); + } + }); + }); +}); diff --git a/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts b/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts index f852068b68961..3912b102b16ba 100644 --- a/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts +++ b/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts @@ -12,6 +12,12 @@ export interface StepDeprecationInfo { message?: string; } +export interface StepPrefixDeprecationInfo { + /** Prefix to match against step type (e.g., 'inference.' matches 'inference.completion') */ + prefix: string; + deprecation: StepDeprecationInfo; +} + export const DEPRECATED_STEP_METADATA: Record = { 'kibana.createCase': { replacementStepType: 'cases.createCase', @@ -39,8 +45,29 @@ export const DEPRECATED_STEP_METADATA: Record = { }, }; +/** + * Prefix-based deprecation for step types. Any step type starting with one of these + * prefixes is treated as deprecated. This avoids enumerating every sub-action when an + * entire connector family is superseded by a purpose-built step. + */ +export const DEPRECATED_STEP_PREFIX_METADATA: StepPrefixDeprecationInfo[] = [ + { prefix: 'inference.', deprecation: { replacementStepType: 'ai.prompt' } }, + { prefix: 'bedrock.', deprecation: { replacementStepType: 'ai.prompt' } }, + { prefix: 'gen-ai.', deprecation: { replacementStepType: 'ai.prompt' } }, + { prefix: 'gemini.', deprecation: { replacementStepType: 'ai.prompt' } }, +]; + export function getStepDeprecationInfo(stepType: string): StepDeprecationInfo | undefined { - return DEPRECATED_STEP_METADATA[stepType]; + const exact = DEPRECATED_STEP_METADATA[stepType]; + if (exact) { + return exact; + } + for (const { prefix, deprecation } of DEPRECATED_STEP_PREFIX_METADATA) { + if (stepType.startsWith(prefix)) { + return deprecation; + } + } + return undefined; } export function isDeprecatedStepType(stepType: string): boolean { diff --git a/src/platform/plugins/shared/workflows_management/common/schema.ts b/src/platform/plugins/shared/workflows_management/common/schema.ts index 78e0cf9657693..9e280bec62de5 100644 --- a/src/platform/plugins/shared/workflows_management/common/schema.ts +++ b/src/platform/plugins/shared/workflows_management/common/schema.ts @@ -17,6 +17,7 @@ import type { import { builtInStepDefinitions, DEPRECATED_STEP_METADATA, + DEPRECATED_STEP_PREFIX_METADATA, generateYamlSchemaFromConnectors, getElasticsearchConnectors, getKibanaConnectors, @@ -375,7 +376,16 @@ export function getDeprecatedStepMetadataMap(): Readonly ({ getAllConnectors: jest.fn(), - getDeprecatedStepMetadataMap: jest.fn(() => ({})), + getDeprecatedStepMetadata: jest.fn(() => undefined), })); jest.mock('../../../trigger_schemas', () => ({ triggerSchemas: { getTriggerDefinitions: jest.fn(() => []) }, @@ -60,7 +60,7 @@ describe('getActionOptions', () => { mockWorkflowsExtensions = workflowsExtensionsMock.createStart(); (getAllConnectors as jest.Mock).mockReturnValue([]); - (getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({}); + (getDeprecatedStepMetadata as jest.Mock).mockReturnValue(undefined); (isDynamicConnector as jest.MockedFunction).mockImplementation( () => false ); @@ -346,9 +346,9 @@ describe('getActionOptions', () => { }; (getAllConnectors as jest.Mock).mockReturnValue([mockConnector]); - (getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({ - 'kibana.createCase': { replacementStepType: 'cases.createCase' }, - }); + (getDeprecatedStepMetadata as jest.Mock).mockImplementation((stepType: string) => + stepType === 'kibana.createCase' ? { replacementStepType: 'cases.createCase' } : undefined + ); mockWorkflowsExtensions.getStepDefinition.mockReturnValue(undefined); const result = getActionOptions(mockEuiTheme, mockWorkflowsExtensions); diff --git a/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts b/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts index 896518443be9c..fa500c49ede16 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts @@ -12,7 +12,7 @@ import { AssistantIcon } from '@kbn/ai-assistant-icon'; import { i18n } from '@kbn/i18n'; import { getBuiltInStepDefinition, isDynamicConnector, StepCategory } from '@kbn/workflows'; import type { WorkflowsExtensionsPublicPluginStart } from '@kbn/workflows-extensions/public'; -import { getAllConnectors, getDeprecatedStepMetadataMap } from '../../../../common/schema'; +import { getAllConnectors, getDeprecatedStepMetadata } from '../../../../common/schema'; import { getStepIconType } from '../../../shared/ui/step_icons/get_step_icon_type'; import { triggerSchemas } from '../../../trigger_schemas'; import type { ActionConnectorGroup, ActionGroup, ActionOptionData } from '../types'; @@ -23,7 +23,6 @@ export function getActionOptions( workflowsExtensions: WorkflowsExtensionsPublicPluginStart ): ActionOptionData[] { const connectors = getAllConnectors(); - const deprecatedStepMetadata = getDeprecatedStepMetadataMap(); const builtInTriggerOptions: ActionOptionData[] = [ { id: 'manual', @@ -267,7 +266,7 @@ export function getActionOptions( const baseTypeInstancesCount: Record = {}; for (const connector of connectors) { - if (!deprecatedStepMetadata[connector.type]) { + if (!getDeprecatedStepMetadata(connector.type)) { const customStepDefinition = workflowsExtensions.getStepDefinition(connector.type); if (customStepDefinition) { const group = stepGroups[customStepDefinition.category]; diff --git a/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.test.ts b/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.test.ts index f592cf22eccd2..9460012d1d8a3 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.test.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.test.ts @@ -66,6 +66,26 @@ describe('validateDeprecatedStepTypes', () => { ]); }); + it('returns a warning for prefix-matched deprecated step types (AI connectors)', () => { + const aiStep = createStepInfo({ + stepId: 'ai_call', + stepType: 'inference.completion', + propInfos: { + type: createPropInfo(['type'], 'inference.completion', [10, 20, 20]), + }, + }); + + const results = validateDeprecatedStepTypes(createWorkflowLookup([aiStep]), mockLineCounter); + + expect(results).toEqual([ + expect.objectContaining({ + owner: 'deprecated-step-validation', + severity: 'warning', + message: 'Step type "inference.completion" is deprecated. Use "ai.prompt" instead.', + }), + ]); + }); + it('returns no warnings for non-deprecated step types', () => { const currentStep = createStepInfo({ stepId: 'create_case', diff --git a/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.ts b/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.ts index 31288329f3f96..ffd192b421602 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_deprecated_step_types.ts @@ -9,7 +9,7 @@ import type { LineCounter } from 'yaml'; import { getDeprecatedStepMessage } from '@kbn/workflows'; -import { getDeprecatedStepMetadataMap } from '../../../../common/schema'; +import { getDeprecatedStepMetadata } from '../../../../common/schema'; import type { WorkflowLookup } from '../../../entities/workflows/store/workflow_detail/utils/build_workflow_lookup'; import type { YamlValidationResult } from '../model/types'; @@ -18,10 +18,9 @@ export function validateDeprecatedStepTypes( lineCounter: LineCounter ): YamlValidationResult[] { const results: YamlValidationResult[] = []; - const deprecatedStepMetadata = getDeprecatedStepMetadataMap(); for (const step of Object.values(workflowLookup.steps)) { - const deprecation = deprecatedStepMetadata[step.stepType]; + const deprecation = getDeprecatedStepMetadata(step.stepType); const typeProp = step.propInfos.type; if (deprecation && typeProp?.valueNode.range) { diff --git a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts index 928ad28da7f95..8346ab0d8619a 100644 --- a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts +++ b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts @@ -16,7 +16,8 @@ import { clearAllYamlProviders, interceptMonacoYamlProvider, } from './intercept_monaco_yaml_provider'; -import { getDeprecatedStepMetadataMap } from '../../../../../common/schema'; + +import { getDeprecatedStepMetadata } from '../../../../../common/schema'; // Mock dependencies jest.mock('./suggestions/get_suggestions', () => ({ @@ -33,7 +34,7 @@ jest.mock('./context/build_autocomplete_context', () => ({ })); jest.mock('../../../../../common/schema', () => ({ - getDeprecatedStepMetadataMap: jest.fn(() => ({})), + getDeprecatedStepMetadata: jest.fn(() => undefined), })); describe('getCompletionItemProvider', () => { @@ -63,7 +64,7 @@ describe('getCompletionItemProvider', () => { } as monaco.languages.CompletionContext; getState = jest.fn(() => ({} as any)); - (getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({}); + (getDeprecatedStepMetadata as jest.Mock).mockReturnValue(undefined); }); afterEach(() => { @@ -170,10 +171,13 @@ describe('getCompletionItemProvider', () => { }), }; - (getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({ + const deprecatedMap: Record = { 'kibana.createCase': { replacementStepType: 'cases.createCase' }, 'kibana.createCaseDefaultSpace': { replacementStepType: 'kibana.createCase' }, - }); + }; + (getDeprecatedStepMetadata as jest.Mock).mockImplementation( + (stepType: string) => deprecatedMap[stepType] + ); monaco.languages.registerCompletionItemProvider(YAML_LANG_ID, yamlProvider); const provider = getCompletionItemProvider(getState); diff --git a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts index d5c6eb6939c61..9a28104fefbd8 100644 --- a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts +++ b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts @@ -8,13 +8,12 @@ */ import { monaco } from '@kbn/monaco'; -import type { StepDeprecationInfo } from '@kbn/workflows'; import { buildAutocompleteContext } from './context/build_autocomplete_context'; import { getAllYamlProviders } from './intercept_monaco_yaml_provider'; import { getSuggestions, isInsideLoopBody } from './suggestions/get_suggestions'; import { isInWorkflowOutputWithBlock } from './suggestions/workflow/get_workflow_outputs_suggestions'; import type { WorkflowKqlCompletionServices } from './suggestions/workflow_kql_completion_services'; -import { getDeprecatedStepMetadataMap } from '../../../../../common/schema'; +import { getDeprecatedStepMetadata } from '../../../../../common/schema'; import type { WorkflowDetailState } from '../../../../entities/workflows/store'; // Unique identifier for the workflow completion provider @@ -64,15 +63,14 @@ function getDeduplicationKey(suggestion: monaco.languages.CompletionItem): strin */ function mapSuggestions( map: Map, - suggestions: monaco.languages.CompletionItem[], - deprecatedStepMetadata: Readonly> + suggestions: monaco.languages.CompletionItem[] ): void { for (const suggestion of suggestions) { const key = getDeduplicationKey(suggestion); // Skip deprecated step types - they still work for backward compatibility // but we don't want to suggest them to users - if (!deprecatedStepMetadata[key]) { + if (!getDeprecatedStepMetadata(key)) { const existing = map.get(key); if (existing) { @@ -121,7 +119,6 @@ export function getCompletionItemProvider( // Incremental deduplication accumulator const deduplicatedMap = new Map(); - const deprecatedStepMetadata = getDeprecatedStepMetadataMap(); // Inside workflow.output's with: block, show only declared output field names so the user // doesn't get generic YAML/JSON Schema keys; skip the YAML provider in that case. @@ -150,7 +147,7 @@ export function getCompletionItemProvider( ); if (result) { // Deduplicate across YAML providers only (snippet beats plain) - mapSuggestions(deduplicatedMap, result.suggestions || [], deprecatedStepMetadata); + mapSuggestions(deduplicatedMap, result.suggestions || []); if (result.incomplete) { isIncomplete = true; } @@ -173,7 +170,7 @@ export function getCompletionItemProvider( // Workflow suggestions always win over YAML duplicates. for (const suggestion of workflowSuggestions) { const key = getDeduplicationKey(suggestion); - if (!deprecatedStepMetadata[key]) { + if (!getDeprecatedStepMetadata(key)) { deduplicatedMap.set(key, suggestion); } } diff --git a/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.test.ts b/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.test.ts index 86121285321fa..628c41efcac13 100644 --- a/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.test.ts +++ b/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.test.ts @@ -18,6 +18,7 @@ jest.mock('../../../common/schema', () => ({ getAllConnectors: (...args: unknown[]) => mockGetAllConnectors(...args), addDynamicConnectorsToCache: (...args: unknown[]) => mockAddDynamicConnectorsToCache(...args), getCachedAllConnectorsMap: () => null, + getDeprecatedStepMetadata: () => undefined, })); const invokeHandler = async (tool: BuiltinToolDefinition, input: unknown, context: unknown) => diff --git a/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.ts b/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.ts index b7238553b9ae3..db0dacbf1e34d 100644 --- a/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.ts +++ b/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool.ts @@ -31,6 +31,7 @@ import { addDynamicConnectorsToCache, getAllConnectors, getCachedAllConnectorsMap, + getDeprecatedStepMetadata, } from '../../../common/schema'; import type { WorkflowsManagementApi } from '../../api/workflows_management_api'; import type { AgentBuilderPluginSetupContract } from '../../types'; @@ -117,7 +118,8 @@ export function formatConnectorStep(connector: ConnectorContractUnion): StepDefi ? buildStepParamsSummary(connector.configSchema) : undefined; const outputSummary = buildOutputSummary(connector.outputSchema); - const deprecationMetadata = formatDeprecationMetadata(connector.type, connector.deprecation); + const deprecation = connector.deprecation ?? getDeprecatedStepMetadata(connector.type); + const deprecationMetadata = formatDeprecationMetadata(connector.type, deprecation); return { id: connector.type, From bf2c3ab9cdc1f10fd0baf0ed160edcb17069ab58 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Thu, 16 Apr 2026 14:41:58 +0400 Subject: [PATCH 2/3] Fix missing getDeprecatedStepMetadata mock in output size test The output size test was missing the getDeprecatedStepMetadata function in its jest.mock for common/schema, causing all 16 tests to fail with TypeError. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../tools/get_step_definitions_tool_output_size.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool_output_size.test.ts b/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool_output_size.test.ts index e048a63a88763..a1659835927f9 100644 --- a/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool_output_size.test.ts +++ b/src/platform/plugins/shared/workflows_management/server/agent_builder/tools/get_step_definitions_tool_output_size.test.ts @@ -17,6 +17,7 @@ jest.mock('../../../common/schema', () => ({ }, addDynamicConnectorsToCache: jest.fn(), getCachedAllConnectorsMap: () => null, + getDeprecatedStepMetadata: () => undefined, })); const MAX_CHARS_PER_STEP = 5000; From 19c2420822ffe187b7f72745748961d2ce446f9b Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Thu, 16 Apr 2026 14:58:21 +0400 Subject: [PATCH 3/3] Address review feedback: use isDeprecatedStepType, cache prefix resolution - Use `isDeprecatedStepType` boolean helper instead of `getDeprecatedStepMetadata` in get_action_options.ts and get_completion_item_provider.ts (NIT from semd) - Extract `getStepPrefixDeprecationInfo` from package's `getStepDeprecationInfo` so prefix-matching logic is reusable without duplication - Move prefix resolution into `getDeprecatedStepMetadataMap` so known connector types are cached, and use `getStepPrefixDeprecationInfo` as fallback in `getDeprecatedStepMetadata` to avoid duplicating the loop Co-Authored-By: Claude Opus 4.6 (1M context) --- .../spec/deprecated_step_metadata.ts | 14 +++++++++----- .../workflows_management/common/schema.ts | 18 +++++++----------- .../lib/get_action_options.test.ts | 10 +++++----- .../lib/get_action_options.ts | 4 ++-- .../get_completion_item_provider.test.ts | 15 ++++++--------- .../get_completion_item_provider.ts | 6 +++--- 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts b/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts index 3912b102b16ba..61787334b57b8 100644 --- a/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts +++ b/src/platform/packages/shared/kbn-workflows/spec/deprecated_step_metadata.ts @@ -57,11 +57,7 @@ export const DEPRECATED_STEP_PREFIX_METADATA: StepPrefixDeprecationInfo[] = [ { prefix: 'gemini.', deprecation: { replacementStepType: 'ai.prompt' } }, ]; -export function getStepDeprecationInfo(stepType: string): StepDeprecationInfo | undefined { - const exact = DEPRECATED_STEP_METADATA[stepType]; - if (exact) { - return exact; - } +export function getStepPrefixDeprecationInfo(stepType: string): StepDeprecationInfo | undefined { for (const { prefix, deprecation } of DEPRECATED_STEP_PREFIX_METADATA) { if (stepType.startsWith(prefix)) { return deprecation; @@ -70,6 +66,14 @@ export function getStepDeprecationInfo(stepType: string): StepDeprecationInfo | return undefined; } +export function getStepDeprecationInfo(stepType: string): StepDeprecationInfo | undefined { + const exact = DEPRECATED_STEP_METADATA[stepType]; + if (exact) { + return exact; + } + return getStepPrefixDeprecationInfo(stepType); +} + export function isDeprecatedStepType(stepType: string): boolean { return getStepDeprecationInfo(stepType) !== undefined; } diff --git a/src/platform/plugins/shared/workflows_management/common/schema.ts b/src/platform/plugins/shared/workflows_management/common/schema.ts index 9e280bec62de5..ba06f944b9ed3 100644 --- a/src/platform/plugins/shared/workflows_management/common/schema.ts +++ b/src/platform/plugins/shared/workflows_management/common/schema.ts @@ -17,10 +17,10 @@ import type { import { builtInStepDefinitions, DEPRECATED_STEP_METADATA, - DEPRECATED_STEP_PREFIX_METADATA, generateYamlSchemaFromConnectors, getElasticsearchConnectors, getKibanaConnectors, + getStepPrefixDeprecationInfo, SystemConnectorsMap, } from '@kbn/workflows'; import { z } from '@kbn/zod/v4'; @@ -364,6 +364,11 @@ export function getDeprecatedStepMetadataMap(): Readonly ({ getAllConnectors: jest.fn(), - getDeprecatedStepMetadata: jest.fn(() => undefined), + isDeprecatedStepType: jest.fn(() => false), })); jest.mock('../../../trigger_schemas', () => ({ triggerSchemas: { getTriggerDefinitions: jest.fn(() => []) }, @@ -60,7 +60,7 @@ describe('getActionOptions', () => { mockWorkflowsExtensions = workflowsExtensionsMock.createStart(); (getAllConnectors as jest.Mock).mockReturnValue([]); - (getDeprecatedStepMetadata as jest.Mock).mockReturnValue(undefined); + (isDeprecatedStepType as jest.Mock).mockReturnValue(false); (isDynamicConnector as jest.MockedFunction).mockImplementation( () => false ); @@ -346,8 +346,8 @@ describe('getActionOptions', () => { }; (getAllConnectors as jest.Mock).mockReturnValue([mockConnector]); - (getDeprecatedStepMetadata as jest.Mock).mockImplementation((stepType: string) => - stepType === 'kibana.createCase' ? { replacementStepType: 'cases.createCase' } : undefined + (isDeprecatedStepType as jest.Mock).mockImplementation( + (stepType: string) => stepType === 'kibana.createCase' ); mockWorkflowsExtensions.getStepDefinition.mockReturnValue(undefined); diff --git a/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts b/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts index fa500c49ede16..14e814ea74bce 100644 --- a/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts +++ b/src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/get_action_options.ts @@ -12,7 +12,7 @@ import { AssistantIcon } from '@kbn/ai-assistant-icon'; import { i18n } from '@kbn/i18n'; import { getBuiltInStepDefinition, isDynamicConnector, StepCategory } from '@kbn/workflows'; import type { WorkflowsExtensionsPublicPluginStart } from '@kbn/workflows-extensions/public'; -import { getAllConnectors, getDeprecatedStepMetadata } from '../../../../common/schema'; +import { getAllConnectors, isDeprecatedStepType } from '../../../../common/schema'; import { getStepIconType } from '../../../shared/ui/step_icons/get_step_icon_type'; import { triggerSchemas } from '../../../trigger_schemas'; import type { ActionConnectorGroup, ActionGroup, ActionOptionData } from '../types'; @@ -266,7 +266,7 @@ export function getActionOptions( const baseTypeInstancesCount: Record = {}; for (const connector of connectors) { - if (!getDeprecatedStepMetadata(connector.type)) { + if (!isDeprecatedStepType(connector.type)) { const customStepDefinition = workflowsExtensions.getStepDefinition(connector.type); if (customStepDefinition) { const group = stepGroups[customStepDefinition.category]; diff --git a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts index 8346ab0d8619a..f48ce5f5b9c81 100644 --- a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts +++ b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.test.ts @@ -17,7 +17,7 @@ import { interceptMonacoYamlProvider, } from './intercept_monaco_yaml_provider'; -import { getDeprecatedStepMetadata } from '../../../../../common/schema'; +import { isDeprecatedStepType } from '../../../../../common/schema'; // Mock dependencies jest.mock('./suggestions/get_suggestions', () => ({ @@ -34,7 +34,7 @@ jest.mock('./context/build_autocomplete_context', () => ({ })); jest.mock('../../../../../common/schema', () => ({ - getDeprecatedStepMetadata: jest.fn(() => undefined), + isDeprecatedStepType: jest.fn(() => false), })); describe('getCompletionItemProvider', () => { @@ -64,7 +64,7 @@ describe('getCompletionItemProvider', () => { } as monaco.languages.CompletionContext; getState = jest.fn(() => ({} as any)); - (getDeprecatedStepMetadata as jest.Mock).mockReturnValue(undefined); + (isDeprecatedStepType as jest.Mock).mockReturnValue(false); }); afterEach(() => { @@ -171,12 +171,9 @@ describe('getCompletionItemProvider', () => { }), }; - const deprecatedMap: Record = { - 'kibana.createCase': { replacementStepType: 'cases.createCase' }, - 'kibana.createCaseDefaultSpace': { replacementStepType: 'kibana.createCase' }, - }; - (getDeprecatedStepMetadata as jest.Mock).mockImplementation( - (stepType: string) => deprecatedMap[stepType] + const deprecatedStepTypes = new Set(['kibana.createCase', 'kibana.createCaseDefaultSpace']); + (isDeprecatedStepType as jest.Mock).mockImplementation((stepType: string) => + deprecatedStepTypes.has(stepType) ); monaco.languages.registerCompletionItemProvider(YAML_LANG_ID, yamlProvider); diff --git a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts index 9a28104fefbd8..cb2ba6a7bc295 100644 --- a/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts +++ b/src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/get_completion_item_provider.ts @@ -13,7 +13,7 @@ import { getAllYamlProviders } from './intercept_monaco_yaml_provider'; import { getSuggestions, isInsideLoopBody } from './suggestions/get_suggestions'; import { isInWorkflowOutputWithBlock } from './suggestions/workflow/get_workflow_outputs_suggestions'; import type { WorkflowKqlCompletionServices } from './suggestions/workflow_kql_completion_services'; -import { getDeprecatedStepMetadata } from '../../../../../common/schema'; +import { isDeprecatedStepType } from '../../../../../common/schema'; import type { WorkflowDetailState } from '../../../../entities/workflows/store'; // Unique identifier for the workflow completion provider @@ -70,7 +70,7 @@ function mapSuggestions( // Skip deprecated step types - they still work for backward compatibility // but we don't want to suggest them to users - if (!getDeprecatedStepMetadata(key)) { + if (!isDeprecatedStepType(key)) { const existing = map.get(key); if (existing) { @@ -170,7 +170,7 @@ export function getCompletionItemProvider( // Workflow suggestions always win over YAML duplicates. for (const suggestion of workflowSuggestions) { const key = getDeduplicationKey(suggestion); - if (!getDeprecatedStepMetadata(key)) { + if (!isDeprecatedStepType(key)) { deduplicatedMap.set(key, suggestion); } }