Refactor workflow-logic-function-interaction (#17699)

# Refactor workflow–logic function interaction

## Why

Workflow code steps and standalone logic functions shared the same build
layer and DB layer, which blurred two use cases: code steps belong to a
workflow version; standalone functions are deployable units. That made
workflow code steps harder to own and evolve.

## Goal

Treat code steps as **workflow-owned**: build and run them in workflow
context, and expose workflow-scoped APIs so the editor can load, test,
and save code step source without going through the generic
logic-function layer.
This commit is contained in:
Charles Bochet
2026-02-05 01:46:55 +01:00
committed by GitHub
parent 752359335e
commit 67a98f77e3
77 changed files with 1750 additions and 1336 deletions
@@ -0,0 +1,97 @@
import { EXECUTE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/executeOneLogicFunction';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { logicFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { useMutation } from '@apollo/client';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { LogicFunctionExecutionStatus } from '~/generated-metadata/graphql';
import { sleep } from '~/utils/sleep';
type ExecuteOneLogicFunctionInput = {
id: string;
payload: object;
forceRebuild?: boolean;
};
type ExecuteOneLogicFunctionResult = {
data?: object | null;
logs: string;
duration: number;
status: LogicFunctionExecutionStatus;
error?: {
errorType: string;
errorMessage: string;
stackTrace: string;
} | null;
};
export const useExecuteLogicFunction = ({
logicFunctionId,
callback,
}: {
logicFunctionId: string;
callback?: (result: object) => void;
}) => {
const [isExecuting, setIsExecuting] = useState(false);
const apolloMetadataClient = useApolloCoreClient();
const [executeOneLogicFunctionMutation] = useMutation<
{ executeOneLogicFunction: ExecuteOneLogicFunctionResult },
{ input: ExecuteOneLogicFunctionInput }
>(EXECUTE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const [logicFunctionTestData, setLogicFunctionTestData] = useRecoilState(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const executeLogicFunction = async ({
forceRebuild = false,
}: { forceRebuild?: boolean } = {}) => {
try {
setIsExecuting(true);
await sleep(200); // Delay artificially to avoid flashing the UI
const result = await executeOneLogicFunctionMutation({
variables: {
input: {
id: logicFunctionId,
payload: logicFunctionTestData.input,
forceRebuild,
},
},
});
setIsExecuting(false);
const executionResult = result?.data?.executeOneLogicFunction;
if (isDefined(executionResult?.data)) {
callback?.(executionResult.data);
}
setLogicFunctionTestData((prev) => ({
...prev,
language: 'json',
height: 300,
output: {
data: executionResult?.data
? JSON.stringify(executionResult.data, null, 4)
: undefined,
logs: executionResult?.logs || '',
duration: executionResult?.duration,
status: (executionResult?.status ??
LogicFunctionExecutionStatus.IDLE) as LogicFunctionExecutionStatus,
error: executionResult?.error
? JSON.stringify(executionResult.error, null, 4)
: undefined,
},
}));
} catch (error) {
setIsExecuting(false);
throw error;
}
};
return { executeLogicFunction, isExecuting };
};