-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
390 additions
and
366 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
325 changes: 56 additions & 269 deletions
325
source/SIL.AppBuilder.Portal/common/public/workflow.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,271 +1,58 @@ | ||
import { setup, assign } from 'xstate'; | ||
import { DatabaseWrites }from '../index.js'; | ||
import type { | ||
AnyEventObject, | ||
StateMachineDefinition, | ||
TransitionDefinition | ||
} from 'xstate'; | ||
|
||
//later: update snapshot on state exits (define a function to do it), store instance id in context | ||
//later: update UserTasks on entry? | ||
export const NoAdminS3 = setup({ | ||
types: { | ||
context: {} as { | ||
//later: narrow types if necessary | ||
instructions: string; | ||
includeFields: string[]; | ||
includeReviewers: boolean; | ||
includeArtifacts: string | boolean; | ||
start?: string; | ||
productId: string; | ||
}, | ||
input: {} as { | ||
productId?: string; | ||
} | ||
} | ||
}).createMachine({ | ||
initial: 'Start', | ||
context: ({ input }) => ({ | ||
instructions: 'waiting', | ||
/** projectName and projectDescription are always included */ | ||
includeFields: [], | ||
/** Reset to false on exit */ | ||
includeReviewers: false, | ||
/** Reset to false on exit */ | ||
includeArtifacts: false, | ||
productId: input.productId | ||
}), | ||
states: { | ||
Start: { | ||
entry: ({ context }) => { | ||
DatabaseWrites.workflowInstances.upsert({ | ||
where: { | ||
ProductId: context.productId | ||
}, | ||
update: {}, | ||
create: { | ||
Snapshot: '', | ||
ProductId: context.productId | ||
} | ||
export type StateNode = { | ||
id: number; | ||
label: string; | ||
connections: { id: number; target: string; label: string }[]; | ||
inCount: number; | ||
start: boolean; | ||
final: boolean; | ||
}; | ||
|
||
export function targetStringFromEvent( | ||
e: TransitionDefinition<any, AnyEventObject>[], | ||
machineId: string | ||
): string { | ||
return ( | ||
e[0] | ||
.toJSON() | ||
.target?.at(0) | ||
?.replace('#' + machineId + '.', '') ?? '' | ||
); | ||
} | ||
|
||
export function transform(machine: StateMachineDefinition<any, AnyEventObject>): StateNode[] { | ||
const id = machine.id; | ||
const lookup = Object.keys(machine.states); | ||
const a = Object.entries(machine.states).map(([k, v]) => { | ||
return { | ||
id: lookup.indexOf(k), | ||
label: k, | ||
connections: Object.values(v.on).map((o) => { | ||
return { | ||
id: lookup.indexOf(targetStringFromEvent(o, id)), | ||
target: targetStringFromEvent(o, id), | ||
label: o[0].eventType | ||
}; | ||
}), | ||
inCount: Object.entries(machine.states) | ||
.map(([k, v]) => { | ||
return Object.values(v.on).map((e) => { | ||
// treat no target on transition as self target | ||
return { from: k, to: targetStringFromEvent(e, id) || k }; | ||
}); | ||
}) | ||
}, | ||
always: [ | ||
{ | ||
guard: ({ context }) => context.start === 'App Builder Configuration', | ||
target: 'App Builder Configuration' | ||
}, | ||
{ | ||
guard: ({ context }) => context.start === 'Author Configuration', | ||
//later: guard project has authors | ||
target: 'Author Configuration' | ||
}, | ||
{ | ||
guard: ({ context }) => context.start === 'Synchronize Data', | ||
target: 'Synchronize Data' | ||
}, | ||
{ | ||
//later: guard project has authors | ||
guard: ({ context }) => context.start === 'Author Download', | ||
target: 'Author Download' | ||
}, | ||
{ | ||
//later: guard project has authors | ||
//note: authors can upload at any time, this state is just to prompt an upload | ||
guard: ({ context }) => context.start === 'Author Upload', | ||
target: 'Author Upload' | ||
}, | ||
{ | ||
guard: ({ context }) => context.start === 'Product Build', | ||
target: 'Product Build' | ||
}, | ||
{ | ||
guard: ({ context }) => context.start === 'Verify And Publish', | ||
target: 'Verify And Publish' | ||
}, | ||
{ | ||
guard: ({ context }) => context.start === 'Publish Product', | ||
target: 'Publish Product' | ||
}, | ||
{ | ||
guard: ({ context }) => context.start === 'Published', | ||
target: 'Published' | ||
}, | ||
{ | ||
target: 'Product Creation' | ||
} | ||
], | ||
on: { | ||
// this is here just so the default start transition shows up in the visualizer | ||
'Default.Auto': { | ||
target: 'Product Creation' | ||
} | ||
} | ||
}, | ||
'Product Creation': { | ||
entry: [ | ||
assign({ instructions: 'waiting' }), | ||
() => { | ||
//later: hook into build engine | ||
console.log('Creating Product'); | ||
} | ||
], | ||
on: { | ||
'Product Created.Auto': { | ||
target: 'App Builder Configuration' | ||
} | ||
} | ||
}, | ||
'App Builder Configuration': { | ||
entry: assign({ | ||
instructions: 'app_configuration', | ||
includeFields: ['storeDescription', 'listingLanguageCode', 'projectURL'] | ||
}), | ||
on: { | ||
'Continue.Owner': { | ||
target: 'Product Build' | ||
}, | ||
'Send to Authors.Owner': { | ||
//later: guard project has authors | ||
target: 'Author Configuration' | ||
} | ||
} | ||
}, | ||
'Author Configuration': { | ||
entry: assign({ | ||
instructions: 'app_configuration', | ||
includeFields: ['storeDescription', 'listingLanguageCode', 'projectURL'] | ||
}), | ||
on: { | ||
'Continue.Author': { | ||
target: 'App Builder Configuration' | ||
}, | ||
'Take Back.Owner': { | ||
target: 'App Builder Configuration' | ||
} | ||
} | ||
}, | ||
'Synchronize Data': { | ||
entry: assign({ | ||
instructions: 'synchronize_data', | ||
includeFields: ['storeDescription', 'listingLanguageCode'] | ||
}), | ||
on: { | ||
'Continue.Owner': { | ||
target: 'Product Build' | ||
}, | ||
'Transfer to Authors.Owner': { | ||
//later: guard project has authors | ||
target: 'Author Download' | ||
} | ||
} | ||
}, | ||
'Author Download': { | ||
entry: assign({ | ||
instructions: 'authors_download', | ||
includeFields: ['storeDescription', 'listingLanguageCode', 'projectURL'] | ||
}), | ||
on: { | ||
'Continue.Author': { | ||
target: 'Author Upload' | ||
}, | ||
'Take Back.Owner': { | ||
target: 'Synchronize Data' | ||
} | ||
} | ||
}, | ||
'Author Upload': { | ||
entry: assign({ | ||
instructions: 'authors_upload', | ||
includeFields: ['storeDescription', 'listingLanguageCode'] | ||
}), | ||
on: { | ||
'Continue.Author': { | ||
target: 'Synchronize Data' | ||
}, | ||
'Take Back.Owner': { | ||
target: 'Synchronize Data' | ||
} | ||
} | ||
}, | ||
'Product Build': { | ||
entry: [ | ||
//later: connect to backend to build product | ||
assign({ | ||
instructions: 'waiting' | ||
}), | ||
() => { | ||
console.log('Building Product'); | ||
} | ||
], | ||
on: { | ||
'Build Successful.Auto': { | ||
target: 'Verify And Publish' | ||
}, | ||
'Build Failed.Auto': { | ||
target: 'Synchronize Data' | ||
} | ||
} | ||
}, | ||
'Verify And Publish': { | ||
entry: assign({ | ||
instructions: 'verify_and_publish', | ||
includeFields: ['storeDescription', 'listingLanguageCode', 'projectURL'], | ||
includeReviewers: true, | ||
includeArtifacts: true | ||
}), | ||
exit: assign({ | ||
includeReviewers: false, | ||
includeArtifacts: false | ||
}), | ||
on: { | ||
'Reject.Owner': { | ||
target: 'Synchronize Data' | ||
}, | ||
'Approve.Owner': { | ||
target: 'Publish Product' | ||
}, | ||
'Email Reviewers.Owner': { | ||
//later: guard project has reviewers | ||
target: 'Email Reviewers' | ||
} | ||
} | ||
}, | ||
'Email Reviewers': { | ||
//later: connect to backend to email reviewers | ||
entry: () => { | ||
console.log('Emailing Reviewers'); | ||
}, | ||
on: { | ||
'Default.Auto': { | ||
target: 'Verify And Publish' | ||
} | ||
} | ||
}, | ||
'Publish Product': { | ||
entry: [ | ||
assign({ instructions: 'waiting' }), | ||
() => { | ||
console.log('Publishing Product'); | ||
} | ||
], | ||
on: { | ||
'Publish Completed.Auto': { | ||
target: 'Published' | ||
}, | ||
'Publish Failed.Auto': { | ||
target: 'Synchronize Data' | ||
} | ||
} | ||
}, | ||
Published: { | ||
entry: assign({ | ||
instructions: '', | ||
includeFields: ['storeDescription', 'listingLanguageCode'] | ||
}), | ||
type: 'final' | ||
} | ||
}, | ||
on: { | ||
jump: { | ||
actions: assign({ | ||
start: ({ event }) => event.target | ||
}), | ||
target: '.Start' | ||
} | ||
} | ||
}); | ||
.reduce((p, c) => { | ||
return p.concat(c); | ||
}, []) | ||
.filter((v) => k === v.to).length, | ||
start: k === 'Start', | ||
final: v.type === 'final' | ||
}; | ||
}); | ||
return a; | ||
} |
Oops, something went wrong.