Skip to content

Commit 2714f6f

Browse files
committed
Create the alert action pub/sub
1 parent 068fedd commit 2714f6f

32 files changed

Lines changed: 1167 additions & 247 deletions

x-pack/platform/plugins/shared/alerting_v2/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ If you want implementation detail for one subsystem, continue with:
4646
- API shape or saved object contracts: inspect `server/routes/` and `server/saved_objects/` together with the relevant subsystem docs
4747
- Workflow triggers (workflows_extensions registration + runtime emission): see the public and server READMEs
4848
- [`public/lib/workflow_extensions/README.md`](public/lib/workflow_extensions/README.md)
49-
- [`server/lib/services/workflow_extensions_service/README.md`](server/lib/services/workflow_extensions_service/README.md)
49+
- [`server/lib/services/workflow_service/README.md`](server/lib/services/workflow_service/README.md)

x-pack/platform/plugins/shared/alerting_v2/public/lib/workflow_extensions/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This folder owns the **public-side (browser) registration** of `alerting_v2` wor
99
- **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.
1010
- **Server setup contract** (`WorkflowsExtensionsServerPluginSetup`) — runtime validation/execution of triggers and steps.
1111

12-
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).
12+
This README covers the **public** side. For the server side (where runtime emission also lives), see [`server/lib/services/workflow_service/README.md`](../../../server/lib/services/workflow_service/README.md).
1313

1414
## Registering a new trigger
1515

x-pack/platform/plugins/shared/alerting_v2/server/lib/events/alert_action_event_publisher/alert_action_event_publisher.mock.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import type { EventBus } from '../event_bus';
99
import { createEventBusMock } from '../event_bus/event_bus.mock';
10-
import type { AlertingDomainEvent } from '../domain_events';
10+
import type { AlertingDomainEvent, AlertingPublisherContext } from '../domain_events';
1111
import { AlertActionEventPublisher } from './alert_action_event_publisher';
1212

