-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(iotevents): support transition events #18768
Changes from 12 commits
cee19c0
674459c
ab42260
f31211e
94685b6
dbc42d0
514a77a
7f52f71
e520bf7
75ad95b
3f3dff0
aebc589
da02f80
a3c094d
beb3341
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, not sure about this change. My comment was only changing this in I would leave this as-is. If you feel passionate that this is the way to go, fine, but then we need a "Breaking change" note in the PR description. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah. I assumed by mistake😅. |
||
} | ||
yamatatsu marked this conversation as resolved.
Show resolved
Hide resolved
yamatatsu marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,46 @@ | ||
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 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 when: 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 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 when: Expression; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine to leave this name as |
||
|
||
/** | ||
* 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. | ||
*/ | ||
|
@@ -28,38 +68,93 @@ export class State { | |
*/ | ||
public readonly stateName: string; | ||
|
||
private readonly transitionEvents: TransitionEvent[] = []; | ||
|
||
constructor(private readonly props: StateProps) { | ||
this.stateName = props.stateName; | ||
} | ||
|
||
/** | ||
* Return the state property JSON. | ||
* Add a transition event to the state. | ||
* The transition event will be 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(targetState: State, options: TransitionOptions) { | ||
const alreadyAdded = this.transitionEvents.some((event) => event.nextState === targetState); | ||
yamatatsu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (alreadyAdded) { | ||
throw new Error(`State '${this.stateName}' already has a transition defined to '${targetState.stateName}'`); | ||
} | ||
|
||
this.transitionEvents.push({ | ||
eventName: options.eventName ?? `${this.stateName}_to_${targetState.stateName}`, | ||
nextState: targetState, | ||
when: options.when, | ||
}); | ||
} | ||
|
||
/** | ||
* 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 _toStateJson(): CfnDetectorModel.StateProperty { | ||
yamatatsu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const { stateName, onEnter } = this.props; | ||
return { | ||
stateName, | ||
onEnter: onEnter && { events: getEventJson(onEnter) }, | ||
}; | ||
public _collectStateJsons(collectedStates: Set<State>): CfnDetectorModel.StateProperty[] { | ||
if (collectedStates.has(this)) { | ||
return []; | ||
} | ||
collectedStates.add(this); | ||
|
||
return [ | ||
this.toStateJson(), | ||
...this.transitionEvents.flatMap(transitionEvent => { | ||
return transitionEvent.nextState._collectStateJsons(collectedStates); | ||
}), | ||
]; | ||
} | ||
|
||
/** | ||
* 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 { | ||
const { onEnter } = this.props; | ||
return { | ||
stateName: this.stateName, | ||
onEnter: onEnter && { events: toEventsJson(onEnter) }, | ||
onInput: { | ||
transitionEvents: toTransitionEventsJson(this.transitionEvents), | ||
}, | ||
}; | ||
} | ||
} | ||
|
||
function toEventsJson(events: Event[]): CfnDetectorModel.EventProperty[] { | ||
return events.map(event => { | ||
return { | ||
eventName: event.eventName, | ||
condition: event.when?.evaluate(), | ||
}; | ||
}); | ||
yamatatsu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
function getEventJson(events: Event[]): CfnDetectorModel.EventProperty[] { | ||
return events.map(e => { | ||
function toTransitionEventsJson(transitionEvents: TransitionEvent[]): CfnDetectorModel.TransitionEventProperty[] | undefined { | ||
if (transitionEvents.length === 0) { | ||
return undefined; | ||
} | ||
|
||
return transitionEvents.map(transitionEvent => { | ||
return { | ||
eventName: e.eventName, | ||
condition: e.condition?.evaluate(), | ||
eventName: transitionEvent.eventName, | ||
condition: transitionEvent.when.evaluate(), | ||
nextState: transitionEvent.nextState.stateName, | ||
}; | ||
}); | ||
yamatatsu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, is this not a weird condition? What does just
currentInput()
mean here? Is this true whenever the current input is not empty?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes🙂. In this case, Every messages to this input cause this event and create new detector by the property
key
.