diff --git a/API.md b/API.md
index 7ff6dc96..e9425caf 100644
--- a/API.md
+++ b/API.md
@@ -127,7 +127,7 @@ to each other.
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { BitmapDashboard } from 'cdk-monitoring-constructs'
@@ -137,20 +137,6 @@ BitmapDashboard.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -341,7 +327,7 @@ public asBitmap(widget: IWidget): CustomWidget
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { BitmapWidgetRenderingSupport } from 'cdk-monitoring-constructs'
@@ -351,20 +337,6 @@ BitmapWidgetRenderingSupport.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -532,7 +504,7 @@ to each other.
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { DashboardWithBitmapCopy } from 'cdk-monitoring-constructs'
@@ -542,20 +514,6 @@ DashboardWithBitmapCopy.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -802,7 +760,7 @@ Gets the dashboard for the requested dashboard type.
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { DefaultDashboardFactory } from 'cdk-monitoring-constructs'
@@ -812,20 +770,6 @@ DefaultDashboardFactory.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -1002,7 +946,7 @@ Gets the dashboard for the requested dashboard type.
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { DynamicDashboardFactory } from 'cdk-monitoring-constructs'
@@ -1012,20 +956,6 @@ DynamicDashboardFactory.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -2218,7 +2148,7 @@ public monitorWebApplicationFirewallAclV2(props: WafV2MonitoringProps): Monitori
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { MonitoringFacade } from 'cdk-monitoring-constructs'
@@ -2228,20 +2158,6 @@ MonitoringFacade.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -2393,7 +2309,7 @@ Creates a new widget factory.
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { MonitoringScope } from 'cdk-monitoring-constructs'
@@ -2403,20 +2319,6 @@ MonitoringScope.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -2486,7 +2388,7 @@ public addSecret(secret: ISecret): void
---
-##### `isConstruct`
+##### ~~`isConstruct`~~
```typescript
import { SecretsManagerMetricsPublisher } from 'cdk-monitoring-constructs'
@@ -2496,20 +2398,6 @@ SecretsManagerMetricsPublisher.isConstruct(x: any)
Checks if `x` is a construct.
-Use this method instead of `instanceof` to properly detect `Construct`
-instances, even when the construct library is symlinked.
-
-Explanation: in JavaScript, multiple copies of the `constructs` library on
-disk are seen as independent, completely different libraries. As a
-consequence, the class `Construct` in each copy of the `constructs` library
-is seen as a different class, and an instance of one class will not test as
-`instanceof` the other class. `npm install` will not create installations
-like this, but users may manually symlink construct libraries together or
-use a monorepo tool: in those cases, multiple copies of the `constructs`
-library can be accidentally installed, and `instanceof` will behave
-unpredictably. It is safest to avoid using `instanceof`, and using
-this type-testing method instead.
-
###### `x`Required
- *Type:* any
@@ -3029,6 +2917,7 @@ const addCompositeAlarmProps: AddCompositeAlarmProps = { ... }
| alarmDescriptionOverride | string | A text included in the generated ticket description body, which fully replaces the generated text. |
| alarmNameOverride | string | If this is defined, the alarm name is set to this exact value. |
| alarmNameSuffix | string | Suffix added to base alarm name. |
+| atLeastOptions | CompositeAlarmAtLeastOptions | Options for AT_LEAST operator. |
| compositeOperator | CompositeAlarmOperator | Logical operator used to aggregate the status individual alarms. |
| customParams | {[ key: string ]: any} | This allows user to attach custom parameters to this alarm, which can later be accessed from the "useCreatedAlarms" method. |
| customTags | string[] | This allows user to attach custom values to this alarm, which can later be accessed from the "useCreatedAlarms" method. |
@@ -3191,6 +3080,21 @@ Alarm names need to be unique.
---
+##### `atLeastOptions`Optional
+
+```typescript
+public readonly atLeastOptions: CompositeAlarmAtLeastOptions;
+```
+
+- *Type:* CompositeAlarmAtLeastOptions
+- *Default:* undefined
+
+Options for AT_LEAST operator.
+
+Required when compositeOperator is AT_LEAST.
+
+---
+
##### `compositeOperator`Optional
```typescript
@@ -12812,6 +12716,54 @@ public readonly addFailedBuildRateAlarm: {[ key: string ]: ErrorRateThreshold};
---
+### CompositeAlarmAtLeastOptions
+
+Configuration for AT_LEAST composite alarm operator.
+
+#### Initializer
+
+```typescript
+import { CompositeAlarmAtLeastOptions } from 'cdk-monitoring-constructs'
+
+const compositeAlarmAtLeastOptions: CompositeAlarmAtLeastOptions = { ... }
+```
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| threshold | AtLeastThreshold | Threshold for AT_LEAST operator. |
+| state | aws-cdk-lib.aws_cloudwatch.AlarmState | Alarm state for AT_LEAST operator. |
+
+---
+
+##### `threshold`Required
+
+```typescript
+public readonly threshold: AtLeastThreshold;
+```
+
+- *Type:* AtLeastThreshold
+
+Threshold for AT_LEAST operator.
+
+Use AtLeastThreshold.count() or AtLeastThreshold.percentage().
+
+---
+
+##### `state`Optional
+
+```typescript
+public readonly state: AlarmState;
+```
+
+- *Type:* aws-cdk-lib.aws_cloudwatch.AlarmState
+- *Default:* ALARM
+
+Alarm state for AT_LEAST operator.
+
+---
+
### ConsumedCapacityThreshold
#### Initializer
@@ -62587,6 +62539,71 @@ public readonly tpsMetric: Metric | MathExpression;
---
+### AtLeastThreshold
+
+Threshold for AT_LEAST operator.
+
+#### Initializers
+
+```typescript
+import { AtLeastThreshold } from 'cdk-monitoring-constructs'
+
+new AtLeastThreshold()
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+
+---
+
+
+#### Static Functions
+
+| **Name** | **Description** |
+| --- | --- |
+| count | Create a count-based threshold. |
+| percentage | Create a percentage-based threshold. |
+
+---
+
+##### `count`
+
+```typescript
+import { AtLeastThreshold } from 'cdk-monitoring-constructs'
+
+AtLeastThreshold.count(count: number)
+```
+
+Create a count-based threshold.
+
+###### `count`Required
+
+- *Type:* number
+
+The minimum number of alarms that must be in the specified state.
+
+---
+
+##### `percentage`
+
+```typescript
+import { AtLeastThreshold } from 'cdk-monitoring-constructs'
+
+AtLeastThreshold.percentage(percentage: number)
+```
+
+Create a percentage-based threshold.
+
+###### `percentage`Required
+
+- *Type:* number
+
+The minimum percentage of alarms (0-100) that must be in the specified state.
+
+---
+
+
+
### AuroraAlarmFactory
#### Initializers
@@ -89167,6 +89184,7 @@ alarm to represent.
| --- | --- |
| AND | trigger only if all the alarms are triggered. |
| OR | trigger if any of the alarms is triggered. |
+| AT_LEAST | trigger if at least M alarms are in the specified state Requires atLeastOptions to be specified. |
---
@@ -89184,6 +89202,13 @@ trigger if any of the alarms is triggered.
---
+##### `AT_LEAST`
+
+trigger if at least M alarms are in the specified state Requires atLeastOptions to be specified.
+
+---
+
+
### DashboardRenderingPreference
Preferred way of rendering dashboard widgets.
diff --git a/lib/common/alarm/AlarmFactory.ts b/lib/common/alarm/AlarmFactory.ts
index 990f8d5e..8de29a87 100644
--- a/lib/common/alarm/AlarmFactory.ts
+++ b/lib/common/alarm/AlarmFactory.ts
@@ -421,6 +421,14 @@ export interface AddCompositeAlarmProps {
*/
readonly compositeOperator?: CompositeAlarmOperator;
+ /**
+ * Options for AT_LEAST operator.
+ * Required when compositeOperator is AT_LEAST.
+ *
+ * @default - undefined
+ */
+ readonly atLeastOptions?: CompositeAlarmAtLeastOptions;
+
/**
* Actions will be suppressed if the suppressor alarm is in the ALARM state.
*
@@ -462,6 +470,86 @@ export enum CompositeAlarmOperator {
* trigger if any of the alarms is triggered
*/
OR,
+
+ /**
+ * trigger if at least M alarms are in the specified state
+ * Requires atLeastOptions to be specified
+ */
+ AT_LEAST,
+}
+
+/**
+ * Threshold for AT_LEAST operator.
+ */
+export abstract class AtLeastThreshold {
+ /**
+ * Create a count-based threshold.
+ * @param count The minimum number of alarms that must be in the specified state
+ */
+ public static count(count: number): AtLeastThreshold {
+ return new AtLeastThresholdCount(count);
+ }
+
+ /**
+ * Create a percentage-based threshold.
+ * @param percentage The minimum percentage of alarms (0-100) that must be in the specified state
+ */
+ public static percentage(percentage: number): AtLeastThreshold {
+ return new AtLeastThresholdPercentage(percentage);
+ }
+
+ /**
+ * @internal
+ */
+ public abstract _renderThreshold(alarms: IAlarm[]): string;
+}
+
+class AtLeastThresholdCount extends AtLeastThreshold {
+ constructor(private readonly count: number) {
+ super();
+ }
+
+ public _renderThreshold(alarms: IAlarm[]): string {
+ if (this.count < 0 || this.count > alarms.length) {
+ throw new Error(
+ `atLeastOptions.threshold count (${this.count}) must be between 0 and ${alarms.length} (number of alarms)`,
+ );
+ }
+ return this.count.toString();
+ }
+}
+
+class AtLeastThresholdPercentage extends AtLeastThreshold {
+ constructor(private readonly percentage: number) {
+ super();
+ }
+
+ public _renderThreshold(_alarms: IAlarm[]): string {
+ if (this.percentage < 0 || this.percentage > 100) {
+ throw new Error(
+ `atLeastOptions.threshold percentage (${this.percentage}) must be between 0 and 100`,
+ );
+ }
+ return `${this.percentage}%`;
+ }
+}
+
+/**
+ * Configuration for AT_LEAST composite alarm operator.
+ */
+export interface CompositeAlarmAtLeastOptions {
+ /**
+ * Threshold for AT_LEAST operator.
+ * Use AtLeastThreshold.count() or AtLeastThreshold.percentage().
+ */
+ readonly threshold: AtLeastThreshold;
+
+ /**
+ * Alarm state for AT_LEAST operator.
+ *
+ * @default - ALARM
+ */
+ readonly state?: AlarmState;
}
export interface AlarmFactoryDefaults {
@@ -868,13 +956,33 @@ export class AlarmFactory {
alarms: AlarmWithAnnotation[],
props: AddCompositeAlarmProps,
): IAlarmRule {
- const alarmRules = alarms.map((alarm) => alarm.alarmRuleWhenAlarming);
const operator = props.compositeOperator ?? CompositeAlarmOperator.OR;
switch (operator) {
- case CompositeAlarmOperator.AND:
+ case CompositeAlarmOperator.AND: {
+ const alarmRules = alarms.map((alarm) => alarm.alarmRuleWhenAlarming);
return AlarmRule.allOf(...alarmRules);
- case CompositeAlarmOperator.OR:
+ }
+ case CompositeAlarmOperator.OR: {
+ const alarmRules = alarms.map((alarm) => alarm.alarmRuleWhenAlarming);
return AlarmRule.anyOf(...alarmRules);
+ }
+ case CompositeAlarmOperator.AT_LEAST: {
+ if (!props.atLeastOptions) {
+ throw new Error(
+ "atLeastOptions must be specified when using AT_LEAST operator",
+ );
+ }
+ const threshold = props.atLeastOptions.threshold;
+ const state = props.atLeastOptions.state ?? AlarmState.ALARM;
+ const alarmObjects = alarms.map((alarm) => alarm.alarm);
+ const alarmNames = alarms.map((alarm) => alarm.alarm.alarmName);
+ const thresholdString = threshold._renderThreshold(alarmObjects);
+ const stateString = state.toString();
+ const alarmList = alarmNames.join(", ");
+ return AlarmRule.fromString(
+ `AT_LEAST(${thresholdString}, ${stateString}, (${alarmList}))`,
+ );
+ }
default:
throw new Error(`Unsupported composite alarm operator: ${operator}`);
}
diff --git a/test/common/alarm/AlarmFactory.test.ts b/test/common/alarm/AlarmFactory.test.ts
index 524cca09..9dd7b7df 100644
--- a/test/common/alarm/AlarmFactory.test.ts
+++ b/test/common/alarm/AlarmFactory.test.ts
@@ -18,6 +18,7 @@ import {
AlarmFactory,
AlarmFactoryDefaults,
AlarmNamingInput,
+ AtLeastThreshold,
CompositeAlarmOperator,
IAlarmActionStrategy,
IAlarmNamingStrategy,
@@ -596,6 +597,174 @@ test("addCompositeAlarm: snapshot for operator", () => {
expect(Template.fromStack(stack)).toMatchSnapshot();
});
+test("addCompositeAlarm: AT_LEAST operator with absolute threshold", () => {
+ const stack = new Stack();
+ const factory = new AlarmFactory(stack, {
+ globalMetricDefaults,
+ globalAlarmDefaults: globalAlarmDefaultsWithDisambiguator,
+ localAlarmNamePrefix: "prefix",
+ });
+ const metric = new Metric({
+ namespace: "DummyNamespace",
+ metricName: "DummyMetric",
+ });
+ const alarm1 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm1",
+ alarmDescription: "Testing alarm 1",
+ threshold: 1,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ const alarm2 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm2",
+ alarmDescription: "Testing alarm 2",
+ threshold: 2,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ const alarm3 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm3",
+ alarmDescription: "Testing alarm 3",
+ threshold: 3,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ factory.addCompositeAlarm([alarm1, alarm2, alarm3], {
+ disambiguator: "CompositeAtLeast",
+ alarmNameSuffix: "CompositeAtLeast",
+ compositeOperator: CompositeAlarmOperator.AT_LEAST,
+ atLeastOptions: { threshold: AtLeastThreshold.count(2) },
+ });
+
+ expect(Template.fromStack(stack)).toMatchSnapshot();
+});
+
+test("addCompositeAlarm: AT_LEAST operator with percentage threshold", () => {
+ const stack = new Stack();
+ const factory = new AlarmFactory(stack, {
+ globalMetricDefaults,
+ globalAlarmDefaults: globalAlarmDefaultsWithDisambiguator,
+ localAlarmNamePrefix: "prefix",
+ });
+ const metric = new Metric({
+ namespace: "DummyNamespace",
+ metricName: "DummyMetric",
+ });
+ const alarm1 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm1",
+ alarmDescription: "Testing alarm 1",
+ threshold: 1,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ const alarm2 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm2",
+ alarmDescription: "Testing alarm 2",
+ threshold: 2,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ const alarm3 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm3",
+ alarmDescription: "Testing alarm 3",
+ threshold: 3,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ const alarm4 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm4",
+ alarmDescription: "Testing alarm 4",
+ threshold: 4,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ factory.addCompositeAlarm([alarm1, alarm2, alarm3, alarm4], {
+ disambiguator: "CompositeAtLeast50Percent",
+ alarmNameSuffix: "CompositeAtLeast50Percent",
+ compositeOperator: CompositeAlarmOperator.AT_LEAST,
+ atLeastOptions: { threshold: AtLeastThreshold.percentage(50) },
+ });
+
+ expect(Template.fromStack(stack)).toMatchSnapshot();
+});
+
+test("addCompositeAlarm: AT_LEAST operator throws error when options is missing", () => {
+ expect(() => {
+ factory.addCompositeAlarm([], {
+ disambiguator: "CompositeAtLeastNoOptions",
+ alarmNameSuffix: "CompositeAtLeastNoOptions",
+ compositeOperator: CompositeAlarmOperator.AT_LEAST,
+ });
+ }).toThrow("atLeastOptions must be specified when using AT_LEAST operator");
+});
+
+test("addCompositeAlarm: AT_LEAST operator throws error when count exceeds alarm count", () => {
+ const stack = new Stack();
+ const factory = new AlarmFactory(stack, {
+ globalMetricDefaults,
+ globalAlarmDefaults: globalAlarmDefaultsWithDisambiguator,
+ localAlarmNamePrefix: "prefix",
+ });
+ const metric = new Metric({
+ namespace: "DummyNamespace",
+ metricName: "DummyMetric",
+ });
+ const alarm1 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm1",
+ alarmDescription: "Testing alarm 1",
+ threshold: 1,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ const alarm2 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm2",
+ alarmDescription: "Testing alarm 2",
+ threshold: 2,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ expect(() => {
+ factory.addCompositeAlarm([alarm1, alarm2], {
+ disambiguator: "CompositeAtLeastTooHigh",
+ alarmNameSuffix: "CompositeAtLeastTooHigh",
+ compositeOperator: CompositeAlarmOperator.AT_LEAST,
+ atLeastOptions: { threshold: AtLeastThreshold.count(5) },
+ });
+ }).toThrow(
+ "atLeastOptions.threshold count (5) must be between 0 and 2 (number of alarms)",
+ );
+});
+
+test("addCompositeAlarm: AT_LEAST operator throws error when percentage is out of range", () => {
+ const stack = new Stack();
+ const factory = new AlarmFactory(stack, {
+ globalMetricDefaults,
+ globalAlarmDefaults: globalAlarmDefaultsWithDisambiguator,
+ localAlarmNamePrefix: "prefix",
+ });
+ const metric = new Metric({
+ namespace: "DummyNamespace",
+ metricName: "DummyMetric",
+ });
+ const alarm1 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm1",
+ alarmDescription: "Testing alarm 1",
+ threshold: 1,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ expect(() => {
+ factory.addCompositeAlarm([alarm1], {
+ disambiguator: "CompositeAtLeastInvalidPercentage",
+ alarmNameSuffix: "CompositeAtLeastInvalidPercentage",
+ compositeOperator: CompositeAlarmOperator.AT_LEAST,
+ atLeastOptions: { threshold: AtLeastThreshold.percentage(150) },
+ });
+ }).toThrow(
+ "atLeastOptions.threshold percentage (150) must be between 0 and 100",
+ );
+});
+
test("addCompositeAlarm: snapshot for suppressor alarm props", () => {
const stack = new Stack();
const factory = new AlarmFactory(stack, {
diff --git a/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap b/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap
index 7dbd76be..cafaef2f 100644
--- a/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap
+++ b/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap
@@ -111,6 +111,267 @@ Object {
}
`;
+exports[`addCompositeAlarm: AT_LEAST operator with absolute threshold 1`] = `
+Object {
+ "Parameters": Object {
+ "BootstrapVersion": Object {
+ "Default": "/cdk-bootstrap/hnb659fds/version",
+ "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
+ "Type": "AWS::SSM::Parameter::Value",
+ },
+ },
+ "Resources": Object {
+ "DummyServiceAlarmsprefixAlarm1F4FCF957": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 1",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm1",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 1,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixAlarm25ADF8B37": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 2",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm2",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 2,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixAlarm308E4C14F": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 3",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm3",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 3,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixCompositeAtLeastBE84311C": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "DummyServiceAlarms-prefix-CompositeAtLeast",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "AT_LEAST(2, ALARM, (",
+ Object {
+ "Ref": "DummyServiceAlarmsprefixAlarm1F4FCF957",
+ },
+ ", ",
+ Object {
+ "Ref": "DummyServiceAlarmsprefixAlarm25ADF8B37",
+ },
+ ", ",
+ Object {
+ "Ref": "DummyServiceAlarmsprefixAlarm308E4C14F",
+ },
+ "))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ },
+ "Rules": Object {
+ "CheckBootstrapVersion": Object {
+ "Assertions": Array [
+ Object {
+ "Assert": Object {
+ "Fn::Not": Array [
+ Object {
+ "Fn::Contains": Array [
+ Array [
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ ],
+ Object {
+ "Ref": "BootstrapVersion",
+ },
+ ],
+ },
+ ],
+ },
+ "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.",
+ },
+ ],
+ },
+ },
+}
+`;
+
+exports[`addCompositeAlarm: AT_LEAST operator with percentage threshold 1`] = `
+Object {
+ "Parameters": Object {
+ "BootstrapVersion": Object {
+ "Default": "/cdk-bootstrap/hnb659fds/version",
+ "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
+ "Type": "AWS::SSM::Parameter::Value",
+ },
+ },
+ "Resources": Object {
+ "DummyServiceAlarmsprefixAlarm1F4FCF957": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 1",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm1",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 1,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixAlarm25ADF8B37": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 2",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm2",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 2,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixAlarm308E4C14F": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 3",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm3",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 3,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixAlarm4782A133C": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 4",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm4",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 4,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixCompositeAtLeast50Percent54242EDB": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "DummyServiceAlarms-prefix-CompositeAtLeast50Percent",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "AT_LEAST(50%, ALARM, (",
+ Object {
+ "Ref": "DummyServiceAlarmsprefixAlarm1F4FCF957",
+ },
+ ", ",
+ Object {
+ "Ref": "DummyServiceAlarmsprefixAlarm25ADF8B37",
+ },
+ ", ",
+ Object {
+ "Ref": "DummyServiceAlarmsprefixAlarm308E4C14F",
+ },
+ ", ",
+ Object {
+ "Ref": "DummyServiceAlarmsprefixAlarm4782A133C",
+ },
+ "))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ },
+ "Rules": Object {
+ "CheckBootstrapVersion": Object {
+ "Assertions": Array [
+ Object {
+ "Assert": Object {
+ "Fn::Not": Array [
+ Object {
+ "Fn::Contains": Array [
+ Array [
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ ],
+ Object {
+ "Ref": "BootstrapVersion",
+ },
+ ],
+ },
+ ],
+ },
+ "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.",
+ },
+ ],
+ },
+ },
+}
+`;
+
exports[`addCompositeAlarm: snapshot for operator 1`] = `
Object {
"Parameters": Object {