Skip to content

Commit

Permalink
Move workflow to server-only
Browse files Browse the repository at this point in the history
  • Loading branch information
FyreByrd committed Sep 18, 2024
1 parent 873d82a commit d3c20fe
Show file tree
Hide file tree
Showing 6 changed files with 390 additions and 366 deletions.
1 change: 1 addition & 0 deletions source/SIL.AppBuilder.Portal/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * as BullMQ from './BullJobTypes.js';
export { scriptoriaQueue } from './bullmq.js';
export { default as DatabaseWrites } from './databaseProxy/index.js';
export { readonlyPrisma as prisma } from './prisma.js';
export { NoAdminS3 } from './workflow.js';
325 changes: 56 additions & 269 deletions source/SIL.AppBuilder.Portal/common/public/workflow.ts
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;
}
Loading

0 comments on commit d3c20fe

Please sign in to comment.