From cee19c060e3f7ed319feb1a6c3cc5a839fbd8110 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Wed, 2 Feb 2022 00:32:46 +0900 Subject: [PATCH 01/13] feat(iotevents): support transition event --- packages/@aws-cdk/aws-iotevents/README.md | 26 ++++++++- .../aws-iotevents/lib/detector-model.ts | 2 +- packages/@aws-cdk/aws-iotevents/lib/event.ts | 25 +++++++- packages/@aws-cdk/aws-iotevents/lib/state.ts | 48 +++++++++++++--- .../aws-iotevents/test/detector-model.test.ts | 57 +++++++++++++++++++ .../test/integ.detector-model.expected.json | 47 ++++++++++++++- .../test/integ.detector-model.ts | 28 ++++++++- 7 files changed, 216 insertions(+), 17 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index 760b6098d9d41..42437126362f4 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -54,13 +54,35 @@ const input = new iotevents.Input(this, 'MyInput', { attributeJsonPaths: ['payload.deviceId', 'payload.temperature'], }); -const onlineState = new iotevents.State({ - stateName: 'online', +const normalState = new iotevents.State({ + stateName: 'normal', onEnter: [{ eventName: 'test-event', condition: iotevents.Expression.currentInput(input), }], }); +const coldState = new iotevents.State({ + stateName: 'cold', +}); + +// transit to coldState when temperature is 10 +normalState.transitionTo({ + eventName: 'to_coldState', + nextState: coldState, + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('10'), + ), +}); +// transit to normalState when temperature is 20 +coldState.transitionTo({ + eventName: 'to_normalState', + nextState: normalState, + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('20'), + ), +}); new iotevents.DetectorModel(this, 'MyDetectorModel', { detectorModelName: 'test-detector-model', // optional diff --git a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts index 5ef50fd871d75..b14af93e41033 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts @@ -123,7 +123,7 @@ export class DetectorModel extends Resource implements IDetectorModel { key: props.detectorKey, detectorModelDefinition: { initialStateName: props.initialState.stateName, - states: [props.initialState._toStateJson()], + states: props.initialState._getStatesJson(), }, roleArn: role.roleArn, }); diff --git a/packages/@aws-cdk/aws-iotevents/lib/event.ts b/packages/@aws-cdk/aws-iotevents/lib/event.ts index 610469db9c32c..36167e1a3d1e3 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/event.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/event.ts @@ -1,14 +1,20 @@ import { Expression } from './expression'; +import { State } from './state'; /** - * Specifies the actions to be performed when the condition evaluates to TRUE. + * The base interface for events. */ -export interface Event { +interface IEventBase { /** * The name of the event. */ readonly eventName: string; +} +/** + * Specifies the actions to be performed when the condition evaluates to TRUE. + */ +export interface IEvent extends IEventBase { /** * The Boolean expression that, when TRUE, causes the actions to be performed. * @@ -16,3 +22,18 @@ export interface Event { */ readonly condition?: Expression; } + +/** + * Specifies the state transition and the actions to be performed when the condition evaluates to TRUE. + */ +export interface ITransitionEvent extends IEventBase { + /** + * The Boolean expression that, when TRUE, causes the state transition and the actions to be performed. + */ + readonly condition: Expression; + + /** + * The next state to transit to. When the resuld of condition expression is TRUE, the state is transited. + */ + readonly nextState: State; +} diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index e16d911d60004..93eea07df1e8a 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -1,4 +1,4 @@ -import { Event } from './event'; +import { IEvent, ITransitionEvent } from './event'; import { CfnDetectorModel } from './iotevents.generated'; /** @@ -16,7 +16,7 @@ export interface StateProps { * * @default - events on enter will not be set */ - readonly onEnter?: Event[]; + readonly onEnter?: IEvent[]; } /** @@ -28,21 +28,46 @@ export class State { */ public readonly stateName: string; + private transitionEvents: ITransitionEvent[] = [] + constructor(private readonly props: StateProps) { this.stateName = props.stateName; } /** - * Return the state property JSON + * Add a transition event to the state. + * + * @param transitionEvent the transition event that triggered if condition is evaluated to TRUE + */ + public transitionTo(transitionEvent:ITransitionEvent) { + this.transitionEvents.push(transitionEvent); + } + + /** + * Return the JSON of the states that is transited to. + * This function is called recursively and collect the states. * * @internal */ - public _toStateJson(): CfnDetectorModel.StateProperty { + public _getStatesJson(states: CfnDetectorModel.StateProperty[] = []): CfnDetectorModel.StateProperty[] { const { stateName, onEnter } = this.props; - return { + + if (states.some(s => s.stateName === stateName)) { + return states; + } + + const newStates: CfnDetectorModel.StateProperty[] = [...states, { stateName, + onInput: { + transitionEvents: this.transitionEvents.length > 0 ? + getTransitionEventJson(this.transitionEvents) : undefined, + }, onEnter: onEnter && { events: getEventJson(onEnter) }, - }; + }]; + + return this.transitionEvents.reduce((acc, transitionEvent) => { + return transitionEvent.nextState._getStatesJson(acc); + }, newStates); } /** @@ -55,7 +80,7 @@ export class State { } } -function getEventJson(events: Event[]): CfnDetectorModel.EventProperty[] { +function getEventJson(events: IEvent[]): CfnDetectorModel.EventProperty[] { return events.map(e => { return { eventName: e.eventName, @@ -63,3 +88,12 @@ function getEventJson(events: Event[]): CfnDetectorModel.EventProperty[] { }; }); } +function getTransitionEventJson(events: ITransitionEvent[]): CfnDetectorModel.TransitionEventProperty[] { + return events.map(e => { + return { + eventName: e.eventName, + condition: e.condition.evaluate(), + nextState: e.nextState.stateName, + }; + }); +} 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 a15ba6a986049..57cdd9551a54c 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -137,6 +137,63 @@ test('can set multiple events to State', () => { }); }); +test('can set states with transitions', () => { + // WHEN + const firstState = new iotevents.State({ + stateName: 'firstState', + onEnter: [{ + eventName: 'test-eventName', + condition: iotevents.Expression.fromString('test-eventCondition'), + }], + }); + const secondState = new iotevents.State({ + stateName: 'secondState', + }); + + firstState.transitionTo({ + eventName: 'firstToSecond', + nextState: secondState, + condition: iotevents.Expression.fromString('test-eventCondition-12'), + }); + secondState.transitionTo({ + eventName: 'secondToFirst', + nextState: firstState, + condition: iotevents.Expression.fromString('test-eventCondition-21'), + }); + + new iotevents.DetectorModel(stack, 'MyDetectorModel', { + initialState: firstState, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', { + DetectorModelDefinition: { + States: [ + { + StateName: 'firstState', + OnInput: { + TransitionEvents: [{ + EventName: 'firstToSecond', + NextState: 'secondState', + Condition: 'test-eventCondition-12', + }], + }, + }, + { + StateName: 'secondState', + OnInput: { + TransitionEvents: [{ + EventName: 'secondToFirst', + NextState: 'firstState', + Condition: 'test-eventCondition-21', + }], + }, + }, + ], + }, + }); +}); + test('can set role', () => { // WHEN const role = iam.Role.fromRoleArn(stack, 'test-role', 'arn:aws:iam::123456789012:role/ForTest'); diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json index 3b1b598427701..2205e02c25bd9 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json @@ -37,7 +37,7 @@ "Type": "AWS::IoTEvents::DetectorModel", "Properties": { "DetectorModelDefinition": { - "InitialStateName": "online", + "InitialStateName": "firstState", "States": [ { "OnEnter": { @@ -63,7 +63,50 @@ } ] }, - "StateName": "online" + "OnInput": { + "TransitionEvents": [ + { + "Condition": { + "Fn::Join": [ + "", + [ + "$input.", + { + "Ref": "MyInput08947B23" + }, + ".payload.temperature == 12" + ] + ] + }, + "EventName": "firstToSecond", + "NextState": "secondState" + } + ] + }, + "StateName": "firstState" + }, + { + "OnInput": { + "TransitionEvents": [ + { + "Condition": { + "Fn::Join": [ + "", + [ + "$input.", + { + "Ref": "MyInput08947B23" + }, + ".payload.temperature == 21" + ] + ] + }, + "EventName": "secondToFirst", + "NextState": "firstState" + } + ] + }, + "StateName": "secondState" } ] }, diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts index dc90a7d505dbf..6d27c655ae2d5 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts @@ -10,8 +10,8 @@ class TestStack extends cdk.Stack { attributeJsonPaths: ['payload.deviceId', 'payload.temperature'], }); - const onlineState = new iotevents.State({ - stateName: 'online', + const firstState = new iotevents.State({ + stateName: 'firstState', onEnter: [{ eventName: 'test-event', // meaning `condition: 'currentInput("test_input") && $input.test_input.payload.temperature == 31.5'` @@ -24,13 +24,35 @@ class TestStack extends cdk.Stack { ), }], }); + const secondState = new iotevents.State({ + stateName: 'secondState', + }); + + // 1st => 2nd + firstState.transitionTo({ + eventName: 'firstToSecond', + nextState: secondState, + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('12'), + ), + }); + // 2st => 1st + secondState.transitionTo({ + eventName: 'secondToFirst', + nextState: firstState, + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('21'), + ), + }); new iotevents.DetectorModel(this, 'MyDetectorModel', { detectorModelName: 'test-detector-model', description: 'test-detector-model-description', evaluationMethod: iotevents.EventEvaluation.SERIAL, detectorKey: 'payload.deviceId', - initialState: onlineState, + initialState: firstState, }); } } From 674459cfc05b3a5e218c955c0cded8718c540114 Mon Sep 17 00:00:00 2001 From: Tatsuya Yamamoto Date: Wed, 2 Feb 2022 13:52:34 +0900 Subject: [PATCH 02/13] Update packages/@aws-cdk/aws-iotevents/lib/state.ts Co-authored-by: Adam Ruka --- packages/@aws-cdk/aws-iotevents/lib/state.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index 93eea07df1e8a..9abc822db2edc 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -28,7 +28,7 @@ export class State { */ public readonly stateName: string; - private transitionEvents: ITransitionEvent[] = [] + private readonly transitionEvents: ITransitionEvent[] = []; constructor(private readonly props: StateProps) { this.stateName = props.stateName; From ab422606d5c1420d09dd9814813255e3a4c9e248 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Wed, 2 Feb 2022 21:46:36 +0900 Subject: [PATCH 03/13] address comments --- packages/@aws-cdk/aws-iotevents/README.md | 9 +- .../aws-iotevents/lib/detector-model.ts | 2 +- packages/@aws-cdk/aws-iotevents/lib/event.ts | 25 +---- packages/@aws-cdk/aws-iotevents/lib/state.ts | 106 +++++++++++++----- .../aws-iotevents/test/detector-model.test.ts | 9 +- .../test/integ.detector-model.expected.json | 4 +- .../test/integ.detector-model.ts | 8 +- 7 files changed, 93 insertions(+), 70 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index 42437126362f4..41c78a3db700b 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -66,18 +66,15 @@ const coldState = new iotevents.State({ }); // transit to coldState when temperature is 10 -normalState.transitionTo({ - eventName: 'to_coldState', - nextState: coldState, +normalState.transitionTo(coldState, { + eventName: 'to_coldState', // optional property, default by combining the names of the States condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('10'), ), }); // transit to normalState when temperature is 20 -coldState.transitionTo({ - eventName: 'to_normalState', - nextState: normalState, +coldState.transitionTo(normalState, { condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('20'), diff --git a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts index b14af93e41033..8b8aaa7244098 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts @@ -123,7 +123,7 @@ export class DetectorModel extends Resource implements IDetectorModel { key: props.detectorKey, detectorModelDefinition: { initialStateName: props.initialState.stateName, - states: props.initialState._getStatesJson(), + states: props.initialState._collectStateJsons(), }, roleArn: role.roleArn, }); diff --git a/packages/@aws-cdk/aws-iotevents/lib/event.ts b/packages/@aws-cdk/aws-iotevents/lib/event.ts index 36167e1a3d1e3..610469db9c32c 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/event.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/event.ts @@ -1,20 +1,14 @@ import { Expression } from './expression'; -import { State } from './state'; /** - * The base interface for events. + * Specifies the actions to be performed when the condition evaluates to TRUE. */ -interface IEventBase { +export interface Event { /** * The name of the event. */ readonly eventName: string; -} -/** - * Specifies the actions to be performed when the condition evaluates to TRUE. - */ -export interface IEvent extends IEventBase { /** * The Boolean expression that, when TRUE, causes the actions to be performed. * @@ -22,18 +16,3 @@ export interface IEvent extends IEventBase { */ readonly condition?: Expression; } - -/** - * Specifies the state transition and the actions to be performed when the condition evaluates to TRUE. - */ -export interface ITransitionEvent extends IEventBase { - /** - * The Boolean expression that, when TRUE, causes the state transition and the actions to be performed. - */ - readonly condition: Expression; - - /** - * The next state to transit to. When the resuld of condition expression is TRUE, the state is transited. - */ - readonly nextState: State; -} diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index 9abc822db2edc..db1970af66fff 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -1,6 +1,44 @@ -import { IEvent, ITransitionEvent } from './event'; +import { Event } from './event'; +import { Expression } from './expression'; import { CfnDetectorModel } from './iotevents.generated'; +/** + * Properties for options of state transition. + */ +export interface TransitionOptions { + /** + * The name of the event. + * + * @default string combining the names of the States as `${originStateName}_to_${targetStateName}` + */ + readonly eventName?: string; + + /** + * The Boolean expression that, when TRUE, causes the state transition and the actions to be performed. + */ + readonly condition: Expression; +} + +/** + * Specifies the state transition and the actions to be performed when the condition evaluates to TRUE. + */ +interface TransitionEvent { + /** + * The name of the event. + */ + readonly eventName: string; + + /** + * The Boolean expression that, when TRUE, causes the state transition and the actions to be performed. + */ + readonly condition: Expression; + + /** + * The next state to transit to. When the resuld of condition expression is TRUE, the state is transited. + */ + readonly nextState: State; +} + /** * Properties for defining a state of a detector */ @@ -16,7 +54,7 @@ export interface StateProps { * * @default - events on enter will not be set */ - readonly onEnter?: IEvent[]; + readonly onEnter?: Event[]; } /** @@ -28,7 +66,7 @@ export class State { */ public readonly stateName: string; - private readonly transitionEvents: ITransitionEvent[] = []; + private readonly transitionEvents: TransitionEvent[] = []; constructor(private readonly props: StateProps) { this.stateName = props.stateName; @@ -36,38 +74,38 @@ export class State { /** * Add a transition event to the state. + * The transition event will be triggered if condition is evaluated to TRUE. * - * @param transitionEvent the transition event that triggered if condition is evaluated to TRUE + * @param targetState the state that will be transit to when the event triggered + * @param options transition options including the condition that causes the state transition */ - public transitionTo(transitionEvent:ITransitionEvent) { - this.transitionEvents.push(transitionEvent); + public transitionTo(targetState: State, options: TransitionOptions) { + this.transitionEvents.push({ + eventName: options.eventName ?? `${this.stateName}_to_${targetState.stateName}`, + nextState: targetState, + condition: options.condition, + }); } /** - * Return the JSON of the states that is transited to. + * Collect states in dependency gragh that constructed by state transitions, + * and return the JSONs of the states. * This function is called recursively and collect the states. * * @internal */ - public _getStatesJson(states: CfnDetectorModel.StateProperty[] = []): CfnDetectorModel.StateProperty[] { - const { stateName, onEnter } = this.props; - - if (states.some(s => s.stateName === stateName)) { - return states; + public _collectStateJsons(collectedStates = new Set()): CfnDetectorModel.StateProperty[] { + if (collectedStates.has(this)) { + return []; } + collectedStates.add(this); - const newStates: CfnDetectorModel.StateProperty[] = [...states, { - stateName, - onInput: { - transitionEvents: this.transitionEvents.length > 0 ? - getTransitionEventJson(this.transitionEvents) : undefined, - }, - onEnter: onEnter && { events: getEventJson(onEnter) }, - }]; - - return this.transitionEvents.reduce((acc, transitionEvent) => { - return transitionEvent.nextState._getStatesJson(acc); - }, newStates); + return [ + this.toStateJson(), + ...this.transitionEvents.flatMap(({ nextState }) => { + return nextState._collectStateJsons(collectedStates); + }), + ]; } /** @@ -78,9 +116,20 @@ export class State { public _onEnterEventsHaveAtLeastOneCondition(): boolean { return this.props.onEnter?.some(event => event.condition) ?? false; } + + private toStateJson(): CfnDetectorModel.StateProperty { + const { stateName, onEnter } = this.props; + return { + stateName, + onInput: { + transitionEvents: toTransitionEventJson(this.transitionEvents), + }, + onEnter: onEnter && { events: toEventJson(onEnter) }, + }; + } } -function getEventJson(events: IEvent[]): CfnDetectorModel.EventProperty[] { +function toEventJson(events: Event[]): CfnDetectorModel.EventProperty[] { return events.map(e => { return { eventName: e.eventName, @@ -88,7 +137,12 @@ function getEventJson(events: IEvent[]): CfnDetectorModel.EventProperty[] { }; }); } -function getTransitionEventJson(events: ITransitionEvent[]): CfnDetectorModel.TransitionEventProperty[] { + +function toTransitionEventJson(events: TransitionEvent[]): CfnDetectorModel.TransitionEventProperty[] | undefined { + if (events.length === 0) { + return undefined; + } + return events.map(e => { return { eventName: e.eventName, 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 57cdd9551a54c..985f2d534d15f 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -150,14 +150,11 @@ test('can set states with transitions', () => { stateName: 'secondState', }); - firstState.transitionTo({ - eventName: 'firstToSecond', - nextState: secondState, + firstState.transitionTo(secondState, { condition: iotevents.Expression.fromString('test-eventCondition-12'), }); - secondState.transitionTo({ + secondState.transitionTo(firstState, { eventName: 'secondToFirst', - nextState: firstState, condition: iotevents.Expression.fromString('test-eventCondition-21'), }); @@ -173,7 +170,7 @@ test('can set states with transitions', () => { StateName: 'firstState', OnInput: { TransitionEvents: [{ - EventName: 'firstToSecond', + EventName: 'firstState_to_secondState', NextState: 'secondState', Condition: 'test-eventCondition-12', }], diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json index 2205e02c25bd9..cbd8e691559e4 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json @@ -78,7 +78,7 @@ ] ] }, - "EventName": "firstToSecond", + "EventName": "firstState_to_secondState", "NextState": "secondState" } ] @@ -101,7 +101,7 @@ ] ] }, - "EventName": "secondToFirst", + "EventName": "secondState_to_firstState", "NextState": "firstState" } ] diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts index 6d27c655ae2d5..a73d93191c91b 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts @@ -29,18 +29,14 @@ class TestStack extends cdk.Stack { }); // 1st => 2nd - firstState.transitionTo({ - eventName: 'firstToSecond', - nextState: secondState, + firstState.transitionTo(secondState, { condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('12'), ), }); // 2st => 1st - secondState.transitionTo({ - eventName: 'secondToFirst', - nextState: firstState, + secondState.transitionTo(firstState, { condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('21'), From f31211e9e6c32d79a7a1431d06a5855c0d753d31 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Wed, 2 Feb 2022 22:31:56 +0900 Subject: [PATCH 04/13] address comments --- .../aws-iotevents/test/detector-model.test.ts | 75 +++++++++++++++---- .../test/integ.detector-model.expected.json | 14 ++-- .../test/integ.detector-model.ts | 14 ++-- 3 files changed, 73 insertions(+), 30 deletions(-) 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 985f2d534d15f..817039a98eacf 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -4,8 +4,10 @@ import * as cdk from '@aws-cdk/core'; import * as iotevents 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', () => { @@ -138,24 +140,50 @@ test('can set multiple events to State', () => { }); test('can set states with transitions', () => { - // WHEN + // GIVEN const firstState = new iotevents.State({ stateName: 'firstState', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.fromString('test-eventCondition'), + condition: iotevents.Expression.currentInput(input), }], }); const secondState = new iotevents.State({ stateName: 'secondState', }); + const thirdState = new iotevents.State({ + stateName: 'thirdState', + }); + // WHEN + // transition as 1st -> 2nd + firstState.transitionTo(secondState, { + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('12.1'), + ), + }); + // transition as 1st -> 2nd, make duprecated transition with different condition firstState.transitionTo(secondState, { - condition: iotevents.Expression.fromString('test-eventCondition-12'), + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('12.2'), + ), }); + // transition as 2nd -> 1st, make circular reference secondState.transitionTo(firstState, { eventName: 'secondToFirst', - condition: iotevents.Expression.fromString('test-eventCondition-21'), + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('21'), + ), + }); + // transition as 2nd -> 3rd, to test recursive calling + secondState.transitionTo(thirdState, { + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('23'), + ), }); new iotevents.DetectorModel(stack, 'MyDetectorModel', { @@ -169,23 +197,40 @@ test('can set states with transitions', () => { { StateName: 'firstState', OnInput: { - TransitionEvents: [{ - EventName: 'firstState_to_secondState', - NextState: 'secondState', - Condition: 'test-eventCondition-12', - }], + TransitionEvents: [ + { + EventName: 'firstState_to_secondState', + NextState: 'secondState', + Condition: '$input.test-input.payload.temperature == 12.1', + }, + { + EventName: 'firstState_to_secondState', + NextState: 'secondState', + Condition: '$input.test-input.payload.temperature == 12.2', + }, + ], }, }, { StateName: 'secondState', OnInput: { - TransitionEvents: [{ - EventName: 'secondToFirst', - NextState: 'firstState', - Condition: 'test-eventCondition-21', - }], + TransitionEvents: [ + { + EventName: 'secondToFirst', + NextState: 'firstState', + Condition: '$input.test-input.payload.temperature == 21', + }, + { + EventName: 'secondState_to_thirdState', + NextState: 'thirdState', + Condition: '$input.test-input.payload.temperature == 23', + }, + ], }, }, + { + StateName: 'thirdState', + }, ], }, }); @@ -248,7 +293,6 @@ test('cannot create without event', () => { describe('Expression', () => { test('currentInput', () => { // WHEN - const input = iotevents.Input.fromInputName(stack, 'MyInput', 'test-input'); new iotevents.DetectorModel(stack, 'MyDetectorModel', { initialState: new iotevents.State({ stateName: 'test-state', @@ -277,7 +321,6 @@ describe('Expression', () => { test('inputAttribute', () => { // WHEN - const input = iotevents.Input.fromInputName(stack, 'MyInput', 'test-input'); new iotevents.DetectorModel(stack, 'MyDetectorModel', { initialState: new iotevents.State({ stateName: 'test-state', diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json index cbd8e691559e4..888869a41e68e 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.expected.json @@ -37,7 +37,7 @@ "Type": "AWS::IoTEvents::DetectorModel", "Properties": { "DetectorModelDefinition": { - "InitialStateName": "firstState", + "InitialStateName": "online", "States": [ { "OnEnter": { @@ -78,12 +78,12 @@ ] ] }, - "EventName": "firstState_to_secondState", - "NextState": "secondState" + "EventName": "online_to_offline", + "NextState": "offline" } ] }, - "StateName": "firstState" + "StateName": "online" }, { "OnInput": { @@ -101,12 +101,12 @@ ] ] }, - "EventName": "secondState_to_firstState", - "NextState": "firstState" + "EventName": "offline_to_online", + "NextState": "online" } ] }, - "StateName": "secondState" + "StateName": "offline" } ] }, diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts index a73d93191c91b..2c59cbfbed3be 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts @@ -10,8 +10,8 @@ class TestStack extends cdk.Stack { attributeJsonPaths: ['payload.deviceId', 'payload.temperature'], }); - const firstState = new iotevents.State({ - stateName: 'firstState', + const onlineState = new iotevents.State({ + stateName: 'online', onEnter: [{ eventName: 'test-event', // meaning `condition: 'currentInput("test_input") && $input.test_input.payload.temperature == 31.5'` @@ -24,19 +24,19 @@ class TestStack extends cdk.Stack { ), }], }); - const secondState = new iotevents.State({ - stateName: 'secondState', + const offlineState = new iotevents.State({ + stateName: 'offline', }); // 1st => 2nd - firstState.transitionTo(secondState, { + onlineState.transitionTo(offlineState, { condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('12'), ), }); // 2st => 1st - secondState.transitionTo(firstState, { + offlineState.transitionTo(onlineState, { condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('21'), @@ -48,7 +48,7 @@ class TestStack extends cdk.Stack { description: 'test-detector-model-description', evaluationMethod: iotevents.EventEvaluation.SERIAL, detectorKey: 'payload.deviceId', - initialState: firstState, + initialState: onlineState, }); } } From 94685b6f2cdac351c52d091dbe14068290fc0c2d Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Wed, 2 Feb 2022 23:03:39 +0900 Subject: [PATCH 05/13] add validation for transitionTo() --- packages/@aws-cdk/aws-iotevents/lib/state.ts | 5 ++ .../aws-iotevents/test/detector-model.test.ts | 54 ++++++++++++------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index db1970af66fff..a6f2f1f8eaeac 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -80,6 +80,11 @@ export class State { * @param options transition options including the condition that causes the state transition */ public transitionTo(targetState: State, options: TransitionOptions) { + const alreadyAdded = this.transitionEvents.some((event) => event.nextState === targetState); + if (alreadyAdded) { + throw new Error(`A state cannot have transitions that transit to duprecated target state, target state name: ${targetState.stateName}`); + } + this.transitionEvents.push({ eventName: options.eventName ?? `${this.stateName}_to_${targetState.stateName}`, nextState: targetState, 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 817039a98eacf..da83fd1061565 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -160,14 +160,7 @@ test('can set states with transitions', () => { firstState.transitionTo(secondState, { condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), - iotevents.Expression.fromString('12.1'), - ), - }); - // transition as 1st -> 2nd, make duprecated transition with different condition - firstState.transitionTo(secondState, { - condition: iotevents.Expression.eq( - iotevents.Expression.inputAttribute(input, 'payload.temperature'), - iotevents.Expression.fromString('12.2'), + iotevents.Expression.fromString('12'), ), }); // transition as 2nd -> 1st, make circular reference @@ -197,18 +190,11 @@ test('can set states with transitions', () => { { StateName: 'firstState', OnInput: { - TransitionEvents: [ - { - EventName: 'firstState_to_secondState', - NextState: 'secondState', - Condition: '$input.test-input.payload.temperature == 12.1', - }, - { - EventName: 'firstState_to_secondState', - NextState: 'secondState', - Condition: '$input.test-input.payload.temperature == 12.2', - }, - ], + TransitionEvents: [{ + EventName: 'firstState_to_secondState', + NextState: 'secondState', + Condition: '$input.test-input.payload.temperature == 12', + }], }, }, { @@ -290,6 +276,34 @@ test('cannot create without event', () => { }).toThrow('Detector Model must have at least one Input with a condition'); }); +test('cannot create transitions that transit to duprecated target state', () => { + const firstState = new iotevents.State({ + stateName: 'firstState', + onEnter: [{ + eventName: 'test-eventName', + }], + }); + const secondState = new iotevents.State({ + stateName: 'secondState', + }); + + firstState.transitionTo(secondState, { + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('12.1'), + ), + }); + + expect(() => { + firstState.transitionTo(secondState, { + condition: iotevents.Expression.eq( + iotevents.Expression.inputAttribute(input, 'payload.temperature'), + iotevents.Expression.fromString('12.2'), + ), + }); + }).toThrow('A state cannot have transitions that transit to duprecated target state, target state name: secondState'); +}); + describe('Expression', () => { test('currentInput', () => { // WHEN From dbc42d09f3e9d1ddca4efa3c7ad06a86f419adf4 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Thu, 3 Feb 2022 01:09:49 +0900 Subject: [PATCH 06/13] fix readme code block --- packages/@aws-cdk/aws-iotevents/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index 41c78a3db700b..ad6eb9f5a4c43 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -86,6 +86,6 @@ new iotevents.DetectorModel(this, 'MyDetectorModel', { description: 'test-detector-model-description', // optional property, default is none evaluationMethod: iotevents.EventEvaluation.SERIAL, // optional property, default is iotevents.EventEvaluation.BATCH detectorKey: 'payload.deviceId', // optional property, default is none and single detector instance will be created and all inputs will be routed to it - initialState: onlineState, + initialState: normalState, }); ``` From 514a77a5e7d9eb099bd6f8c71b6b6b08e2e85058 Mon Sep 17 00:00:00 2001 From: Tatsuya Yamamoto Date: Thu, 3 Feb 2022 08:52:52 +0900 Subject: [PATCH 07/13] Update packages/@aws-cdk/aws-iotevents/lib/state.ts Co-authored-by: Adam Ruka --- packages/@aws-cdk/aws-iotevents/lib/state.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index a6f2f1f8eaeac..e44359a619c69 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -82,7 +82,7 @@ export class State { public transitionTo(targetState: State, options: TransitionOptions) { const alreadyAdded = this.transitionEvents.some((event) => event.nextState === targetState); if (alreadyAdded) { - throw new Error(`A state cannot have transitions that transit to duprecated target state, target state name: ${targetState.stateName}`); + throw new Error(`State '${this.stateName}' already has a transition defined to '${targetState.stateName}'`); } this.transitionEvents.push({ From 7f52f71e71d34116f593de10c026ea5aa76b3bf5 Mon Sep 17 00:00:00 2001 From: Tatsuya Yamamoto Date: Thu, 3 Feb 2022 08:57:46 +0900 Subject: [PATCH 08/13] Update packages/@aws-cdk/aws-iotevents/lib/state.ts Co-authored-by: Adam Ruka --- packages/@aws-cdk/aws-iotevents/lib/state.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index e44359a619c69..dd496d2286d7a 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -107,8 +107,8 @@ export class State { return [ this.toStateJson(), - ...this.transitionEvents.flatMap(({ nextState }) => { - return nextState._collectStateJsons(collectedStates); + ...this.transitionEvents.flatMap(transitionEvent => { + return transitionEvent.nextState._collectStateJsons(collectedStates); }), ]; } From e520bf762e68604c382138edfefc45cca58f688f Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Thu, 3 Feb 2022 11:03:09 +0900 Subject: [PATCH 09/13] address comments --- packages/@aws-cdk/aws-iotevents/README.md | 12 ++++---- .../aws-iotevents/lib/detector-model.ts | 2 +- packages/@aws-cdk/aws-iotevents/lib/state.ts | 30 +++++++++---------- .../aws-iotevents/test/detector-model.test.ts | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index ad6eb9f5a4c43..23384344b475a 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -54,8 +54,8 @@ const input = new iotevents.Input(this, 'MyInput', { attributeJsonPaths: ['payload.deviceId', 'payload.temperature'], }); -const normalState = new iotevents.State({ - stateName: 'normal', +const warmState = new iotevents.State({ + stateName: 'warm', onEnter: [{ eventName: 'test-event', condition: iotevents.Expression.currentInput(input), @@ -66,15 +66,15 @@ const coldState = new iotevents.State({ }); // transit to coldState when temperature is 10 -normalState.transitionTo(coldState, { +warmState.transitionTo(coldState, { eventName: 'to_coldState', // optional property, default by combining the names of the States condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('10'), ), }); -// transit to normalState when temperature is 20 -coldState.transitionTo(normalState, { +// transit to warmState when temperature is 20 +coldState.transitionTo(warmState, { condition: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('20'), @@ -86,6 +86,6 @@ new iotevents.DetectorModel(this, 'MyDetectorModel', { description: 'test-detector-model-description', // optional property, default is none evaluationMethod: iotevents.EventEvaluation.SERIAL, // optional property, default is iotevents.EventEvaluation.BATCH detectorKey: 'payload.deviceId', // optional property, default is none and single detector instance will be created and all inputs will be routed to it - initialState: normalState, + initialState: warmState, }); ``` diff --git a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts index 8b8aaa7244098..4cce8458be7af 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts @@ -123,7 +123,7 @@ export class DetectorModel extends Resource implements IDetectorModel { key: props.detectorKey, detectorModelDefinition: { initialStateName: props.initialState.stateName, - states: props.initialState._collectStateJsons(), + states: props.initialState._collectStateJsons(new Set()), }, roleArn: role.roleArn, }); diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index dd496d2286d7a..01d14a263de39 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -99,7 +99,7 @@ export class State { * * @internal */ - public _collectStateJsons(collectedStates = new Set()): CfnDetectorModel.StateProperty[] { + public _collectStateJsons(collectedStates: Set): CfnDetectorModel.StateProperty[] { if (collectedStates.has(this)) { return []; } @@ -123,36 +123,36 @@ export class State { } private toStateJson(): CfnDetectorModel.StateProperty { - const { stateName, onEnter } = this.props; + const { onEnter } = this.props; return { - stateName, + stateName: this.stateName, + onEnter: onEnter && { events: toEventsJson(onEnter) }, onInput: { - transitionEvents: toTransitionEventJson(this.transitionEvents), + transitionEvents: toTransitionEventsJson(this.transitionEvents), }, - onEnter: onEnter && { events: toEventJson(onEnter) }, }; } } -function toEventJson(events: Event[]): CfnDetectorModel.EventProperty[] { - return events.map(e => { +function toEventsJson(events: Event[]): CfnDetectorModel.EventProperty[] { + return events.map(event => { return { - eventName: e.eventName, - condition: e.condition?.evaluate(), + eventName: event.eventName, + condition: event.condition?.evaluate(), }; }); } -function toTransitionEventJson(events: TransitionEvent[]): CfnDetectorModel.TransitionEventProperty[] | undefined { - if (events.length === 0) { +function toTransitionEventsJson(transitionEvents: TransitionEvent[]): CfnDetectorModel.TransitionEventProperty[] | undefined { + if (transitionEvents.length === 0) { return undefined; } - return events.map(e => { + return transitionEvents.map(transitionEvent => { return { - eventName: e.eventName, - condition: e.condition.evaluate(), - nextState: e.nextState.stateName, + eventName: transitionEvent.eventName, + condition: transitionEvent.condition.evaluate(), + nextState: transitionEvent.nextState.stateName, }; }); } 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 da83fd1061565..65b910d9b30c3 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -301,7 +301,7 @@ test('cannot create transitions that transit to duprecated target state', () => iotevents.Expression.fromString('12.2'), ), }); - }).toThrow('A state cannot have transitions that transit to duprecated target state, target state name: secondState'); + }).toThrow('State `firstState` already has a transition defined to `secondState`'); }); describe('Expression', () => { From 75ad95b9152ad94019815d4aee8cc76d39682a72 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Thu, 3 Feb 2022 12:16:32 +0900 Subject: [PATCH 10/13] fix test --- packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 65b910d9b30c3..51a5766fca519 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -301,7 +301,7 @@ test('cannot create transitions that transit to duprecated target state', () => iotevents.Expression.fromString('12.2'), ), }); - }).toThrow('State `firstState` already has a transition defined to `secondState`'); + }).toThrow("State 'firstState' already has a transition defined to 'secondState'"); }); describe('Expression', () => { From 3f3dff09d73c9ae8d019d59880ecf6f27c97631c Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Fri, 4 Feb 2022 23:33:30 +0900 Subject: [PATCH 11/13] rename `condition` to `when` --- packages/@aws-cdk/aws-iotevents/README.md | 6 ++-- packages/@aws-cdk/aws-iotevents/lib/event.ts | 5 ++-- packages/@aws-cdk/aws-iotevents/lib/state.ts | 20 +++++++------ .../aws-iotevents/test/detector-model.test.ts | 30 +++++++++---------- .../test/integ.detector-model.ts | 8 ++--- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index 23384344b475a..9d9a7b49435b8 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -58,7 +58,7 @@ const warmState = new iotevents.State({ stateName: 'warm', onEnter: [{ eventName: 'test-event', - condition: iotevents.Expression.currentInput(input), + when: iotevents.Expression.currentInput(input), }], }); const coldState = new iotevents.State({ @@ -68,14 +68,14 @@ const coldState = new iotevents.State({ // transit to coldState when temperature is 10 warmState.transitionTo(coldState, { eventName: 'to_coldState', // optional property, default by combining the names of the States - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('10'), ), }); // transit to warmState when temperature is 20 coldState.transitionTo(warmState, { - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('20'), ), diff --git a/packages/@aws-cdk/aws-iotevents/lib/event.ts b/packages/@aws-cdk/aws-iotevents/lib/event.ts index 610469db9c32c..e8aeca05c76e0 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/event.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/event.ts @@ -10,9 +10,10 @@ export interface Event { readonly eventName: string; /** - * The Boolean expression that, when TRUE, causes the actions to be performed. + * The condition that is used to determine to cause the actions. + * When this was evaluated to TRUE, the actions are triggered. * * @default - none (the actions are always executed) */ - readonly condition?: Expression; + readonly when?: Expression; } diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index 01d14a263de39..944c1537efae6 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -14,9 +14,10 @@ export interface TransitionOptions { readonly eventName?: string; /** - * The Boolean expression that, when TRUE, causes the state transition and the actions to be performed. + * The condition that is used to determine to cause the state transition and the actions. + * When this was evaluated to TRUE, the state transition and the actions are triggered. */ - readonly condition: Expression; + readonly when: Expression; } /** @@ -29,9 +30,10 @@ interface TransitionEvent { readonly eventName: string; /** - * The Boolean expression that, when TRUE, causes the state transition and the actions to be performed. + * The condition that is used to determine to cause the state transition and the actions. + * When this was evaluated to TRUE, the state transition and the actions are triggered. */ - readonly condition: Expression; + readonly when: Expression; /** * The next state to transit to. When the resuld of condition expression is TRUE, the state is transited. @@ -88,7 +90,7 @@ export class State { this.transitionEvents.push({ eventName: options.eventName ?? `${this.stateName}_to_${targetState.stateName}`, nextState: targetState, - condition: options.condition, + when: options.when, }); } @@ -114,12 +116,12 @@ export class State { } /** - * returns true if this state has at least one condition via events + * Returns true if this state has at least one condition as `Event.when`s. * * @internal */ public _onEnterEventsHaveAtLeastOneCondition(): boolean { - return this.props.onEnter?.some(event => event.condition) ?? false; + return this.props.onEnter?.some(event => event.when) ?? false; } private toStateJson(): CfnDetectorModel.StateProperty { @@ -138,7 +140,7 @@ function toEventsJson(events: Event[]): CfnDetectorModel.EventProperty[] { return events.map(event => { return { eventName: event.eventName, - condition: event.condition?.evaluate(), + condition: event.when?.evaluate(), }; }); } @@ -151,7 +153,7 @@ function toTransitionEventsJson(transitionEvents: TransitionEvent[]): CfnDetecto return transitionEvents.map(transitionEvent => { return { eventName: transitionEvent.eventName, - condition: transitionEvent.condition.evaluate(), + condition: transitionEvent.when.evaluate(), nextState: transitionEvent.nextState.stateName, }; }); 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 51a5766fca519..1993ca6e395ca 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -17,7 +17,7 @@ test('Default property', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.fromString('test-eventCondition'), + when: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -59,7 +59,7 @@ test('can get detector model name', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.fromString('test-eventCondition'), + when: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -91,7 +91,7 @@ test.each([ stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.fromString('test-eventCondition'), + when: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -108,7 +108,7 @@ test('can set multiple events to State', () => { onEnter: [ { eventName: 'test-eventName1', - condition: iotevents.Expression.fromString('test-eventCondition'), + when: iotevents.Expression.fromString('test-eventCondition'), }, { eventName: 'test-eventName2', @@ -145,7 +145,7 @@ test('can set states with transitions', () => { stateName: 'firstState', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.currentInput(input), + when: iotevents.Expression.currentInput(input), }], }); const secondState = new iotevents.State({ @@ -158,7 +158,7 @@ test('can set states with transitions', () => { // WHEN // transition as 1st -> 2nd firstState.transitionTo(secondState, { - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('12'), ), @@ -166,14 +166,14 @@ test('can set states with transitions', () => { // transition as 2nd -> 1st, make circular reference secondState.transitionTo(firstState, { eventName: 'secondToFirst', - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('21'), ), }); // transition as 2nd -> 3rd, to test recursive calling secondState.transitionTo(thirdState, { - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('23'), ), @@ -231,7 +231,7 @@ test('can set role', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.fromString('test-eventCondition'), + when: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -288,7 +288,7 @@ test('cannot create transitions that transit to duprecated target state', () => }); firstState.transitionTo(secondState, { - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('12.1'), ), @@ -296,7 +296,7 @@ test('cannot create transitions that transit to duprecated target state', () => expect(() => { firstState.transitionTo(secondState, { - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('12.2'), ), @@ -312,7 +312,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.currentInput(input), + when: iotevents.Expression.currentInput(input), }], }), }); @@ -340,7 +340,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.inputAttribute(input, 'json.path'), + when: iotevents.Expression.inputAttribute(input, 'json.path'), }], }), }); @@ -368,7 +368,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.fromString('"aaa"'), iotevents.Expression.fromString('"bbb"'), ), @@ -399,7 +399,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - condition: iotevents.Expression.and( + when: iotevents.Expression.and( iotevents.Expression.fromString('true'), iotevents.Expression.fromString('false'), ), diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts index 2c59cbfbed3be..7d30b277fdd4a 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts @@ -14,8 +14,8 @@ class TestStack extends cdk.Stack { stateName: 'online', onEnter: [{ eventName: 'test-event', - // meaning `condition: 'currentInput("test_input") && $input.test_input.payload.temperature == 31.5'` - condition: iotevents.Expression.and( + // meaning `when: 'currentInput("test_input") && $input.test_input.payload.temperature == 31.5'` + when: iotevents.Expression.and( iotevents.Expression.currentInput(input), iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), @@ -30,14 +30,14 @@ class TestStack extends cdk.Stack { // 1st => 2nd onlineState.transitionTo(offlineState, { - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('12'), ), }); // 2st => 1st offlineState.transitionTo(onlineState, { - condition: iotevents.Expression.eq( + when: iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), iotevents.Expression.fromString('21'), ), From da02f80ebaafdfd0bfdb7fb7e7f93283bdc81f47 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Sat, 5 Feb 2022 16:39:48 +0900 Subject: [PATCH 12/13] address comments --- packages/@aws-cdk/aws-iotevents/README.md | 2 +- packages/@aws-cdk/aws-iotevents/lib/event.ts | 5 ++- packages/@aws-cdk/aws-iotevents/lib/state.ts | 35 ++++++++----------- .../aws-iotevents/test/detector-model.test.ts | 20 +++++------ .../test/integ.detector-model.ts | 4 +-- 5 files changed, 30 insertions(+), 36 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index 820ed12b075dc..809bac071ef7d 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -58,7 +58,7 @@ const warmState = new iotevents.State({ stateName: 'warm', onEnter: [{ eventName: 'test-event', - when: iotevents.Expression.currentInput(input), + condition: iotevents.Expression.currentInput(input), }], }); const coldState = new iotevents.State({ diff --git a/packages/@aws-cdk/aws-iotevents/lib/event.ts b/packages/@aws-cdk/aws-iotevents/lib/event.ts index e8aeca05c76e0..610469db9c32c 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/event.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/event.ts @@ -10,10 +10,9 @@ export interface Event { readonly eventName: string; /** - * The condition that is used to determine to cause the actions. - * When this was evaluated to TRUE, the actions are triggered. + * The Boolean expression that, when TRUE, causes the actions to be performed. * * @default - none (the actions are always executed) */ - readonly when?: Expression; + readonly condition?: Expression; } diff --git a/packages/@aws-cdk/aws-iotevents/lib/state.ts b/packages/@aws-cdk/aws-iotevents/lib/state.ts index 161a0a54b0108..67ee6a32802ec 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/state.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/state.ts @@ -30,10 +30,9 @@ interface TransitionEvent { readonly eventName: string; /** - * The condition that is used to determine to cause the state transition and the actions. - * When this was evaluated to TRUE, the state transition and the actions are triggered. + * The Boolean expression that, when TRUE, causes the state transition and the actions to be performed. */ - readonly when: Expression; + readonly condition: Expression; /** * The next state to transit to. When the resuld of condition expression is TRUE, the state is transited. @@ -82,7 +81,7 @@ export class State { * @param options transition options including the condition that causes the state transition */ public transitionTo(targetState: State, options: TransitionOptions) { - const alreadyAdded = this.transitionEvents.some((event) => event.nextState === targetState); + const alreadyAdded = this.transitionEvents.some(transitionEvent => transitionEvent.nextState === targetState); if (alreadyAdded) { throw new Error(`State '${this.stateName}' already has a transition defined to '${targetState.stateName}'`); } @@ -90,7 +89,7 @@ export class State { this.transitionEvents.push({ eventName: options.eventName ?? `${this.stateName}_to_${targetState.stateName}`, nextState: targetState, - when: options.when, + condition: options.when, }); } @@ -116,12 +115,12 @@ export class State { } /** - * Returns true if this state has at least one condition as `Event.when`s. + * Returns true if this state has at least one condition via events. * * @internal */ public _onEnterEventsHaveAtLeastOneCondition(): boolean { - return this.props.onEnter?.some(event => event.when) ?? false; + return this.props.onEnter?.some(event => event.condition) ?? false; } private toStateJson(): CfnDetectorModel.StateProperty { @@ -137,12 +136,10 @@ export class State { } function toEventsJson(events: Event[]): CfnDetectorModel.EventProperty[] { - return events.map(event => { - return { - eventName: event.eventName, - condition: event.when?.evaluate(), - }; - }); + return events.map(event => ({ + eventName: event.eventName, + condition: event.condition?.evaluate(), + })); } function toTransitionEventsJson(transitionEvents: TransitionEvent[]): CfnDetectorModel.TransitionEventProperty[] | undefined { @@ -150,11 +147,9 @@ function toTransitionEventsJson(transitionEvents: TransitionEvent[]): CfnDetecto return undefined; } - return transitionEvents.map(transitionEvent => { - return { - eventName: transitionEvent.eventName, - condition: transitionEvent.when.evaluate(), - nextState: transitionEvent.nextState.stateName, - }; - }); + return transitionEvents.map(transitionEvent => ({ + eventName: transitionEvent.eventName, + condition: transitionEvent.condition.evaluate(), + nextState: transitionEvent.nextState.stateName, + })); } 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 1993ca6e395ca..c90a10cf34374 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -17,7 +17,7 @@ test('Default property', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.fromString('test-eventCondition'), + condition: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -59,7 +59,7 @@ test('can get detector model name', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.fromString('test-eventCondition'), + condition: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -91,7 +91,7 @@ test.each([ stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.fromString('test-eventCondition'), + condition: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -108,7 +108,7 @@ test('can set multiple events to State', () => { onEnter: [ { eventName: 'test-eventName1', - when: iotevents.Expression.fromString('test-eventCondition'), + condition: iotevents.Expression.fromString('test-eventCondition'), }, { eventName: 'test-eventName2', @@ -145,7 +145,7 @@ test('can set states with transitions', () => { stateName: 'firstState', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.currentInput(input), + condition: iotevents.Expression.currentInput(input), }], }); const secondState = new iotevents.State({ @@ -231,7 +231,7 @@ test('can set role', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.fromString('test-eventCondition'), + condition: iotevents.Expression.fromString('test-eventCondition'), }], }), }); @@ -312,7 +312,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.currentInput(input), + condition: iotevents.Expression.currentInput(input), }], }), }); @@ -340,7 +340,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.inputAttribute(input, 'json.path'), + condition: iotevents.Expression.inputAttribute(input, 'json.path'), }], }), }); @@ -368,7 +368,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.eq( + condition: iotevents.Expression.eq( iotevents.Expression.fromString('"aaa"'), iotevents.Expression.fromString('"bbb"'), ), @@ -399,7 +399,7 @@ describe('Expression', () => { stateName: 'test-state', onEnter: [{ eventName: 'test-eventName', - when: iotevents.Expression.and( + condition: iotevents.Expression.and( iotevents.Expression.fromString('true'), iotevents.Expression.fromString('false'), ), diff --git a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts index 7d30b277fdd4a..5f6d2839f3a93 100644 --- a/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/test/integ.detector-model.ts @@ -14,8 +14,8 @@ class TestStack extends cdk.Stack { stateName: 'online', onEnter: [{ eventName: 'test-event', - // meaning `when: 'currentInput("test_input") && $input.test_input.payload.temperature == 31.5'` - when: iotevents.Expression.and( + // meaning `condition: 'currentInput("test_input") && $input.test_input.payload.temperature == 31.5'` + condition: iotevents.Expression.and( iotevents.Expression.currentInput(input), iotevents.Expression.eq( iotevents.Expression.inputAttribute(input, 'payload.temperature'), From a3c094d6215894b202592d5107f0e42fa7630135 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Sun, 6 Feb 2022 15:18:05 +0900 Subject: [PATCH 13/13] fix comments --- packages/@aws-cdk/aws-iotevents/lib/detector-model.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts index ff43e7d845f69..35128bc4531e6 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/detector-model.ts @@ -21,13 +21,14 @@ export interface IDetectorModel extends IResource { */ export enum EventEvaluation { /** - * When setting to SERIAL, variables are updated and event conditions are evaluated in the order - * that the events are defined. + * When setting to BATCH, variables within a state are updated and events within a state are + * performed only after all event conditions are evaluated. */ BATCH = 'BATCH', + /** - * When setting to BATCH, variables within a state are updated and events within a state are - * performed only after all event conditions are evaluated. + * When setting to SERIAL, variables are updated and event conditions are evaluated in the order + * that the events are defined. */ SERIAL = 'SERIAL', }