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
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/alerting_v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Alerting V2 workflows_extensions integration
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add an entry for defining a step definition?


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.
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/alerting_v2/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"security",
"encryptedSavedObjects",
"workflowsManagement",
"workflowsExtensions",
"expressions",
"uiActions",
"fieldFormats",
Expand Down
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/alerting_v2/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
9 changes: 9 additions & 0 deletions x-pack/platform/plugins/shared/alerting_v2/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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({
Expand Down
Original file line number Diff line number Diff line change
@@ -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).
]);
}
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand we have two WorkflowExtensionsService ?

constructor(private readonly workflowsExtensions: WorkflowsExtensionsPublicPluginSetup) {}

public registerPublicTriggerDefinitions(triggerDefinitions: PublicTriggerDefinition[]): void {
triggerDefinitions.forEach((triggerDefinition) =>
this.workflowsExtensions.registerTriggerDefinition(triggerDefinition)
);
}
}
Original file line number Diff line number Diff line change
@@ -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<WorkflowExtensionsServiceContract>;
Original file line number Diff line number Diff line change
@@ -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 });
});
});
Original file line number Diff line number Diff line change
@@ -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<string, unknown>
): Promise<void>;
}

export class WorkflowExtensionsService implements WorkflowExtensionsServiceContract {
constructor(
private readonly workflowsExtensionsSetup: WorkflowsExtensionsServerPluginSetup,
private readonly getWorkflowsExtensionsStart: () => WorkflowsExtensionsServerPluginStart
) {}

public registerStepDefinitions(stepDefintions: ServerStepDefinitionOrLoader[]): void {
stepDefintions.forEach((stepDefinition) =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
stepDefintions.forEach((stepDefinition) =>
stepDefinitions.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<string, unknown>
): Promise<void> {
const client = await this.getWorkflowsExtensionsStart().getClient(request);
await client.emitEvent(triggerId, payload);
}
}
Original file line number Diff line number Diff line change
@@ -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).
]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<AlertingServerSetupDependencies['workflowsExtensions']>('workflowsExtensions')
);
const getWorkflowsExtensionsStart = () =>
get(
PluginStart<AlertingServerStartDependencies['workflowsExtensions']>('workflowsExtensions')
);
return new WorkflowExtensionsService(workflowsExtensionsSetup, getWorkflowsExtensionsStart);
})
.inSingletonScope();
bind(WorkflowExtensionsServiceToken).toService(WorkflowExtensionsService);
bind(ResourceManager).toSelf().inSingletonScope();

bind(EsServiceInternalToken)
Expand Down
6 changes: 6 additions & 0 deletions x-pack/platform/plugins/shared/alerting_v2/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -41,6 +45,7 @@ export interface AlertingServerSetupDependencies {
spaces: SpacesPluginSetup;
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
workflowsManagement: WorkflowsServerPluginSetup;
workflowsExtensions: WorkflowsExtensionsServerPluginSetup;
eventLog: IEventLogService;
usageCollection?: UsageCollectionSetup;
agentBuilder?: AgentBuilderPluginSetup;
Expand All @@ -54,4 +59,5 @@ export interface AlertingServerStartDependencies {
data: DataPluginStart;
security: SecurityPluginStart;
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
workflowsExtensions: WorkflowsExtensionsServerPluginStart;
}
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/alerting_v2/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading