Skip to content

Commit 68efb36

Browse files
kdelemmeclaude
andcommitted
chore(alerting_v2): simplify and clean up dispatcher
- tick_summary.ts: use stages.at(-1)?.counts ?? ZERO_COUNTS instead of manual length-guard + index arithmetic - state_counts.ts: replace Object.fromEntries(entries.map(…)) in toSpanLabels with a direct property-assignment loop (no intermediate arrays) - queries.ts: fuse the two alertEpisodes traversals in getAlertEpisodeSuppressionsQueries into a single for...of loop that builds minLastEventTimestamp and the uniquePairKeySet together - types.ts: export CLEAN_HALT_REASONS next to DispatcherHaltReason so the watermark-advancement policy lives at the type boundary - dispatcher.ts: use CLEAN_HALT_REASONS.has() in extractAdvanceableWatermark; adding a new controlled halt reason now requires only one change Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6d08878 commit 68efb36

5 files changed

Lines changed: 32 additions & 27 deletions

File tree

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
type DispatcherPipelineResult,
1818
} from './execution_pipeline';
1919
import { buildTickSummary, emitTickSummary, startHrtime } from './telemetry';
20+
import { CLEAN_HALT_REASONS } from './types';
2021
import type { DispatcherExecutionParams, DispatcherExecutionResult } from './types';
2122

2223
export interface DispatcherServiceContract {
@@ -99,12 +100,8 @@ export class DispatcherService implements DispatcherServiceContract {
99100
* data loss.
100101
*/
101102
function extractAdvanceableWatermark(result: DispatcherPipelineResult): string | undefined {
102-
if (
103-
!result.completed &&
104-
result.haltReason !== 'no_episodes' &&
105-
result.haltReason !== 'no_actions'
106-
) {
107-
return undefined;
108-
}
109-
return result.finalState.nextEventWatermark;
103+
const isClean =
104+
result.completed ||
105+
(result.haltReason !== undefined && CLEAN_HALT_REASONS.has(result.haltReason));
106+
return isClean ? result.finalState.nextEventWatermark : undefined;
110107
}

x-pack/platform/plugins/shared/alerting_v2/server/lib/dispatcher/queries.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -145,20 +145,16 @@ export const getAlertEpisodeSuppressionsQueries = (
145145
): EsqlRequest[] => {
146146
if (alertEpisodes.length === 0) return [];
147147

148-
const minLastEventTimestamp =
149-
alertEpisodes.reduce<string | undefined>((min, ep) => {
150-
const parsedTimestamp = new Date(ep.last_event_timestamp);
151-
if (Number.isNaN(parsedTimestamp.getTime())) {
152-
return min;
153-
}
154-
155-
const normalizedTimestamp = parsedTimestamp.toISOString();
156-
return min === undefined || normalizedTimestamp < min ? normalizedTimestamp : min;
157-
}, undefined) ?? new Date(0).toISOString();
158-
159-
const uniquePairKeys = [
160-
...new Set(alertEpisodes.map((ep) => `${ep.rule_id}${PAIR_SEPARATOR}${ep.group_hash}`)),
161-
];
148+
let minLastEventTimestamp: string | undefined;
149+
const uniquePairKeySet = new Set<string>();
150+
for (const ep of alertEpisodes) {
151+
if (minLastEventTimestamp === undefined || ep.last_event_timestamp < minLastEventTimestamp) {
152+
minLastEventTimestamp = ep.last_event_timestamp;
153+
}
154+
uniquePairKeySet.add(`${ep.rule_id}${PAIR_SEPARATOR}${ep.group_hash}`);
155+
}
156+
const uniquePairKeys = [...uniquePairKeySet];
157+
const minTs = minLastEventTimestamp ?? new Date(0).toISOString();
162158

163159
return chunkInClauseLiterals(uniquePairKeys).map((chunk) => {
164160
const pairValues = chunk.map((key) => esql.str(key));
@@ -167,7 +163,7 @@ export const getAlertEpisodeSuppressionsQueries = (
167163
| EVAL _pair_key = CONCAT(rule_id, ${PAIR_SEPARATOR}, group_hash)
168164
| WHERE _pair_key IN (${pairValues})
169165
| WHERE action_type IN ("ack", "unack", "deactivate", "activate", "snooze", "unsnooze")
170-
| WHERE action_type != "snooze" OR expiry > ${minLastEventTimestamp}::datetime
166+
| WHERE action_type != "snooze" OR expiry > ${minTs}::datetime
171167
| INLINE STATS
172168
last_snooze_action = LAST(action_type, @timestamp) WHERE action_type IN ("snooze", "unsnooze")
173169
BY rule_id, group_hash

x-pack/platform/plugins/shared/alerting_v2/server/lib/dispatcher/telemetry/state_counts.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ export function computeStateCounts(state: DispatcherPipelineState): DispatcherSt
3434
* APM-facing shapes are allowed to drift independently.
3535
*/
3636
export function toSpanLabels(counts: DispatcherStageCounts): Record<string, number> {
37-
return Object.fromEntries(
38-
Object.entries(counts).map(([key, value]) => [`count_${key}`, value as number])
39-
);
37+
const labels: Record<string, number> = {};
38+
for (const key of Object.keys(counts) as Array<keyof DispatcherStageCounts>) {
39+
labels[`count_${key}`] = counts[key];
40+
}
41+
return labels;
4042
}

x-pack/platform/plugins/shared/alerting_v2/server/lib/dispatcher/telemetry/tick_summary.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function buildTickSummary({
5656
completed,
5757
halt_reason: haltReason,
5858
stages,
59-
totals: stages.length === 0 ? ZERO_COUNTS : stages[stages.length - 1].counts,
59+
totals: stages.at(-1)?.counts ?? ZERO_COUNTS,
6060
};
6161
}
6262

x-pack/platform/plugins/shared/alerting_v2/server/lib/dispatcher/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,16 @@ export type DispatcherStepHaltReason = 'no_episodes' | 'no_actions';
199199
*/
200200
export type DispatcherHaltReason = DispatcherStepHaltReason | 'step_error';
201201

202+
/**
203+
* Halt reasons that allow the event watermark to advance. Defined here,
204+
* next to the halt-reason types, so adding a new controlled reason only
205+
* requires a single change.
206+
*/
207+
export const CLEAN_HALT_REASONS: ReadonlySet<DispatcherHaltReason> = new Set<DispatcherHaltReason>([
208+
'no_episodes',
209+
'no_actions',
210+
]);
211+
202212
export type DispatcherStepOutput =
203213
| { type: 'continue'; data?: Partial<Omit<DispatcherPipelineState, 'input'>> }
204214
| {

0 commit comments

Comments
 (0)