From 7a0d9aeb25eb106054dd019c9e54171150840598 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 30 Jan 2025 15:00:10 +0000 Subject: [PATCH] Refactor to ensure automation context isn't initialised before it is ready. Passing uninitialised stores to the derived store was causing issues --- .../[application]/automation/_layout.svelte | 3 + .../builder/src/stores/builder/automations.ts | 240 ++++++++++-------- 2 files changed, 136 insertions(+), 107 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte index 19e950f638..f133b5e2cb 100644 --- a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte @@ -32,6 +32,9 @@ onMount(async () => { await automationStore.actions.initAppSelf() + // Init the binding evaluation context + automationStore.actions.initContext() + $automationStore.showTestPanel = false }) diff --git a/packages/builder/src/stores/builder/automations.ts b/packages/builder/src/stores/builder/automations.ts index a0413098f6..f331cb1d87 100644 --- a/packages/builder/src/stores/builder/automations.ts +++ b/packages/builder/src/stores/builder/automations.ts @@ -95,6 +95,106 @@ const getFinalDefinitions = ( } const automationActions = (store: AutomationStore) => ({ + /** + * Generates a derived store acting as an evaluation contect + * for bindings in automations + * + * @returns {Readable} + */ + generateContext: (): Readable | 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, res: Record) => { + acc[res.id] = res.outputs + return acc + }, + {} + ), + env: ($env?.variables || []).reduce( + (acc: Record, variable: Record) => { + 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 * This matches the context used on the server @@ -1509,29 +1609,14 @@ const automationActions = (store: AutomationStore) => ({ }, }) -class AutomationStore extends BudiStore { - history: HistoryStore - actions: ReturnType - - 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) - } +export interface AutomationContext { + user: AppSelfResponse | null + trigger?: TriggerTestOutputs + steps: Record + env: Record + settings: Record } -export const automationStore = new AutomationStore() -export const automationHistoryStore = automationStore.history - export class SelectedAutomationStore extends DerivedBudiStore< AutomationState, DerivedAutomationState @@ -1592,93 +1677,34 @@ export class SelectedAutomationStore extends DerivedBudiStore< super(initialAutomationState, makeDerivedStore) } } -export const selectedAutomation = new SelectedAutomationStore(automationStore) -export interface AutomationContext { - user: AppSelfResponse - trigger?: TriggerTestOutputs - steps: Record - env: Record - settings: Record +class AutomationStore extends BudiStore { + history: HistoryStore + actions: ReturnType + selected: SelectedAutomationStore + context: Readable | undefined + + 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 = derived( - [organisation, selectedAutomation, environment, tables], - ([$organisation, $selectedAutomation, $env, $tables]) => { - const { platformUrl: url, company, logoUrl: logo } = $organisation +export const automationStore = new AutomationStore() - 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, res: Record) => { - acc[res.id] = res.outputs - return acc - }, - {} - ), - env: ($env?.variables || []).reduce( - (acc: Record, variable: Record) => { - acc[variable.name] = "" - return acc - }, - {} - ), - settings: { url, company, logo }, - } - } -) +export const automationHistoryStore = automationStore.history +export const selectedAutomation = automationStore.selected +export const evaluationContext = automationStore.context