diff --git a/packages/backend/package.json b/packages/backend/package.json index cf0c0930..2b0fa80a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -18,6 +18,7 @@ "dependencies": { "@automatisch/web": "0.1.0", "@octokit/oauth-methods": "^1.2.6", + "@types/lodash.get": "^4.4.6", "axios": "0.24.0", "bcrypt": "^5.0.1", "cors": "^2.8.5", @@ -32,6 +33,7 @@ "graphql-type-json": "^0.3.2", "http-errors": "~1.6.3", "knex": "^0.95.11", + "lodash.get": "^4.4.2", "morgan": "^1.10.0", "nodemailer": "6.7.0", "objection": "^3.0.0", diff --git a/packages/backend/src/graphql/mutations/create-flow.ts b/packages/backend/src/graphql/mutations/create-flow.ts index 1da0494f..a28ac11d 100644 --- a/packages/backend/src/graphql/mutations/create-flow.ts +++ b/packages/backend/src/graphql/mutations/create-flow.ts @@ -1,7 +1,7 @@ import Step from '../../models/step'; import flowType, { flowInputType } from '../types/flow'; import RequestWithCurrentUser from '../../types/express/request-with-current-user'; -import StepEnumType from '../../types/step-enum-type'; +import { StepType } from '../../types/step'; type Params = { input: { @@ -21,7 +21,7 @@ const createFlowResolver = async ( await Step.query().insert({ flowId: flow.id, - type: StepEnumType.Trigger, + type: StepType.Trigger, position: 1, appKey, }); diff --git a/packages/backend/src/graphql/mutations/create-step.ts b/packages/backend/src/graphql/mutations/create-step.ts index 956734b8..b00512b3 100644 --- a/packages/backend/src/graphql/mutations/create-step.ts +++ b/packages/backend/src/graphql/mutations/create-step.ts @@ -1,7 +1,7 @@ import { GraphQLNonNull } from 'graphql'; import stepType, { stepInputType } from '../types/step'; import RequestWithCurrentUser from '../../types/express/request-with-current-user'; -import StepEnumType from '../../types/step-enum-type'; +import { StepType } from '../../types/step'; type Params = { input: { @@ -42,7 +42,7 @@ const createStepResolver = async ( const step = await flow.$relatedQuery('steps').insertAndFetch({ key: input.key, appKey: input.appKey, - type: StepEnumType.Action, + type: StepType.Action, position: previousStep.position + 1, parameters: {}, }); diff --git a/packages/backend/src/models/step.ts b/packages/backend/src/models/step.ts index 1929be9c..0f6ea058 100644 --- a/packages/backend/src/models/step.ts +++ b/packages/backend/src/models/step.ts @@ -2,14 +2,14 @@ import Base from './base'; import Flow from './flow'; import Connection from './connection'; import ExecutionStep from './execution-step'; -import StepEnumType from '../types/step-enum-type'; +import { StepType } from '../types/step'; class Step extends Base { id!: number; flowId!: string; key: string; appKey: string; - type!: StepEnumType; + type!: StepType; connectionId?: string; status: string; position: number; diff --git a/packages/backend/src/services/processor.ts b/packages/backend/src/services/processor.ts index 10dd4f80..46646563 100644 --- a/packages/backend/src/services/processor.ts +++ b/packages/backend/src/services/processor.ts @@ -1,13 +1,19 @@ +import get from 'lodash.get'; import App from '../models/app'; import Flow from '../models/flow'; import Step from '../models/step'; import Execution from '../models/execution'; import ExecutionStep from '../models/execution-step'; +import { StepType } from '../types/step'; + +type ExecutionSteps = Record; class Processor { flow: Flow; untilStep: Step; + static variableRegExp = /({{step\.\d*\..+?}})/g; + constructor(flow: Flow, untilStep: Step) { this.flow = flow; this.untilStep = untilStep; @@ -26,48 +32,71 @@ class Processor { let previousExecutionStep: ExecutionStep; let fetchedData; + const priorExecutionSteps: ExecutionSteps = {}; for await (const step of steps) { const appData = App.findOneByKey(step.appKey); + const { + appKey, + connection, + key, + type, + parameters: rawParameters = {}, + id + } = step; + const isTrigger = type === StepType.Trigger; + const AppClass = (await import(`../apps/${appKey}`)).default; + const computedParameters = Processor.computeParameters(rawParameters, priorExecutionSteps); + const appInstance = new AppClass(appData, connection.data, computedParameters); + const commands = isTrigger ? appInstance.triggers : appInstance.actions; + const command = commands[key]; + fetchedData = await command.run(); - if (step.type.toString() === 'trigger') { - const appClass = (await import(`../apps/${step.appKey}`)).default; - const appInstance = new appClass(appData, step.connection.data); - fetchedData = await appInstance.triggers[step.key].run(); + previousExecutionStep = await execution + .$relatedQuery('executionSteps') + .insertAndFetch({ + stepId: id, + status: 'success', + dataIn: previousExecutionStep?.dataOut, + dataOut: fetchedData, + }); - previousExecutionStep = await execution - .$relatedQuery('executionSteps') - .insertAndFetch({ - stepId: step.id, - status: 'success', - dataOut: fetchedData, - }); - } else { - const appClass = (await import(`../apps/${step.appKey}`)).default; - const appInstance = new appClass( - appData, - step.connection.data, - step.parameters - ); - fetchedData = await appInstance.actions[step.key].run(); + priorExecutionSteps[id] = previousExecutionStep; - previousExecutionStep = await execution - .$relatedQuery('executionSteps') - .insertAndFetch({ - stepId: step.id, - status: 'success', - dataIn: previousExecutionStep.dataOut, - dataOut: fetchedData, - }); - } - - if (step.id === this.untilStep.id) { + if (id === this.untilStep.id) { return fetchedData; } } return fetchedData; } + + static computeParameters(parameters: Step["parameters"], executionSteps: ExecutionSteps): Step["parameters"] { + const entries = Object.entries(parameters); + return entries.reduce((result, [key, value]: [string, string]) => { + const parts = value.split(Processor.variableRegExp); + + const computedValue = parts.map((part: string) => { + const isVariable = part.match(Processor.variableRegExp); + if (isVariable) { + const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '') as string; + const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.'); + const keyPath = keyPaths.join('.'); + const executionStep = executionSteps[stepId.toString() as string]; + const data = executionStep?.dataOut; + const dataValue = get(data, keyPath); + return dataValue; + } + + return part; + }).join(''); + + return { + ...result, + [key]: computedValue, + } + }, {}); + } } export default Processor; diff --git a/packages/backend/src/types/step-enum-type.ts b/packages/backend/src/types/step-enum-type.ts deleted file mode 100644 index 0af1e51c..00000000 --- a/packages/backend/src/types/step-enum-type.ts +++ /dev/null @@ -1,6 +0,0 @@ -enum StepEnumType { - Trigger = 'trigger', - Action = 'action', -} - -export default StepEnumType; diff --git a/packages/backend/src/types/step.ts b/packages/backend/src/types/step.ts new file mode 100644 index 00000000..4eaacd26 --- /dev/null +++ b/packages/backend/src/types/step.ts @@ -0,0 +1,4 @@ +export enum StepType { + Trigger = 'trigger', + Action = 'action', +} diff --git a/yarn.lock b/yarn.lock index 47724030..6c6e4227 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4166,6 +4166,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash.get@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240" + integrity sha512-E6zzjR3GtNig8UJG/yodBeJeIOtgPkMgsLjDU3CbgCAPC++vJ0eCMnJhVpRZb/ENqEFlov1+3K9TKtY4UdWKtQ== + dependencies: + "@types/lodash" "*" + "@types/lodash.template@^4.5.0": version "4.5.0" resolved "https://registry.yarnpkg.com/@types/lodash.template/-/lodash.template-4.5.0.tgz#277654af717ed37ce2687c69f8f221c550276b7a"