Skip to content

Commit 8a01d27

Browse files
committed
add type generics and deep lookup
1 parent ca6fb05 commit 8a01d27

9 files changed

Lines changed: 93 additions & 51 deletions

File tree

src/platform/packages/shared/kbn-workflows/types/v1.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -498,12 +498,16 @@ export interface InternalConnectorContract extends BaseConnectorContract {
498498
};
499499
}
500500

501-
export interface StepPropertyHandler<T = unknown> {
501+
export interface StepPropertyHandler<
502+
T = unknown,
503+
TConfig extends Record<string, unknown> = Record<string, unknown>,
504+
TInput extends Record<string, unknown> = Record<string, unknown>
505+
> {
502506
/**
503507
* Entity selection configuration for the property.
504508
* Provides a unified interface for search, resolution, and decoration of entity references.
505509
*/
506-
selection?: PropertySelectionHandler<Exclude<T, undefined>>;
510+
selection?: PropertySelectionHandler<Exclude<T, undefined>, TConfig, TInput>;
507511
/**
508512
* Connector ID selection configuration for the property.
509513
* Used to resolve connector IDs for custom steps.
@@ -513,19 +517,29 @@ export interface StepPropertyHandler<T = unknown> {
513517
connectorIdSelection?: ConnectorIdSelectionHandler;
514518
}
515519

516-
export interface PropertySelectionHandler<T = unknown> {
520+
export interface PropertySelectionHandler<
521+
T = unknown,
522+
TConfig extends Record<string, unknown> = Record<string, unknown>,
523+
TInput extends Record<string, unknown> = Record<string, unknown>
524+
> {
517525
/**
518526
* Search for options matching the input query.
519527
* Used by autocomplete dropdowns when the user types.
520528
*/
521-
search: (input: string, context: SelectionContext) => Promise<SelectionOption<T>[]>;
529+
search: (
530+
input: string,
531+
context: SelectionContext<TConfig, TInput>
532+
) => Promise<SelectionOption<T>[]>;
522533

523534
/**
524535
* Resolve an entity by its value.
525536
* Used when loading existing values or when a value is pasted.
526537
* Returns null if the entity is not found.
527538
*/
528-
resolve: (value: T, context: SelectionContext) => Promise<SelectionOption<T> | null>;
539+
resolve: (
540+
value: T,
541+
context: SelectionContext<TConfig, TInput>
542+
) => Promise<SelectionOption<T> | null>;
529543

530544
/**
531545
* Get detailed information for the current value.
@@ -534,7 +548,7 @@ export interface PropertySelectionHandler<T = unknown> {
534548
*/
535549
getDetails: (
536550
input: string,
537-
context: SelectionContext,
551+
context: SelectionContext<TConfig, TInput>,
538552
option: SelectionOption<T> | null
539553
) => Promise<SelectionDetails>;
540554
}
@@ -569,22 +583,28 @@ export interface SelectionDetails {
569583
* Intermediate map nodes that have no scalar value are **not** represented;
570584
* a missing key means "not yet defined in the YAML", not "empty object".
571585
*/
572-
export interface StepSelectionValues {
586+
export interface StepSelectionValues<
587+
TConfig extends Record<string, unknown> = Record<string, unknown>,
588+
TInput extends Record<string, unknown> = Record<string, unknown>
589+
> {
573590
/** Root-level step properties (everything outside the `with` block). */
574-
config: Record<string, unknown>;
591+
config: TConfig;
575592
/** Properties nested under the `with` block. */
576-
input: Record<string, unknown>;
593+
input: TInput;
577594
}
578595

579-
export interface SelectionContext {
596+
export interface SelectionContext<
597+
TConfig extends Record<string, unknown> = Record<string, unknown>,
598+
TInput extends Record<string, unknown> = Record<string, unknown>
599+
> {
580600
/** The step type ID (e.g., "onechat.runAgent") */
581601
stepType: string;
582602
/** The property path ("config" or "input") */
583603
scope: 'config' | 'input';
584604
/** The property key (e.g., "agent_id") */
585605
propertyKey: string;
586606
/** Sibling values of the current step, keyed by scope. */
587-
values: StepSelectionValues;
607+
values: StepSelectionValues<TConfig, TInput>;
588608
}
589609

590610
export interface ConnectorIdSelectionHandler {

src/platform/plugins/shared/workflows_extensions/dev_docs/STEPS.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,6 @@ interface SelectionContext {
450450
For example, if the YAML step has `with: { owner: securitySolution }`, the handler
451451
can read `context.values.input.owner`. Missing properties are `undefined`.
452452

453-
Only scalar leaf values are included — intermediate YAML map nodes that have no
454-
scalar value are not represented.
455-
456453
#### Example Implementation
457454

458455
For a complete working example, see the `external_step` implementation in `examples/workflows_extensions_example`:

src/platform/plugins/shared/workflows_extensions/public/step_registry/types.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,32 @@ export interface EditorHandlers<
5959
Output extends z.ZodType = z.ZodType,
6060
Config extends z.ZodObject = z.ZodObject
6161
> {
62-
config?: EditorHandlersConfig<Config>;
63-
input?: EditorHandlersInput<Input>;
62+
config?: EditorHandlersConfig<Config, Input>;
63+
input?: EditorHandlersInput<Input, Config>;
6464
dynamicSchema?: DynamicSchema<Input, Output, Config>;
6565
}
6666

67-
export type EditorHandlersConfig<Config extends z.ZodObject = z.ZodObject> = {
68-
[K in DotKeysOf<z.infer<Config>>]?: StepPropertyHandler<DotObject<z.infer<Config>>[K]>;
67+
export type EditorHandlersConfig<
68+
Config extends z.ZodObject = z.ZodObject,
69+
Input extends z.ZodType = z.ZodType
70+
> = {
71+
[K in DotKeysOf<z.infer<Config>>]?: StepPropertyHandler<
72+
DotObject<z.infer<Config>>[K],
73+
z.infer<Config>,
74+
Input extends z.ZodObject ? z.infer<Input> : Record<string, unknown>
75+
>;
6976
};
7077

71-
export type EditorHandlersInput<Input extends z.ZodType = z.ZodType> = Input extends z.ZodObject
78+
export type EditorHandlersInput<
79+
Input extends z.ZodType = z.ZodType,
80+
Config extends z.ZodObject = z.ZodObject
81+
> = Input extends z.ZodObject
7282
? {
73-
[K in DotKeysOf<z.infer<Input>>]?: StepPropertyHandler<DotObject<z.infer<Input>>[K]>;
83+
[K in DotKeysOf<z.infer<Input>>]?: StepPropertyHandler<
84+
DotObject<z.infer<Input>>[K],
85+
z.infer<Config>,
86+
z.infer<Input>
87+
>;
7488
}
7589
: {};
7690

src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/utils/build_workflow_lookup.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,20 @@ steps:
634634
expect(values.input).toEqual({ inputs: { field1: 'value1', field2: 'value2' } });
635635
});
636636

637+
it('should handle array values under with', () => {
638+
const step = getStep(`
639+
steps:
640+
- name: s1
641+
type: my.step
642+
with:
643+
field1:
644+
- value1
645+
- value2
646+
`);
647+
const values = buildStepSelectionValues(step);
648+
expect(values.input).toEqual({ field1: ['value1', 'value2'] });
649+
});
650+
637651
it('should return empty objects when step has no custom properties', () => {
638652
const step = getStep(`
639653
steps:

src/platform/plugins/shared/workflows_management/public/entities/workflows/store/workflow_detail/utils/build_workflow_lookup.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,18 @@ export interface StepPropInfo {
2727
path: string[];
2828
keyNode: YAML.Scalar<unknown>;
2929
/** Value node: always a scalar (leaf property). Intermediate map nodes are not recorded in propInfos. */
30-
valueNode: YAML.Scalar<unknown>;
30+
valueNode: YAML.Scalar<unknown> | YAML.YAMLMap<unknown, unknown>;
3131
}
3232

3333
/**
34-
* Get plain JavaScript value from a step property value node (scalar).
34+
* Get plain JavaScript value from a step property value node.
35+
* Handles scalars (returns `.value`) and collection nodes like sequences
36+
* (returns the JSON representation via `.toJSON()`).
3537
*/
3638
export function getValueFromValueNode(valueNode: StepPropInfo['valueNode']): unknown {
3739
if (!valueNode) return undefined;
40+
if (YAML.isScalar(valueNode)) return valueNode.value;
41+
if ('toJSON' in valueNode && typeof valueNode.toJSON === 'function') return valueNode.toJSON();
3842
return (valueNode as { value?: unknown }).value;
3943
}
4044

src/platform/plugins/shared/workflows_management/public/features/validate_workflow_yaml/lib/validate_custom_properties.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,20 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
import type { StepSelectionValues } from '@kbn/workflows';
1011
import { getSchemaAtPath } from '@kbn/workflows/common/utils/zod/get_schema_at_path';
1112
import { z } from '@kbn/zod/v4';
1213

14+
import { validateCustomProperties } from './validate_custom_properties';
15+
import { stepSchemas } from '../../../../common/step_schemas';
16+
import {
17+
getCachedOption,
18+
getCachedSearchOption,
19+
getCacheKeyForValue,
20+
setCachedOption,
21+
} from '../../../shared/lib/custom_property_selection_cache';
22+
import type { CustomPropertyItem } from '../model/types';
23+
1324
// Mock the dependencies
1425
jest.mock('../../../../common/step_schemas', () => ({
1526
stepSchemas: {
@@ -28,17 +39,6 @@ jest.mock('../../../shared/lib/custom_property_selection_cache', () => ({
2839
setCachedOption: jest.fn(),
2940
}));
3041

31-
import { validateCustomProperties } from './validate_custom_properties';
32-
import { stepSchemas } from '../../../../common/step_schemas';
33-
import {
34-
getCachedOption,
35-
getCachedSearchOption,
36-
getCacheKeyForValue,
37-
setCachedOption,
38-
} from '../../../shared/lib/custom_property_selection_cache';
39-
import type { StepSelectionValues } from '@kbn/workflows';
40-
import type { CustomPropertyItem } from '../model/types';
41-
4242
const EMPTY_VALUES: StepSelectionValues = { config: {}, input: {} };
4343

4444
const mockGetAllConnectorsMapCache = stepSchemas.getAllConnectorsMapCache as jest.MockedFunction<

src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/suggestions/custom_property/get_custom_property_suggestions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import {
1515
type StepPropertyHandler,
1616
} from '@kbn/workflows';
1717
import {
18-
getValueFromValueNode,
1918
buildStepSelectionValues,
19+
getValueFromValueNode,
2020
} from '../../../../../../entities/workflows/store/workflow_detail/utils/build_workflow_lookup';
2121
import { cacheSearchOptions } from '../../../../../../shared/lib/custom_property_selection_cache';
2222
import type { AutocompleteContext } from '../../context/autocomplete.types';

x-pack/platform/plugins/shared/cases/public/workflows/case_id_selection_handler.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import type { PropertySelectionHandler, SelectionOption } from '@kbn/workflows/types/latest';
8+
import type { SelectionOption } from '@kbn/workflows/types/latest';
99
import { SortFieldCase } from '../../common/ui';
1010
import { getCases, resolveCase } from '../containers/api';
1111
import { DEFAULT_FILTER_OPTIONS } from '../containers/constants';
@@ -23,25 +23,18 @@ const toCaseSelectionOption = (theCase: CaseSelectionShape): SelectionOption<str
2323
description: theCase.description,
2424
});
2525

26-
export const caseIdSelection: PropertySelectionHandler<string> = {
27-
search: async (input, context) => {
28-
console.log('context', context);
26+
export const caseIdSelection = {
27+
search: async (input: string) => {
2928
const query = input.trim();
30-
// if (query.length === 0) {
31-
// return [];
32-
// }
33-
34-
// TODO: get the owner from the context values
35-
// context.values.config.owner
36-
// context.values.input.case_id
37-
// context.values.input.comment
29+
if (query.length === 0) {
30+
return [];
31+
}
3832

3933
const response = await getCases({
4034
filterOptions: {
4135
...DEFAULT_FILTER_OPTIONS,
4236
search: query,
43-
// searchFields: ['name'],
44-
owner: ['securitySolution', 'observability', 'cases'],
37+
searchFields: ['title'],
4538
},
4639
queryParams: {
4740
page: 1,
@@ -53,7 +46,7 @@ export const caseIdSelection: PropertySelectionHandler<string> = {
5346

5447
return response.cases.map((theCase) => toCaseSelectionOption(theCase));
5548
},
56-
resolve: async (value, context) => {
49+
resolve: async (value: string) => {
5750
const caseId = value.trim();
5851
if (caseId.length === 0) {
5952
return null;

x-pack/platform/plugins/shared/cases/public/workflows/get_case.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import React from 'react';
99
import { createPublicStepDefinition } from '@kbn/workflows-extensions/public';
1010
import { getCaseStepCommonDefinition } from '../../common/workflows/steps/get_case';
11-
import { caseIdInputEditorHandlers } from './case_id_selection_handler';
11+
// import { caseIdInputEditorHandlers } from './case_id_selection_handler';
1212

1313
export const getCaseStepDefinition = createPublicStepDefinition({
1414
...getCaseStepCommonDefinition,
@@ -18,5 +18,5 @@ export const getCaseStepDefinition = createPublicStepDefinition({
1818
}))
1919
),
2020
// TODO: enable one case_id can be a template AND an inputHandler
21-
editorHandlers: caseIdInputEditorHandlers,
21+
// editorHandlers: caseIdInputEditorHandlers,
2222
});

0 commit comments

Comments
 (0)