App logic function as step (#17525)

- Add logic function as step
- Rename previous one as Code

<img width="494" height="573" alt="Capture d’écran 2026-01-28 à 16 15
29"
src="https://github.com/user-attachments/assets/82f7e720-20da-4926-b5bc-94a33cd1f53a"
/>
<img width="494" height="264" alt="Capture d’écran 2026-01-28 à 16 15
39"
src="https://github.com/user-attachments/assets/a0444ae9-8c50-418d-8c5f-68c4da08fce7"
/>
<img width="494" height="264" alt="Capture d’écran 2026-01-28 à 16 15
58"
src="https://github.com/user-attachments/assets/3025db09-4e59-421e-8dc5-f3a7a7326554"
/>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Thomas Trompette
2026-01-29 11:59:24 +01:00
committed by GitHub
parent 22c7e207e3
commit 3ad7a9ac94
42 changed files with 551 additions and 90 deletions
@@ -14,6 +14,7 @@ import { FilterWorkflowAction } from 'src/modules/workflow/workflow-executor/wor
import { FormWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action';
import { IfElseWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/if-else/if-else.workflow-action';
import { IteratorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/iterator.workflow-action';
import { LogicFunctionWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/logic-function.workflow-action';
import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action';
import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action';
import { FindRecordsWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action';
@@ -26,6 +27,7 @@ import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workf
export class WorkflowActionFactory {
constructor(
private readonly codeWorkflowAction: CodeWorkflowAction,
private readonly logicFunctionWorkflowAction: LogicFunctionWorkflowAction,
private readonly createRecordWorkflowAction: CreateRecordWorkflowAction,
private readonly upsertRecordWorkflowAction: UpsertRecordWorkflowAction,
private readonly updateRecordWorkflowAction: UpdateRecordWorkflowAction,
@@ -45,6 +47,8 @@ export class WorkflowActionFactory {
switch (stepType) {
case WorkflowActionType.CODE:
return this.codeWorkflowAction;
case WorkflowActionType.LOGIC_FUNCTION:
return this.logicFunctionWorkflowAction;
case WorkflowActionType.SEND_EMAIL:
return this.toolExecutorWorkflowAction;
case WorkflowActionType.CREATE_RECORD:
@@ -0,0 +1,10 @@
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
export const isWorkflowLogicFunctionAction = (
action: WorkflowAction,
): action is WorkflowAction & { type: WorkflowActionType.LOGIC_FUNCTION } => {
return action.type === WorkflowActionType.LOGIC_FUNCTION;
};
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
import { LogicFunctionWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/logic-function.workflow-action';
@Module({
imports: [LogicFunctionModule, WorkspaceManyOrAllFlatEntityMapsCacheModule],
providers: [LogicFunctionWorkflowAction],
exports: [LogicFunctionWorkflowAction],
})
export class LogicFunctionActionModule {}
@@ -0,0 +1,88 @@
import { Injectable } from '@nestjs/common';
import { resolveInput } from 'twenty-shared/utils';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/logic-function.service';
import {
WorkflowStepExecutorException,
WorkflowStepExecutorExceptionCode,
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import { isWorkflowLogicFunctionAction } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/guards/is-workflow-logic-function-action.guard';
import { WorkflowLogicFunctionActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/types/workflow-logic-function-action-input.type';
@Injectable()
export class LogicFunctionWorkflowAction implements WorkflowAction {
constructor(
private readonly logicFunctionService: LogicFunctionService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
async execute({
currentStepId,
steps,
context,
runInfo,
}: WorkflowActionInput): Promise<WorkflowActionOutput> {
const step = findStepOrThrow({
stepId: currentStepId,
steps,
});
if (!isWorkflowLogicFunctionAction(step)) {
throw new WorkflowStepExecutorException(
'Step is not a logic function action',
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
);
}
const workflowActionInput = resolveInput(
step.settings.input,
context,
) as WorkflowLogicFunctionActionInput;
try {
const { workspaceId } = runInfo;
const { flatLogicFunctionMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
const logicFunction =
flatLogicFunctionMaps.byId[workflowActionInput.logicFunctionId];
if (!logicFunction) {
throw new WorkflowStepExecutorException(
`Logic function with id ${workflowActionInput.logicFunctionId} not found`,
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
);
}
const version = logicFunction.latestVersion ?? 'draft';
const result = await this.logicFunctionService.executeOneLogicFunction({
id: workflowActionInput.logicFunctionId,
workspaceId,
payload: workflowActionInput.logicFunctionInput,
version,
});
if (result.error) {
return { error: result.error.errorMessage };
}
return { result: result.data || {} };
} catch (error) {
return { error: error.message };
}
}
}
@@ -0,0 +1,7 @@
export type WorkflowLogicFunctionActionInput = {
logicFunctionId: string;
logicFunctionInput: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
};
@@ -0,0 +1,6 @@
import { type WorkflowLogicFunctionActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/types/workflow-logic-function-action-input.type';
import { type BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
export type WorkflowLogicFunctionActionSettings = BaseWorkflowActionSettings & {
input: WorkflowLogicFunctionActionInput;
};
@@ -1,5 +1,6 @@
export enum WorkflowActionType {
CODE = 'CODE',
LOGIC_FUNCTION = 'LOGIC_FUNCTION',
SEND_EMAIL = 'SEND_EMAIL',
CREATE_RECORD = 'CREATE_RECORD',
UPDATE_RECORD = 'UPDATE_RECORD',
@@ -6,6 +6,7 @@ import { type WorkflowFormActionSettings } from 'src/modules/workflow/workflow-e
import { type WorkflowHttpRequestActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/types/workflow-http-request-action-settings.type';
import { type WorkflowIfElseActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/if-else/types/workflow-if-else-action-settings.type';
import { type WorkflowIteratorActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/types/workflow-iterator-action-settings.type';
import { type WorkflowLogicFunctionActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/types/workflow-logic-function-action-settings.type';
import { type WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type';
import {
type WorkflowCreateRecordActionSettings,
@@ -40,6 +41,11 @@ export type WorkflowCodeAction = BaseWorkflowAction & {
settings: WorkflowCodeActionSettings;
};
export type WorkflowLogicFunctionAction = BaseWorkflowAction & {
type: WorkflowActionType.LOGIC_FUNCTION;
settings: WorkflowLogicFunctionActionSettings;
};
export type WorkflowSendEmailAction = BaseWorkflowAction & {
type: WorkflowActionType.SEND_EMAIL;
settings: WorkflowSendEmailActionSettings;
@@ -111,6 +117,7 @@ export type WorkflowDelayAction = BaseWorkflowAction & {
export type WorkflowAction =
| WorkflowCodeAction
| WorkflowLogicFunctionAction
| WorkflowSendEmailAction
| WorkflowCreateRecordAction
| WorkflowUpdateRecordAction
@@ -13,6 +13,7 @@ import { FilterActionModule } from 'src/modules/workflow/workflow-executor/workf
import { FormActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module';
import { IfElseActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/if-else/if-else-action.module';
import { IteratorActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/iterator-action.module';
import { LogicFunctionActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/logic-function/logic-function-action.module';
import { RecordCRUDActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module';
import { ToolExecutorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action';
import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service';
@@ -23,6 +24,7 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
WorkflowCommonModule,
WorkflowRunModule,
CodeActionModule,
LogicFunctionActionModule,
DelayActionModule,
RecordCRUDActionModule,
FormActionModule,