Refactor to ensure automation context isn't initialised before it is ready. Passing uninitialised stores to the derived store was causing issues

This commit is contained in:
Dean 2025-01-30 15:00:10 +00:00
parent 420bd39aad
commit 7a0d9aeb25
2 changed files with 136 additions and 107 deletions

View File

@ -32,6 +32,9 @@
onMount(async () => { onMount(async () => {
await automationStore.actions.initAppSelf() await automationStore.actions.initAppSelf()
// Init the binding evaluation context
automationStore.actions.initContext()
$automationStore.showTestPanel = false $automationStore.showTestPanel = false
}) })

View File

@ -95,6 +95,106 @@ const getFinalDefinitions = (
} }
const automationActions = (store: AutomationStore) => ({ const automationActions = (store: AutomationStore) => ({
/**
* Generates a derived store acting as an evaluation contect
* for bindings in automations
*
* @returns {Readable<AutomationContext>}
*/
generateContext: (): Readable<AutomationContext> | undefined => {
if (!organisation || !store.selected || !environment || !tables) {
console.error("Automations: Required context stores are uninitialised")
return
}
return derived(
[organisation, store.selected, environment, tables],
([$organisation, $selectedAutomation, $env, $tables]) => {
const { platformUrl: url, company, logoUrl: logo } = $organisation
const results: TestAutomationResponse | undefined =
$selectedAutomation?.testResults
const testData: TriggerTestOutputs | undefined =
$selectedAutomation.data?.testData
const triggerDef = $selectedAutomation.data?.definition?.trigger
const isWebhook = triggerDef?.stepId === TriggerStepID.WEBHOOK
const isRowAction = triggerDef?.stepId === TriggerStepID.ROW_ACTION
const rowActionTableId = triggerDef?.inputs?.tableId
const rowActionTable = rowActionTableId
? $tables.list.find(table => table._id === rowActionTableId)
: undefined
let triggerData: TriggerTestOutputs | undefined
if (results && isAutomationResults(results)) {
const automationTrigger: AutomationTestTrigger | undefined =
results?.trigger
const outputs: TriggerTestOutputs | undefined =
automationTrigger?.outputs
triggerData = outputs ? outputs : undefined
if (triggerData) {
if (isRowAction && rowActionTable) {
const rowTrigger = triggerData as RowActionTriggerOutputs
// Row action table must always be retrieved as it is never
// returned in the test results
rowTrigger.table = rowActionTable
} else if (isWebhook) {
const webhookTrigger = triggerData as WebhookTriggerOutputs
// Ensure it displays in the event that the configuration have been skipped
webhookTrigger.body = webhookTrigger.body ?? {}
}
}
// Clean up unnecessary data from the context
// Meta contains UI/UX config data. Non-bindable
delete triggerData?.meta
} else {
// Substitute test data in place of the trigger data if the test hasn't been run
triggerData = testData
}
// AppSelf context required to mirror server user context
const userContext = $selectedAutomation.appSelf || {}
// Extract step results from a valid response
const stepResults =
results && isAutomationResults(results) ? results?.steps : []
return {
user: userContext,
// Merge in the trigger data.
...(triggerData ? { trigger: { ...triggerData } } : {}),
// This will initially be empty for each step but will populate
// upon running the test.
steps: stepResults.reduce(
(acc: Record<string, any>, res: Record<string, any>) => {
acc[res.id] = res.outputs
return acc
},
{}
),
env: ($env?.variables || []).reduce(
(acc: Record<string, any>, variable: Record<string, any>) => {
acc[variable.name] = ""
return acc
},
{}
),
settings: { url, company, logo },
}
}
)
},
/**
* Initialise the automation evaluation context
*/
initContext: () => {
store.context = store.actions.generateContext()
},
/** /**
* Fetches the app user context used for live evaluation * Fetches the app user context used for live evaluation
* This matches the context used on the server * This matches the context used on the server
@ -1509,29 +1609,14 @@ const automationActions = (store: AutomationStore) => ({
}, },
}) })
class AutomationStore extends BudiStore<AutomationState> { export interface AutomationContext {
history: HistoryStore<Automation> user: AppSelfResponse | null
actions: ReturnType<typeof automationActions> trigger?: TriggerTestOutputs
steps: Record<string, AutomationStep>
constructor() { env: Record<string, any>
super(initialAutomationState) settings: Record<string, any>
this.actions = automationActions(this)
this.history = createHistoryStore({
getDoc: this.actions.getDefinition.bind(this),
selectDoc: this.actions.select.bind(this),
})
// Then wrap save and delete with history
const originalSave = this.actions.save.bind(this.actions)
const originalDelete = this.actions.delete.bind(this.actions)
this.actions.save = this.history.wrapSaveDoc(originalSave)
this.actions.delete = this.history.wrapDeleteDoc(originalDelete)
}
} }
export const automationStore = new AutomationStore()
export const automationHistoryStore = automationStore.history
export class SelectedAutomationStore extends DerivedBudiStore< export class SelectedAutomationStore extends DerivedBudiStore<
AutomationState, AutomationState,
DerivedAutomationState DerivedAutomationState
@ -1592,93 +1677,34 @@ export class SelectedAutomationStore extends DerivedBudiStore<
super(initialAutomationState, makeDerivedStore) super(initialAutomationState, makeDerivedStore)
} }
} }
export const selectedAutomation = new SelectedAutomationStore(automationStore)
export interface AutomationContext { class AutomationStore extends BudiStore<AutomationState> {
user: AppSelfResponse history: HistoryStore<Automation>
trigger?: TriggerTestOutputs actions: ReturnType<typeof automationActions>
steps: Record<string, AutomationStep> selected: SelectedAutomationStore
env: Record<string, any> context: Readable<AutomationContext> | undefined
settings: Record<string, any>
constructor() {
super(initialAutomationState)
this.actions = automationActions(this)
this.history = createHistoryStore({
getDoc: this.actions.getDefinition.bind(this),
selectDoc: this.actions.select.bind(this),
})
// Then wrap save and delete with history
const originalSave = this.actions.save.bind(this.actions)
const originalDelete = this.actions.delete.bind(this.actions)
this.actions.save = this.history.wrapSaveDoc(originalSave)
this.actions.delete = this.history.wrapDeleteDoc(originalDelete)
this.selected = new SelectedAutomationStore(this)
// this.context =
}
} }
export const evaluationContext: Readable<AutomationContext> = derived( export const automationStore = new AutomationStore()
[organisation, selectedAutomation, environment, tables],
([$organisation, $selectedAutomation, $env, $tables]) => {
const { platformUrl: url, company, logoUrl: logo } = $organisation
const results: TestAutomationResponse | undefined = export const automationHistoryStore = automationStore.history
$selectedAutomation?.testResults export const selectedAutomation = automationStore.selected
export const evaluationContext = automationStore.context
const testData: TriggerTestOutputs | undefined =
$selectedAutomation.data?.testData
const triggerDef = $selectedAutomation.data?.definition?.trigger
const isWebhook = triggerDef?.stepId === TriggerStepID.WEBHOOK
const isRowAction = triggerDef?.stepId === TriggerStepID.ROW_ACTION
const rowActionTableId = triggerDef?.inputs?.tableId
const rowActionTable = rowActionTableId
? $tables.list.find(table => table._id === rowActionTableId)
: undefined
let triggerData: TriggerTestOutputs | undefined
if (results && isAutomationResults(results)) {
const automationTrigger: AutomationTestTrigger | undefined =
results?.trigger
const outputs: TriggerTestOutputs | undefined = automationTrigger?.outputs
triggerData = outputs ? outputs : undefined
if (triggerData) {
if (isRowAction && rowActionTable) {
const rowTrigger = triggerData as RowActionTriggerOutputs
// Row action table must always be retrieved as it is never
// returned in the test results
rowTrigger.table = rowActionTable
} else if (isWebhook) {
const webhookTrigger = triggerData as WebhookTriggerOutputs
// Ensure it displays in the event that the configuration have been skipped
webhookTrigger.body = webhookTrigger.body ?? {}
}
}
// Clean up unnecessary data from the context
// Meta contains UI/UX config data. Non-bindable
delete triggerData?.meta
} else {
// Substitute test data in place of the trigger data if the test hasn't been run
triggerData = testData
}
// AppSelf context required to mirror server user context
const userContext = $selectedAutomation.appSelf || {}
// Extract step results from a valid response
const stepResults =
results && isAutomationResults(results) ? results?.steps : []
return {
user: userContext,
// Merge in the trigger data.
...(triggerData ? { trigger: { ...triggerData } } : {}),
// This will initially be empty for each step but will populate
// upon running the test.
steps: stepResults.reduce(
(acc: Record<string, any>, res: Record<string, any>) => {
acc[res.id] = res.outputs
return acc
},
{}
),
env: ($env?.variables || []).reduce(
(acc: Record<string, any>, variable: Record<string, any>) => {
acc[variable.name] = ""
return acc
},
{}
),
settings: { url, company, logo },
}
}
)