refactor: Implement test run helper to work with services
This commit is contained in:
55
packages/backend/src/services/action.ts
Normal file
55
packages/backend/src/services/action.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import Step from '../models/step';
|
||||
import Flow from '../models/flow';
|
||||
import Execution from '../models/execution';
|
||||
import ExecutionStep from '../models/execution-step';
|
||||
import computeParameters from '../helpers/compute-parameters';
|
||||
import globalVariable from '../helpers/global-variable';
|
||||
|
||||
type ProcessActionOptions = {
|
||||
flowId: string;
|
||||
executionId: string;
|
||||
stepId: string;
|
||||
};
|
||||
|
||||
export const processAction = async (options: ProcessActionOptions) => {
|
||||
const { flowId, stepId, executionId } = options;
|
||||
|
||||
const step = await Step.query().findById(stepId).throwIfNotFound();
|
||||
const execution = await Execution.query()
|
||||
.findById(executionId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const $ = await globalVariable({
|
||||
flow: await Flow.query().findById(flowId).throwIfNotFound(),
|
||||
app: await step.getApp(),
|
||||
step: step,
|
||||
connection: await step.$relatedQuery('connection'),
|
||||
execution: execution,
|
||||
});
|
||||
|
||||
const priorExecutionSteps = await ExecutionStep.query().where({
|
||||
execution_id: $.execution.id,
|
||||
});
|
||||
|
||||
const computedParameters = computeParameters(
|
||||
$.step.parameters,
|
||||
priorExecutionSteps
|
||||
);
|
||||
|
||||
const actionCommand = await step.getActionCommand();
|
||||
|
||||
$.step.parameters = computedParameters;
|
||||
const actionOutput = await actionCommand.run($);
|
||||
|
||||
const executionStep = await execution
|
||||
.$relatedQuery('executionSteps')
|
||||
.insertAndFetch({
|
||||
stepId: $.step.id,
|
||||
status: actionOutput.error ? 'failure' : 'success',
|
||||
dataIn: computedParameters,
|
||||
dataOut: actionOutput.error ? null : actionOutput.data.raw,
|
||||
errorDetails: actionOutput.error,
|
||||
});
|
||||
|
||||
return { flowId, stepId, executionId, executionStep };
|
||||
};
|
24
packages/backend/src/services/flow.ts
Normal file
24
packages/backend/src/services/flow.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import Flow from '../models/flow';
|
||||
import globalVariable from '../helpers/global-variable';
|
||||
|
||||
type ProcessFlowOptions = {
|
||||
flowId: string;
|
||||
testRun?: boolean;
|
||||
};
|
||||
|
||||
export const processFlow = async (options: ProcessFlowOptions) => {
|
||||
const flow = await Flow.query().findById(options.flowId).throwIfNotFound();
|
||||
|
||||
const triggerStep = await flow.getTriggerStep();
|
||||
const triggerCommand = await triggerStep.getTriggerCommand();
|
||||
|
||||
const $ = await globalVariable({
|
||||
flow,
|
||||
connection: await triggerStep.$relatedQuery('connection'),
|
||||
app: await triggerStep.getApp(),
|
||||
step: triggerStep,
|
||||
testRun: options.testRun,
|
||||
});
|
||||
|
||||
return await triggerCommand.run($);
|
||||
};
|
@@ -1,73 +0,0 @@
|
||||
import get from 'lodash.get';
|
||||
import { IActionOutput } from '@automatisch/types';
|
||||
|
||||
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 globalVariable from '../helpers/global-variable';
|
||||
|
||||
type ExecutionSteps = Record<string, ExecutionStep>;
|
||||
|
||||
type ProcessorOptions = {
|
||||
untilStep?: Step;
|
||||
testRun?: boolean;
|
||||
};
|
||||
|
||||
class Processor {
|
||||
flow: Flow;
|
||||
untilStep?: Step;
|
||||
testRun?: boolean;
|
||||
|
||||
static variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[\da-zA-Z-]+)+}})/g;
|
||||
|
||||
constructor(flow: Flow, processorOptions: ProcessorOptions) {
|
||||
this.flow = flow;
|
||||
this.untilStep = processorOptions.untilStep;
|
||||
this.testRun = processorOptions.testRun;
|
||||
}
|
||||
|
||||
static computeParameters(
|
||||
parameters: Step['parameters'],
|
||||
executionSteps: ExecutionStep[]
|
||||
): Step['parameters'] {
|
||||
const entries = Object.entries(parameters);
|
||||
return entries.reduce((result, [key, value]: [string, unknown]) => {
|
||||
if (typeof value === '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.find((executionStep) => {
|
||||
return executionStep.stepId === stepId;
|
||||
});
|
||||
const data = executionStep?.dataOut;
|
||||
const dataValue = get(data, keyPath);
|
||||
return dataValue;
|
||||
}
|
||||
|
||||
return part;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return {
|
||||
...result,
|
||||
[key]: computedValue,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
export default Processor;
|
65
packages/backend/src/services/test-run.ts
Normal file
65
packages/backend/src/services/test-run.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import Step from '../models/step';
|
||||
import { processFlow } from '../services/flow';
|
||||
import { processTrigger } from '../services/trigger';
|
||||
import { processAction } from '../services/action';
|
||||
|
||||
type TestRunOptions = {
|
||||
stepId: string;
|
||||
};
|
||||
|
||||
const testRun = async (options: TestRunOptions) => {
|
||||
const untilStep = await Step.query()
|
||||
.findById(options.stepId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const flow = await untilStep.$relatedQuery('flow');
|
||||
const [triggerStep, ...actionSteps] = await flow
|
||||
.$relatedQuery('steps')
|
||||
.withGraphFetched('connection')
|
||||
.orderBy('position', 'asc');
|
||||
|
||||
const { data, error: triggerError } = await processFlow({
|
||||
flowId: flow.id,
|
||||
testRun: true,
|
||||
});
|
||||
|
||||
const firstTriggerDataItem = data[0];
|
||||
|
||||
const { executionId, executionStep: triggerExecutionStep } =
|
||||
await processTrigger({
|
||||
flowId: flow.id,
|
||||
stepId: triggerStep.id,
|
||||
triggerDataItem: firstTriggerDataItem,
|
||||
testRun: true,
|
||||
});
|
||||
|
||||
if (triggerError) {
|
||||
const { executionStep: triggerExecutionStepWithError } =
|
||||
await processTrigger({
|
||||
flowId: flow.id,
|
||||
stepId: triggerStep.id,
|
||||
error: triggerError,
|
||||
testRun: true,
|
||||
});
|
||||
|
||||
return { executionStep: triggerExecutionStepWithError };
|
||||
}
|
||||
|
||||
if (triggerStep.id === untilStep.id) {
|
||||
return { executionStep: triggerExecutionStep };
|
||||
}
|
||||
|
||||
for (const actionStep of actionSteps) {
|
||||
const { executionStep: actionExecutionStep } = await processAction({
|
||||
flowId: flow.id,
|
||||
stepId: actionStep.id,
|
||||
executionId,
|
||||
});
|
||||
|
||||
if (actionStep.id === untilStep.id || actionExecutionStep.errorDetails) {
|
||||
return { executionStep: actionExecutionStep };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default testRun;
|
46
packages/backend/src/services/trigger.ts
Normal file
46
packages/backend/src/services/trigger.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { IJSONObject, ITriggerDataItem } from '@automatisch/types';
|
||||
import Step from '../models/step';
|
||||
import Flow from '../models/flow';
|
||||
import Execution from '../models/execution';
|
||||
import globalVariable from '../helpers/global-variable';
|
||||
|
||||
type ProcessTriggerOptions = {
|
||||
flowId: string;
|
||||
stepId: string;
|
||||
triggerDataItem?: ITriggerDataItem;
|
||||
error?: IJSONObject;
|
||||
testRun?: boolean;
|
||||
};
|
||||
|
||||
export const processTrigger = async (options: ProcessTriggerOptions) => {
|
||||
const { flowId, stepId, triggerDataItem, error, testRun } = options;
|
||||
|
||||
const step = await Step.query().findById(stepId).throwIfNotFound();
|
||||
|
||||
const $ = await globalVariable({
|
||||
flow: await Flow.query().findById(flowId).throwIfNotFound(),
|
||||
app: await step.getApp(),
|
||||
step: step,
|
||||
connection: await step.$relatedQuery('connection'),
|
||||
});
|
||||
|
||||
// check if we already process this trigger data item or not!
|
||||
|
||||
const execution = await Execution.query().insert({
|
||||
flowId: $.flow.id,
|
||||
testRun,
|
||||
internalId: triggerDataItem.meta.internalId,
|
||||
});
|
||||
|
||||
const executionStep = await execution
|
||||
.$relatedQuery('executionSteps')
|
||||
.insertAndFetch({
|
||||
stepId: $.step.id,
|
||||
status: error ? 'failure' : 'success',
|
||||
dataIn: $.step.parameters,
|
||||
dataOut: !error ? triggerDataItem.raw : null,
|
||||
errorDetails: error,
|
||||
});
|
||||
|
||||
return { flowId, stepId, executionId: execution.id, executionStep };
|
||||
};
|
Reference in New Issue
Block a user