diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/logic-function-action/components/WorkflowEditActionLogicFunction.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/logic-function-action/components/WorkflowEditActionLogicFunction.tsx
index c33752df83..b1d1da4963 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/logic-function-action/components/WorkflowEditActionLogicFunction.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/logic-function-action/components/WorkflowEditActionLogicFunction.tsx
@@ -1,20 +1,38 @@
-import { getFunctionInputFromInputSchema } from 'twenty-shared/workflow';
-import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput';
+import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
+import { LogicFunctionLogs } from '@/logic-functions/components/LogicFunctionLogs';
+import { LogicFunctionTestInputInitEffect } from '@/logic-functions/components/LogicFunctionTestInputInitEffect';
+import { useExecuteLogicFunction } from '@/logic-functions/hooks/useExecuteLogicFunction';
import { useGetOneLogicFunction } from '@/logic-functions/hooks/useGetOneLogicFunction';
+import { InputLabel } from '@/ui/input/components/InputLabel';
+import { TabList } from '@/ui/layout/tab-list/components/TabList';
+import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
+import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { type WorkflowLogicFunctionAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
+import { WorkflowStepCmdEnterButton } from '@/workflow/workflow-steps/components/WorkflowStepCmdEnterButton';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { WorkflowEditActionCodeFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields';
+import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput';
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue';
+import { WORKFLOW_LOGIC_FUNCTION_ACTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/logic-function-action/constants/WorkflowLogicFunctionActionTabListComponentId';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { isObject } from '@sniptt/guards';
import { useMemo } from 'react';
+import { getOutputSchemaFromValue } from 'twenty-shared/logic-function';
import { isDefined } from 'twenty-shared/utils';
-import { Callout } from 'twenty-ui/display';
-import { useDebouncedCallback } from 'use-debounce';
+import { getFunctionInputFromInputSchema } from 'twenty-shared/workflow';
+import {
+ Callout,
+ IconPlayerPlay,
+ IconSettingsAutomation,
+} from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
+import { useDebouncedCallback } from 'use-debounce';
+
+const INPUT_TAB_ID = 'input';
+const TEST_TAB_ID = 'test';
const StyledContainer = styled.div`
display: flex;
@@ -22,6 +40,17 @@ const StyledContainer = styled.div`
gap: ${themeCssVariables.spacing[4]};
`;
+const StyledTabListContainer = styled.div`
+ background-color: ${themeCssVariables.background.secondary};
+ padding-left: ${themeCssVariables.spacing[2]};
+`;
+
+const StyledResultContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ position: relative;
+`;
+
type WorkflowEditActionLogicFunctionProps = {
action: WorkflowLogicFunctionAction;
actionOptions:
@@ -46,6 +75,11 @@ export const WorkflowEditActionLogicFunction = ({
id: logicFunctionId,
});
+ const activeTabId = useAtomComponentStateValue(
+ activeTabIdComponentState,
+ WORKFLOW_LOGIC_FUNCTION_ACTION_TAB_LIST_COMPONENT_ID,
+ );
+
const functionInput = useMemo(() => {
const toolInputSchema = logicFunction?.toolInputSchema;
@@ -86,6 +120,34 @@ export const WorkflowEditActionLogicFunction = ({
500,
);
+ const updateOutputSchemaFromTestResult = (testResult: object) => {
+ if (actionOptions.readonly === true) {
+ return;
+ }
+
+ const newOutputSchema = getOutputSchemaFromValue(testResult);
+
+ updateAction({
+ ...action,
+ settings: { ...action.settings, outputSchema: newOutputSchema },
+ });
+ };
+
+ const {
+ executeLogicFunction,
+ isExecuting,
+ logicFunctionTestData,
+ updateLogicFunctionInput,
+ } = useExecuteLogicFunction({
+ logicFunctionId,
+ callback: updateOutputSchemaFromTestResult,
+ });
+
+ const testInput = mergeDefaultFunctionInputAndFunctionInput({
+ newInput: functionInput,
+ oldInput: logicFunctionTestData.input,
+ });
+
const handleInputChange = (value: unknown, path: string[]) => {
const updatedFunctionInput = setNestedValue(functionInput, path, value);
@@ -100,34 +162,115 @@ export const WorkflowEditActionLogicFunction = ({
});
};
+ const handleTestInputChange = (value: unknown, path: string[]) => {
+ if (actionOptions.readonly === true) {
+ return;
+ }
+
+ const updatedTestFunctionInput = setNestedValue(testInput, path, value);
+
+ updateLogicFunctionInput(updatedTestFunctionInput);
+ };
+
+ const handleTestFunction = async () => {
+ if (actionOptions.readonly === true) {
+ return;
+ }
+
+ await executeLogicFunction();
+ };
+
if (loading) {
return null;
}
const hasInputFields = Object.keys(functionInput).length > 0;
+ const tabs = [
+ {
+ id: INPUT_TAB_ID,
+ title: t`Input`,
+ Icon: IconSettingsAutomation,
+ },
+ {
+ id: TEST_TAB_ID,
+ title: t`Test`,
+ Icon: IconPlayerPlay,
+ },
+ ];
+
return (
<>
+
+
+
+
-
- {hasInputFields ? (
+ {activeTabId === TEST_TAB_ID ? (
+ <>
- ) : (
-
- )}
-
+
+ {t`Result`}
+
+
+ {logicFunctionTestData.output.logs.length > 0 && (
+
+
+
+ )}
+ >
+ ) : (
+
+ {hasInputFields ? (
+
+ ) : (
+
+ )}
+
+ )}
- {!actionOptions.readonly && }
+ {!actionOptions.readonly && (
+ ,
+ ]
+ : []
+ }
+ />
+ )}
>
);
};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/logic-function-action/constants/WorkflowLogicFunctionActionTabListComponentId.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/logic-function-action/constants/WorkflowLogicFunctionActionTabListComponentId.ts
new file mode 100644
index 0000000000..be435dcfd9
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/logic-function-action/constants/WorkflowLogicFunctionActionTabListComponentId.ts
@@ -0,0 +1,2 @@
+export const WORKFLOW_LOGIC_FUNCTION_ACTION_TAB_LIST_COMPONENT_ID =
+ 'workflow-logic-function-action-tab-list-component-id';
diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/__tests__/computeStepOutputSchema.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/__tests__/computeStepOutputSchema.test.ts
index 90282d0538..d3d6f40ad4 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/__tests__/computeStepOutputSchema.test.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/__tests__/computeStepOutputSchema.test.ts
@@ -61,6 +61,15 @@ describe('computeStepOutputSchema', () => {
expect(result).toBeUndefined();
});
+
+ it('should return undefined for LOGIC_FUNCTION step type', () => {
+ const result = computeStepOutputSchema({
+ step: { type: 'LOGIC_FUNCTION', settings: {} } as any,
+ objectMetadataItems: [],
+ });
+
+ expect(result).toBeUndefined();
+ });
});
describe('DATABASE_EVENT trigger', () => {
@@ -466,6 +475,10 @@ describe('shouldComputeOutputSchemaOnFrontend', () => {
expect(shouldComputeOutputSchemaOnFrontend('ITERATOR')).toBe(false);
});
+ it('should return false for LOGIC_FUNCTION', () => {
+ expect(shouldComputeOutputSchemaOnFrontend('LOGIC_FUNCTION')).toBe(false);
+ });
+
it('should return true for DATABASE_EVENT', () => {
expect(shouldComputeOutputSchemaOnFrontend('DATABASE_EVENT')).toBe(true);
});
diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts
index 103a278cb4..0a16fa6e1a 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/computeStepOutputSchema.ts
@@ -17,6 +17,7 @@ const PERSISTED_OUTPUT_SCHEMA_TYPES = [
'AI_AGENT',
'CODE',
'HTTP_REQUEST',
+ 'LOGIC_FUNCTION',
'WEBHOOK',
'ITERATOR',
];
diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts
index 8dd4915835..84fe0221bf 100644
--- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts
+++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts
@@ -224,9 +224,22 @@ export class WorkflowVersionStepOperationsWorkspaceService {
type: WorkflowActionType.LOGIC_FUNCTION,
settings: {
...BASE_STEP_DEFINITION,
+ outputSchema: {
+ link: {
+ isLeaf: true,
+ icon: 'IconVariable',
+ tab: 'test',
+ label: 'Generate Function Output',
+ },
+ _outputSchemaType: 'LINK',
+ },
input: {
logicFunctionId,
- logicFunctionInput: {},
+ logicFunctionInput: isDefined(flatLogicFunction.toolInputSchema)
+ ? (getFunctionInputFromInputSchema([
+ flatLogicFunction.toolInputSchema,
+ ])[0] ?? {})
+ : {},
},
},
},