|
|
|
@ -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( |