From 48367c5ab13692d2b97d81a2fb3e6e69e4d8bb95 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Tue, 15 Oct 2024 15:16:40 +0200 Subject: [PATCH] Resolve variables --- .../exceptions/workflow-executor.exception.ts | 1 + .../utils/variable-resolver.util.ts | 76 +++++++++++++++++++ .../workflow-executor.module.ts | 10 +-- .../workflow-executor.workspace-service.ts | 19 +++-- 4 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 packages/twenty-server/src/modules/workflow/workflow-executor/utils/variable-resolver.util.ts diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-executor.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-executor.exception.ts index cdba717b8f67c..aee8cc34dc458 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-executor.exception.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-executor.exception.ts @@ -8,4 +8,5 @@ export class WorkflowExecutorException extends CustomException { export enum WorkflowExecutorExceptionCode { WORKFLOW_FAILED = 'WORKFLOW_FAILED', + VARIABLE_EVALUATION_FAILED = 'VARIABLE_EVALUATION_FAILED', } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/utils/variable-resolver.util.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/utils/variable-resolver.util.ts new file mode 100644 index 0000000000000..4f933ad06d514 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/utils/variable-resolver.util.ts @@ -0,0 +1,76 @@ +import { isNil, isString } from '@nestjs/common/utils/shared.utils'; + +import Handlebars from 'handlebars'; + +import { + WorkflowExecutorException, + WorkflowExecutorExceptionCode, +} from 'src/modules/workflow/workflow-executor/exceptions/workflow-executor.exception'; + +const VARIABLE_PATTERN = RegExp('\\{\\{(.*?)\\}\\}', 'g'); + +export const resolve = ( + unresolvedInput: any, + context: Record, +) => { + if (isNil(unresolvedInput)) { + return unresolvedInput; + } + + if (isString(unresolvedInput)) { + return resolveString(unresolvedInput, context); + } + + const resolvedInput = unresolvedInput; + + if (Array.isArray(unresolvedInput)) { + for (let i = 0; i < unresolvedInput.length; ++i) { + resolvedInput[i] = resolve(unresolvedInput[i], context); + } + } else if (typeof unresolvedInput === 'object') { + const entries = Object.entries(unresolvedInput); + + for (const [key, value] of entries) { + resolvedInput[key] = resolve(value, context); + } + } + + return resolvedInput; +}; + +const resolveString = ( + input: string, + context: Record, +): string => { + const matchedTokens = input.match(VARIABLE_PATTERN); + + if (!matchedTokens || matchedTokens.length === 0) { + return input; + } + + if (matchedTokens.length === 1 && matchedTokens[0] === input) { + return evalFromContext(input, context); + } + + return input.replace(VARIABLE_PATTERN, (matchedToken, _) => { + const processedToken = evalFromContext(matchedToken, context); + + return processedToken; + }); +}; + +const evalFromContext = ( + input: string, + context: Record, +): string => { + try { + const inferedInput = Handlebars.compile(input)(context); + + return inferedInput ?? ''; + } catch (exception) { + throw new WorkflowExecutorException( + `Failed to evaluate variable ${input}: ${exception}`, + WorkflowExecutorExceptionCode.VARIABLE_EVALUATION_FAILED, + ); + } +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts index 24ae66fd7f11f..4cfc2d9888d7d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts @@ -1,13 +1,13 @@ import { Module } from '@nestjs/common'; -import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; -import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service'; -import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory'; -import { CodeWorkflowAction } from 'src/modules/serverless/workflow-actions/code.workflow-action'; -import { SendEmailWorkflowAction } from 'src/modules/mail-sender/workflow-actions/send-email.workflow-action'; import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { SendEmailWorkflowAction } from 'src/modules/mail-sender/workflow-actions/send-email.workflow-action'; import { MessagingGmailDriverModule } from 'src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module'; +import { CodeWorkflowAction } from 'src/modules/serverless/workflow-actions/code.workflow-action'; +import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; +import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory'; +import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service'; @Module({ imports: [ diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts index b1d9fb6b03bb5..878f83318a4a0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts @@ -6,6 +6,7 @@ import { } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory'; import { WorkflowStep } from 'src/modules/workflow/workflow-executor/types/workflow-action.type'; +import { resolve } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util'; const MAX_RETRIES_ON_FAILURE = 3; @@ -28,7 +29,7 @@ export class WorkflowExecutorWorkspaceService { currentStepIndex: number; steps: WorkflowStep[]; output: WorkflowExecutorOutput; - context?: Record; + context: Record; attemptCount?: number; }): Promise { if (currentStepIndex >= steps.length) { @@ -39,12 +40,20 @@ export class WorkflowExecutorWorkspaceService { const workflowAction = this.workflowActionFactory.get(step.type); + const inferedStepInput = await resolve(step.settings.input, context); + const result = await workflowAction.execute({ - step, + step: { + ...step, + settings: { + ...step.settings, + input: inferedStepInput, + }, + }, context, }); - const stepOutput = output.steps[step.name]; + const stepOutput = output.steps[step.id]; const error = result.error?.errorMessage ?? @@ -68,7 +77,7 @@ export class WorkflowExecutorWorkspaceService { ...output, steps: { ...output.steps, - [step.name]: updatedStepOutput, + [step.id]: updatedStepOutput, }, }; @@ -78,7 +87,7 @@ export class WorkflowExecutorWorkspaceService { steps, context: { ...context, - [step.name]: result.result, + [step.id]: result.result, }, output: updatedOutput, });