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
@@ -1,4 +1,5 @@
import { WorkflowActionMenuItems } from '@/command-menu/pages/workflow/action/components/WorkflowActionMenuItems';
import { useGetManyLogicFunctions } from '@/settings/logic-functions/hooks/useGetManyLogicFunctions';
import { type WorkflowActionType } from '@/workflow/types/Workflow';
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
@@ -7,19 +8,40 @@ import { CORE_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constan
import { FLOW_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/FlowActions';
import { HUMAN_INPUT_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/HumanInputActions';
import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions';
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { IconFunction } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { FeatureFlagKey } from '~/generated/graphql';
export type WorkflowActionSelection =
| { type: WorkflowActionType; logicFunctionId?: undefined; name?: undefined }
| { type: 'LOGIC_FUNCTION'; logicFunctionId: string; name: string };
export const CommandMenuWorkflowSelectAction = ({
onActionSelected,
}: {
onActionSelected: (actionType: WorkflowActionType) => void;
onActionSelected: (selection: WorkflowActionSelection) => void;
}) => {
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const theme = useTheme();
const { t } = useLingui();
const { logicFunctions } = useGetManyLogicFunctions();
const toolFunctions = logicFunctions.filter((fn) => fn.isTool === true);
const handleActionClick = (actionType: WorkflowActionType) => {
onActionSelected({ type: actionType });
};
const handleFunctionClick = (logicFunctionId: string, name: string) => {
onActionSelected({ type: 'LOGIC_FUNCTION', logicFunctionId, name });
};
return (
<RightDrawerStepListContainer>
<RightDrawerWorkflowSelectStepTitle>
@@ -27,7 +49,7 @@ export const CommandMenuWorkflowSelectAction = ({
</RightDrawerWorkflowSelectStepTitle>
<WorkflowActionMenuItems
actions={RECORD_ACTIONS}
onClick={onActionSelected}
onClick={handleActionClick}
/>
{isAiEnabled && (
@@ -37,7 +59,7 @@ export const CommandMenuWorkflowSelectAction = ({
</RightDrawerWorkflowSelectStepTitle>
<WorkflowActionMenuItems
actions={AI_ACTIONS}
onClick={onActionSelected}
onClick={handleActionClick}
/>
</>
)}
@@ -47,7 +69,7 @@ export const CommandMenuWorkflowSelectAction = ({
</RightDrawerWorkflowSelectStepTitle>
<WorkflowActionMenuItems
actions={FLOW_ACTIONS}
onClick={onActionSelected}
onClick={handleActionClick}
/>
<RightDrawerWorkflowSelectStepTitle>
@@ -55,7 +77,7 @@ export const CommandMenuWorkflowSelectAction = ({
</RightDrawerWorkflowSelectStepTitle>
<WorkflowActionMenuItems
actions={CORE_ACTIONS}
onClick={onActionSelected}
onClick={handleActionClick}
/>
<RightDrawerWorkflowSelectStepTitle>
@@ -63,8 +85,33 @@ export const CommandMenuWorkflowSelectAction = ({
</RightDrawerWorkflowSelectStepTitle>
<WorkflowActionMenuItems
actions={HUMAN_INPUT_ACTIONS}
onClick={onActionSelected}
onClick={handleActionClick}
/>
{toolFunctions.length > 0 && (
<>
<RightDrawerWorkflowSelectStepTitle>
{t`Applications`}
</RightDrawerWorkflowSelectStepTitle>
{toolFunctions.map((fn) => (
<MenuItem
key={fn.id}
withIconContainer={true}
LeftIcon={() => (
<IconFunction
color={getActionIconColorOrThrow({
theme,
actionType: 'LOGIC_FUNCTION',
})}
size={16}
/>
)}
text={fn.name}
onClick={() => handleFunctionClick(fn.id, fn.name)}
/>
))}
</>
)}
</RightDrawerStepListContainer>
);
};
@@ -1,5 +1,8 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { CommandMenuWorkflowSelectAction } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction';
import {
CommandMenuWorkflowSelectAction,
type WorkflowActionSelection,
} from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -55,7 +58,7 @@ export const CommandMenuWorkflowCreateStepContent = () => {
await updateStep(updatedStep);
};
const handleCreateStep = async (actionType: WorkflowActionType) => {
const handleCreateStep = async (selection: WorkflowActionSelection) => {
if (!isDefined(workflowVisualizerWorkflowId)) {
throw new Error(
'Workflow ID must be configured for the edge when creating a step',
@@ -66,11 +69,13 @@ export const CommandMenuWorkflowCreateStepContent = () => {
workflowInsertStepIds;
const createdStep = await createStep({
newStepType: actionType,
newStepType: selection.type,
parentStepId,
nextStepId,
position,
connectionOptions,
logicFunctionId: selection.logicFunctionId,
name: selection.name,
});
if (!isDefined(createdStep)) {
@@ -1,5 +1,8 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { CommandMenuWorkflowSelectAction } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction';
import {
CommandMenuWorkflowSelectAction,
type WorkflowActionSelection,
} from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
@@ -34,7 +37,7 @@ export const CommandMenuWorkflowEditStepTypeContent = () => {
commandMenuNavigationStackState,
);
const handleUpdateStepType = async (actionType: WorkflowActionType) => {
const handleUpdateStepType = async (selection: WorkflowActionSelection) => {
if (!isDefined(workflowVisualizerWorkflowId)) {
throw new Error(
'Workflow ID must be configured for the edge when creating a step',
@@ -49,10 +52,26 @@ export const CommandMenuWorkflowEditStepTypeContent = () => {
throw new Error('Step not found');
}
const { updatedStep } = await updateStep({
...existingStep,
type: actionType,
} as WorkflowAction);
const stepUpdate =
selection.type === 'LOGIC_FUNCTION'
? {
...existingStep,
type: selection.type,
name: selection.name,
settings: {
...existingStep.settings,
input: {
logicFunctionId: selection.logicFunctionId,
logicFunctionInput: {},
},
},
}
: {
...existingStep,
type: selection.type,
};
const { updatedStep } = await updateStep(stepUpdate as WorkflowAction);
if (!isDefined(updatedStep)) {
return;