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
38 changes: 20 additions & 18 deletions x-pack/platform/plugins/shared/alerting_v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ If you want the system architecture, read [`server/README.md`](server/README.md)

If you want implementation detail for one subsystem, continue with:

| Area | Document |
| --- | --- |
| High-level plugin architecture | [`server/README.md`](server/README.md) |
| Rule execution pipeline | [`server/lib/rule_executor/README.md`](server/lib/rule_executor/README.md) |
| Alert lifecycle / episodes | [`server/lib/director/README.md`](server/lib/director/README.md) |
| Notification dispatch pipeline | [`server/lib/dispatcher/README.md`](server/lib/dispatcher/README.md) |
| Elasticsearch resources and schemas | [`server/resources/README.md`](server/resources/README.md) |
| Area | Document |
| ----------------------------------- | -------------------------------------------------------------------------- |
| High-level plugin architecture | [`server/README.md`](server/README.md) |
| Rule execution pipeline | [`server/lib/rule_executor/README.md`](server/lib/rule_executor/README.md) |
| Alert lifecycle / episodes | [`server/lib/director/README.md`](server/lib/director/README.md) |
| Notification dispatch pipeline | [`server/lib/dispatcher/README.md`](server/lib/dispatcher/README.md) |
| Elasticsearch resources and schemas | [`server/resources/README.md`](server/resources/README.md) |

## Repository guide

| Path | Purpose |
| --- | --- |
| `public/` | Management UI for rules, alerts/episodes, and notification policies. |
| `server/routes/` | HTTP APIs for rules, alert actions, notification policies, and suggestions. |
| `server/saved_objects/` | Persisted models and mappings for rules and notification policies. |
| `server/lib/rule_executor/` | Per-rule Task Manager execution pipeline. |
| `server/lib/director/` | Episode state engine for alert rules. |
| `server/lib/dispatcher/` | Asynchronous notification matching, throttling, and delivery. |
| `server/resources/` | Data streams and ES|QL views (`.rule-events`, `.alert-actions`, alert episode views). |
| `server/setup/` | Dependency injection, task registration, routes, and plugin startup wiring. |
| Path | Purpose |
| --------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `public/` | Management UI for rules, alerts/episodes, and notification policies. |
| `server/routes/` | HTTP APIs for rules, alert actions, notification policies, and suggestions. |
| `server/saved_objects/` | Persisted models and mappings for rules and notification policies. |
| `server/lib/rule_executor/` | Per-rule Task Manager execution pipeline. |
| `server/lib/director/` | Episode state engine for alert rules. |
| `server/lib/dispatcher/` | Asynchronous notification matching, throttling, and delivery. |
| `server/resources/` | Data streams and ES | QL views (`.rule-events`, `.alert-actions`, alert episode views). |
| `server/setup/` | Dependency injection, task registration, routes, and plugin startup wiring. |

## Quick mental model

Expand All @@ -44,4 +44,6 @@ 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)
- Workflow triggers (workflows_extensions registration + runtime emission): see the public and server READMEs
- [`public/lib/workflow_extensions/README.md`](public/lib/workflow_extensions/README.md)
- [`server/lib/services/workflow_extensions_service/README.md`](server/lib/services/workflow_extensions_service/README.md)

This file was deleted.

