Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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');
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, StepDeprecationInfo> = {
'kibana.createCase': {
replacementStepType: 'cases.createCase',
Expand Down Expand Up @@ -39,8 +45,33 @@ export const DEPRECATED_STEP_METADATA: Record<string, StepDeprecationInfo> = {
},
};

/**
* 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 getStepPrefixDeprecationInfo(stepType: string): StepDeprecationInfo | undefined {
for (const { prefix, deprecation } of DEPRECATED_STEP_PREFIX_METADATA) {
if (stepType.startsWith(prefix)) {
return deprecation;
}
}
return undefined;
}

export function getStepDeprecationInfo(stepType: string): StepDeprecationInfo | undefined {
return DEPRECATED_STEP_METADATA[stepType];
const exact = DEPRECATED_STEP_METADATA[stepType];
if (exact) {
return exact;
}
return getStepPrefixDeprecationInfo(stepType);
}

export function isDeprecatedStepType(stepType: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
generateYamlSchemaFromConnectors,
getElasticsearchConnectors,
getKibanaConnectors,
getStepPrefixDeprecationInfo,
SystemConnectorsMap,
} from '@kbn/workflows';
import { z } from '@kbn/zod/v4';
Expand Down Expand Up @@ -363,6 +364,11 @@ export function getDeprecatedStepMetadataMap(): Readonly<Record<string, StepDepr
for (const connector of getAllConnectorsInternal()) {
if (connector.deprecation) {
deprecatedStepMetadata[connector.type] = connector.deprecation;
} else {
const prefixMatch = getStepPrefixDeprecationInfo(connector.type);
if (prefixMatch) {
deprecatedStepMetadata[connector.type] = prefixMatch;
}
}
}

Expand All @@ -375,7 +381,7 @@ export function getDeprecatedStepMetadataMap(): Readonly<Record<string, StepDepr
}

export function getDeprecatedStepMetadata(stepType: string): StepDeprecationInfo | undefined {
return getDeprecatedStepMetadataMap()[stepType];
return getDeprecatedStepMetadataMap()[stepType] ?? getStepPrefixDeprecationInfo(stepType);
}

export function isDeprecatedStepType(stepType: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import type { WorkflowsExtensionsPublicPluginStart } from '@kbn/workflows-extens
import { workflowsExtensionsMock } from '@kbn/workflows-extensions/public/mocks';
import { z } from '@kbn/zod/v4';
import { flattenOptions, getActionOptions } from './get_action_options';
import { getAllConnectors, getDeprecatedStepMetadataMap } 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 { ActionOptionData } from '../types';
import { isActionGroup, isActionOption } from '../types';

jest.mock('../../../../common/schema', () => ({
getAllConnectors: jest.fn(),
getDeprecatedStepMetadataMap: jest.fn(() => ({})),
isDeprecatedStepType: jest.fn(() => false),
}));
jest.mock('../../../trigger_schemas', () => ({
triggerSchemas: { getTriggerDefinitions: jest.fn(() => []) },
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('getActionOptions', () => {
mockWorkflowsExtensions = workflowsExtensionsMock.createStart();

(getAllConnectors as jest.Mock).mockReturnValue([]);
(getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({});
(isDeprecatedStepType as jest.Mock).mockReturnValue(false);
(isDynamicConnector as jest.MockedFunction<typeof isDynamicConnector>).mockImplementation(
() => false
);
Expand Down Expand Up @@ -346,9 +346,9 @@ describe('getActionOptions', () => {
};

(getAllConnectors as jest.Mock).mockReturnValue([mockConnector]);
(getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({
'kibana.createCase': { replacementStepType: 'cases.createCase' },
});
(isDeprecatedStepType as jest.Mock).mockImplementation(
(stepType: string) => stepType === 'kibana.createCase'
);
mockWorkflowsExtensions.getStepDefinition.mockReturnValue(undefined);

const result = getActionOptions(mockEuiTheme, mockWorkflowsExtensions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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';
Expand All @@ -23,7 +23,6 @@ export function getActionOptions(
workflowsExtensions: WorkflowsExtensionsPublicPluginStart
): ActionOptionData[] {
const connectors = getAllConnectors();
const deprecatedStepMetadata = getDeprecatedStepMetadataMap();
const builtInTriggerOptions: ActionOptionData[] = [
{
id: 'manual',
Expand Down Expand Up @@ -267,7 +266,7 @@ export function getActionOptions(
const baseTypeInstancesCount: Record<string, number> = {};

for (const connector of connectors) {
if (!deprecatedStepMetadata[connector.type]) {
if (!isDeprecatedStepType(connector.type)) {
const customStepDefinition = workflowsExtensions.getStepDefinition(connector.type);
if (customStepDefinition) {
const group = stepGroups[customStepDefinition.category];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
clearAllYamlProviders,
interceptMonacoYamlProvider,
} from './intercept_monaco_yaml_provider';
import { getDeprecatedStepMetadataMap } from '../../../../../common/schema';

import { isDeprecatedStepType } from '../../../../../common/schema';

// Mock dependencies
jest.mock('./suggestions/get_suggestions', () => ({
Expand All @@ -33,7 +34,7 @@ jest.mock('./context/build_autocomplete_context', () => ({
}));

jest.mock('../../../../../common/schema', () => ({
getDeprecatedStepMetadataMap: jest.fn(() => ({})),
isDeprecatedStepType: jest.fn(() => false),
}));

describe('getCompletionItemProvider', () => {
Expand Down Expand Up @@ -63,7 +64,7 @@ describe('getCompletionItemProvider', () => {
} as monaco.languages.CompletionContext;

getState = jest.fn(() => ({} as any));
(getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({});
(isDeprecatedStepType as jest.Mock).mockReturnValue(false);
});

afterEach(() => {
Expand Down Expand Up @@ -170,10 +171,10 @@ describe('getCompletionItemProvider', () => {
}),
};

(getDeprecatedStepMetadataMap as jest.Mock).mockReturnValue({
'kibana.createCase': { replacementStepType: 'cases.createCase' },
'kibana.createCaseDefaultSpace': { replacementStepType: 'kibana.createCase' },
});
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);

const provider = getCompletionItemProvider(getState);
Expand Down
Loading
Loading