Skip to content

Instantly share code, notes, and snippets.

@xavxyz
Created April 12, 2022 08:35
Show Gist options
  • Save xavxyz/3b7d67e151a34af7f1e994fe01b1f06c to your computer and use it in GitHub Desktop.
Save xavxyz/3b7d67e151a34af7f1e994fe01b1f06c to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
// helper functions, dev env only
const {
MISSION_STATUS: { QUALIFICATION, MATCHING, WIN },
MISSION_SELECTION_ANSWER,
SELECTION_STATUS
} = getConstants();
const guards = getGuards();
const mutations = getMutations();
const qualificationStates = {
id: "QUALIFICATION",
initial: QUALIFICATION.TO_QUALIFY.value,
states: {
[QUALIFICATION.TO_QUALIFY.value]: {
on: {
CLICK_OK: {
target: QUALIFICATION.QUALIFIED.value,
actions: assign({
displayPipeline: context => !context.displayPipeline
})
},
CLICK_NOT_OK: QUALIFICATION.OUT_OF_SCOPE.value
}
},
[QUALIFICATION.QUALIFIED.value]: {
on: {
AUTO_NEXT: {
target: "#MATCHING",
cond: guards.firstFreelanceSelected
},
MANUAL_ROLLBACK: {
target: QUALIFICATION.TO_QUALIFY.value,
actions: assign({
displayPipeline: context => !context.displayPipeline
})
}
}
},
[QUALIFICATION.OUT_OF_SCOPE.value]: {
on: {
MANUAL_ROLLBACK: QUALIFICATION.TO_QUALIFY.value
}
}
}
};
const matchingStates = {
id: "MATCHING",
initial: MATCHING.SEARCH.value,
states: {
[MATCHING.SEARCH.value]: {
on: {
AUTO_NEXT: {
target: MATCHING.WAITING_FREELANCES_INTEREST.value,
cond: guards.firstFreelanceNotified
}
}
},
[MATCHING.WAITING_FREELANCES_INTEREST.value]: {
on: {
AUTO_NEXT: {
target: MATCHING.WAITING_CLIENT_INTEREST.value,
cond: guards.firstClientNotified
},
AUTO_DEAD: {
target: MATCHING.FREELANCES_NOT_INTERESTED.value,
cond: guards.allFreelancesNotInterested
}
}
},
[MATCHING.FREELANCES_NOT_INTERESTED.value]: {
on: {
MANUAL_ROLLBACK: MATCHING.WAITING_FREELANCES_INTEREST.value
}
},
[MATCHING.WAITING_CLIENT_INTEREST.value]: {
on: {
AUTO_NEXT: {
target: MATCHING.INTERVIEWS.value,
cond: guards.firstFreelanceInInterview
},
AUTO_DEAD: {
target: MATCHING.CLIENT_NOT_INTERESTED.value,
cond: guards.clientNotInterested
}
}
},
[MATCHING.CLIENT_NOT_INTERESTED.value]: {
on: {
MANUAL_ROLLBACK: MATCHING.WAITING_CLIENT_INTEREST.value
}
},
[MATCHING.INTERVIEWS.value]: {
on: {
AUTO_NEXT: {
target: MATCHING.WAITING_AGREEMENT.value,
cond: guards.interviewHappened
}
}
},
[MATCHING.WAITING_AGREEMENT.value]: {
on: {
AUTO_NEXT: {
target: "#WIN",
cond: guards.freelanceInWin
},
AUTO_DEAD: {
target: MATCHING.NO_AGREEMENT.value,
cond: guards.atLeastOneNoAgreement
}
}
},
[MATCHING.NO_AGREEMENT.value]: {
on: {
MANUAL_ROLLBACK: MATCHING.INTERVIEWS.value
}
}
}
};
const winStates = {
id: "WIN",
initial: WIN.AGREEMENT.value,
states: {
[WIN.AGREEMENT.value]: {
on: {
AUTO_NEXT: {
target: WIN.ONGOING.value,
cond: guards.missionStartsToday
}
}
},
[WIN.ONGOING.value]: {
on: {
AUTO_NEXT: {
target: WIN.DONE.value,
cond: guards.missionEndsToday
}
}
},
[WIN.DONE.value]: {
type: "final"
}
}
};
// Mimic automatic mission status possible update
const autoHooks = [send("AUTO_NEXT"), send("AUTO_DEAD")];
const selectionPipelineStates = {
initial: "NO_SELECTION",
states: {
NO_SELECTION: {
on: {
SELECT_FREELANCE: {
target: SELECTION_STATUS.SELECTED,
actions: ["selectFreelance", ...autoHooks],
cond: guards.pipelineDisplayed
}
}
},
[SELECTION_STATUS.SELECTED]: {
on: {
CARD_FORWARD: SELECTION_STATUS.PENDINGFREELANCE
},
entry: ["updateSelection", ...autoHooks]
},
[SELECTION_STATUS.PENDINGFREELANCE]: {
on: {
CARD_BACKWARD: SELECTION_STATUS.SELECTED,
CARD_FORWARD: SELECTION_STATUS.PENDINGCLIENT,
NOTIFY_FREELANCE: {
actions: ["notifyFreelance", ...autoHooks]
},
FREELANCE_INTERESTED: {
actions: ["freelanceInterested", ...autoHooks]
},
FREELANCE_NOT_INTERESTED: {
actions: ["freelanceNotInterested", ...autoHooks]
}
},
entry: ["updateSelection", ...autoHooks]
},
[SELECTION_STATUS.PENDINGCLIENT]: {
on: {
CARD_BACKWARD: SELECTION_STATUS.PENDINGFREELANCE,
CARD_FORWARD: SELECTION_STATUS.INTERVIEW,
NOTIFY_CLIENT: {
actions: ["notifyClient", ...autoHooks]
},
CLIENT_INTERESTED: {
actions: ["clientInterested", ...autoHooks]
},
CLIENT_NOT_INTERESTED: {
actions: ["clientNotInterested", ...autoHooks]
}
},
entry: ["updateSelection", ...autoHooks]
},
[SELECTION_STATUS.INTERVIEW]: {
on: {
CARD_BACKWARD: SELECTION_STATUS.PENDINGCLIENT,
INTERVIEW_HAPPENED: {
actions: ["interviewHappened", ...autoHooks]
},
FREELANCE_AGREED: {
actions: ["freelanceAgreed", ...autoHooks]
},
FREELANCE_DISAGREED: {
actions: ["freelanceDisagreed", ...autoHooks]
},
CLIENT_AGREED: {
actions: ["clientAgreed", ...autoHooks]
},
CLIENT_DISAGREED: {
actions: ["clientDisagreed", ...autoHooks]
},
TA_SETS_WIN: SELECTION_STATUS.OK,
TA_SETS_LOOSE: SELECTION_STATUS.KO
},
entry: [
"updateSelection",
"freelanceInterested",
"clientInterested",
...autoHooks
]
},
[SELECTION_STATUS.OK]: {
type: "final",
entry: ["updateSelection", ...autoHooks]
},
[SELECTION_STATUS.KO]: {
type: "final",
entry: ["updateSelection", ...autoHooks]
}
}
};
const missionStatusStates = {
initial: "QUALIFICATION",
states: {
QUALIFICATION: qualificationStates,
MATCHING: matchingStates,
WIN: winStates
}
};
const workflowMachine = Machine(
{
id: "workflow",
type: "parallel",
context: {
displayPipeline: false,
selection: {}
},
states: {
missionStatus: missionStatusStates,
selectionPipeline: selectionPipelineStates
}
},
{ guards, actions: mutations }
);
function getMutations() {
return {
updateSelection(context, event, metaAction) {
context.selection.status = metaAction.state.value.selectionPipeline;
},
selectFreelance(context) {
context.selection = {
status: SELECTION_STATUS.SELECTED,
freelanceAnswer: MISSION_SELECTION_ANSWER.NONE,
clientAnswer: MISSION_SELECTION_ANSWER.NONE
};
},
notifyFreelance(context) {
context.selection.freelanceAnswer = MISSION_SELECTION_ANSWER.PENDING;
},
freelanceInterested(context) {
context.selection.freelanceAnswer = MISSION_SELECTION_ANSWER.OK;
},
freelanceNotInterested(context) {
context.selection.freelanceAnswer = MISSION_SELECTION_ANSWER.KO;
},
notifyClient(context) {
context.selection.clientAnswer = MISSION_SELECTION_ANSWER.PENDING;
},
clientInterested(context) {
context.selection.clientAnswer = MISSION_SELECTION_ANSWER.OK;
},
clientNotInterested(context) {
context.selection.clientAnswer = MISSION_SELECTION_ANSWER.KO;
},
interviewHappened(context) {
context.selection.freelanceAnswer =
MISSION_SELECTION_ANSWER.AGREEMENT_PENDING;
context.selection.clientAnswer =
MISSION_SELECTION_ANSWER.AGREEMENT_PENDING;
},
freelanceAgreed(context) {
context.selection.freelanceAnswer = MISSION_SELECTION_ANSWER.AGREEMENT_OK;
},
freelanceDisagreed(context) {
context.selection.freelanceAnswer = MISSION_SELECTION_ANSWER.AGREEMENT_KO;
},
clientAgreed(context) {
context.selection.clientAnswer = MISSION_SELECTION_ANSWER.AGREEMENT_OK;
},
clientDisagreed(context) {
context.selection.clientAnswer = MISSION_SELECTION_ANSWER.AGREEMENT_KO;
}
};
}
function getGuards() {
return {
pipelineDisplayed: context => context.displayPipeline,
// qualification
firstFreelanceSelected: context =>
context.selection.status === SELECTION_STATUS.SELECTED,
// matching
firstFreelanceNotified: context =>
context.selection.freelanceAnswer === MISSION_SELECTION_ANSWER.PENDING,
firstClientNotified: context =>
context.selection.clientAnswer === MISSION_SELECTION_ANSWER.PENDING,
allFreelancesNotInterested: context =>
context.selection.freelanceAnswer === MISSION_SELECTION_ANSWER.KO,
firstFreelanceInInterview: context =>
context.selection.status === SELECTION_STATUS.INTERVIEW,
clientNotInterested: context =>
context.selection.clientAnswer === MISSION_SELECTION_ANSWER.KO,
interviewHappened: context =>
context.selection.clientAnswer ===
MISSION_SELECTION_ANSWER.AGREEMENT_PENDING &&
context.selection.freelanceAnswer ===
MISSION_SELECTION_ANSWER.AGREEMENT_PENDING,
atLeastOneNoAgreement: context =>
context.selection.clientAnswer ===
MISSION_SELECTION_ANSWER.AGREEMENT_KO ||
context.selection.freelanceAnswer ===
MISSION_SELECTION_ANSWER.AGREEMENT_KO,
freelanceInWin: context => context.selection.status === SELECTION_STATUS.OK,
// win
missionStartsToday: context => true,
missionEndsToday: context => true
};
}
function getConstants() {
return {
SELECTION_STATUS: {
SELECTED: "selected",
PENDINGCLIENT: "pendingClient",
PENDINGFREELANCE: "pendingFreelance",
INTERVIEW: "interview",
OK: "ok",
KO: "ko"
},
MISSION_SELECTION_ANSWER: {
NONE: "none",
PENDING: "pending",
OK: "ok",
KO: "ko",
// new values
AGREEMENT_PENDING: "AGREEMENT_PENDING",
AGREEMENT_OK: "AGREEMENT_OK",
AGREEMENT_KO: "AGREEMENT_KO"
},
MISSION_STATUS: {
QUALIFICATION: {
TO_QUALIFY: {
value: "TO_QUALIFY",
oldValue: "pending",
label: "🤔 Mission à qualifier"
},
QUALIFIED: {
value: "QUALIFIED",
oldValue: "active",
label: "✅ Mission qualifiée"
},
OUT_OF_SCOPE: {
value: "OUT_OF_SCOPE",
oldValue: "stashed",
label: "❌ Hors cible"
}
},
MATCHING: {
SEARCH: {
value: "SEARCH",
oldValue: "searching",
label: "🔍 Recherche en cours"
},
WAITING_FREELANCES_INTEREST: {
value: "WAITING_FREELANCES_INTEREST",
oldValue: "freelanceNotified",
label: "⏱ Attente intérêt Freelances"
},
FREELANCES_NOT_INTERESTED: {
value: "FREELANCES_NOT_INTERESTED",
oldValue: "inactive",
label: "❌ Freelances pas intéressés"
},
WAITING_CLIENT_INTEREST: {
value: "WAITING_CLIENT_INTEREST",
oldValue: "clientNotified",
label: "⏱ Attente intérêt Client"
},
CLIENT_NOT_INTERESTED: {
value: "CLIENT_NOT_INTERESTED",
oldValue: "ko",
label: "❌ Client pas intéressé"
},
INTERVIEWS: {
value: "INTERVIEWS",
oldValue: "interview",
label: "🤞 Entretiens"
},
WAITING_AGREEMENT: {
value: "WAITING_AGREEMENT",
oldValue: "interview",
label: "⏱ Attente accord Client/Freelance"
},
NO_AGREEMENT: {
value: "NO_AGREEMENT",
oldValue: "interview",
label: "❌ Pas d'accord Client/Freelance"
}
},
WIN: {
AGREEMENT: {
value: "AGREEMENT",
oldValue: "ok",
label: "🤝 Accord Client/Freelance"
},
ONGOING: {
value: "ONGOING",
label: "💰 Mission en cours"
},
DONE: {
value: "DONE",
oldValue: "completed",
label: "✅ Terminées"
}
}
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment