diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeEditableContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeEditableContent.tsx index 0de4daa8b3..d354574497 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeEditableContent.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeEditableContent.tsx @@ -15,6 +15,7 @@ import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-node import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle'; import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId'; import { useConnectionState } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useConnectionState'; +import { isNodeTitleHighlighted } from '@/workflow/workflow-diagram/workflow-nodes/utils/isNodeTitleHighlighted'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { Position } from '@xyflow/react'; @@ -87,6 +88,11 @@ export const WorkflowDiagramStepNodeEditableContent = ({ event.stopPropagation(); }; + const nodeTitleHighlighted = isNodeTitleHighlighted({ + nodeType: data.nodeType, + actionType: data.nodeType === 'action' ? data.actionType : undefined, + }); + return ( <> {capitalize(data.nodeType)} - {data.name} + + {data.name} + {selected && ( diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx index 11314a4313..5d7936d242 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx @@ -47,6 +47,9 @@ export const WorkflowDiagramStepNodeIcon = ({ case 'AI_AGENT': { return ; } + case 'EMPTY': { + return null; + } default: { return ( {capitalize(data.nodeType)} - {data.name} + + {data.name} + diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/utils/isNodeTitleHighlighted.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/utils/isNodeTitleHighlighted.ts new file mode 100644 index 0000000000..d4b772cd39 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/utils/isNodeTitleHighlighted.ts @@ -0,0 +1,12 @@ +import { type WorkflowActionType } from '@/workflow/types/Workflow'; +import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; + +export const isNodeTitleHighlighted = ({ + nodeType, + actionType, +}: { + nodeType: WorkflowDiagramStepNodeData['nodeType']; + actionType?: WorkflowActionType; +}) => { + return !(nodeType === 'action' && actionType === 'EMPTY'); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx index 57d458c34b..d6aa9f6882 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepNodeDetail.tsx @@ -250,7 +250,6 @@ export const WorkflowRunStepNodeDetail = ({ return ( - ); + return ; } default: return assertUnreachable( diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty.tsx index c2a058af66..9e43f9ddeb 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty.tsx @@ -1,12 +1,8 @@ -import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader'; +import { CommandMenuWorkflowSelectAction } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction'; +import { CommandMenuWorkflowEditStepType } from '@/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStepType'; import { type WorkflowEmptyAction } from '@/workflow/types/Workflow'; -import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter'; -import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; -import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader'; -import { useIcons } from 'twenty-ui/display'; type WorkflowEditActionEmptyProps = { - action: WorkflowEmptyAction; actionOptions: | { readonly: true; @@ -18,37 +14,11 @@ type WorkflowEditActionEmptyProps = { }; export const WorkflowEditActionEmpty = ({ - action, actionOptions, }: WorkflowEditActionEmptyProps) => { - const { getIcon } = useIcons(); - const { headerTitle, headerIcon, headerIconColor, headerType } = - useWorkflowActionHeader({ - action, - defaultTitle: 'Empty Node', - }); + if (actionOptions.readonly === true) { + return {}} />; + } - return ( - <> - { - if (actionOptions.readonly === true) { - return; - } - - actionOptions.onActionUpdate({ - ...action, - name: newName, - }); - }} - Icon={getIcon(headerIcon)} - iconColor={headerIconColor} - initialTitle={headerTitle} - headerType={headerType} - disabled={actionOptions.readonly} - /> - {'Empty Node'} - {!actionOptions.readonly && } - - ); + return ; }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/iterator-action/WorkflowEditActionIterator.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/iterator-action/WorkflowEditActionIterator.tsx index ea4da14c4d..fd66b293e7 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/iterator-action/WorkflowEditActionIterator.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/iterator-action/WorkflowEditActionIterator.tsx @@ -1,11 +1,15 @@ import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader'; -import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; +import { FormArrayFieldInput } from '@/object-record/record-field/ui/form-types/components/FormArrayFieldInput'; +import { type FieldArrayValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { type WorkflowIteratorAction } from '@/workflow/types/Workflow'; +import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { useLingui } from '@lingui/react/macro'; +import { isArray, isString } from '@sniptt/guards'; import { useState } from 'react'; +import { isDefined } from 'twenty-shared/utils'; import { useDebouncedCallback } from 'use-debounce'; type WorkflowEditActionIteratorProps = { @@ -20,6 +24,20 @@ type WorkflowEditActionIteratorProps = { }; }; +const stringifyArrayItems = (array: FieldArrayValue) => { + return array.map((item) => { + if (!isDefined(item)) { + return ''; + } + + if (isString(item)) { + return item; + } + + return JSON.stringify(item); + }); +}; + export const WorkflowEditActionIterator = ({ action, actionOptions, @@ -32,8 +50,18 @@ export const WorkflowEditActionIterator = ({ const { t } = useLingui(); + const defaultItems = isDefined(action.settings.input.items) + ? action.settings.input.items + : []; + + const parsedItems = isStandaloneVariableString(defaultItems) + ? defaultItems + : isArray(defaultItems) + ? stringifyArrayItems(defaultItems) + : stringifyArrayItems(JSON.parse(defaultItems)); + const [formData, setFormData] = useState({ - items: action.settings.input.items || [], + items: parsedItems, initialLoopStepIds: action.settings.input.initialLoopStepIds || [], }); @@ -57,7 +85,10 @@ export const WorkflowEditActionIterator = ({ 1000, ); - const handleFieldChange = (field: string, value: any) => { + const handleFieldChange = ( + field: string, + value: string | FieldArrayValue, + ) => { if (actionOptions.readonly === true) { return; } @@ -89,15 +120,13 @@ export const WorkflowEditActionIterator = ({ disabled={actionOptions.readonly} /> - + handleFieldChange('items', value) } - onChange={(value: string) => handleFieldChange('items', value)} readonly={actionOptions.readonly} VariablePicker={WorkflowVariablePicker} /> diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow.ts index 10e8066656..139c8fd5e2 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow.ts @@ -24,7 +24,7 @@ export const getActionHeaderTypeOrThrow = (actionType: WorkflowActionType) => { return msg`Iterator`; } case 'EMPTY': { - return msg`Empty Node`; + return msg`Add an Action`; } default: assertUnreachable(actionType, `Unsupported action type: ${actionType}`); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts index 040fb29b15..92c8336476 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts @@ -23,6 +23,8 @@ export const getActionIcon = (actionType: WorkflowActionType) => { return HUMAN_INPUT_ACTIONS.find((item) => item.type === actionType)?.icon; case 'ITERATOR': return 'IconRepeat'; + case 'EMPTY': + return 'IconSettingsAutomation'; default: return 'IconDefault'; } 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 673e587050..398f7e74db 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 @@ -515,7 +515,7 @@ export class WorkflowVersionStepOperationsWorkspaceService { const emptyNodeStep: WorkflowEmptyAction = { id: v4(), - name: 'Empty Node', + name: 'Add an Action', type: WorkflowActionType.EMPTY, valid: true, nextStepIds: [iteratorStepId],