Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c6ff65f
Introduce an event bus
cnasikas May 19, 2026
6d07213
Event bus improvements
cnasikas May 19, 2026
4eaaa3c
Add unit tests
cnasikas May 19, 2026
80ac903
Merge branch 'main' into alerting_v2_event_bus
cnasikas May 19, 2026
c09ec2d
Changes from make api-docs
kibanamachine May 19, 2026
cd3fefd
Merge branch 'main' into alerting_v2_event_bus
cnasikas May 20, 2026
e43a1b4
Switch to setImmediate and EventEmitterAsyncResource
cnasikas May 20, 2026
9afde85
PR feedback
cnasikas May 20, 2026
28a9188
Create alert action publisher
cnasikas May 20, 2026
0f8e5c0
Fix TS errors
cnasikas May 20, 2026
068fedd
Merge branch 'alerting_v2_event_bus' into alerting-v2-workflow-trigge…
cnasikas May 20, 2026
2714f6f
Create the alert action pub/sub
cnasikas May 21, 2026
0c4978c
Adapting to the new bus.
adcoelho May 21, 2026
1dd5101
Merge remote-tracking branch 'upstream/main' into alerting-v2-workflo…
adcoelho May 22, 2026
94de798
Changes from node scripts/check
kibanamachine May 22, 2026
c06e33b
Type fixes
adcoelho May 22, 2026
741b21f
Merge branch 'main' into alerting-v2-workflow-trigger-assign-episode
adcoelho May 22, 2026
e7a671b
fix test
adcoelho May 22, 2026
fe5bd86
Add logger.
adcoelho May 22, 2026
ee0a69f
Merge branch 'main' into alerting-v2-workflow-trigger-assign-episode
adcoelho May 22, 2026
3aafc90
updating limits
adcoelho May 22, 2026
7a15f24
Merge branch 'main' into alerting-v2-workflow-trigger-assign-episode
adcoelho May 22, 2026
a6569f5
Merge branch 'main' into alerting-v2-workflow-trigger-assign-episode
cnasikas May 25, 2026
a45ed7f
Fix limits
cnasikas May 25, 2026
0a9a061
PR feedback
cnasikas May 25, 2026
19b1d78
Merge branch 'main' into alerting-v2-workflow-trigger-assign-episode
elasticmachine May 29, 2026
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
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pageLoadAssetSize:
aiAssistantManagementSelection: 11569
aiops: 15227
alerting: 22371
alertingVTwo: 51297
alertingVTwo: 56591
apm: 54371
apmSourcesAccess: 2278
automaticImport: 18629
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
* and copy the schemaHash from the response for the trigger id.
*/
export const APPROVED_TRIGGER_DEFINITIONS: Array<{ id: string; schemaHash: string }> = [
{
id: 'alertingV2.episodeAssigned',
schemaHash: 'fce48752c788e4620a70cf8d33040117c7a46c78508a5cf98c5c0fa93f631ad0',
},
{
id: 'cases.caseCreated',
schemaHash: '5b562db9463664a1e28ff2f3ee7edec229e83912569190cd8a83f53d38da9ed8',
Expand Down
2 changes: 1 addition & 1 deletion x-pack/platform/plugins/shared/alerting_v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ If you want implementation detail for one subsystem, continue with:
- API shape or saved object contracts: inspect `server/routes/` and `server/saved_objects/` together with the relevant subsystem docs
- 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)
- [`server/lib/services/workflow_service/README.md`](server/lib/services/workflow_service/README.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { z } from '@kbn/zod/v4';
import type { CommonTriggerDefinition } from '@kbn/workflows-extensions/common';

export const EPISODE_ASSIGNED_TRIGGER_ID = 'alertingV2.episodeAssigned' as const;
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.

Do we really need to specify V2 here? isn't that an implementation detail?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We want to distinguish between the existing alerting and the new one (v2) in case we want to introduce triggers for the v1 alerting in the future. Any suggestions?

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.

don't have any suggestions atm, maybe @tinnytintin10 could help

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.

Discussed briefly this one with @tinnytintin10, suggestion is to remove V2 from name, as it also felt a bit as implementation detail. The next step will be for us to offer extra metadata for supporting V1 and V2 if you decide to ship something for alerting V1. Also you can explicitly put in the description that this one is related to V2 if that could help users further, wdyt?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@tiamliu based on our recent conversations around how to cater to "mixed experiences" effectively, please check my thinking here?

I think we should call these kinds of events/triggers "alerting.[event_name]" and as much as possible make them version-agnostic. For events that apply to both, we can send them through the same flow, and include "version" as a payload discriminator for people who only want to handle one or the other for some reason (likely suggested by a consultant, support, etc).

Very rough examples:

for alerting.ruleSnoozed
payload might be

{
  "rule.id":  "some-id-for-rule",
  "alerting.version": 2,
  "timestamp": //...
    // ...etc
}

for alerting.alertAssigned and alerting.alertAcknowledged, maybe only v2 alerts have these features, but that's still okay.

Thoughts?

Copy link
Copy Markdown
Member

@cnasikas cnasikas May 28, 2026

Choose a reason for hiding this comment

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

Thank you both for the suggestions, and really appreciate being thorough here, as the trigger ID is not an internal name, but it is part of the public contract users put in their workflow YAML. For events that genuinely exist in both V1 and V2 with the same schemas, a common payload makes a lot of sense, and it will avoid users from having to configure the same workflow twice, one for V1 and one for V2. Where I would like to push back is that V2 has a totally different philosophy, for example, episodes. V1 has alert instances and recoveries with a different lifecycle, so for a lot of V2 events, there is no conceptually equivalent V1 event to merge with. For these V2 events, the version is always going to be 2, never anything else, and presenting a V2-only trigger as if it were version-agnostic is exactly the confusing mixed experience we're trying to prevent. We move the version from the trigger ID to the payload, which is still user-facing.

Some technical concerns when/if the payload diverges between versions on the same events: When we register a trigger, we need to provide a zod schema. We need to construct the schema with a discriminated union based on alerting.version having one schema per version. Workflow authors will have to know which fields to use and how to branch between different versions. We would also need to document which field is on which version. Also, if in the future we need to introduce versioning on the schema of the v2 payload (I hope we will never have to do this 🙂 ), we would need to have a second discriminator (alerting.v2.version) for the new schema. Separating the events based on its top level discriminator (the trigger ID) eliminates these issues, especially if each one's schemas want to evolve differently. Lastly, if we namespace on the trigger ID, it will be easier to deprecate v1 triggers in the future while we are deprecating the alerting v1, telemetry, and audit become a bit easier (not scanning the payload to figure out the version), and discoverability in the workflows UI is unambiguous (a user browsing their trigger catalog knows immediately which alerting world it belongs to).

On the leaking implementation detail in the trigger ID, I hear the concern, but all of our APIs already have the V2 in their path and are exposed to the users. I would suggest keeping the distinction of v1 and v2 in the trigger ID level (maybe rename it to alerting.v2.episodeAssigned or something very different) when the events are different, and keep the common event framework agnostic if the events are the same with the same payload.

Very happy to be wrong about specific cases, though, and if you got an event in mind where the shared-id pattern works even for V2-only features, I would love to talk it through.


export const episodeAssignedPayloadSchema = z
.object({
occurredAt: z.string().describe(

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
i18n.translate('xpack.alertingVTwo.triggers.episodeAssigned.schema.occurredAt', {
defaultMessage: 'ISO timestamp of when the assignment occurred.',
})
),
groupHash: z.string().describe(

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
i18n.translate('xpack.alertingVTwo.triggers.episodeAssigned.schema.groupHash', {
defaultMessage: 'Stable hash of the alert grouping the episode belongs to.',
})
),
episodeId: z.string().describe(

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
i18n.translate('xpack.alertingVTwo.triggers.episodeAssigned.schema.episodeId', {
defaultMessage: 'Identifier of the alerting episode whose assignee changed.',
})
),
ruleId: z.string().describe(

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
i18n.translate('xpack.alertingVTwo.triggers.episodeAssigned.schema.ruleId', {
defaultMessage: 'Identifier of the alerting rule the episode belongs to.',
})
),
spaceId: z.string().describe(

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
i18n.translate('xpack.alertingVTwo.triggers.episodeAssigned.schema.spaceId', {
defaultMessage: 'Kibana space the episode lives in.',
})
),
actorUid: z
.string()

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
Comment on lines +41 to +42
.nullable()
.describe(
i18n.translate('xpack.alertingVTwo.triggers.episodeAssigned.schema.actorUid', {
defaultMessage:
'User-profile uid of the actor who changed the assignee, or null when performed by an internal/system context.',
})
),
assigneeUid: z
.string()

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
Comment on lines +50 to +51
.nullable()
.describe(
i18n.translate('xpack.alertingVTwo.triggers.episodeAssigned.schema.assigneeUid', {
defaultMessage:
'User-profile uid of the new assignee, or null when the episode was unassigned.',
})
),
})
.strict();

export type EpisodeAssignedPayload = z.infer<typeof episodeAssignedPayloadSchema>;

export const episodeAssignedTriggerCommonDefinition: CommonTriggerDefinition<
typeof episodeAssignedPayloadSchema
> = {
id: EPISODE_ASSIGNED_TRIGGER_ID,
eventSchema: episodeAssignedPayloadSchema,
title: i18n.translate('xpack.alertingVTwo.workflowTriggers.episodeAssigned.title', {
defaultMessage: 'Alerting - Episode assigned',
}),
description: i18n.translate('xpack.alertingVTwo.workflowTriggers.episodeAssigned.description', {
defaultMessage: 'Emitted when an alerting episode is assigned to a user.',
}),
documentation: {
details: i18n.translate(
'xpack.alertingVTwo.workflowTriggers.episodeAssigned.documentation.details',
{
defaultMessage:
'Emitted after an episode assign action is persisted with a non-null assignee. The payload includes event.episodeId, event.ruleId, event.spaceId, and event.assigneeUid for trigger conditions.',
}
),
examples: [
i18n.translate('xpack.alertingVTwo.workflowTriggers.episodeAssigned.documentation.example', {
defaultMessage: `## Run for a specific rule
\`\`\`yaml
triggers:
- type: {triggerId}
on:
condition: 'event.ruleId: "my-rule-id"'
\`\`\``,
values: {
triggerId: EPISODE_ASSIGNED_TRIGGER_ID,
},
}),
],
},
snippets: {
condition: 'event.assigneeUid: "user-profile-uid"',
},
};
Comment thread
cnasikas marked this conversation as resolved.
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.
*/

export {
EPISODE_ASSIGNED_TRIGGER_ID,
episodeAssignedPayloadSchema,
episodeAssignedTriggerCommonDefinition,
} from './episode_assigned';
export type { EpisodeAssignedPayload } from './episode_assigned';
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This folder owns the **public-side (browser) registration** of `alerting_v2` wor
- **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).
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).

