diff --git a/x-pack/platform/plugins/shared/alerting_v2/README.md b/x-pack/platform/plugins/shared/alerting_v2/README.md index 22acd2cc4f547..df751dcd97157 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/README.md +++ b/x-pack/platform/plugins/shared/alerting_v2/README.md @@ -44,3 +44,4 @@ If you want implementation detail for one subsystem, continue with: - Notification matching, grouping, throttling, or delivery: start in [`server/lib/dispatcher/README.md`](server/lib/dispatcher/README.md) - Document shape or ES|QL views: start in [`server/resources/README.md`](server/resources/README.md) - API shape or saved object contracts: inspect `server/routes/` and `server/saved_objects/` together with the relevant subsystem docs +- Workflow triggers (workflows_extensions registration, server + public wiring): start in [`common/workflows_extensions/README.md`](common/workflows_extensions/README.md) diff --git a/x-pack/platform/plugins/shared/alerting_v2/common/workflows_extensions/README.md b/x-pack/platform/plugins/shared/alerting_v2/common/workflows_extensions/README.md new file mode 100644 index 0000000000000..c37bdd5ea92e6 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/common/workflows_extensions/README.md @@ -0,0 +1,42 @@ +# Alerting V2 workflows_extensions integration + +This document explains how to register workflow triggers in `alerting_v2` using the new wrapper services. + +## Architecture + +- `server/lib/services/workflow_extensions_service/workflow_extensions_service.ts` + - Wraps `WorkflowsExtensionsServerPluginSetup` and `WorkflowsExtensionsServerPluginStart`. + - Use `registerTriggerDefinitions(...)` and `registerStepDefinitions(...)` during setup. + - Use `emitEvent(...)` at runtime when you need to emit a trigger event. +- `public/services/workflow_extensions_service.ts` + - Wraps `WorkflowsExtensionsPublicPluginSetup`. + - Use `registerPublicTriggerDefinitions(...)` during public setup to register trigger UI metadata. +- `public/lib/workflow_extensions/register_trigger_definitions.ts` + - Exports `registerTriggerDefinitions(service)`; calls `registerPublicTriggerDefinitions([...])`. + +## Typical trigger setup flow + +1. Define a shared trigger definition in `common` (`id` + `eventSchema`) with Zod. +2. Register it on the server in `server/lib/workflow_extensions/register_trigger_definitions.ts` via `registerTriggerDefinitions(service)`, which calls `WorkflowExtensionsService.registerTriggerDefinitions(...)`. +3. Define a public trigger definition (`PublicTriggerDefinition`) with UI metadata (title, description, icon, docs). +4. Register it on the public side in `public/lib/workflow_extensions/register_trigger_definitions.ts` via `registerTriggerDefinitions(service)`. +5. Add/update the trigger schema hash in: + - `src/platform/plugins/shared/workflows_extensions/test/scout/api/fixtures/approved_trigger_definitions.ts` + +## Server usage + +`bind_on_setup.ts` calls `registerTriggerDefinitions(container.get(WorkflowExtensionsService))` from `server/lib/workflow_extensions/register_trigger_definitions.ts`. + +Add your `ServerTriggerDefinition` entries to the array inside that function. + +## Public usage + +`public/index.ts` calls `registerTriggerDefinitions(container.get(WorkflowExtensionsService))` from `public/lib/workflow_extensions/register_trigger_definitions.ts`. + +Add your `PublicTriggerDefinition` entries to the array inside that function. + +## Notes + +- Registration should happen in setup, not start. +- Keep server and public trigger IDs aligned. +- Public registration controls Workflows UI discoverability; server registration controls runtime validation/execution. diff --git a/x-pack/platform/plugins/shared/alerting_v2/kibana.jsonc b/x-pack/platform/plugins/shared/alerting_v2/kibana.jsonc index 4f98e2190f87e..097b0abf45d82 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/kibana.jsonc +++ b/x-pack/platform/plugins/shared/alerting_v2/kibana.jsonc @@ -21,6 +21,7 @@ "security", "encryptedSavedObjects", "workflowsManagement", + "workflowsExtensions", "expressions", "uiActions", "fieldFormats", diff --git a/x-pack/platform/plugins/shared/alerting_v2/moon.yml b/x-pack/platform/plugins/shared/alerting_v2/moon.yml index 6c2df3a6956a5..17eede6a86745 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/moon.yml +++ b/x-pack/platform/plugins/shared/alerting_v2/moon.yml @@ -68,6 +68,7 @@ dependsOn: - '@kbn/encrypted-saved-objects-plugin' - '@kbn/event-log-plugin' - '@kbn/workflows-management-plugin' + - '@kbn/workflows-extensions' - '@kbn/workflows' - '@kbn/core-http-server-utils' - '@kbn/eval-kql' diff --git a/x-pack/platform/plugins/shared/alerting_v2/public/index.ts b/x-pack/platform/plugins/shared/alerting_v2/public/index.ts index 0e22c5969bb71..78469bfc0cb1b 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/public/index.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/public/index.ts @@ -27,6 +27,8 @@ import { ALERTING_V2_EXPERIMENTAL_FEATURES_SETTING_ID } from '../common/advanced import { ActionPoliciesApi } from './services/action_policies_api'; import { RulesApi } from './services/rules_api'; import { WorkflowsApi } from './services/workflows_api'; +import { WorkflowExtensionsService } from './services/workflow_extensions_service'; +import { registerTriggerDefinitions } from './lib/workflow_extensions/register_trigger_definitions'; import { setKibanaServices } from './kibana_services'; import { DynamicRuleFormFlyout } from './create_rule_form_flyout'; import type { AlertingV2PublicStart } from './types'; @@ -38,12 +40,19 @@ export const module = new ContainerModule(({ bind }) => { bind(RulesApi).toSelf().inSingletonScope(); bind(ActionPoliciesApi).toSelf().inSingletonScope(); bind(WorkflowsApi).toSelf().inSingletonScope(); + bind(WorkflowExtensionsService) + .toDynamicValue( + ({ get }) => new WorkflowExtensionsService(get(PluginSetup('workflowsExtensions'))) + ) + .inSingletonScope(); bind(Start).toConstantValue({ DynamicRuleFormFlyout, } satisfies AlertingV2PublicStart); bind(OnSetup).toConstantValue((container) => { const getStartServices = container.get(CoreSetup('getStartServices')); + registerTriggerDefinitions(container.get(WorkflowExtensionsService)); + getStartServices().then(([coreStart]) => { const diContainer = coreStart.injection.getContainer(); setKibanaServices({ diff --git a/x-pack/platform/plugins/shared/alerting_v2/public/lib/workflow_extensions/register_trigger_definitions.ts b/x-pack/platform/plugins/shared/alerting_v2/public/lib/workflow_extensions/register_trigger_definitions.ts new file mode 100644 index 0000000000000..a8ba884d0bc31 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/public/lib/workflow_extensions/register_trigger_definitions.ts @@ -0,0 +1,20 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { WorkflowExtensionsPublicServiceContract } from '../../services/workflow_extensions_service'; + +/** + * Registers all alerting-v2 public workflow trigger definitions (UI metadata). + * Call once during plugin setup with the resolved {@link WorkflowExtensionsService}. + */ +export function registerTriggerDefinitions( + workflowExtensionsService: WorkflowExtensionsPublicServiceContract +): void { + workflowExtensionsService.registerPublicTriggerDefinitions([ + // Add PublicTriggerDefinition entries here (spread common id + eventSchema + title, icon, docs). + ]); +} diff --git a/x-pack/platform/plugins/shared/alerting_v2/public/services/workflow_extensions_service.ts b/x-pack/platform/plugins/shared/alerting_v2/public/services/workflow_extensions_service.ts new file mode 100644 index 0000000000000..e21dba3d876eb --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/public/services/workflow_extensions_service.ts @@ -0,0 +1,23 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { WorkflowsExtensionsPublicPluginSetup } from '@kbn/workflows-extensions/public'; +import type { PublicTriggerDefinition } from '@kbn/workflows-extensions/public'; + +export interface WorkflowExtensionsPublicServiceContract { + registerPublicTriggerDefinitions(triggerDefinitions: PublicTriggerDefinition[]): void; +} + +export class WorkflowExtensionsService implements WorkflowExtensionsPublicServiceContract { + constructor(private readonly workflowsExtensions: WorkflowsExtensionsPublicPluginSetup) {} + + public registerPublicTriggerDefinitions(triggerDefinitions: PublicTriggerDefinition[]): void { + triggerDefinitions.forEach((triggerDefinition) => + this.workflowsExtensions.registerTriggerDefinition(triggerDefinition) + ); + } +} diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/tokens.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/tokens.ts new file mode 100644 index 0000000000000..398a706688501 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/tokens.ts @@ -0,0 +1,13 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ServiceIdentifier } from 'inversify'; +import type { WorkflowExtensionsServiceContract } from './workflow_extensions_service'; + +export const WorkflowExtensionsServiceToken = Symbol.for( + 'alerting_v2.WorkflowExtensionsService' +) as ServiceIdentifier; diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/workflow_extensions_service.test.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/workflow_extensions_service.test.ts new file mode 100644 index 0000000000000..43280ddf41b86 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/workflow_extensions_service.test.ts @@ -0,0 +1,58 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from '@kbn/zod/v4'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import { workflowsExtensionsMock } from '@kbn/workflows-extensions/server/mocks'; +import { WorkflowExtensionsService } from './workflow_extensions_service'; + +function createService() { + const setup = workflowsExtensionsMock.createSetup(); + const start = workflowsExtensionsMock.createStart(); + const service = new WorkflowExtensionsService(setup, () => start); + return { service, setup, start }; +} + +describe('WorkflowExtensionsService', () => { + it('registerTriggerDefinitions delegates to workflows extensions setup', () => { + const { service, setup } = createService(); + const triggerDefinition = { + id: 'alerting-v2-unit-test.trigger' as const, + eventSchema: z.object({ key: z.string() }), + }; + + service.registerTriggerDefinitions([triggerDefinition]); + + expect(setup.registerTriggerDefinition).toHaveBeenCalledTimes(1); + expect(setup.registerTriggerDefinition).toHaveBeenCalledWith(triggerDefinition); + }); + + it('registerStepDefinitions delegates to workflows extensions setup', () => { + const { service, setup } = createService(); + const stepLoader = () => Promise.resolve(undefined); + + service.registerStepDefinitions([stepLoader]); + + expect(setup.registerStepDefinition).toHaveBeenCalledTimes(1); + expect(setup.registerStepDefinition).toHaveBeenCalledWith(stepLoader); + }); + + it('emitEvent resolves the workflows client and delegates emitEvent', async () => { + const { service, start } = createService(); + const emitEvent = jest.fn().mockResolvedValue(undefined); + start.getClient.mockResolvedValue({ + isWorkflowsAvailable: true, + emitEvent, + }); + const request = httpServerMock.createKibanaRequest(); + + await service.emitEvent(request, 'some.trigger', { a: 1 }); + + expect(start.getClient).toHaveBeenCalledWith(request); + expect(emitEvent).toHaveBeenCalledWith('some.trigger', { a: 1 }); + }); +}); diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/workflow_extensions_service.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/workflow_extensions_service.ts new file mode 100644 index 0000000000000..995ca5f4d95c9 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/services/workflow_extensions_service/workflow_extensions_service.ts @@ -0,0 +1,59 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaRequest } from '@kbn/core/server'; +import type { + WorkflowsExtensionsServerPluginSetup, + WorkflowsExtensionsServerPluginStart, + ServerTriggerDefinition, +} from '@kbn/workflows-extensions/server'; + +type ServerStepDefinitionOrLoader = Parameters< + WorkflowsExtensionsServerPluginSetup['registerStepDefinition'] +>[0]; + +export interface WorkflowExtensionsServiceContract { + /** + * Registers all alerting-v2-owned workflow triggers and steps. Call once during + * plugin setup (see bind_on_setup). + */ + registerTriggerDefinitions(triggerDefinitions: ServerTriggerDefinition[]): void; + registerStepDefinitions(stepDefintions: ServerStepDefinitionOrLoader[]): void; + emitEvent( + request: KibanaRequest, + triggerId: string, + payload: Record + ): Promise; +} + +export class WorkflowExtensionsService implements WorkflowExtensionsServiceContract { + constructor( + private readonly workflowsExtensionsSetup: WorkflowsExtensionsServerPluginSetup, + private readonly getWorkflowsExtensionsStart: () => WorkflowsExtensionsServerPluginStart + ) {} + + public registerStepDefinitions(stepDefintions: ServerStepDefinitionOrLoader[]): void { + stepDefintions.forEach((stepDefinition) => + this.workflowsExtensionsSetup.registerStepDefinition(stepDefinition) + ); + } + + public registerTriggerDefinitions(triggerDefinitions: ServerTriggerDefinition[]): void { + triggerDefinitions.forEach((triggerDefinition) => + this.workflowsExtensionsSetup.registerTriggerDefinition(triggerDefinition) + ); + } + + public async emitEvent( + request: KibanaRequest, + triggerId: string, + payload: Record + ): Promise { + const client = await this.getWorkflowsExtensionsStart().getClient(request); + await client.emitEvent(triggerId, payload); + } +} diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/lib/workflow_extensions/register_trigger_definitions.ts b/x-pack/platform/plugins/shared/alerting_v2/server/lib/workflow_extensions/register_trigger_definitions.ts new file mode 100644 index 0000000000000..7461468353dc2 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting_v2/server/lib/workflow_extensions/register_trigger_definitions.ts @@ -0,0 +1,20 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { WorkflowExtensionsServiceContract } from '../services/workflow_extensions_service/workflow_extensions_service'; + +/** + * Registers all alerting-v2 server-side workflow trigger definitions. + * Call once during plugin setup with the resolved {@link WorkflowExtensionsService}. + */ +export function registerTriggerDefinitions( + workflowExtensionsService: WorkflowExtensionsServiceContract +): void { + workflowExtensionsService.registerTriggerDefinitions([ + // Add CommonTriggerDefinition-backed entries here (import from common when added). + ]); +} diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_on_setup.ts b/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_on_setup.ts index 276ad2ee107c2..9c6ec1124d5db 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_on_setup.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_on_setup.ts @@ -23,6 +23,8 @@ import { createRuleSmlType } from '../agent_builder/sml/rule_sml_type'; import { registerSkills } from '../agent_builder/skills/register_skills'; import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects'; import { EventLoggerToken } from '../lib/services/event_log_service/tokens'; +import { WorkflowExtensionsService } from '../lib/services/workflow_extensions_service/workflow_extensions_service'; +import { registerTriggerDefinitions } from '../lib/workflow_extensions/register_trigger_definitions'; import { ACTION_POLICY_EVENT_ACTIONS, ACTION_POLICY_EVENT_PROVIDER, @@ -71,6 +73,8 @@ export function bindOnSetup({ bind }: ContainerModuleLoadOptions) { }); container.bind(EventLoggerToken).toConstantValue(eventLogger); + registerTriggerDefinitions(container.get(WorkflowExtensionsService)); + // Trigger task registration via onActivation callbacks container.getAll(TaskDefinition); diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_services.ts b/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_services.ts index b92a1364a0205..c756539aa5d07 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_services.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/setup/bind_services.ts @@ -58,6 +58,8 @@ import { TaskRunnerFactoryToken, } from '../lib/services/task_run_scope_service/create_task_runner'; import { UserService } from '../lib/services/user_service/user_service'; +import { WorkflowExtensionsService } from '../lib/services/workflow_extensions_service/workflow_extensions_service'; +import { WorkflowExtensionsServiceToken } from '../lib/services/workflow_extensions_service/tokens'; import { ApiKeyServiceSavedObjectsClientToken } from '../lib/services/api_key_service/tokens'; import { API_KEY_PENDING_INVALIDATION_TYPE, @@ -108,6 +110,19 @@ export function bindServices({ bind }: ContainerModuleLoadOptions) { bind(LoggerServiceToken).toService(LoggerService); bind(EventLogService).toSelf().inSingletonScope(); bind(EventLogServiceToken).toService(EventLogService); + bind(WorkflowExtensionsService) + .toDynamicValue(({ get }) => { + const workflowsExtensionsSetup = get( + PluginSetup('workflowsExtensions') + ); + const getWorkflowsExtensionsStart = () => + get( + PluginStart('workflowsExtensions') + ); + return new WorkflowExtensionsService(workflowsExtensionsSetup, getWorkflowsExtensionsStart); + }) + .inSingletonScope(); + bind(WorkflowExtensionsServiceToken).toService(WorkflowExtensionsService); bind(ResourceManager).toSelf().inSingletonScope(); bind(EsServiceInternalToken) diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/types.ts b/x-pack/platform/plugins/shared/alerting_v2/server/types.ts index 3e91f0ddf9801..90aef20df01c7 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/types.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/types.ts @@ -21,6 +21,10 @@ import type { EncryptedSavedObjectsPluginStart, } from '@kbn/encrypted-saved-objects-plugin/server'; import type { WorkflowsServerPluginSetup } from '@kbn/workflows-management-plugin/server'; +import type { + WorkflowsExtensionsServerPluginSetup, + WorkflowsExtensionsServerPluginStart, +} from '@kbn/workflows-extensions/server'; import type { IEventLogService } from '@kbn/event-log-plugin/server'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import type { AgentBuilderPluginSetup } from '@kbn/agent-builder-plugin/server'; @@ -41,6 +45,7 @@ export interface AlertingServerSetupDependencies { spaces: SpacesPluginSetup; encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; workflowsManagement: WorkflowsServerPluginSetup; + workflowsExtensions: WorkflowsExtensionsServerPluginSetup; eventLog: IEventLogService; usageCollection?: UsageCollectionSetup; agentBuilder?: AgentBuilderPluginSetup; @@ -54,4 +59,5 @@ export interface AlertingServerStartDependencies { data: DataPluginStart; security: SecurityPluginStart; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; + workflowsExtensions: WorkflowsExtensionsServerPluginStart; } diff --git a/x-pack/platform/plugins/shared/alerting_v2/tsconfig.json b/x-pack/platform/plugins/shared/alerting_v2/tsconfig.json index 3e974db56d906..3fd9c6b3fe6ec 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/tsconfig.json +++ b/x-pack/platform/plugins/shared/alerting_v2/tsconfig.json @@ -64,6 +64,7 @@ "@kbn/encrypted-saved-objects-plugin", "@kbn/event-log-plugin", "@kbn/workflows-management-plugin", + "@kbn/workflows-extensions", "@kbn/workflows", "@kbn/core-http-server-utils", "@kbn/eval-kql",