mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
approval flow poc
This commit is contained in:
parent
95779754fb
commit
a6959e39d0
101
src/lib/types/models/state-machine.ts
Normal file
101
src/lib/types/models/state-machine.ts
Normal 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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
84
src/lib/types/models/suggest-changes-approval.ts
Normal file
84
src/lib/types/models/suggest-changes-approval.ts
Normal 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 */
|
||||
}
|
Loading…
Reference in New Issue
Block a user