From 1848f85475ae8a0bd6e8797e9a0f5faaa7f55d06 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 20 Jul 2022 19:05:54 +0100 Subject: [PATCH] Converting main automation thread to typescript. --- packages/server/src/constants/index.js | 5 - packages/server/src/db/utils.js | 9 ++ .../server/src/definitions/automations.ts | 35 ++++++ .../threads/{automation.js => automation.ts} | 118 +++++++++++------- .../types/src/documents/app/automation.ts | 12 +- 5 files changed, 128 insertions(+), 51 deletions(-) create mode 100644 packages/server/src/definitions/automations.ts rename packages/server/src/threads/{automation.js => automation.ts} (78%) diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 3aa5b2fb7b..60f3c981d6 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -208,10 +208,5 @@ exports.AutomationErrors = { FAILURE_CONDITION: "FAILURE_CONDITION_MET", } -exports.LoopStepTypes = { - ARRAY: "Array", - STRING: "String", -} - // pass through the list from the auth/core lib exports.ObjectStoreBuckets = ObjectStoreBuckets diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 152b0e9d33..7634a63202 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -41,6 +41,7 @@ const DocumentTypes = { METADATA: "metadata", MEM_VIEW: "view", USER_FLAG: "flag", + AUTOMATION_METADATA: "meta_au", } const InternalTables = { @@ -311,6 +312,14 @@ exports.generateQueryID = datasourceId => { }${SEPARATOR}${datasourceId}${SEPARATOR}${newid()}` } +/** + * Generates a metadata ID for automations, used to track errors in recurring + * automations etc. + */ +exports.generateAutomationMetadataID = automationId => { + return `${DocumentTypes.AUTOMATION_METADATA}${SEPARATOR}${automationId}` +} + /** * Gets parameters for retrieving a query, this is a utility function for the getDocParams function. */ diff --git a/packages/server/src/definitions/automations.ts b/packages/server/src/definitions/automations.ts new file mode 100644 index 0000000000..e8cb3e9702 --- /dev/null +++ b/packages/server/src/definitions/automations.ts @@ -0,0 +1,35 @@ +import { Automation, AutomationResults, AutomationStep } from "@budibase/types" + +export enum LoopStepTypes { + ARRAY = "Array", + STRING = "String", +} + +export interface LoopStep extends AutomationStep { + inputs: { + option: LoopStepTypes + [key: string]: any + } +} + +export interface LoopInput { + binding: string[] | string +} + +export interface TriggerOutput { + metadata?: any + appId?: string + timestamp?: number +} + +export interface AutomationEvent { + data: { + automation: Automation + event: any + } +} + +export interface AutomationContext extends AutomationResults { + steps: any[] + trigger: any +} diff --git a/packages/server/src/threads/automation.js b/packages/server/src/threads/automation.ts similarity index 78% rename from packages/server/src/threads/automation.js rename to packages/server/src/threads/automation.ts index 8880f0cbcb..6e3b989558 100644 --- a/packages/server/src/threads/automation.js +++ b/packages/server/src/threads/automation.ts @@ -1,36 +1,43 @@ -require("./utils").threadSetup() -const actions = require("../automations/actions") -const automationUtils = require("../automations/automationUtils") -const AutomationEmitter = require("../events/AutomationEmitter") -const { processObject } = require("@budibase/string-templates") -const { DocumentTypes } = require("../db/utils") -const { definitions: triggerDefs } = require("../automations/triggerInfo") +import { threadSetup } from "./utils" +threadSetup() +import { default as actions } from "../automations/actions" +import { default as automationUtils } from "../automations/automationUtils" +import { default as AutomationEmitter } from "../events/AutomationEmitter" +import { generateAutomationMetadataID } from "../db/utils" +import { definitions as triggerDefs } from "../automations/triggerInfo" +import { AutomationErrors } from "../constants" +import { storeLog } from "../automations/logging" +import { Automation, AutomationStep } from "@budibase/types" +import { + LoopStep, + LoopStepTypes, + LoopInput, + AutomationEvent, + TriggerOutput, + AutomationContext, +} from "../definitions/automations" const { doInAppContext, getAppDB } = require("@budibase/backend-core/context") -const { AutomationErrors, LoopStepTypes } = require("../constants") -const { storeLog } = require("../automations/logging") +const { processObject } = require("@budibase/string-templates") const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId - const CRON_STEP_ID = triggerDefs.CRON.stepId const STOPPED_STATUS = { success: true, status: "STOPPED" } const { cloneDeep } = require("lodash/fp") const env = require("../environment") -function typecastForLooping(loopStep, input) { +function typecastForLooping(loopStep: LoopStep, input: LoopInput) { if (!input || !input.binding) { return null } - const isArray = Array.isArray(input.binding), - isString = typeof input.binding === "string" try { switch (loopStep.inputs.option) { case LoopStepTypes.ARRAY: - if (isString) { + if (typeof input.binding === "string") { return JSON.parse(input.binding) } break case LoopStepTypes.STRING: - if (isArray) { + if (Array.isArray(input.binding)) { return input.binding.join(",") } break @@ -41,7 +48,7 @@ function typecastForLooping(loopStep, input) { return input.binding } -function getLoopIterations(loopStep, input) { +function getLoopIterations(loopStep: LoopStep, input: LoopInput) { const binding = typecastForLooping(loopStep, input) if (!loopStep || !binding) { return 1 @@ -57,11 +64,18 @@ function getLoopIterations(loopStep, input) { * inputs and handles any outputs. */ class Orchestrator { - constructor(automation, triggerOutput = {}) { + _metadata: any + _chainCount: number + _appId: string + _automation: Automation + _emitter: any + _context: AutomationContext + executionOutput: AutomationContext + + constructor(automation: Automation, triggerOutput: TriggerOutput) { this._metadata = triggerOutput.metadata this._chainCount = this._metadata ? this._metadata.automationChainCount : 0 - this._appId = triggerOutput.appId - this._app = null + this._appId = triggerOutput.appId as string const triggerStepId = automation.definition.trigger.stepId triggerOutput = this.cleanupTriggerOutputs(triggerStepId, triggerOutput) // remove from context @@ -79,14 +93,14 @@ class Orchestrator { this.updateExecutionOutput(triggerId, triggerStepId, null, triggerOutput) } - cleanupTriggerOutputs(stepId, triggerOutput) { + cleanupTriggerOutputs(stepId: string, triggerOutput: TriggerOutput) { if (stepId === CRON_STEP_ID) { triggerOutput.timestamp = Date.now() } return triggerOutput } - async getStepFunctionality(stepId) { + async getStepFunctionality(stepId: string) { let step = await actions.getAction(stepId) if (step == null) { throw `Cannot find automation step by name ${stepId}` @@ -94,16 +108,18 @@ class Orchestrator { return step } - async getApp() { - if (this._app) { - return this._app - } + async getMetadata() { + const metadataId = generateAutomationMetadataID(this._automation._id) const db = getAppDB() - this._app = await db.get(DocumentTypes.APP_METADATA) - return this._app + let metadata: any + try { + metadata = await db.get(metadataId) + } catch (err) { + metadata = {} + } } - updateExecutionOutput(id, stepId, inputs, outputs) { + updateExecutionOutput(id: string, stepId: string, inputs: any, outputs: any) { const stepObj = { id, stepId, inputs, outputs } // first entry is always the trigger (constructor) if (this.executionOutput.steps.length === 0) { @@ -112,7 +128,15 @@ class Orchestrator { this.executionOutput.steps.push(stepObj) } - updateContextAndOutput(loopStepNumber, step, output, result) { + updateContextAndOutput( + loopStepNumber: number | undefined, + step: AutomationStep, + output: any, + result: { success: boolean; status: string } + ) { + if (!loopStepNumber) { + throw new Error("No loop step number provided.") + } this.executionOutput.steps.splice(loopStepNumber, 0, { id: step.id, stepId: step.stepId, @@ -133,11 +157,11 @@ class Orchestrator { async execute() { let automation = this._automation let stopped = false - let loopStep = null + let loopStep: AutomationStep | undefined = undefined let stepCount = 0 - let loopStepNumber = null - let loopSteps = [] + let loopStepNumber: any = undefined + let loopSteps: LoopStep[] | undefined = [] for (let step of automation.definition.steps) { stepCount++ let input, @@ -151,7 +175,7 @@ class Orchestrator { if (loopStep) { input = await processObject(loopStep.inputs, this._context) - iterations = getLoopIterations(loopStep, input) + iterations = getLoopIterations(loopStep as LoopStep, input) } for (let index = 0; index < iterations; index++) { @@ -166,14 +190,17 @@ class Orchestrator { let tempOutput = { items: loopSteps, iterations: iterationCount } try { - newInput.binding = typecastForLooping(loopStep, newInput) + newInput.binding = typecastForLooping( + loopStep as LoopStep, + newInput + ) } catch (err) { this.updateContextAndOutput(loopStepNumber, step, tempOutput, { status: AutomationErrors.INCORRECT_TYPE, success: false, }) - loopSteps = null - loopStep = null + loopSteps = undefined + loopStep = undefined break } @@ -223,8 +250,8 @@ class Orchestrator { status: AutomationErrors.MAX_ITERATIONS, success: true, }) - loopSteps = null - loopStep = null + loopSteps = undefined + loopStep = undefined break } @@ -232,7 +259,7 @@ class Orchestrator { const currentItem = this._context.steps[loopStepNumber]?.currentItem if (currentItem && typeof currentItem === "object") { isFailure = Object.keys(currentItem).some(value => { - return currentItem[value] === loopStep.inputs.failure + return currentItem[value] === loopStep?.inputs.failure }) } else { isFailure = currentItem && currentItem === loopStep.inputs.failure @@ -243,8 +270,8 @@ class Orchestrator { status: AutomationErrors.FAILURE_CONDITION, success: false, }) - loopSteps = null - loopStep = null + loopSteps = undefined + loopStep = undefined break } } @@ -295,7 +322,7 @@ class Orchestrator { if (loopStep) { iterationCount++ if (index === iterations - 1) { - loopStep = null + loopStep = undefined this._context.steps.splice(loopStepNumber, 1) break } @@ -316,7 +343,7 @@ class Orchestrator { }) this._context.steps.splice(loopStepNumber, 0, tempOutput) - loopSteps = null + loopSteps = undefined } } @@ -326,7 +353,10 @@ class Orchestrator { } } -module.exports = (input, callback) => { +module.exports = ( + input: AutomationEvent, + callback: (error: any, response?: any) => void +) => { const appId = input.data.event.appId doInAppContext(appId, async () => { const automationOrchestrator = new Orchestrator( diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index 97248ff173..050cfc2d35 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -12,6 +12,14 @@ export interface Automation extends Document { export interface AutomationStep { id: string stepId: string + inputs: { + [key: string]: any + } + schema: { + inputs: { + [key: string]: any + } + } } export interface AutomationTrigger { @@ -26,8 +34,8 @@ export enum AutomationStatus { } export interface AutomationResults { - automationId: string - status: string + automationId?: string + status?: string trigger?: any steps: { stepId: string