Skip to content

Commit 37dc569

Browse files
authored
[Alerting V2] Fix stale throttle.interval on strategy change (elastic#268764)
1 parent aa1f25d commit 37dc569

16 files changed

Lines changed: 240 additions & 42 deletions

File tree

x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/action_policy_data_schema.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ export type ThrottleStrategy = z.infer<typeof throttleStrategySchema>;
6565

6666
const throttleSchema = z.object({
6767
strategy: throttleStrategySchema.optional().describe('The throttle strategy.'),
68-
interval: durationSchema.optional().describe('The throttle interval duration (e.g. 5m, 1h).'),
68+
interval: durationSchema
69+
.nullish()
70+
.describe(
71+
'The throttle interval duration (e.g. 5m, 1h), or null when the strategy is intervalless.'
72+
),
6973
});
7074

7175
const PER_EPISODE_STRATEGIES = new Set<string>([
@@ -76,10 +80,13 @@ const PER_EPISODE_STRATEGIES = new Set<string>([
7680
const AGGREGATE_STRATEGIES = new Set<string>(['time_interval', 'every_time']);
7781
const STRATEGIES_REQUIRING_INTERVAL = new Set<string>(['per_status_interval', 'time_interval']);
7882

83+
export const needsInterval = (strategy: string | undefined): boolean =>
84+
strategy != null && STRATEGIES_REQUIRING_INTERVAL.has(strategy);
85+
7986
interface ValidationPayload {
8087
value: {
8188
groupingMode?: string | null;
82-
throttle?: { strategy?: string; interval?: string } | null;
89+
throttle?: { strategy?: string; interval?: string | null } | null;
8390
type?: string;
8491
ruleId?: string;
8592
};
@@ -91,7 +98,7 @@ const validateStrategyInterval = (payload: ValidationPayload) => {
9198
const strategy = data.throttle?.strategy;
9299
if (!strategy) return;
93100

94-
if (STRATEGIES_REQUIRING_INTERVAL.has(strategy) && !data.throttle?.interval) {
101+
if (needsInterval(strategy) && !data.throttle?.interval) {
95102
issues.push({
96103
code: 'custom',
97104
message: `Strategy "${strategy}" requires an interval to be defined`,

x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/action_policy_response_schema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const validResponse = {
2424
groupBy: ['host.name'],
2525
tags: ['production'],
2626
groupingMode: 'per_episode' as const,
27-
throttle: { strategy: 'on_status_change' as const },
27+
throttle: { strategy: 'on_status_change' as const, interval: null },
2828
snoozedUntil: null,
2929
auth: { owner: 'user-1', createdByUser: true },
3030
createdBy: 'user-1',

x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/action_policy_response_schema.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ export const actionPolicyResponseSchema = z.object({
3838
throttle: z
3939
.object({
4040
strategy: throttleStrategySchema.optional().describe('The throttle strategy.'),
41-
interval: durationSchema.optional().describe('The throttle interval duration (e.g. 5m, 1h).'),
41+
interval: durationSchema
42+
.nullable()
43+
.describe(
44+
'The throttle interval duration (e.g. 5m, 1h), or null when the strategy is intervalless.'
45+
),
4246
})
4347
.nullable()
4448
.describe('The throttle configuration for notifications.'),

x-pack/platform/plugins/shared/alerting_v2/public/components/action_policy/details_flyout/action_policy_details_flyout.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ describe('ActionPolicyDetailsFlyout', () => {
195195
policy: createPolicy({
196196
groupingMode: 'per_episode',
197197
groupBy: null,
198-
throttle: { strategy: 'on_status_change' },
198+
throttle: { strategy: 'on_status_change', interval: null },
199199
}),
200200
});
201201

x-pack/platform/plugins/shared/alerting_v2/public/components/action_policy/form/form_utils.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('action policy form utils', () => {
2828
description: 'Description',
2929
type: 'global',
3030
groupingMode: 'per_episode',
31-
throttle: { strategy: 'on_status_change' },
31+
throttle: { strategy: 'on_status_change', interval: null },
3232
destinations: [{ type: 'workflow', id: 'workflow-1' }],
3333
});
3434
});
@@ -53,13 +53,23 @@ describe('action policy form utils', () => {
5353
});
5454
});
5555

56-
it('omits throttle interval for strategies that do not require it', () => {
56+
it('emits interval: null for strategies that do not require it', () => {
5757
const payload = toCreatePayload({
5858
...state,
5959
throttleStrategy: 'every_time',
6060
});
6161

62-
expect(payload.throttle).toEqual({ strategy: 'every_time' });
62+
expect(payload.throttle).toEqual({ strategy: 'every_time', interval: null });
63+
});
64+
65+
it('emits interval: null when strategy does not need interval, even if state holds a stale value', () => {
66+
const payload = toCreatePayload({
67+
...state,
68+
throttleStrategy: 'on_status_change',
69+
throttleInterval: '5m',
70+
});
71+
72+
expect(payload.throttle).toEqual({ strategy: 'on_status_change', interval: null });
6373
});
6474
});
6575

@@ -73,7 +83,7 @@ describe('action policy form utils', () => {
7383
tags: null,
7484
matcher: null,
7585
groupBy: null,
76-
throttle: { strategy: 'on_status_change' },
86+
throttle: { strategy: 'on_status_change', interval: null },
7787
destinations: [{ type: 'workflow', id: 'workflow-1' }],
7888
});
7989
});

x-pack/platform/plugins/shared/alerting_v2/public/components/action_policy/form/form_utils.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@
88
import type {
99
CreateActionPolicyData,
1010
ActionPolicyResponse,
11-
ThrottleStrategy,
1211
UpdateActionPolicyBody,
1312
} from '@kbn/alerting-v2-schemas';
13+
import { needsInterval } from '@kbn/alerting-v2-schemas';
1414
import { DEFAULT_STRATEGY_FOR_MODE } from './constants';
1515
import type { ActionPolicyFormState } from './types';
1616

17-
export const needsInterval = (strategy: ThrottleStrategy): boolean =>
18-
strategy === 'per_status_interval' || strategy === 'time_interval';
17+
export { needsInterval };
1918

2019
const buildThrottle = (state: ActionPolicyFormState) => ({
2120
strategy: state.throttleStrategy,
22-
...(needsInterval(state.throttleStrategy) ? { interval: state.throttleInterval } : {}),
21+
interval: needsInterval(state.throttleStrategy) ? state.throttleInterval : null,
2322
});
2423

2524
export const toFormState = (response: ActionPolicyResponse): ActionPolicyFormState => {

x-pack/platform/plugins/shared/alerting_v2/public/components/action_policy/form/use_action_policy_form.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('useActionPolicyForm', () => {
8585
description: 'A description',
8686
type: 'global',
8787
groupingMode: 'per_episode',
88-
throttle: { strategy: 'on_status_change' },
88+
throttle: { strategy: 'on_status_change', interval: null },
8989
destinations: [],
9090
});
9191
});
@@ -114,7 +114,7 @@ describe('useActionPolicyForm', () => {
114114
expect(payload).not.toHaveProperty('matcher');
115115
expect(payload).not.toHaveProperty('groupBy');
116116
expect(payload.groupingMode).toBe('per_episode');
117-
expect(payload.throttle).toEqual({ strategy: 'on_status_change' });
117+
expect(payload.throttle).toEqual({ strategy: 'on_status_change', interval: null });
118118
});
119119
});
120120

x-pack/platform/plugins/shared/alerting_v2/public/components/action_policy/form_flyout/action_policy_form_flyout.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ describe('ActionPolicyFormFlyout', () => {
151151
description: 'Description from test',
152152
type: 'global',
153153
groupingMode: 'per_episode',
154-
throttle: { strategy: 'on_status_change' },
154+
throttle: { strategy: 'on_status_change', interval: null },
155155
destinations: [{ type: 'workflow', id: 'wf-1' }],
156156
});
157157
});

x-pack/platform/plugins/shared/alerting_v2/public/pages/action_policy_form_page/action_policy_form_page.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('ActionPolicyFormPage', () => {
209209
description: 'Description from test',
210210
type: 'global',
211211
groupingMode: 'per_episode',
212-
throttle: { strategy: 'on_status_change' },
212+
throttle: { strategy: 'on_status_change', interval: null },
213213
destinations: [{ type: 'workflow', id: 'workflow-1' }],
214214
},
215215
expect.objectContaining({ onSuccess: expect.any(Function) })

x-pack/platform/plugins/shared/alerting_v2/public/pages/list_action_policies_page/list_action_policies_page.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const createPolicy = (overrides: Partial<ActionPolicyResponse> = {}): ActionPoli
164164
groupBy: null,
165165
tags: null,
166166
groupingMode: null,
167-
throttle: { strategy: undefined, interval: undefined },
167+
throttle: { strategy: undefined, interval: null },
168168
snoozedUntil: null,
169169
auth: {
170170
owner: 'elastic',

0 commit comments

Comments
 (0)