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
Expand Up @@ -7,6 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { i18n } from '@kbn/i18n';
import { z } from '@kbn/zod/v4';
import type { CommonTriggerDefinition } from '@kbn/workflows-extensions/common';

Expand Down Expand Up @@ -34,8 +35,69 @@ export const customTriggerEventSchema = z.object({

export type CustomTriggerEvent = z.infer<typeof customTriggerEventSchema>;

/** Shared trigger definition (id + eventSchema) for use by public and server. */
/** Shared trigger definition for use by public and server. */
export const commonCustomTriggerDefinition: CommonTriggerDefinition = {
id: CUSTOM_TRIGGER_ID,
eventSchema: customTriggerEventSchema,
title: i18n.translate('workflowsExtensionsExample.customTrigger.title', {
defaultMessage: 'Custom trigger',
}),
description: i18n.translate('workflowsExtensionsExample.customTrigger.description', {
defaultMessage:
'Emitted when a custom event occurs. Used by the workflows extensions example plugin.',
}),
documentation: {
details: i18n.translate('workflowsExtensionsExample.customTrigger.documentation.details', {
defaultMessage:
'Emitted when a custom event occurs. Events can include an optional category (e.g. alerts, notifications, audit, demo). In the `on` block, add a condition (KQL) to filter when this workflow runs using event properties: `event.category`, `event.message`, `event.source`.',
}),
examples: [
i18n.translate(
'workflowsExtensionsExample.customTrigger.documentation.exampleMatchCategory',
{
defaultMessage: `## Match by category (conditional subscription)
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.category: "alerts"'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}
),
i18n.translate('workflowsExtensionsExample.customTrigger.documentation.exampleMatchMessage', {
defaultMessage: `## Match any message
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.message: *'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}),
i18n.translate('workflowsExtensionsExample.customTrigger.documentation.exampleMatchSource', {
defaultMessage: `## Match events from a specific source
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.source: "api"'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}),
i18n.translate('workflowsExtensionsExample.customTrigger.documentation.exampleMatchError', {
defaultMessage: `## Match message containing "error"
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.message: *error*'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}),
],
},
snippets: {
condition: 'event.category: "alerts"',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { i18n } from '@kbn/i18n';
import { z } from '@kbn/zod/v4';
import type { CommonTriggerDefinition } from '@kbn/workflows-extensions/common';

Expand All @@ -20,8 +21,35 @@ export const loopTriggerEventSchema = z.object({

export type LoopTriggerEvent = z.infer<typeof loopTriggerEventSchema>;

/** Shared trigger definition (id + eventSchema) for use by public and server. */
/** Shared trigger definition for use by public and server. */
export const commonLoopTriggerDefinition: CommonTriggerDefinition = {
id: LOOP_TRIGGER_ID,
eventSchema: loopTriggerEventSchema,
title: i18n.translate('workflowsExtensionsExample.loopTrigger.title', {
defaultMessage: 'Loop trigger',
}),
description: i18n.translate('workflowsExtensionsExample.loopTrigger.description', {
defaultMessage:
'Emitted for the event-chain depth demo. Start or re-emit via POST to /api/workflows_extensions_example/emit_loop.',
}),
documentation: {
details: i18n.translate('workflowsExtensionsExample.loopTrigger.documentation.details', {
defaultMessage:
'Used to demonstrate the event-chain depth guardrail (workflowsExecutionEngine.eventDriven.maxChainDepth, default 10). Emit via the emit_loop endpoint; a workflow should use a kibana.request step to POST back to that endpoint with the next iteration so chain depth headers propagate until the guardrail stops scheduling.',
}),
examples: [
i18n.translate('workflowsExtensionsExample.loopTrigger.documentation.exampleStart', {
defaultMessage: `## Start the loop
\`\`\`bash
curl -X POST -u elastic:changeme -H 'Content-Type: application/json' \\
'http://localhost:5601/api/workflows_extensions_example/emit_loop' \\
-d '{}'
\`\`\`
(iteration defaults to 0.)`,
}),
],
},
snippets: {
condition: '',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,78 +7,13 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { i18n } from '@kbn/i18n';
import type { PublicTriggerDefinition } from '@kbn/workflows-extensions/public';
import React from 'react';
import {
CUSTOM_TRIGGER_ID,
commonCustomTriggerDefinition,
} from '../../common/triggers/custom_trigger';
import type { PublicTriggerDefinition } from '@kbn/workflows-extensions/public';
import { commonCustomTriggerDefinition } from '../../common/triggers/custom_trigger';

export const customTriggerPublicDefinition: PublicTriggerDefinition = {
...commonCustomTriggerDefinition,
title: i18n.translate('workflowsExtensionsExample.customTrigger.title', {
defaultMessage: 'Custom trigger',
}),
description: i18n.translate('workflowsExtensionsExample.customTrigger.description', {
defaultMessage:
'Emitted when a custom event occurs. Used by the workflows extensions example plugin.',
}),
icon: React.lazy(() =>
import('@elastic/eui/es/components/icon/assets/star').then(({ icon }) => ({ default: icon }))
),
documentation: {
details: i18n.translate('workflowsExtensionsExample.customTrigger.documentation.details', {
defaultMessage:
'Emitted when a custom event occurs. Events can include an optional category (e.g. alerts, notifications, audit, demo). In the `on` block, add a condition (KQL) to filter when this workflow runs using event properties: `event.category`, `event.message`, `event.source`.',
}),
examples: [
i18n.translate(
'workflowsExtensionsExample.customTrigger.documentation.exampleMatchCategory',
{
defaultMessage: `## Match by category (conditional subscription)
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.category: "alerts"'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}
),
i18n.translate('workflowsExtensionsExample.customTrigger.documentation.exampleMatchMessage', {
defaultMessage: `## Match any message
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.message: *'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}),
i18n.translate('workflowsExtensionsExample.customTrigger.documentation.exampleMatchSource', {
defaultMessage: `## Match events from a specific source
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.source: "api"'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}),
i18n.translate('workflowsExtensionsExample.customTrigger.documentation.exampleMatchError', {
defaultMessage: `## Match message containing "error"
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.message: *error*'
\`\`\``,
values: { triggerId: CUSTOM_TRIGGER_ID },
}),
],
},
snippets: {
condition: 'event.category: "alerts"',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,13 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { i18n } from '@kbn/i18n';
import type { PublicTriggerDefinition } from '@kbn/workflows-extensions/public';
import React from 'react';
import type { PublicTriggerDefinition } from '@kbn/workflows-extensions/public';
import { commonLoopTriggerDefinition } from '../../common/triggers/loop_trigger';

export const loopTriggerPublicDefinition: PublicTriggerDefinition = {
...commonLoopTriggerDefinition,
title: i18n.translate('workflowsExtensionsExample.loopTrigger.title', {
defaultMessage: 'Loop trigger',
}),
description: i18n.translate('workflowsExtensionsExample.loopTrigger.description', {
defaultMessage:
'Emitted for the event-chain depth demo. Start or re-emit via POST to /api/workflows_extensions_example/emit_loop.',
}),
icon: React.lazy(() =>
import('@elastic/eui/es/components/icon/assets/refresh').then(({ icon }) => ({ default: icon }))
),
documentation: {
details: i18n.translate('workflowsExtensionsExample.loopTrigger.documentation.details', {
defaultMessage:
'Used to demonstrate the event-chain depth guardrail (workflowsExecutionEngine.eventDriven.maxChainDepth, default 10). Emit via the emit_loop endpoint; a workflow should use a kibana.request step to POST back to that endpoint with the next iteration so chain depth headers propagate until the guardrail stops scheduling.',
}),
examples: [
i18n.translate('workflowsExtensionsExample.loopTrigger.documentation.exampleStart', {
defaultMessage: `## Start the loop
\`\`\`bash
curl -X POST -u elastic:changeme -H 'Content-Type: application/json' \\
'http://localhost:5601/api/workflows_extensions_example/emit_loop' \\
-d '{}'
\`\`\`
(iteration defaults to 0.)`,
}),
],
},
snippets: {
condition: '',
},
};
4 changes: 2 additions & 2 deletions src/platform/plugins/shared/workflows_extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ This separation ensures that:

The trigger registry follows the same pattern as steps:

- **Server-side registry**: Stores trigger definitions (id + `eventSchema` for payload validation). Other plugins register during `setup()`.
- **Public-side registry**: Stores UI definition (title, description, icon, documentation, snippets) so the workflows UI can display triggers and help users subscribe.
- **Server-side registry**: Stores trigger definitions (`id`, `eventSchema`, title, description, documentation, snippets). Other plugins register the shared **common** definition during `setup()`.
- **Public-side registry**: Spreads the same common definition and adds **icon** (and optional async loader) for the workflows UI.

**Async registration (public only):** The public trigger registry accepts either a definition or a **loader function** `() => Promise<PublicTriggerDefinition>`. Using a loader (e.g. `() => import('./my_trigger').then(m => m.myTriggerDefinition)`) keeps trigger modules and heavy deps out of your plugin's main bundle. Loaders are resolved in the background; `workflowsExtensions.isReady()` waits for both step and trigger loaders before the workflows UI renders.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
*/

export type { CommonStepDefinition } from './step_registry/types';
export type { CommonTriggerDefinition } from './trigger_registry/types';
export type {
CommonTriggerDefinition,
TriggerDocumentation,
TriggerSnippets,
} from './trigger_registry/types';
export { EVENT_FIELD_PREFIX } from './trigger_registry/constants';
export {
DataMapStepTypeId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,65 @@

import type { z } from '@kbn/zod/v4';

/**
* Documentation for a trigger (aligned with steps: details + examples).
*/
export interface TriggerDocumentation {
/**
* Detailed description with usage examples (markdown supported)
*/
details?: string;
/**
* Usage examples as markdown strings (e.g. "## Title\n```yaml\n...").
*/
examples?: string[];
}

/**
* Pre-filled snippet values when the user adds this trigger from the UI.
*/
export interface TriggerSnippets {
/**
* KQL condition pre-filled in the trigger's `on.condition` when the user adds this
* trigger from the UI (actions menu or YAML autocomplete).
* Must be valid KQL and only reference properties from the event schema (validated at registration).
*/
condition?: string;
}

/**
* Shared trigger contract (common to server and public).
*
* Constraints (enforced at registration):
* - id: globally unique, namespaced format <solution>.<event>
* - eventSchema: must be a Zod object schema that rejects unknown fields
*
* Server and agent tooling read title, description, documentation, and snippets from here.
* Public definitions spread this object and add UI-only fields (e.g. icon).
*/
export interface CommonTriggerDefinition<EventSchema extends z.ZodType = z.ZodType> {
/** Globally unique, namespaced identifier (e.g. cases.updated, alerts.severity_high) */
/** Globally unique, namespaced identifier (e.g. cases.updated, alerts.recovered) */
id: string;
/**
* Payload contract (Zod object schema; must reject unknown fields by default).
* Adding descriptions to properties (e.g. with .describe()) is recommended so they will
* help users to understand the data they will receive with the event.
*/
eventSchema: EventSchema;
/**
* Short human-readable name for this trigger (UI and agent catalog label).
*/
title?: string;
/**
* User-facing description of when this trigger is emitted.
*/
description?: string;
/**
* Documentation (details + YAML examples), aligned with step definitions.
*/
documentation?: TriggerDocumentation;
/**
* Pre-filled values for snippet insertion (e.g. on.condition).
*/
snippets?: TriggerSnippets;
}
Loading
Loading