## Registering a new trigger

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
* 2.0.
*/

import type {
PublicTriggerDefinition,
WorkflowsExtensionsPublicPluginSetup,
} from '@kbn/workflows-extensions/public';
import type { WorkflowsExtensionsPublicPluginSetup } from '@kbn/workflows-extensions/public';
import { episodeAssignedTriggerPublicDefinition } from './triggers/episode_assigned';

/**
* Registers all alerting-v2 public workflow trigger definitions (UI metadata).
Expand All @@ -17,11 +15,5 @@ import type {
export function registerTriggerDefinitions(
workflowsExtensions: WorkflowsExtensionsPublicPluginSetup
): void {
const triggerDefinitions: PublicTriggerDefinition[] = [
// Add PublicTriggerDefinition entries here (spread common id + eventSchema + title, icon, docs).
];

for (const definition of triggerDefinitions) {
workflowsExtensions.registerTriggerDefinition(definition);
}
workflowsExtensions.registerTriggerDefinition(episodeAssignedTriggerPublicDefinition);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 React from 'react';
import type { PublicTriggerDefinition } from '@kbn/workflows-extensions/public';
import { episodeAssignedTriggerCommonDefinition } from '../../../../common/workflows/triggers';

const EpisodeAssignedIcon = React.lazy(() =>
// @ts-expect-error EUI does not ship `.d.ts` files for deep `icon/assets/*`
// subpaths. Other plugins work around this with an ambient `eui_icons.d.ts`
// (see e.g. x-pack/platform/plugins/shared/cases/public/workflows/eui_icons.d.ts).
import('@elastic/eui/es/components/icon/assets/user').then(({ icon }) => ({
default: icon,
}))
);

export const episodeAssignedTriggerPublicDefinition: PublicTriggerDefinition = {
...episodeAssignedTriggerCommonDefinition,
icon: EpisodeAssignedIcon,
};
2 changes: 2 additions & 0 deletions x-pack/platform/plugins/shared/alerting_v2/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { bindOnSetup } from './setup/bind_on_setup';
import { bindOnStart } from './setup/bind_on_start';
import { bindRoutes } from './setup/bind_routes';
import { bindServices } from './setup/bind_services';
import { bindEvents } from './setup/bind_events';
import { bindRuleExecutionServices } from './setup/bind_rule_executor';
import { bindDispatcherExecutionServices } from './setup/bind_dispatcher_executor';
import { bindTasks } from './setup/bind_tasks';
Expand All @@ -28,6 +29,7 @@ export const module = new ContainerModule((options) => {
bindContract(options);
bindRoutes(options);
bindServices(options);
bindEvents(options);
bindRuleExecutionServices(options);
bindDispatcherExecutionServices(options);
bindTasks(options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import type { ElasticsearchClient } from '@kbn/core/server';
import type { UserProfileServiceStart } from '@kbn/core-user-profile-server';
import type { DeeplyMockedApi } from '@kbn/core-elasticsearch-client-server-mocks';
import { httpServerMock } from '@kbn/core-http-server-mocks';
import type { PublicMethodsOf } from '@kbn/utility-types';
import { createAlertActionEventPublisher } from '../events/alert_action_event_publisher/alert_action_event_publisher.mock';
import type { AlertActionEventPublisher } from '../events/alert_action_event_publisher/alert_action_event_publisher';
import { createQueryService } from '../services/query_service/query_service.mock';
import { createStorageService } from '../services/storage_service/storage_service.mock';
import { createUserService, createUserProfile } from '../services/user_service/user_service.mock';
Expand All @@ -19,21 +22,32 @@ export function createAlertActionsClient(): {
queryServiceEsClient: DeeplyMockedApi<ElasticsearchClient>;
storageServiceEsClient: jest.Mocked<ElasticsearchClient>;
userProfileService: jest.Mocked<UserProfileServiceStart>;
alertActionEventPublisher: AlertActionEventPublisher;
} {
const { queryService, mockEsClient: queryServiceEsClient } = createQueryService();
const { storageService, mockEsClient: storageServiceEsClient } = createStorageService();
const { userService, userProfileService } = createUserService();
const { publisher: alertActionEventPublisher } = createAlertActionEventPublisher();
const request = httpServerMock.createKibanaRequest();

userProfileService.getCurrent.mockResolvedValue(createUserProfile('test-uid'));

const alertActionsClient = new AlertActionsClient(
queryService,
storageService,
userService,
'default'
request,
'default',
alertActionEventPublisher
);

return { alertActionsClient, queryServiceEsClient, storageServiceEsClient, userProfileService };
return {
alertActionsClient,
queryServiceEsClient,
storageServiceEsClient,
userProfileService,
alertActionEventPublisher,
};
}

export function createAlertActionsClientMock(): jest.Mocked<PublicMethodsOf<AlertActionsClient>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { UserProfileServiceStart } from '@kbn/core-user-profile-server';
import type { DeeplyMockedApi } from '@kbn/core-elasticsearch-client-server-mocks';
import type { BulkCreateAlertActionItemBody } from '@kbn/alerting-v2-schemas';
import { ALERT_EPISODE_ACTION_TYPE, type CreateAlertActionBody } from '@kbn/alerting-v2-schemas';
import type { AlertActionEventPublisher } from '../events/alert_action_event_publisher/alert_action_event_publisher';
import type { AlertActionsClient } from './alert_actions_client';
import { createAlertActionsClient } from './alert_actions_client.mock';
import {
Expand All @@ -24,14 +25,18 @@ describe('AlertActionsClient', () => {
let queryServiceEsClient: DeeplyMockedApi<ElasticsearchClient>;
let storageServiceEsClient: jest.Mocked<ElasticsearchClient>;
let userProfileService: jest.Mocked<UserProfileServiceStart>;
let alertActionEventPublisher: AlertActionEventPublisher;
let emitEpisodeActionsSpy: jest.SpyInstance;

beforeEach(() => {
({
alertActionsClient: client,
queryServiceEsClient,
storageServiceEsClient,
userProfileService,
alertActionEventPublisher,
} = createAlertActionsClient());
emitEpisodeActionsSpy = jest.spyOn(alertActionEventPublisher, 'emitEpisodeActions');
storageServiceEsClient.bulk.mockResolvedValueOnce({ items: [], errors: false, took: 1 });
});

Expand Down Expand Up @@ -216,6 +221,83 @@ describe('AlertActionsClient', () => {
});
});

describe('episode action domain events', () => {
it('calls emitEpisodeActions with the persisted assign action document', async () => {
queryServiceEsClient.esql.query.mockResolvedValueOnce(getAlertEventESQLResponse());

await client.createAction({
groupHash: 'test-group-hash',
action: {
action_type: ALERT_EPISODE_ACTION_TYPE.ASSIGN,
episode_id: 'episode-1',
assignee_uid: 'assignee-uid-1',
},
});

expect(emitEpisodeActionsSpy).toHaveBeenCalledTimes(1);
expect(emitEpisodeActionsSpy).toHaveBeenCalledWith(expect.anything(), [
expect.objectContaining({
action_type: ALERT_EPISODE_ACTION_TYPE.ASSIGN,
assignee_uid: 'assignee-uid-1',
episode_id: 'episode-1',
group_hash: 'test-group-hash',
actor: 'test-uid',
}),
]);
});

it('does not call emitEpisodeActions when persistence fails', async () => {
queryServiceEsClient.esql.query.mockResolvedValueOnce(getEmptyESQLResponse());

await expect(
client.createAction({
groupHash: 'unknown-group-hash',
action: {
action_type: ALERT_EPISODE_ACTION_TYPE.ASSIGN,
episode_id: 'episode-1',
assignee_uid: 'assignee-uid-1',
},
})
).rejects.toThrow();

expect(emitEpisodeActionsSpy).not.toHaveBeenCalled();
});

it('calls emitEpisodeActions for bulk assign actions only among persisted docs', async () => {
const actions: BulkCreateAlertActionItemBody[] = [
{
group_hash: 'group-hash-1',
action_type: ALERT_EPISODE_ACTION_TYPE.ASSIGN,
episode_id: 'episode-1',
assignee_uid: 'assignee-uid-1',
},
{
group_hash: 'group-hash-2',
action_type: ALERT_EPISODE_ACTION_TYPE.ACK,
episode_id: 'episode-2',
},
];

queryServiceEsClient.esql.query.mockResolvedValueOnce(
getBulkAlertEventsESQLResponse([
{ group_hash: 'group-hash-1', episode_id: 'episode-1' },
{ group_hash: 'group-hash-2', episode_id: 'episode-2' },
])
);

await client.createBulkActions(actions);

expect(emitEpisodeActionsSpy).toHaveBeenCalledTimes(1);
expect(emitEpisodeActionsSpy.mock.calls[0][1]).toHaveLength(2);
expect(emitEpisodeActionsSpy.mock.calls[0][1][0]).toMatchObject({
action_type: ALERT_EPISODE_ACTION_TYPE.ASSIGN,
});
expect(emitEpisodeActionsSpy.mock.calls[0][1][1]).toMatchObject({
action_type: ALERT_EPISODE_ACTION_TYPE.ACK,
});
});
});

describe('error codes and details', () => {
it('attaches ALERT_EVENT_NOT_FOUND code with group_hash and episode_id details on createAction', async () => {
queryServiceEsClient.esql.query.mockResolvedValueOnce(getEmptyESQLResponse());
Expand Down
Loading
Loading