From 409def8431d80732a203b88834fccdd78a988d71 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Mon, 28 Oct 2024 18:42:09 +0100 Subject: [PATCH] Fix manual trigger output schema (#8150) - add schema for manual trigger - split into sub functions - handle case with no variables --- .../WorkflowEditTriggerManualForm.tsx | 1 + .../components/SearchVariablesDropdown.tsx | 2 +- .../SearchVariablesDropdownStepItem.tsx | 11 +- .../useAvailableVariablesInWorkflowStep.ts | 24 ++- .../utils/getTriggerStepName.ts | 28 +++ .../src/modules/workflow/types/Workflow.ts | 1 + .../utils/getManualTriggerDefaultSettings.ts | 2 + .../resolvers/workflow-builder.resolver.ts | 14 +- .../send-email.workflow-action.ts | 9 +- .../generate-fake-object-record-event.ts | 77 +++++++ .../utils/generate-fake-object-record.ts | 11 + .../workflow-builder.service.ts | 204 ++++++++++++------ .../types/workflow-step-settings.type.ts | 4 + 13 files changed, 305 insertions(+), 83 deletions(-) rename packages/twenty-front/src/modules/workflow/{ => search-variables}/hooks/useAvailableVariablesInWorkflowStep.ts (77%) create mode 100644 packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts create mode 100644 packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts create mode 100644 packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record.ts diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerManualForm.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerManualForm.tsx index f5c453ac0..d6a170858 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerManualForm.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerManualForm.tsx @@ -87,6 +87,7 @@ export const WorkflowEditTriggerManualForm = ({ ...trigger, settings: { objectType: updatedObject, + outputSchema: {}, }, }); }} diff --git a/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdown.tsx b/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdown.tsx index 15dc75dd4..17042658c 100644 --- a/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdown.tsx +++ b/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdown.tsx @@ -6,13 +6,13 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { SearchVariablesDropdownStepItem } from '@/workflow/search-variables/components/SearchVariablesDropdownStepItem'; import SearchVariablesDropdownStepSubItem from '@/workflow/search-variables/components/SearchVariablesDropdownStepSubItem'; import { SEARCH_VARIABLES_DROPDOWN_ID } from '@/workflow/search-variables/constants/SearchVariablesDropdownId'; +import { useAvailableVariablesInWorkflowStep } from '@/workflow/search-variables/hooks/useAvailableVariablesInWorkflowStep'; import { StepOutputSchema } from '@/workflow/search-variables/types/StepOutputSchema'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { Editor } from '@tiptap/react'; import { useState } from 'react'; import { IconVariable } from 'twenty-ui'; -import { useAvailableVariablesInWorkflowStep } from '@/workflow/hooks/useAvailableVariablesInWorkflowStep'; const StyledDropdownVariableButtonContainer = styled( StyledDropdownButtonContainer, diff --git a/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdownStepItem.tsx b/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdownStepItem.tsx index c4887931b..c641f5460 100644 --- a/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdownStepItem.tsx +++ b/packages/twenty-front/src/modules/workflow/search-variables/components/SearchVariablesDropdownStepItem.tsx @@ -1,3 +1,4 @@ +import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect'; import { StepOutputSchema } from '@/workflow/search-variables/types/StepOutputSchema'; @@ -10,7 +11,7 @@ export const SearchVariablesDropdownStepItem = ({ steps, onSelect, }: SearchVariablesDropdownStepItemProps) => { - return ( + return steps.length > 0 ? ( <> {steps.map((item, _index) => ( ))} + ) : ( + {}} + text="No variables available" + LeftIcon={undefined} + hasSubMenu={false} + /> ); }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useAvailableVariablesInWorkflowStep.ts b/packages/twenty-front/src/modules/workflow/search-variables/hooks/useAvailableVariablesInWorkflowStep.ts similarity index 77% rename from packages/twenty-front/src/modules/workflow/hooks/useAvailableVariablesInWorkflowStep.ts rename to packages/twenty-front/src/modules/workflow/search-variables/hooks/useAvailableVariablesInWorkflowStep.ts index 88e0a05c0..6fac1f9b7 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useAvailableVariablesInWorkflowStep.ts +++ b/packages/twenty-front/src/modules/workflow/search-variables/hooks/useAvailableVariablesInWorkflowStep.ts @@ -1,11 +1,13 @@ -import { capitalize } from '~/utils/string/capitalize'; -import { useRecoilValue } from 'recoil'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { StepOutputSchema } from '@/workflow/search-variables/types/StepOutputSchema'; +import { getTriggerStepName } from '@/workflow/search-variables/utils/getTriggerStepName'; +import { workflowIdState } from '@/workflow/states/workflowIdState'; import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState'; import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; +import isEmpty from 'lodash.isempty'; +import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -import { StepOutputSchema } from '@/workflow/search-variables/types/StepOutputSchema'; +import { isEmptyObject } from '~/utils/isEmptyObject'; export const useAvailableVariablesInWorkflowStep = (): StepOutputSchema[] => { const workflowId = useRecoilValue(workflowIdState); @@ -41,20 +43,22 @@ export const useAvailableVariablesInWorkflowStep = (): StepOutputSchema[] => { const result = []; if ( - workflow.currentVersion.trigger?.type === 'DATABASE_EVENT' && - isDefined(workflow.currentVersion.trigger?.settings?.outputSchema) + isDefined(workflow.currentVersion.trigger) && + isDefined(workflow.currentVersion.trigger?.settings?.outputSchema) && + !isEmptyObject(workflow.currentVersion.trigger?.settings?.outputSchema) ) { - const [object, action] = - workflow.currentVersion.trigger.settings.eventName.split('.'); result.push({ id: 'trigger', - name: `${capitalize(object)} is ${capitalize(action)}`, + name: getTriggerStepName(workflow.currentVersion.trigger), outputSchema: workflow.currentVersion.trigger.settings.outputSchema, }); } previousSteps.forEach((previousStep) => { - if (isDefined(previousStep.settings.outputSchema)) { + if ( + isDefined(previousStep.settings.outputSchema) && + !isEmpty(previousStep.settings.outputSchema) + ) { result.push({ id: previousStep.id, name: previousStep.name, diff --git a/packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts b/packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts new file mode 100644 index 000000000..ae9c4a8e4 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts @@ -0,0 +1,28 @@ +import { + WorkflowDatabaseEventTrigger, + WorkflowTrigger, +} from '@/workflow/types/Workflow'; +import { capitalize } from '~/utils/string/capitalize'; + +export const getTriggerStepName = (trigger: WorkflowTrigger): string => { + switch (trigger.type) { + case 'DATABASE_EVENT': + return getDatabaseEventTriggerStepName(trigger); + case 'MANUAL': + if (!trigger.settings.objectType) { + return 'Manual trigger'; + } + + return 'Manual trigger for ' + capitalize(trigger.settings.objectType); + default: + return ''; + } +}; + +const getDatabaseEventTriggerStepName = ( + trigger: WorkflowDatabaseEventTrigger, +): string => { + const [object, action] = trigger.settings.eventName.split('.'); + + return `${capitalize(object)} is ${capitalize(action)}`; +}; diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts index 565c38558..4f753436d 100644 --- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts +++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts @@ -69,6 +69,7 @@ export type WorkflowManualTrigger = BaseTrigger & { type: 'MANUAL'; settings: { objectType?: string; + outputSchema: object; }; }; diff --git a/packages/twenty-front/src/modules/workflow/utils/getManualTriggerDefaultSettings.ts b/packages/twenty-front/src/modules/workflow/utils/getManualTriggerDefaultSettings.ts index 0e3cafd6f..c72934226 100644 --- a/packages/twenty-front/src/modules/workflow/utils/getManualTriggerDefaultSettings.ts +++ b/packages/twenty-front/src/modules/workflow/utils/getManualTriggerDefaultSettings.ts @@ -16,11 +16,13 @@ export const getManualTriggerDefaultSettings = ({ case 'EVERYWHERE': { return { objectType: undefined, + outputSchema: {}, }; } case 'WHEN_RECORD_SELECTED': { return { objectType: activeObjectMetadataItems[0].nameSingular, + outputSchema: {}, }; } } diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts index f35e3f23e..b3dccb01e 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts @@ -1,16 +1,16 @@ -import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { UseFilters, UseGuards } from '@nestjs/common'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; import graphqlTypeJson from 'graphql-type-json'; -import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; -import { WorkflowTriggerGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter'; -import { OutputSchema } from 'src/modules/workflow/workflow-executor/types/workflow-step-settings.type'; -import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ComputeStepOutputSchemaInput } from 'src/engine/core-modules/workflow/dtos/compute-step-output-schema-input.dto'; +import { WorkflowTriggerGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkflowBuilderService } from 'src/modules/workflow/workflow-builder/workflow-builder.service'; +import { OutputSchema } from 'src/modules/workflow/workflow-executor/types/workflow-step-settings.type'; @Resolver() @UseGuards(WorkspaceAuthGuard, UserAuthGuard) diff --git a/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts b/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts index 103045f88..b833120bb 100644 --- a/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts +++ b/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts @@ -19,7 +19,10 @@ import { WorkflowStepExecutorExceptionCode, } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; import { WorkflowActionResult } from 'src/modules/workflow/workflow-executor/types/workflow-action-result.type'; -import { WorkflowSendEmailStepInput } from 'src/modules/workflow/workflow-executor/types/workflow-step-settings.type'; +import { + WorkflowSendEmailStepInput, + WorkflowSendEmailStepOutputSchema, +} from 'src/modules/workflow/workflow-executor/types/workflow-step-settings.type'; import { isDefined } from 'src/utils/is-defined'; @Injectable() @@ -112,7 +115,9 @@ export class SendEmailWorkflowAction implements WorkflowAction { this.logger.log(`Email sent successfully`); - return { result: { success: true } }; + return { + result: { success: true } satisfies WorkflowSendEmailStepOutputSchema, + }; } catch (error) { return { error }; } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts new file mode 100644 index 000000000..d10b7fb37 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts @@ -0,0 +1,77 @@ +import { v4 } from 'uuid'; + +import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; +import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event'; +import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event'; +import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/utils/generate-fake-object-record'; + +export const generateFakeObjectRecordEvent = ( + objectMetadataEntity: ObjectMetadataEntity, + action: 'created' | 'updated' | 'deleted' | 'destroyed', +): + | ObjectRecordCreateEvent + | ObjectRecordUpdateEvent + | ObjectRecordDeleteEvent + | ObjectRecordDestroyEvent => { + const recordId = v4(); + const userId = v4(); + const workspaceMemberId = v4(); + + const after = generateFakeObjectRecord(objectMetadataEntity); + + if (action === 'created') { + return { + recordId, + userId, + workspaceMemberId, + objectMetadata: objectMetadataEntity, + properties: { + after, + }, + } satisfies ObjectRecordCreateEvent; + } + + const before = generateFakeObjectRecord(objectMetadataEntity); + + if (action === 'updated') { + return { + recordId, + userId, + workspaceMemberId, + objectMetadata: objectMetadataEntity, + properties: { + before, + after, + diff: after, + }, + } satisfies ObjectRecordUpdateEvent; + } + + if (action === 'deleted') { + return { + recordId, + userId, + workspaceMemberId, + objectMetadata: objectMetadataEntity, + properties: { + before, + }, + } satisfies ObjectRecordDeleteEvent; + } + + if (action === 'destroyed') { + return { + recordId, + userId, + workspaceMemberId, + objectMetadata: objectMetadataEntity, + properties: { + before, + }, + } satisfies ObjectRecordDestroyEvent; + } + + throw new Error(`Unknown action '${action}'`); +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record.ts new file mode 100644 index 000000000..38c925f4e --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record.ts @@ -0,0 +1,11 @@ +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { generateFakeValue } from 'src/engine/utils/generate-fake-value'; + +export const generateFakeObjectRecord = ( + objectMetadataEntity: ObjectMetadataEntity, +): Entity => + objectMetadataEntity.fields.reduce((acc, field) => { + acc[field.name] = generateFakeValue(field.type); + + return acc; + }, {} as Entity); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-builder.service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-builder.service.ts index 743197a90..e9c15add7 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-builder.service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-builder.service.ts @@ -1,24 +1,26 @@ -import { InjectRepository } from '@nestjs/typeorm'; import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { join } from 'path'; import { Repository } from 'typeorm'; +import { generateFakeObjectRecordEvent } from 'src/engine/core-modules/event-emitter/utils/generate-fake-object-record-event'; +import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; import { CodeIntrospectionService } from 'src/modules/code-introspection/code-introspection.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { - WorkflowTrigger, - WorkflowTriggerType, -} from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; +import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/utils/generate-fake-object-record'; import { WorkflowActionType, WorkflowStep, } from 'src/modules/workflow/workflow-executor/types/workflow-action.type'; +import { WorkflowSendEmailStepOutputSchema } from 'src/modules/workflow/workflow-executor/types/workflow-step-settings.type'; +import { + WorkflowTrigger, + WorkflowTriggerType, +} from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; import { isDefined } from 'src/utils/is-defined'; -import { generateFakeObjectRecordEvent } from 'src/engine/core-modules/event-emitter/utils/generate-fake-object-record-event'; -import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name'; @Injectable() export class WorkflowBuilderService { @@ -35,77 +37,155 @@ export class WorkflowBuilderService { }: { step: WorkflowTrigger | WorkflowStep; workspaceId: string; - }) { + }): Promise { const stepType = step.type; switch (stepType) { case WorkflowTriggerType.DATABASE_EVENT: { - const [nameSingular, action] = step.settings.eventName.split('.'); + return await this.computeDatabaseEventTriggerOutputSchema({ + eventName: step.settings.eventName, + workspaceId, + objectMetadataRepository: this.objectMetadataRepository, + }); + } + case WorkflowTriggerType.MANUAL: { + const { objectType } = step.settings; - if (!['created', 'updated', 'deleted', 'destroyed'].includes(action)) { + if (!objectType) { return {}; } - const objectMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular, - workspaceId, - }, - relations: ['fields'], - }); - - if (!isDefined(objectMetadata)) { - return {}; - } - - return generateFakeObjectRecordEvent( - objectMetadata, - action as 'created' | 'updated' | 'deleted' | 'destroyed', - ); + return await this.computeManualTriggerOutputSchema({ + objectType, + workspaceId, + objectMetadataRepository: this.objectMetadataRepository, + }); } case WorkflowActionType.SEND_EMAIL: { - return { success: true }; + return this.computeSendEmailActionOutputSchema(); } case WorkflowActionType.CODE: { const { serverlessFunctionId, serverlessFunctionVersion } = step.settings.input; - if (serverlessFunctionId === '') { - return {}; - } - - const sourceCode = ( - await this.serverlessFunctionService.getServerlessFunctionSourceCode( - workspaceId, - serverlessFunctionId, - serverlessFunctionVersion, - ) - )?.[join('src', INDEX_FILE_NAME)]; - - if (!isDefined(sourceCode)) { - return {}; - } - - const fakeFunctionInput = - this.codeIntrospectionService.generateInputData(sourceCode); - - // handle the case when event parameter is destructured: - // (event: {param1: string; param2: number}) VS ({param1, param2}: {param1: string; param2: number}) - const formattedInput = Object.values(fakeFunctionInput)[0]; - - const resultFromFakeInput = - await this.serverlessFunctionService.executeOneServerlessFunction( - serverlessFunctionId, - workspaceId, - formattedInput, - serverlessFunctionVersion, - ); - - return resultFromFakeInput.data ?? {}; + return await this.computeCodeActionOutputSchema({ + serverlessFunctionId, + serverlessFunctionVersion, + workspaceId, + serverlessFunctionService: this.serverlessFunctionService, + codeIntrospectionService: this.codeIntrospectionService, + }); } default: - throw new Error(`Unknown type ${stepType}`); + return {}; } } + + private async computeDatabaseEventTriggerOutputSchema({ + eventName, + workspaceId, + objectMetadataRepository, + }: { + eventName: string; + workspaceId: string; + objectMetadataRepository: Repository; + }) { + const [nameSingular, action] = eventName.split('.'); + + if (!['created', 'updated', 'deleted', 'destroyed'].includes(action)) { + return {}; + } + + const objectMetadata = await objectMetadataRepository.findOneOrFail({ + where: { + nameSingular, + workspaceId, + }, + relations: ['fields'], + }); + + if (!isDefined(objectMetadata)) { + return {}; + } + + return generateFakeObjectRecordEvent( + objectMetadata, + action as 'created' | 'updated' | 'deleted' | 'destroyed', + ); + } + + private async computeManualTriggerOutputSchema({ + objectType, + workspaceId, + objectMetadataRepository, + }: { + objectType: string; + workspaceId: string; + objectMetadataRepository: Repository; + }) { + const objectMetadata = await objectMetadataRepository.findOneOrFail({ + where: { + nameSingular: objectType, + workspaceId, + }, + relations: ['fields'], + }); + + if (!isDefined(objectMetadata)) { + return {}; + } + + return generateFakeObjectRecord(objectMetadata); + } + + private computeSendEmailActionOutputSchema(): WorkflowSendEmailStepOutputSchema { + return { success: true }; + } + + private async computeCodeActionOutputSchema({ + serverlessFunctionId, + serverlessFunctionVersion, + workspaceId, + serverlessFunctionService, + codeIntrospectionService, + }: { + serverlessFunctionId: string; + serverlessFunctionVersion: string; + workspaceId: string; + serverlessFunctionService: ServerlessFunctionService; + codeIntrospectionService: CodeIntrospectionService; + }) { + if (serverlessFunctionId === '') { + return {}; + } + + const sourceCode = ( + await serverlessFunctionService.getServerlessFunctionSourceCode( + workspaceId, + serverlessFunctionId, + serverlessFunctionVersion, + ) + )?.[join('src', INDEX_FILE_NAME)]; + + if (!isDefined(sourceCode)) { + return {}; + } + + const fakeFunctionInput = + codeIntrospectionService.generateInputData(sourceCode); + + // handle the case when event parameter is destructured: + // (event: {param1: string; param2: number}) VS ({param1, param2}: {param1: string; param2: number}) + const formattedInput = Object.values(fakeFunctionInput)[0]; + + const resultFromFakeInput = + await serverlessFunctionService.executeOneServerlessFunction( + serverlessFunctionId, + workspaceId, + formattedInput, + serverlessFunctionVersion, + ); + + return resultFromFakeInput.data ?? {}; + } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts index bd8f3da0d..99697bc2b 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts @@ -30,6 +30,10 @@ export type WorkflowSendEmailStepInput = { body?: string; }; +export type WorkflowSendEmailStepOutputSchema = { + success: boolean; +}; + export type WorkflowSendEmailStepSettings = BaseWorkflowStepSettings & { input: WorkflowSendEmailStepInput; };