From 8caf70b4d271c4ddc77b3e8a6810e1a6ae165da1 Mon Sep 17 00:00:00 2001 From: Tatsuya Yamamoto Date: Wed, 3 Aug 2022 14:05:26 +0900 Subject: [PATCH] feat(iotevents): support timer actions (#19949) This PR is a part of roadmap in #17711. And if this PR is merged, I will close this issue and create some good first issues to implement rest actions and expressions. This PR supports the timer actions (`SetTimerAction`, `ResetTimerAction` and `ClearTimerAction`) and `timeout()` expression. These allow to embed a timer to the state machine of the detector model. Below figure illustrate the state machine [Device Heartbeat](https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-examples-dhb.html). This detector model is used to alert if the message is interrupted for a certain period of time. The integ-test included in this PR is example of creating Device Heartbeat detector model. ```mermaid stateDiagram-v2 [*] --> Online: set a timer on input message Online --> Online: reset the timer\non input messages Online --> Offline: timeout the timer Offline --> Online: input messages ``` ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-iotevents-actions/README.md | 79 +++++- .../lib/clear-timer-action.ts | 22 ++ .../aws-iotevents-actions/lib/index.ts | 4 + .../lib/reset-timer-action.ts | 22 ++ .../lib/set-timer-action.ts | 29 ++ .../lib/timer-duration.ts | 54 ++++ .../aws-iotevents-actions/package.json | 1 + .../test/iot/clear-timer-action.test.ts | 44 +++ .../test/iot/integ.timer-actions.ts | 64 +++++ .../test/iot/reset-timer-action.test.ts | 44 +++ .../test/iot/set-timer-action.test.ts | 57 ++++ ...aultTestDeployAssert89D9000B.template.json | 1 + .../iot/timer-actions.integ.snapshot/cdk.out | 1 + .../timer-actions.integ.snapshot/integ.json | 11 + ...vents-timer-actions-test-stack.assets.json | 19 ++ ...nts-timer-actions-test-stack.template.json | 151 ++++++++++ .../manifest.json | 49 ++++ .../timer-actions.integ.snapshot/tree.json | 268 ++++++++++++++++++ .../@aws-cdk/aws-iotevents/lib/expression.ts | 9 + .../aws-iotevents/test/detector-model.test.ts | 1 + 20 files changed, 924 insertions(+), 6 deletions(-) create mode 100644 packages/@aws-cdk/aws-iotevents-actions/lib/clear-timer-action.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/lib/reset-timer-action.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/lib/set-timer-action.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/lib/timer-duration.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/clear-timer-action.test.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/integ.timer-actions.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/reset-timer-action.test.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/set-timer-action.test.ts create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/TimerActionsDefaultTestDeployAssert89D9000B.template.json create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.assets.json create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.template.json create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/tree.json diff --git a/packages/@aws-cdk/aws-iotevents-actions/README.md b/packages/@aws-cdk/aws-iotevents-actions/README.md index 486ddd8124c34..c4c9eaa13d96d 100644 --- a/packages/@aws-cdk/aws-iotevents-actions/README.md +++ b/packages/@aws-cdk/aws-iotevents-actions/README.md @@ -24,9 +24,78 @@ AWS IoT Events can trigger actions when it detects a specified event or transiti Currently supported are: +- Use timer - Set variable to detector instanse - Invoke a Lambda function +## Use timer + +The code snippet below creates an Action that creates the timer with duration in seconds. + +```ts +import * as iotevents from '@aws-cdk/aws-iotevents'; +import * as actions from '@aws-cdk/aws-iotevents-actions'; + +declare const input: iotevents.IInput; + +const state = new iotevents.State({ + stateName: 'MyState', + onEnter: [{ + eventName: 'test-event', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.SetTimerAction('MyTimer', { + duration: cdk.Duration.seconds(60), + }), + ], + }], +}); +``` + +Setting duration by [IoT Events Expression](https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-expressions.html): + +```ts +new actions.SetTimerAction('MyTimer', { + durationExpression: iotevents.Expression.inputAttribute(input, 'payload.durationSeconds'), +}) +``` + +And the timer can be reset and cleared. Below is an example of general +[Device HeartBeat](https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-examples-dhb.html) +Detector Model: + +```ts +const online = new iotevents.State({ + stateName: 'Online', + onEnter: [{ + eventName: 'enter-event', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.SetTimerAction('MyTimer', { + duration: cdk.Duration.seconds(60), + }), + ], + }], + onInput: [{ + eventName: 'input-event', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.ResetTimerAction('MyTimer'), + ], + }], + onExit: [{ + eventName: 'exit-event', + actions: [ + new actions.ClearTimerAction('MyTimer'), + ], + }], +}); +const offline = new iotevents.State({ stateName: 'Offline' }); + +online.transitionTo(offline, { when: iotevents.Expression.timeout('MyTimer') }); +offline.transitionTo(online, { when: iotevents.Expression.currentInput(input) }); +``` + ## Set variable to detector instanse The code snippet below creates an Action that set variable to detector instanse @@ -44,12 +113,10 @@ const state = new iotevents.State({ eventName: 'test-event', condition: iotevents.Expression.currentInput(input), actions: [ - actions: [ - new actions.SetVariableAction( - 'MyVariable', - iotevents.Expression.inputAttribute(input, 'payload.temperature'), - ), - ], + new actions.SetVariableAction( + 'MyVariable', + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + ), ], }], }); diff --git a/packages/@aws-cdk/aws-iotevents-actions/lib/clear-timer-action.ts b/packages/@aws-cdk/aws-iotevents-actions/lib/clear-timer-action.ts new file mode 100644 index 0000000000000..d219dfd8d9f76 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/lib/clear-timer-action.ts @@ -0,0 +1,22 @@ +import * as iotevents from '@aws-cdk/aws-iotevents'; +import { Construct } from 'constructs'; + +/** + * The action to delete an existing timer. + */ +export class ClearTimerAction implements iotevents.IAction { + /** + * @param timerName the name of the timer + */ + constructor(private readonly timerName: string) {} + + bind(_scope: Construct, _options: iotevents.ActionBindOptions): iotevents.ActionConfig { + return { + configuration: { + clearTimer: { + timerName: this.timerName, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iotevents-actions/lib/index.ts b/packages/@aws-cdk/aws-iotevents-actions/lib/index.ts index e51394d301376..77b06b1419f2d 100644 --- a/packages/@aws-cdk/aws-iotevents-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-iotevents-actions/lib/index.ts @@ -1,2 +1,6 @@ +export * from './clear-timer-action'; export * from './set-variable-action'; export * from './lambda-invoke-action'; +export * from './reset-timer-action'; +export * from './set-timer-action'; +export * from './timer-duration'; diff --git a/packages/@aws-cdk/aws-iotevents-actions/lib/reset-timer-action.ts b/packages/@aws-cdk/aws-iotevents-actions/lib/reset-timer-action.ts new file mode 100644 index 0000000000000..21e094f970f22 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/lib/reset-timer-action.ts @@ -0,0 +1,22 @@ +import * as iotevents from '@aws-cdk/aws-iotevents'; +import { Construct } from 'constructs'; + +/** + * The action to reset an existing timer. + */ +export class ResetTimerAction implements iotevents.IAction { + /** + * @param timerName the name of the timer + */ + constructor(private readonly timerName: string) {} + + bind(_scope: Construct, _options: iotevents.ActionBindOptions): iotevents.ActionConfig { + return { + configuration: { + resetTimer: { + timerName: this.timerName, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iotevents-actions/lib/set-timer-action.ts b/packages/@aws-cdk/aws-iotevents-actions/lib/set-timer-action.ts new file mode 100644 index 0000000000000..bffa2acdbeec5 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/lib/set-timer-action.ts @@ -0,0 +1,29 @@ +import * as iotevents from '@aws-cdk/aws-iotevents'; +import { Construct } from 'constructs'; +import { TimerDuration } from './timer-duration'; + +/** + * The action to create a timer with duration in seconds. + */ +export class SetTimerAction implements iotevents.IAction { + /** + * @param timerName the name of the timer + * @param timerDuration the duration of the timer + */ + constructor( + private readonly timerName: string, + private readonly timerDuration: TimerDuration, + ) { + } + + bind(_scope: Construct, _options: iotevents.ActionBindOptions): iotevents.ActionConfig { + return { + configuration: { + setTimer: { + timerName: this.timerName, + durationExpression: this.timerDuration._bind(), + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iotevents-actions/lib/timer-duration.ts b/packages/@aws-cdk/aws-iotevents-actions/lib/timer-duration.ts new file mode 100644 index 0000000000000..e9fe41be3c544 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/lib/timer-duration.ts @@ -0,0 +1,54 @@ +import * as iotevents from '@aws-cdk/aws-iotevents'; +import { Duration } from '@aws-cdk/core'; + +/** + * The duration of the timer. + */ +export abstract class TimerDuration { + /** + * Create a timer-duration from Duration. + * + * The range of the duration is 60-31622400 seconds. + * The evaluated result of the duration expression is rounded down to the nearest whole number. + * For example, if you set the timer to 60.99 seconds, the evaluated result of the duration expression is 60 seconds. + */ + public static fromDuration(duration: Duration): TimerDuration { + const seconds = duration.toSeconds(); + if (seconds < 60) { + throw new Error(`duration cannot be less than 60 seconds, got: ${duration.toString()}`); + } + if (seconds > 31622400) { + throw new Error(`duration cannot be greater than 31622400 seconds, got: ${duration.toString()}`); + } + return new TimerDurationImpl(seconds.toString()); + } + + /** + * Create a timer-duration from Expression. + * + * You can use a string expression that includes numbers, variables ($variable.), + * and input values ($input..) as the duration. + * + * The range of the duration is 60-31622400 seconds. + * The evaluated result of the duration expression is rounded down to the nearest whole number. + * For example, if you set the timer to 60.99 seconds, the evaluated result of the duration expression is 60 seconds. + */ + public static fromExpression(expression: iotevents.Expression): TimerDuration { + return new TimerDurationImpl(expression.evaluate()); + } + + /** + * @internal + */ + public abstract _bind(): string; +} + +class TimerDurationImpl extends TimerDuration { + constructor(private readonly durationExpression: string) { + super(); + } + + public _bind() { + return this.durationExpression; + } +} diff --git a/packages/@aws-cdk/aws-iotevents-actions/package.json b/packages/@aws-cdk/aws-iotevents-actions/package.json index 8f6edec334e5f..63c6c90ea0e15 100644 --- a/packages/@aws-cdk/aws-iotevents-actions/package.json +++ b/packages/@aws-cdk/aws-iotevents-actions/package.json @@ -73,6 +73,7 @@ "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", "jest": "^27.5.1" diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/clear-timer-action.test.ts b/packages/@aws-cdk/aws-iotevents-actions/test/iot/clear-timer-action.test.ts new file mode 100644 index 0000000000000..1f3d2195633a2 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/clear-timer-action.test.ts @@ -0,0 +1,44 @@ +import { Template } from '@aws-cdk/assertions'; +import * as iotevents from '@aws-cdk/aws-iotevents'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +let stack: cdk.Stack; +let input: iotevents.IInput; +beforeEach(() => { + stack = new cdk.Stack(); + input = iotevents.Input.fromInputName(stack, 'MyInput', 'test-input'); +}); + +test('Default property', () => { + // WHEN + new iotevents.DetectorModel(stack, 'MyDetectorModel', { + initialState: new iotevents.State({ + stateName: 'test-state', + onEnter: [{ + eventName: 'test-eventName', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.ClearTimerAction('MyTimer'), + ], + }], + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', { + DetectorModelDefinition: { + States: [{ + OnEnter: { + Events: [{ + Actions: [{ + ClearTimer: { + TimerName: 'MyTimer', + }, + }], + }], + }, + }], + }, + }); +}); diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/integ.timer-actions.ts b/packages/@aws-cdk/aws-iotevents-actions/test/iot/integ.timer-actions.ts new file mode 100644 index 0000000000000..cdaf780145a25 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/integ.timer-actions.ts @@ -0,0 +1,64 @@ +/** + * Stack verification steps: + * * put a message + * * aws iotevents-data batch-put-message --region=us-east-1 --messages=messageId=(date | md5),inputName=test_input,payload=(echo '{"payload":{"deviceId":"000"}}' | base64) + */ +import * as iotevents from '@aws-cdk/aws-iotevents'; +import * as cdk from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import * as actions from '../../lib'; + +/** + * This example will creates the detector model for Device HeartBeat Monitoring. + * + * @see https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-examples-dhb.html + */ +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const input = new iotevents.Input(this, 'MyInput', { + inputName: 'test_input', + attributeJsonPaths: ['payload.deviceId'], + }); + + const online = new iotevents.State({ + stateName: 'Online', + onEnter: [{ + eventName: 'enter-event', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.SetTimerAction('MyTimer', actions.TimerDuration.fromDuration(cdk.Duration.seconds(60))), + ], + }], + onInput: [{ + eventName: 'input-event', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.ResetTimerAction('MyTimer'), + ], + }], + onExit: [{ + eventName: 'exit-event', + actions: [ + new actions.ClearTimerAction('MyTimer'), + ], + }], + }); + const offline = new iotevents.State({ stateName: 'Offline' }); + + online.transitionTo(offline, { when: iotevents.Expression.timeout('MyTimer') }); + offline.transitionTo(online, { when: iotevents.Expression.currentInput(input) }); + + new iotevents.DetectorModel(this, 'MyDetectorModel', { + detectorKey: 'payload.deviceId', + initialState: online, + }); + } +} + +// GIVEN +const app = new cdk.App(); +const stack = new TestStack(app, 'iotevents-timer-actions-test-stack'); +new IntegTest(app, 'TimerActions', { testCases: [stack] }); +app.synth(); diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/reset-timer-action.test.ts b/packages/@aws-cdk/aws-iotevents-actions/test/iot/reset-timer-action.test.ts new file mode 100644 index 0000000000000..40c9ff5e7e91a --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/reset-timer-action.test.ts @@ -0,0 +1,44 @@ +import { Template } from '@aws-cdk/assertions'; +import * as iotevents from '@aws-cdk/aws-iotevents'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +let stack: cdk.Stack; +let input: iotevents.IInput; +beforeEach(() => { + stack = new cdk.Stack(); + input = iotevents.Input.fromInputName(stack, 'MyInput', 'test-input'); +}); + +test('Default property', () => { + // WHEN + new iotevents.DetectorModel(stack, 'MyDetectorModel', { + initialState: new iotevents.State({ + stateName: 'test-state', + onEnter: [{ + eventName: 'test-eventName', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.ResetTimerAction('MyTimer'), + ], + }], + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', { + DetectorModelDefinition: { + States: [{ + OnEnter: { + Events: [{ + Actions: [{ + ResetTimer: { + TimerName: 'MyTimer', + }, + }], + }], + }, + }], + }, + }); +}); diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/set-timer-action.test.ts b/packages/@aws-cdk/aws-iotevents-actions/test/iot/set-timer-action.test.ts new file mode 100644 index 0000000000000..5f2653c620222 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/set-timer-action.test.ts @@ -0,0 +1,57 @@ +import { Template } from '@aws-cdk/assertions'; +import * as iotevents from '@aws-cdk/aws-iotevents'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +let stack: cdk.Stack; +let input: iotevents.IInput; +beforeEach(() => { + stack = new cdk.Stack(); + input = iotevents.Input.fromInputName(stack, 'MyInput', 'test-input'); +}); + +test.each([ + ['Can set duration', actions.TimerDuration.fromDuration(cdk.Duration.minutes(2)), '120'], + ['Can set durationExpression', actions.TimerDuration.fromExpression(iotevents.Expression.fromString('test-expression')), 'test-expression'], +])('%s', (_, durationOption, durationExpression) => { + // WHEN + new iotevents.DetectorModel(stack, 'MyDetectorModel', { + initialState: new iotevents.State({ + stateName: 'test-state', + onEnter: [{ + eventName: 'test-eventName', + condition: iotevents.Expression.currentInput(input), + actions: [ + new actions.SetTimerAction('MyTimer', durationOption), + ], + }], + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', { + DetectorModelDefinition: { + States: [{ + OnEnter: { + Events: [{ + Actions: [{ + SetTimer: { + TimerName: 'MyTimer', + DurationExpression: durationExpression, + }, + }], + }], + }, + }], + }, + }); +}); + +test.each([ + ['duration less than 60 seconds', cdk.Duration.seconds(59), 'duration cannot be less than 60 seconds, got: Duration.seconds(59)'], + ['duration greater than 31622400 seconds', cdk.Duration.seconds(31622401), 'duration cannot be greater than 31622400 seconds, got: Duration.seconds(31622401)'], +])('Cannot set %', (_, duration, errorMessage) => { + expect(() => { + actions.TimerDuration.fromDuration(duration); + }).toThrow(errorMessage); +}); diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/TimerActionsDefaultTestDeployAssert89D9000B.template.json b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/TimerActionsDefaultTestDeployAssert89D9000B.template.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/TimerActionsDefaultTestDeployAssert89D9000B.template.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..588d7b269d34f --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/integ.json b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/integ.json new file mode 100644 index 0000000000000..be3cb597b0fe5 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/integ.json @@ -0,0 +1,11 @@ +{ + "version": "20.0.0", + "testCases": { + "TimerActions/DefaultTest": { + "stacks": [ + "iotevents-timer-actions-test-stack" + ], + "assertionStack": "TimerActionsDefaultTestDeployAssert89D9000B" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.assets.json b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.assets.json new file mode 100644 index 0000000000000..69a4b6590cf96 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "20.0.0", + "files": { + "2e8c935c8b92198714514946548737d7e2ec921f7635995c80095bcb79441bea": { + "source": { + "path": "iotevents-timer-actions-test-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2e8c935c8b92198714514946548737d7e2ec921f7635995c80095bcb79441bea.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.template.json b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.template.json new file mode 100644 index 0000000000000..cf78b96310b13 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/iotevents-timer-actions-test-stack.template.json @@ -0,0 +1,151 @@ +{ + "Resources": { + "MyInput08947B23": { + "Type": "AWS::IoTEvents::Input", + "Properties": { + "InputDefinition": { + "Attributes": [ + { + "JsonPath": "payload.deviceId" + } + ] + }, + "InputName": "test_input" + } + }, + "MyDetectorModelDetectorModelRoleF2FB4D88": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iotevents.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyDetectorModel559C0B0E": { + "Type": "AWS::IoTEvents::DetectorModel", + "Properties": { + "DetectorModelDefinition": { + "InitialStateName": "Online", + "States": [ + { + "OnEnter": { + "Events": [ + { + "Actions": [ + { + "SetTimer": { + "DurationExpression": "60", + "TimerName": "MyTimer" + } + } + ], + "Condition": { + "Fn::Join": [ + "", + [ + "currentInput(\"", + { + "Ref": "MyInput08947B23" + }, + "\")" + ] + ] + }, + "EventName": "enter-event" + } + ] + }, + "OnExit": { + "Events": [ + { + "Actions": [ + { + "ClearTimer": { + "TimerName": "MyTimer" + } + } + ], + "EventName": "exit-event" + } + ] + }, + "OnInput": { + "Events": [ + { + "Actions": [ + { + "ResetTimer": { + "TimerName": "MyTimer" + } + } + ], + "Condition": { + "Fn::Join": [ + "", + [ + "currentInput(\"", + { + "Ref": "MyInput08947B23" + }, + "\")" + ] + ] + }, + "EventName": "input-event" + } + ], + "TransitionEvents": [ + { + "Condition": "timeout(\"MyTimer\")", + "EventName": "Online_to_Offline", + "NextState": "Offline" + } + ] + }, + "StateName": "Online" + }, + { + "OnInput": { + "TransitionEvents": [ + { + "Condition": { + "Fn::Join": [ + "", + [ + "currentInput(\"", + { + "Ref": "MyInput08947B23" + }, + "\")" + ] + ] + }, + "EventName": "Offline_to_Online", + "NextState": "Online" + } + ] + }, + "StateName": "Offline" + } + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "MyDetectorModelDetectorModelRoleF2FB4D88", + "Arn" + ] + }, + "Key": "payload.deviceId" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..e8f8b1d56f962 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/manifest.json @@ -0,0 +1,49 @@ +{ + "version": "20.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "iotevents-timer-actions-test-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "iotevents-timer-actions-test-stack.template.json", + "validateOnSynth": false + }, + "metadata": { + "/iotevents-timer-actions-test-stack/MyInput/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyInput08947B23" + } + ], + "/iotevents-timer-actions-test-stack/MyDetectorModel/DetectorModelRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyDetectorModelDetectorModelRoleF2FB4D88" + } + ], + "/iotevents-timer-actions-test-stack/MyDetectorModel/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyDetectorModel559C0B0E" + } + ] + }, + "displayName": "iotevents-timer-actions-test-stack" + }, + "TimerActionsDefaultTestDeployAssert89D9000B": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "TimerActionsDefaultTestDeployAssert89D9000B.template.json", + "validateOnSynth": false + }, + "displayName": "TimerActions/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/tree.json b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/tree.json new file mode 100644 index 0000000000000..f60b628102a08 --- /dev/null +++ b/packages/@aws-cdk/aws-iotevents-actions/test/iot/timer-actions.integ.snapshot/tree.json @@ -0,0 +1,268 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "iotevents-timer-actions-test-stack": { + "id": "iotevents-timer-actions-test-stack", + "path": "iotevents-timer-actions-test-stack", + "children": { + "MyInput": { + "id": "MyInput", + "path": "iotevents-timer-actions-test-stack/MyInput", + "children": { + "Resource": { + "id": "Resource", + "path": "iotevents-timer-actions-test-stack/MyInput/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IoTEvents::Input", + "aws:cdk:cloudformation:props": { + "inputDefinition": { + "attributes": [ + { + "jsonPath": "payload.deviceId" + } + ] + }, + "inputName": "test_input" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iotevents.CfnInput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iotevents.Input", + "version": "0.0.0" + } + }, + "MyDetectorModel": { + "id": "MyDetectorModel", + "path": "iotevents-timer-actions-test-stack/MyDetectorModel", + "children": { + "DetectorModelRole": { + "id": "DetectorModelRole", + "path": "iotevents-timer-actions-test-stack/MyDetectorModel/DetectorModelRole", + "children": { + "Resource": { + "id": "Resource", + "path": "iotevents-timer-actions-test-stack/MyDetectorModel/DetectorModelRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iotevents.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "iotevents-timer-actions-test-stack/MyDetectorModel/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IoTEvents::DetectorModel", + "aws:cdk:cloudformation:props": { + "detectorModelDefinition": { + "initialStateName": "Online", + "states": [ + { + "stateName": "Online", + "onEnter": { + "events": [ + { + "eventName": "enter-event", + "condition": { + "Fn::Join": [ + "", + [ + "currentInput(\"", + { + "Ref": "MyInput08947B23" + }, + "\")" + ] + ] + }, + "actions": [ + { + "setTimer": { + "timerName": "MyTimer", + "durationExpression": "60" + } + } + ] + } + ] + }, + "onInput": { + "events": [ + { + "eventName": "input-event", + "condition": { + "Fn::Join": [ + "", + [ + "currentInput(\"", + { + "Ref": "MyInput08947B23" + }, + "\")" + ] + ] + }, + "actions": [ + { + "resetTimer": { + "timerName": "MyTimer" + } + } + ] + } + ], + "transitionEvents": [ + { + "eventName": "Online_to_Offline", + "condition": "timeout(\"MyTimer\")", + "nextState": "Offline" + } + ] + }, + "onExit": { + "events": [ + { + "eventName": "exit-event", + "actions": [ + { + "clearTimer": { + "timerName": "MyTimer" + } + } + ] + } + ] + } + }, + { + "stateName": "Offline", + "onInput": { + "transitionEvents": [ + { + "eventName": "Offline_to_Online", + "condition": { + "Fn::Join": [ + "", + [ + "currentInput(\"", + { + "Ref": "MyInput08947B23" + }, + "\")" + ] + ] + }, + "nextState": "Online" + } + ] + } + } + ] + }, + "roleArn": { + "Fn::GetAtt": [ + "MyDetectorModelDetectorModelRoleF2FB4D88", + "Arn" + ] + }, + "key": "payload.deviceId" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iotevents.CfnDetectorModel", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iotevents.DetectorModel", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "TimerActions": { + "id": "TimerActions", + "path": "TimerActions", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "TimerActions/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "TimerActions/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.58" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "TimerActions/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents/lib/expression.ts b/packages/@aws-cdk/aws-iotevents/lib/expression.ts index 8ad98de5d5496..925fd6a7201f5 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/expression.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/expression.ts @@ -20,6 +20,15 @@ export abstract class Expression { return this.fromString(`currentInput("${input.inputName}")`); } + /** + * Create a expression for function `timeout("timer-name")`. + * It is evaluated to true if the specified timer has elapsed. + * You can define a timer only using the `setTimer` action. + */ + public static timeout(timerName: string): Expression { + return this.fromString(`timeout("${timerName}")`); + } + /** * Create a expression for get an input attribute as `$input.TemperatureInput.temperatures[2]`. */ diff --git a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts index 2b485648059bd..6e6688d8b2527 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -501,6 +501,7 @@ describe('Expression', () => { const E = iotevents.Expression; test.each([ ['currentInput', (testInput: iotevents.IInput) => E.currentInput(testInput), 'currentInput("test-input")'], + ['timeout', () => E.timeout('test-timer'), 'timeout("test-timer")'], ['inputAttribute', (testInput: iotevents.IInput) => E.inputAttribute(testInput, 'json.path'), '$input.test-input.json.path'], ['add', () => E.add(E.fromString('5'), E.fromString('2')), '5 + 2'], ['subtract', () => E.subtract(E.fromString('5'), E.fromString('2')), '5 - 2'],