Skip to content

Commit 75f7b7c

Browse files
author
Eugene Cheung
authored
feat(facade): add ability to specify default actions by alarm disambiguator (#268)
Actions are resolved in the following order: 1. `actionOverride` from that alarm's props, if present 2. `disambiguatorAction` from the facade's defaults, if present (**new**) 3. Default `action` from the facade's defaults, if present 4. Finally fallbacks to a no-op action --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 78f588b commit 75f7b7c

File tree

3 files changed

+112
-3
lines changed

3 files changed

+112
-3
lines changed

API.md

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/common/alarm/AlarmFactory.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,16 @@ export interface AlarmFactoryDefaults {
379379
*/
380380
readonly action?: IAlarmActionStrategy;
381381

382+
/**
383+
* Optional alarm action for each disambiguator.
384+
*
385+
* @default - Global alarm action if defined.
386+
*/
387+
readonly disambiguatorAction?: Record<
388+
PredefinedAlarmDisambiguators | string,
389+
IAlarmActionStrategy
390+
>;
391+
382392
/**
383393
* Custom strategy to create annotations for alarms.
384394
*
@@ -487,8 +497,10 @@ export class AlarmFactory {
487497
props.actionsEnabled,
488498
props.disambiguator
489499
);
490-
const action =
491-
props.actionOverride ?? this.globalAlarmDefaults.action ?? noopAction();
500+
const action = this.determineAction(
501+
props.disambiguator,
502+
props.actionOverride
503+
);
492504
const alarmName = this.alarmNamingStrategy.getName(props);
493505
const alarmNameSuffix = props.alarmNameSuffix;
494506
const alarmLabel = this.alarmNamingStrategy.getWidgetLabel(props);
@@ -704,6 +716,27 @@ export class AlarmFactory {
704716
return false;
705717
}
706718

719+
protected determineAction(
720+
disambiguator?: string,
721+
actionOverride?: IAlarmActionStrategy
722+
): IAlarmActionStrategy {
723+
// Explicit override
724+
if (actionOverride) {
725+
return actionOverride;
726+
}
727+
728+
// Default by disambiugator
729+
if (
730+
disambiguator &&
731+
this.globalAlarmDefaults.disambiguatorAction?.[disambiguator]
732+
) {
733+
return this.globalAlarmDefaults.disambiguatorAction[disambiguator];
734+
}
735+
736+
// Default global action
737+
return this.globalAlarmDefaults.action ?? noopAction();
738+
}
739+
707740
get shouldUseDefaultDedupeForError() {
708741
return this.globalAlarmDefaults.useDefaultDedupeForError ?? true;
709742
}

test/common/alarm/AlarmFactory.test.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Shading,
88
TreatMissingData,
99
} from "aws-cdk-lib/aws-cloudwatch";
10+
import { Topic } from "aws-cdk-lib/aws-sns";
1011
import { Construct } from "constructs";
1112

1213
import {
@@ -15,11 +16,17 @@ import {
1516
AlarmFactoryDefaults,
1617
CompositeAlarmOperator,
1718
MetricFactoryDefaults,
19+
multipleActions,
1820
noopAction,
21+
SnsAlarmActionStrategy,
1922
} from "../../../lib";
2023

2124
const stack = new Stack();
2225
const construct = new Construct(stack, "SampleConstruct");
26+
27+
const snsAction = new SnsAlarmActionStrategy({
28+
onAlarmTopic: new Topic(stack, "Dummy2"),
29+
});
2330
const globalMetricDefaults: MetricFactoryDefaults = {
2431
namespace: "DummyNamespace",
2532
};
@@ -39,6 +46,9 @@ const globalAlarmDefaultsWithDisambiguator: AlarmFactoryDefaults = {
3946
datapointsToAlarm: 6,
4047
// we do not care about alarm actions in this test
4148
action: noopAction(),
49+
disambiguatorAction: {
50+
DisambiguatedAction: snsAction,
51+
},
4252
};
4353
const factory = new AlarmFactory(construct, {
4454
globalMetricDefaults,
@@ -116,6 +126,7 @@ test("addAlarm: verify actions enabled", () => {
116126
...props,
117127
alarmNameSuffix: "DisabledByDefault",
118128
});
129+
119130
const template = Template.fromStack(stack);
120131
template.hasResourceProperties("AWS::CloudWatch::Alarm", {
121132
ActionsEnabled: true,
@@ -154,6 +165,7 @@ test("addAlarm: description can be overridden", () => {
154165
alarmNameSuffix: "Suffix6B",
155166
alarmDescriptionOverride: "New Description",
156167
});
168+
157169
expect(alarm1.alarmDescription).toEqual("Description");
158170
expect(alarm2.alarmDescription).toEqual("New Description");
159171
});
@@ -179,6 +191,7 @@ test("addAlarm: evaluateLowSampleCountPercentile can be overridden", () => {
179191
evaluateLowSampleCountPercentile: true,
180192
alarmNameSuffix: "TrueValue",
181193
});
194+
182195
expect(Template.fromStack(stack)).toMatchSnapshot();
183196
});
184197

@@ -194,6 +207,7 @@ test("addAlarm: period override is propagated to alarm metric", () => {
194207
alarmNameSuffix: "TwoHoursPeriod",
195208
period: Duration.hours(2),
196209
});
210+
197211
const alarm1hConfig = (alarm1h.alarm as Alarm).metric.toMetricConfig();
198212
expect(alarm1hConfig.metricStat?.period).toStrictEqual(Duration.hours(1));
199213
const alarm2hConfig = (alarm2h.alarm as Alarm).metric.toMetricConfig();
@@ -225,6 +239,7 @@ test("addAlarm: fill is propagated to alarm annotation", () => {
225239
comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
226240
fillAlarmRange: true,
227241
});
242+
228243
expect(alarmNone.annotation.fill).toBeUndefined();
229244
expect(alarmAbove.annotation.fill).toStrictEqual(Shading.ABOVE);
230245
expect(alarmBelow.annotation.fill).toStrictEqual(Shading.BELOW);
@@ -245,6 +260,7 @@ test("addAlarm: annotation overrides are applied", () => {
245260
overrideAnnotationVisibility: false,
246261
overrideAnnotationColor: "NewColor",
247262
});
263+
248264
expect(alarm.annotation).toStrictEqual({
249265
color: "NewColor",
250266
label: "NewLabel",
@@ -266,8 +282,8 @@ test("addAlarm: check created alarms when minMetricSamplesToAlarm is used", () =
266282
comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
267283
minMetricSamplesToAlarm: 42,
268284
});
269-
const template = Template.fromStack(stack);
270285

286+
const template = Template.fromStack(stack);
271287
template.hasResourceProperties("AWS::CloudWatch::Alarm", {
272288
AlarmName: "DummyServiceAlarms-prefix-none",
273289
MetricName: "DummyMetric1",
@@ -285,6 +301,7 @@ test("addAlarm: check created alarms when minMetricSamplesToAlarm is used", () =
285301
Threshold: 42,
286302
TreatMissingData: "breaching",
287303
});
304+
288305
const alarmRuleCapture = new Capture();
289306
template.hasResourceProperties("AWS::CloudWatch::CompositeAlarm", {
290307
AlarmName: "DummyServiceAlarms-prefix-none-WithSamples",
@@ -345,5 +362,50 @@ test("addCompositeAlarm: snapshot for operator", () => {
345362
alarmNameSuffix: "CompositeOr",
346363
compositeOperator: CompositeAlarmOperator.OR,
347364
});
365+
348366
expect(Template.fromStack(stack)).toMatchSnapshot();
349367
});
368+
369+
test("addAlarm: original actionOverride with a different action gets preserved", () => {
370+
const originalActionOverride = new SnsAlarmActionStrategy({
371+
onAlarmTopic: new Topic(stack, "Dummy1"),
372+
});
373+
374+
const alarm = factory.addAlarm(metric, {
375+
...props,
376+
alarmNameSuffix: "OriginalActionOverridePreserved",
377+
actionOverride: originalActionOverride,
378+
});
379+
380+
expect(alarm.action).toStrictEqual(originalActionOverride);
381+
});
382+
383+
test("addAlarm: original actionOverride with multipleActions gets preserved", () => {
384+
const action1 = snsAction;
385+
const action2 = noopAction();
386+
387+
const originalActionOverride = multipleActions(action1, action2);
388+
389+
const alarm = factory.addAlarm(metric, {
390+
...props,
391+
alarmNameSuffix: "OriginalActionOverridePreservedInMultipleActions",
392+
actionOverride: originalActionOverride,
393+
});
394+
395+
expect(alarm.action).toStrictEqual(multipleActions(action1, action2));
396+
});
397+
398+
test("addAlarm: disambigatorAction takes precedence over default action", () => {
399+
const stack = new Stack();
400+
const factory = new AlarmFactory(stack, {
401+
globalMetricDefaults,
402+
globalAlarmDefaults: globalAlarmDefaultsWithDisambiguator,
403+
localAlarmNamePrefix: "prefix",
404+
});
405+
const alarm = factory.addAlarm(metric, {
406+
...props,
407+
disambiguator: "DisambiguatedAction",
408+
});
409+
410+
expect(alarm.action).toStrictEqual(snsAction);
411+
});

0 commit comments

Comments
 (0)