1313
/**
@@ -17,9 +17,9 @@ import { AlertActionEventPublisher } from './alert_action_event_publisher';
1717
*/
1818
export function createAlertActionEventPublisher(): {
1919
publisher: AlertActionEventPublisher;
20-
eventBus: jest.Mocked<EventBus<AlertingDomainEvent>>;
20+
eventBus: jest.Mocked<EventBus<AlertingDomainEvent, AlertingPublisherContext>>;
2121
} {
22-
const eventBus = createEventBusMock<AlertingDomainEvent>();
22+
const eventBus = createEventBusMock<AlertingDomainEvent, AlertingPublisherContext>();
2323

2424
return {
2525
publisher: new AlertActionEventPublisher(eventBus),

x-pack/platform/plugins/shared/alerting_v2/server/lib/events/alert_action_event_publisher/alert_action_event_publisher.test.ts

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

8+
import type { KibanaRequest } from '@kbn/core/server';
9+
import { httpServerMock } from '@kbn/core-http-server-mocks';
810
import type { EventBus } from '../event_bus';
9-
import type { AlertingDomainEvent } from '../domain_events';
11+
import type { AlertingDomainEvent, AlertingPublisherContext } from '../domain_events';
1012
import { createAlertActionEventPublisher } from './alert_action_event_publisher.mock';
1113
import type { AlertActionEventPublisher } from './alert_action_event_publisher';
1214
import { EPISODE_ASSIGNED_EVENT_TYPE } from './events';
@@ -15,10 +17,12 @@ describe('AlertActionEventPublisher', () => {
1517
jest.useFakeTimers().setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
1618

1719
let publisher: AlertActionEventPublisher;
18-
let eventBus: jest.Mocked<EventBus<AlertingDomainEvent>>;
20+
let eventBus: jest.Mocked<EventBus<AlertingDomainEvent, AlertingPublisherContext>>;
21+
let request: KibanaRequest;
1922

2023
beforeEach(() => {
2124
({ publisher, eventBus } = createAlertActionEventPublisher());
25+
request = httpServerMock.createKibanaRequest();
2226
});
2327

2428
afterEach(() => {
@@ -30,8 +34,8 @@ describe('AlertActionEventPublisher', () => {
3034
});
3135

3236
describe('emitEpisodeAssigned', () => {
33-
it('publishes an `episode.assigned` event with the canonical envelope and payload shape', () => {
34-
publisher.emitEpisodeAssigned({
37+
it('publishes an `episode.assigned` event with the canonical envelope and payload shape, alongside the publishing KibanaRequest as bus context', () => {
38+
publisher.emitEpisodeAssigned(request, {
3539
groupHash: 'group-hash-1',
3640
episodeId: 'episode-1',
3741
ruleId: 'rule-1',
@@ -42,22 +46,28 @@ describe('AlertActionEventPublisher', () => {
4246
});
4347

4448
expect(eventBus.publish).toHaveBeenCalledTimes(1);
45-
expect(eventBus.publish).toHaveBeenCalledWith({
46-
type: EPISODE_ASSIGNED_EVENT_TYPE,
47-
occurredAt: '2025-02-02T12:34:56.000Z',
48-
groupHash: 'group-hash-1',
49-
episodeId: 'episode-1',
50-
ruleId: 'rule-1',
51-
spaceId: 'default',
52-
actorUid: 'actor-uid-1',
53-
payload: {
54-
assigneeUid: 'user-uid-1',
49+
expect(eventBus.publish).toHaveBeenCalledWith(
50+
{
51+
type: EPISODE_ASSIGNED_EVENT_TYPE,
52+
occurredAt: '2025-02-02T12:34:56.000Z',
53+
groupHash: 'group-hash-1',
54+
episodeId: 'episode-1',
55+
ruleId: 'rule-1',
56+
spaceId: 'default',
57+
actorUid: 'actor-uid-1',
58+
payload: {
59+
assigneeUid: 'user-uid-1',
60+
},
5561
},
56-
});
62+
{ request }
63+
);
64+
// Reference equality: the publisher must not wrap or rebuild the request.
65+
expect(eventBus.publish.mock.calls[0][1]).toEqual({ request });
66+
expect(eventBus.publish.mock.calls[0][1]?.request).toBe(request);
5767
});
5868

5969
it('defaults `occurredAt` to the current ISO timestamp when omitted', () => {
60-
publisher.emitEpisodeAssigned({
70+
publisher.emitEpisodeAssigned(request, {
6171
groupHash: 'group-hash-1',
6272
episodeId: 'episode-1',
6373
ruleId: 'rule-1',
@@ -67,12 +77,13 @@ describe('AlertActionEventPublisher', () => {
6777
});
6878

6979
expect(eventBus.publish).toHaveBeenCalledWith(
70-
expect.objectContaining({ occurredAt: '2026-01-01T00:00:00.000Z' })
80+
expect.objectContaining({ occurredAt: '2026-01-01T00:00:00.000Z' }),
81+
{ request }
7182
);
7283
});
7384

7485
it('preserves a null assigneeUid in the payload (unassign case)', () => {
75-
publisher.emitEpisodeAssigned({
86+
publisher.emitEpisodeAssigned(request, {
7687
groupHash: 'group-hash-1',
7788
episodeId: 'episode-1',
7889
ruleId: 'rule-1',
@@ -82,12 +93,13 @@ describe('AlertActionEventPublisher', () => {
8293
});
8394

8495
expect(eventBus.publish).toHaveBeenCalledWith(
85-
expect.objectContaining({ payload: { assigneeUid: null } })
96+
expect.objectContaining({ payload: { assigneeUid: null } }),
97+
{ request }
8698
);
8799
});
88100

89101
it('preserves a null actorUid on the envelope (internal / system actor case)', () => {
90-
publisher.emitEpisodeAssigned({
102+
publisher.emitEpisodeAssigned(request, {
91103
groupHash: 'group-hash-1',
92104
episodeId: 'episode-1',
93105
ruleId: 'rule-1',
@@ -96,7 +108,35 @@ describe('AlertActionEventPublisher', () => {
96108
actorUid: null,
97109
});
98110

99-
expect(eventBus.publish).toHaveBeenCalledWith(expect.objectContaining({ actorUid: null }));
111+
expect(eventBus.publish).toHaveBeenCalledWith(expect.objectContaining({ actorUid: null }), {
112+
request,
113+
});
114+
});
115+
116+
it('uses the request supplied per emit call (publisher is request-agnostic across invocations)', () => {
117+
const otherRequest = httpServerMock.createKibanaRequest();
118+
119+
publisher.emitEpisodeAssigned(request, {
120+
groupHash: 'group-hash-1',
121+
episodeId: 'episode-1',
122+
ruleId: 'rule-1',
123+
spaceId: 'default',
124+
assigneeUid: 'user-uid-1',
125+
actorUid: 'actor-uid-1',
126+
});
127+
128+
publisher.emitEpisodeAssigned(otherRequest, {
129+
groupHash: 'group-hash-2',
130+
episodeId: 'episode-2',
131+
ruleId: 'rule-2',
132+
spaceId: 'default',
133+
assigneeUid: 'user-uid-2',
134+
actorUid: 'actor-uid-2',
135+
});
136+
137+
expect(eventBus.publish).toHaveBeenCalledTimes(2);
138+
expect(eventBus.publish.mock.calls[0][1]?.request).toBe(request);
139+
expect(eventBus.publish.mock.calls[1][1]?.request).toBe(otherRequest);
100140
});
101141
});
102142
});

x-pack/platform/plugins/shared/alerting_v2/server/lib/events/alert_action_event_publisher/alert_action_event_publisher.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
* 2.0.
66
*/
77

8+
import type { KibanaRequest } from '@kbn/core/server';
89
import { inject, injectable } from 'inversify';
910
import type { EventBus } from '../event_bus';
10-
import { AlertingDomainEventBusToken, type AlertingDomainEvent } from '../domain_events';
11+
import {
12+
AlertingDomainEventBusToken,
13+
type AlertingDomainEvent,
14+
type AlertingPublisherContext,
15+
} from '../domain_events';
1116
import {
1217
EPISODE_ASSIGNED_EVENT_TYPE,
1318
type AlertActionEventEnvelope,
@@ -46,9 +51,16 @@ export interface EmitEpisodeAssignedParams extends BaseEmitAlertActionParams {
4651
*
4752
* One method per concrete event. Additional `emit*` methods will be added
4853
* as the other alert-action types (ack, snooze, tag, …) start publishing.
54+
*
55+
* Every method takes the publishing call site's `KibanaRequest` as its
56+
* first argument. The request is propagated on the bus as
57+
* {@link AlertingPublisherContext} so request-scoped subscribers (e.g.
58+
* the workflow subscriber, which needs a user-scoped workflows client)
59+
* can operate under the same auth identity that produced the event,
60+
* even though the bus dispatches asynchronously.
4961
*/
5062
export interface AlertActionEventPublisherContract {
51-
emitEpisodeAssigned(params: EmitEpisodeAssignedParams): void;
63+
emitEpisodeAssigned(request: KibanaRequest, params: EmitEpisodeAssignedParams): void;
5264
}
5365

5466
/**
@@ -62,20 +74,28 @@ export interface AlertActionEventPublisherContract {
6274
* {@link AlertActionEventPublisher#buildEnvelope}, including the
6375
* `occurredAt` timestamp default.
6476
* 2. Builds the event-specific `payload`.
65-
* 3. Tags the result with the event's `type` discriminator and publishes.
77+
* 3. Tags the result with the event's `type` discriminator and
78+
* publishes it alongside the publishing `KibanaRequest` as the bus's
79+
* {@link AlertingPublisherContext}.
6680
*
6781
* Publishing is fire-and-forget — `publish` returns synchronously and
6882
* subscriber work runs on the next event-loop iteration. See
6983
* {@link EventBus} for the dispatch contract.
84+
*
85+
* The `request` passed here determines the auth identity under which
86+
* downstream subscribers (notably the workflow subscriber) operate.
87+
* Background-task callers that have no inbound request must synthesise
88+
* a system request at their call site and pass it here. Do not pass
89+
* `null`/`undefined` or attempt to forge headers in the publisher.
7090
*/
7191
@injectable()
7292
export class AlertActionEventPublisher implements AlertActionEventPublisherContract {
7393
constructor(
7494
@inject(AlertingDomainEventBusToken)
75-
private readonly eventBus: EventBus<AlertingDomainEvent>
95+
private readonly eventBus: EventBus<AlertingDomainEvent, AlertingPublisherContext>
7696
) {}
7797

78-
public emitEpisodeAssigned(params: EmitEpisodeAssignedParams): void {
98+
public emitEpisodeAssigned(request: KibanaRequest, params: EmitEpisodeAssignedParams): void {
7999
const event: EpisodeAssignedEvent = {
80100
type: EPISODE_ASSIGNED_EVENT_TYPE,
81101
...this.buildEnvelope(params),
@@ -84,7 +104,7 @@ export class AlertActionEventPublisher implements AlertActionEventPublisherContr
84104
},
85105
};
86106

87-
this.eventBus.publish(event);
107+
this.eventBus.publish(event, { request });
88108
}
89109

90110
private buildEnvelope(common: BaseEmitAlertActionParams): AlertActionEventEnvelope {

0 commit comments

Comments
 (0)