1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-10 17:53:36 +02:00

approval flow poc

This commit is contained in:
andreas-unleash 2022-10-31 13:56:04 +02:00
parent 95779754fb
commit a6959e39d0
No known key found for this signature in database
GPG Key ID: 9730C6804AC8FBF8
2 changed files with 185 additions and 0 deletions

View File

@ -0,0 +1,101 @@
/*
* StateMachine.ts
* TypeScript finite state machine class with async transformations using promises.
* https://github.com/eram/ts-fsm/blob/master/src/stateMachine.ts
*/
export interface ITransition<STATE, EVENT> {
fromState: STATE;
event: EVENT;
toState: STATE;
cb?: (...args: unknown[]) => Promise<void>;
}
export function transition<STATE, EVENT>(
fromState: STATE,
event: EVENT,
toState: STATE,
cb?: (...args: unknown[]) => Promise<void>,
): ITransition<STATE, EVENT> {
return { fromState, event, toState, cb };
}
export class StateMachine<STATE, EVENT> {
protected current: STATE;
// initalize the state-machine
constructor(
initState: STATE,
protected transitions: ITransition<STATE, EVENT>[] = [],
) {
this.current = initState;
}
addTransitions(transitions: ITransition<STATE, EVENT>[]): void {
transitions.forEach((tran) => this.transitions.push(tran));
}
getState(): STATE {
return this.current;
}
can(event: EVENT): boolean {
return this.transitions.some(
(trans) =>
trans.fromState === this.current && trans.event === event,
);
}
isFinal(): boolean {
// search for a transition that starts from current state.
// if none is found it's a terminal state.
return this.transitions.every(
(trans) => trans.fromState !== this.current,
);
}
// post event asynch
async dispatch(event: EVENT, ...args: unknown[]): Promise<void> {
return new Promise<void>((resolve, reject) => {
// delay execution to make it async
setTimeout(
(me: this) => {
// find transition
const found = this.transitions.some((tran) => {
if (
tran.fromState === me.current &&
tran.event === event
) {
me.current = tran.toState;
if (tran.cb) {
try {
tran.cb(args).then(resolve).catch(reject);
} catch (e) {
console.error(
'Exception caught in callback',
e,
);
reject();
}
} else {
resolve();
}
return true;
}
return false;
});
// no such transition
if (!found) {
console.error(
`no transition: from ${me.current.toString()} event ${event.toString()}`,
);
reject();
}
},
0,
this,
);
});
}
}

View File

@ -0,0 +1,84 @@
import { StateMachine, transition } from './state-machine';
export enum Events {
SUBMIT,
REJECT,
APPROVE,
REQUEST_CHANGES,
APPLY,
CANCEL,
}
export enum States {
DRAFT,
IN_REViEW,
APPROVED,
CHANGES_REQUESTED,
APPLIED,
REJECTED,
CANCELLED,
}
/* eslint-disable */
class ApprovalFlow extends StateMachine<States, Events> {
constructor(init: States = States.DRAFT) {
super(init);
const s = States;
const e = Events;
this.addTransitions([
// fromState event toState callback
transition(s.DRAFT, e.SUBMIT, s.IN_REViEW, this.onSubmitted.bind(this)),
transition(s.DRAFT, e.CANCEL, s.CANCELLED, this.onCancelled.bind(this)),
transition(s.IN_REViEW, e.APPROVE, s.APPROVED, this.onApproved.bind(this)),
transition(s.APPROVED, e.APPLY, s.APPLIED, this.onApplied.bind(this)),
transition(s.APPROVED, e.CANCEL, s.CANCELLED, this.onCancelled.bind(this)),
transition(s.IN_REViEW, e.CANCEL, s.CANCELLED, this.onCancelled.bind(this)),
transition(s.IN_REViEW, e.REQUEST_CHANGES,s.CHANGES_REQUESTED, this.onChangesRequested.bind(this)),
transition(s.IN_REViEW, e.REJECT, s.REJECTED, this.onRejected.bind(this)),
transition(s.CHANGES_REQUESTED,e.SUBMIT, s.DRAFT, this.onSubmitted.bind(this)),
]);
}
// public methods
async submitDraft() { return this.dispatch(Events.SUBMIT); }
async cancel() { return this.dispatch(Events.CANCEL); }
async approve() { return this.dispatch(Events.APPROVE); }
async apply() { return this.dispatch(Events.APPLY); }
async reject() { return this.dispatch(Events.REJECT); }
async requestChanges() { return this.dispatch(Events.REQUEST_CHANGES); }
// transition callbacks
private async onSubmitted() {
console.log(`onSubmitted...`);
}
private async onRejected() {
console.log(`onRejected...`);
}
private async onChangesRequested() {
console.log(`onApproved...`);
}
private async onCancelled() {
console.log(`onCancelled...`);
}
private async onApproved() {
console.log(`onApproved...`);
}
private async onApplied() {
console.log(`onApplied...`);
}
/* eslint-enable */
}