12 changes: 5 additions & 7 deletions x-pack/platform/plugins/shared/alerting_v2/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type { AgentBuilderPluginStart } from '@kbn/agent-builder-plugin/public';
import type { WorkflowsExtensionsPublicPluginSetup } from '@kbn/workflows-extensions/public';
import {
ALERTING_V2_SECTION_ID,
ALERTING_V2_RULES_APP_ID,
Expand All @@ -27,7 +28,6 @@ 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';
Expand All @@ -40,18 +40,16 @@ 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'));
const workflowsExtensionsSetup = container.get(
PluginSetup('workflowsExtensions')
) as WorkflowsExtensionsPublicPluginSetup;

registerTriggerDefinitions(container.get(WorkflowExtensionsService));
registerTriggerDefinitions(workflowsExtensionsSetup);

getStartServices().then(([coreStart]) => {
const diContainer = coreStart.injection.getContainer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Alerting V2 — public workflow extensions

This folder owns the **public-side (browser) registration** of `alerting_v2` workflow trigger UI metadata with the `workflows_extensions` plugin.

## Why this exists separately from the server

`workflows_extensions` exposes two contracts that need to be populated at setup time:

- **Public setup contract** (`WorkflowsExtensionsPublicPluginSetup`) — UI metadata for the Workflows builder (title, description, icon, docs, snippets). This is what controls discoverability of a trigger in the Workflows UI.
- **Server setup contract** (`WorkflowsExtensionsServerPluginSetup`) — runtime validation/execution of triggers and steps.

This README covers the **public** side. For the server side (where runtime emission also lives), see [`server/lib/services/workflow_extensions_service/README.md`](../../../server/lib/services/workflow_extensions_service/README.md).

## Registering a new trigger

Add public trigger definitions in [`register_trigger_definitions.ts`](register_trigger_definitions.ts).

The helper receives the `workflowsExtensions` setup contract and registers every `PublicTriggerDefinition`:

```ts
export function registerTriggerDefinitions(
workflowsExtensions: WorkflowsExtensionsPublicPluginSetup
): void {
const triggerDefinitions: PublicTriggerDefinition[] = [
// Add trigger UI metadata here.
];

for (const definition of triggerDefinitions) {
workflowsExtensions.registerTriggerDefinition(definition);
}
}
```

When adding a trigger:

1. Define the shared trigger (`id` + Zod `eventSchema`) in `common/`.
2. Register it on the **server** via the server-side `registerTriggerDefinitions` helper (so the runtime validates and dispatches it).
3. Add the public `PublicTriggerDefinition` to this file's `triggerDefinitions` array.
4. Update the trigger schema hash fixture in `src/platform/plugins/shared/workflows_extensions/test/scout/api/fixtures/approved_trigger_definitions.ts`.
5. Keep the server and public trigger ids aligned.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@
* 2.0.
*/

import type { WorkflowExtensionsPublicServiceContract } from '../../services/workflow_extensions_service';
import type {
PublicTriggerDefinition,
WorkflowsExtensionsPublicPluginSetup,
} from '@kbn/workflows-extensions/public';

/**
* Registers all alerting-v2 public workflow trigger definitions (UI metadata).
* Call once during plugin setup with the resolved {@link WorkflowExtensionsService}.
* Call once during plugin setup with the `workflowsExtensions` setup contract.
*/
export function registerTriggerDefinitions(
workflowExtensionsService: WorkflowExtensionsPublicServiceContract
workflowsExtensions: WorkflowsExtensionsPublicPluginSetup
): void {
workflowExtensionsService.registerPublicTriggerDefinitions([
const triggerDefinitions: PublicTriggerDefinition[] = [
// Add PublicTriggerDefinition entries here (spread common id + eventSchema + title, icon, docs).
]);
];

for (const definition of triggerDefinitions) {
workflowsExtensions.registerTriggerDefinition(definition);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Alerting V2 server

This README explains how `alerting_v2` integrates with `workflows_extensions` on the server:

- Registering workflow triggers during setup.
- Emitting workflow trigger events at request time.

For public trigger UI metadata, see [`public/lib/workflow_extensions/README.md`](../../../../public/lib/workflow_extensions/README.md).

## Registering a new trigger

Add server-side trigger definitions in [`server/lib/workflow_extensions/register_trigger_definitions.ts`](../../workflow_extensions/register_trigger_definitions.ts).

The helper receives the `workflowsExtensions` setup contract and registers every `ServerTriggerDefinition`:

```ts
export function registerTriggerDefinitions(
workflowsExtensions: WorkflowsExtensionsServerPluginSetup
): void {
const triggerDefinitions: ServerTriggerDefinition[] = [
// Add trigger definitions here.
];

for (const definition of triggerDefinitions) {
workflowsExtensions.registerTriggerDefinition(definition);
}
}
```

This helper is called from [`server/setup/bind_on_setup.ts`](../../../setup/bind_on_setup.ts) inside the `OnSetup` callback, so registration happens during plugin setup.

When adding a trigger:

1. Define the shared trigger id and event schema in `common/`.
2. Add the server `ServerTriggerDefinition` to `register_trigger_definitions.ts`.
3. Add the matching public `PublicTriggerDefinition` so the trigger appears in the Workflows UI.
4. Keep the server and public trigger ids aligned.

## Emitting an event

Runtime code should emit events through `WorkflowExtensionsService`.

Inject `WorkflowExtensionsServiceToken` into a request-scoped service or consumer:

```ts
@injectable()
class SomeRequestScopedConsumer {
constructor(
@inject(WorkflowExtensionsServiceToken)
private readonly workflowExtensions: WorkflowExtensionsServiceContract
) {}

async run() {
await this.workflowExtensions.emitEvent('alerting_v2.some-trigger', { ruleId: '…' });
}
}
```

The service exposes a single method:

```ts
emitEvent(triggerId: string, payload: Record<string, unknown>): Promise<void>
```

The service uses a request-scoped `WorkflowsClient`, so emitted events include the current request auth context.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
*/

import type { ServiceIdentifier } from 'inversify';
import type { WorkflowsClient } from '@kbn/workflows/server/types';
import type { WorkflowExtensionsServiceContract } from './workflow_extensions_service';

export const WorkflowExtensionsServiceToken = Symbol.for(
'alerting_v2.WorkflowExtensionsService'
) as ServiceIdentifier<WorkflowExtensionsServiceContract>;

export const WorkflowsClientToken = Symbol.for(
'alerting_v2.WorkflowsClient'
) as ServiceIdentifier<WorkflowsClient>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,24 @@
* 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();
it('emitEvent delegates to the injected workflows client', async () => {
const start = workflowsExtensionsMock.createStart();
const emitEvent = jest.fn().mockResolvedValue(undefined);
start.getClient.mockResolvedValue({
isWorkflowsAvailable: true,
emitEvent,
});
const request = httpServerMock.createKibanaRequest();

await service.emitEvent(request, 'some.trigger', { a: 1 });
const client = await start.getClient(httpServerMock.createKibanaRequest());
const service = new WorkflowExtensionsService(client);

await service.emitEvent('some.trigger', { a: 1 });

expect(start.getClient).toHaveBeenCalledWith(request);
expect(emitEvent).toHaveBeenCalledWith('some.trigger', { a: 1 });
});
});
Loading
Loading