From b8cd4491c2e2390f0956b83543a6eb58b93cbc4e Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Wed, 6 Aug 2025 19:00:48 +0200 Subject: [PATCH] Replace `useOnSelectionChange` with `onClick` listeners for steps opening (#13672) This PR cleans a few parts of the workflow features and fixes a few improper behaviors. Here is a non-exaustive list of improved things: - Clicking on an action will always reset the command menu's navigation stack. Previously, the behavior wasn't unified between workflows, workflow versions and workflow runs. - Opening a step in the side panel is now down in the `onClick` event handler put on individual Reactflow nodes. This makes interoperability between filters (which are buttons on edges) and traditional actions. - Simplified the code that automatically opens pending forms in the side panel. This feature should now work more predictably. - Splitted the `WorkflowDiagramEmptyTrigger` component into `WorkflowDiagramEmptyTriggerEditable` and `WorkflowDiagramEmptyTriggerReadonly`. This makes handling events easier as the behavior isn't the same in both cases. - Dropped all `useOnSelectionChange` hooks that were used to open steps in the side panel upon user selection. - Created a specific `WorkflowRunDiagramStepNode` instead of using `WorkflowDiagramStepNodeReadonly` in `WorkflowRunDiagramCanvas` - Deleted `useHandleWorkflowRunDiagramCanvasInit` as it was used to open the initially selected step in the command menu, which is now handled in an effect that works for all cases, including workflow run's state refreshing. Closes https://github.com/twentyhq/core-team-issues/issues/1227 Closes https://github.com/twentyhq/twenty/issues/11923 --- ...CommandMenuWorkflowSelectActionContent.tsx | 37 +++++- ...kflowRunOpeningInCommandMenuSideEffects.ts | 8 +- .../WorkflowDiagramCanvasEditable.tsx | 6 +- .../WorkflowDiagramCanvasEditableEffect.tsx | 104 +-------------- .../WorkflowDiagramCanvasReadonly.tsx | 7 +- .../WorkflowDiagramCanvasReadonlyEffect.tsx | 76 ----------- .../WorkflowDiagramDefaultEdgeEditable.tsx | 20 ++- .../WorkflowDiagramEmptyTrigger.tsx | 23 ---- .../WorkflowDiagramEmptyTriggerEditable.tsx | 57 ++++++++ .../WorkflowDiagramEmptyTriggerReadonly.tsx | 82 ++++++++++++ .../WorkflowDiagramFilterEdgeEditable.tsx | 19 ++- .../WorkflowDiagramFilterEdgeReadonly.tsx | 14 ++ .../WorkflowDiagramStepNodeBase.tsx | 3 + .../WorkflowDiagramStepNodeEditable.tsx | 41 ++++++ ...WorkflowDiagramStepNodeEditableContent.tsx | 13 +- .../WorkflowDiagramStepNodeReadonly.tsx | 56 +++++++- .../components/WorkflowRunDiagramCanvas.tsx | 12 +- .../WorkflowRunDiagramCanvasEffect.tsx | 91 ------------- .../components/WorkflowRunDiagramStepNode.tsx | 75 +++++++++++ .../WorkflowRunVisualizerEffect.tsx | 124 +++++++++--------- ...owDiagramEmptyTriggerEditable.stories.tsx} | 20 +-- .../constants/CreateStepNodeWidth.ts | 8 -- .../useTriggerNodeSelection.test.tsx | 79 ----------- .../useHandleWorkflowRunDiagramCanvasInit.ts | 124 ------------------ .../hooks/useStartNodeCreation.ts | 23 +++- .../hooks/useTriggerNodeSelection.ts | 39 ------ ...agramTriggerNodeSelectionComponentState.ts | 9 -- ...mAutomaticallyOpenedStepsComponentState.ts | 2 +- ...lowRunStepToOpenByDefaultComponentState.ts | 16 --- .../workflow-steps/hooks/useDeleteStep.ts | 5 +- .../hooks/useVariableDropdown.ts | 51 ++++++- 31 files changed, 564 insertions(+), 680 deletions(-) delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerEditable.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramStepNode.tsx rename packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/{WorkflowDiagramEmptyTrigger.stories.tsx => WorkflowDiagramEmptyTriggerEditable.stories.tsx} (76%) delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/constants/CreateStepNodeWidth.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/__tests__/useTriggerNodeSelection.test.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useTriggerNodeSelection.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState.ts diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx index 12cc5e7b34e..7cafc819fba 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx @@ -1,4 +1,8 @@ +import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +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'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { WorkflowActionType, WorkflowWithCurrentVersion, @@ -10,6 +14,9 @@ import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep'; import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState'; import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions'; import { useFilteredOtherActions } from '@/workflow/workflow-steps/workflow-actions/hooks/useFilteredOtherActions'; +import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; import { MenuItemCommand } from 'twenty-ui/navigation'; @@ -30,22 +37,50 @@ export const CommandMenuWorkflowSelectActionContent = ({ const [workflowInsertStepIds, setWorkflowInsertStepIds] = useRecoilComponentState(workflowInsertStepIdsComponentState); + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + + const workflowVisualizerWorkflowId = useRecoilComponentValue( + workflowVisualizerWorkflowIdComponentState, + ); + const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu(); + const handleCreateStep = async (actionType: WorkflowActionType) => { + if (!isDefined(workflowVisualizerWorkflowId)) { + throw new Error( + 'Workflow ID must be configured for the edge when creating a step', + ); + } + const { parentStepId, nextStepId, position } = workflowInsertStepIds; - await createStep({ + const createdStep = await createStep({ newStepType: actionType, parentStepId, nextStepId, position, }); + if (!isDefined(createdStep)) { + return; + } + setWorkflowInsertStepIds({ parentStepId: undefined, nextStepId: undefined, position: undefined, }); + closeRightClickMenu(); + + setCommandMenuNavigationStack([]); + + openWorkflowEditStepInCommandMenu( + workflowVisualizerWorkflowId, + createdStep.name, + getIcon(getActionIcon(createdStep.type as WorkflowActionType)), + ); }; return ( diff --git a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts index a9f3af0dfb1..3c7fe1d723b 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts @@ -112,7 +112,13 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => { recordId, }), }), - (steps) => [...steps, stepToOpenByDefault.id], + (steps) => [ + ...steps, + { + stepId: stepToOpenByDefault.id, + isInRightDrawer: true, + }, + ], ); openWorkflowRunViewStepInCommandMenu({ workflowId: workflowRunRecord.workflowId, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx index 1438d9a3415..e3975b03959 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx @@ -4,7 +4,7 @@ import { WorkflowDiagramBlankEdge } from '@/workflow/workflow-diagram/components import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase'; import { WorkflowDiagramCanvasEditableEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditableEffect'; import { WorkflowDiagramDefaultEdgeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeEditable'; -import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger'; +import { WorkflowDiagramEmptyTriggerEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerEditable'; import { WorkflowDiagramFilterEdgeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeEditable'; import { WorkflowDiagramFilteringDisabledEdgeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilteringDisabledEdgeEditable'; import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable'; @@ -19,7 +19,7 @@ import { useCreateEdge } from '@/workflow/workflow-steps/hooks/useCreateEdge'; import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge'; import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; -import { Connection, ReactFlowProvider, addEdge } from '@xyflow/react'; +import { addEdge, Connection, ReactFlowProvider } from '@xyflow/react'; import React from 'react'; import { isDefined } from 'twenty-shared/utils'; @@ -118,7 +118,7 @@ export const WorkflowDiagramCanvasEditable = ({ { - const { getIcon } = useIcons(); - - const { - openWorkflowTriggerTypeInCommandMenu, - openWorkflowEditStepInCommandMenu, - } = useWorkflowCommandMenu(); - const { setEdgeSelected, clearEdgeSelection } = useEdgeSelected(); const isWorkflowBranchEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED, ); - const setWorkflowSelectedNode = useSetRecoilComponentState( - workflowSelectedNodeComponentState, - ); - - const setCommandMenuNavigationStack = useSetRecoilState( - commandMenuNavigationStackState, - ); - - const { isInRightDrawer } = useContext(ActionMenuContext); - - const workflowVisualizerWorkflowId = useRecoilComponentValue( - workflowVisualizerWorkflowIdComponentState, - ); - - const handleSelectedNodes = useCallback( - ({ nodes }: OnSelectionChangeParams) => { - const selectedNode = nodes[0] as WorkflowDiagramNode | undefined; - - if (!isInRightDrawer) { - setCommandMenuNavigationStack([]); - } - - if (!isDefined(selectedNode)) { - return; - } - - const isEmptyTriggerNode = selectedNode.type === EMPTY_TRIGGER_STEP_ID; - if (isEmptyTriggerNode) { - if (isDefined(workflowVisualizerWorkflowId)) { - openWorkflowTriggerTypeInCommandMenu(workflowVisualizerWorkflowId); - return; - } - - return; - } - - const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData; - - setWorkflowSelectedNode(selectedNode.id); - - if (isDefined(workflowVisualizerWorkflowId)) { - openWorkflowEditStepInCommandMenu( - workflowVisualizerWorkflowId, - selectedNodeData.name, - getIcon(getWorkflowNodeIconKey(selectedNodeData)), - ); - - return; - } - }, - [ - isInRightDrawer, - setCommandMenuNavigationStack, - workflowVisualizerWorkflowId, - openWorkflowTriggerTypeInCommandMenu, - openWorkflowEditStepInCommandMenu, - getIcon, - setWorkflowSelectedNode, - ], - ); - const handleSelectedEdges = useCallback( ({ edges }: OnSelectionChangeParams) => { if (!isWorkflowBranchEnabled) { @@ -119,19 +33,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => { [isWorkflowBranchEnabled, setEdgeSelected, clearEdgeSelection], ); - const handleSelectionChange = useCallback( - (onSelectionChangeParams: OnSelectionChangeParams) => { - handleSelectedNodes(onSelectionChangeParams); - handleSelectedEdges(onSelectionChangeParams); - }, - [handleSelectedNodes, handleSelectedEdges], - ); - useOnSelectionChange({ - onChange: handleSelectionChange, + onChange: handleSelectedEdges, }); - useTriggerNodeSelection(); - return null; }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly.tsx index e103e640b44..1eede1062cb 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly.tsx @@ -1,8 +1,7 @@ import { WorkflowVersionStatus } from '@/workflow/types/Workflow'; import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase'; -import { WorkflowDiagramCanvasReadonlyEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect'; import { WorkflowDiagramDefaultEdgeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeReadonly'; -import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger'; +import { WorkflowDiagramEmptyTriggerReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly'; import { WorkflowDiagramFilterEdgeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeReadonly'; import { WorkflowDiagramFilteringDisabledEdgeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilteringDisabledEdgeReadonly'; import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly'; @@ -23,7 +22,7 @@ export const WorkflowDiagramCanvasReadonly = ({ - - ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx deleted file mode 100644 index 32a3496a5b9..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; -import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; -import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState'; -import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection'; -import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; -import { - WorkflowDiagramNode, - WorkflowDiagramStepNodeData, -} from '@/workflow/workflow-diagram/types/WorkflowDiagram'; -import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; -import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { useIcons } from 'twenty-ui/display'; - -export const WorkflowDiagramCanvasReadonlyEffect = () => { - const { getIcon } = useIcons(); - const setWorkflowSelectedNode = useSetRecoilComponentState( - workflowSelectedNodeComponentState, - ); - const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu(); - - const workflowVisualizerWorkflowId = useRecoilComponentValue( - workflowVisualizerWorkflowIdComponentState, - ); - const workflowVisualizerWorkflowVersionId = useRecoilComponentValue( - workflowVisualizerWorkflowVersionIdComponentState, - ); - - const handleSelectionChange = useCallback( - ({ nodes }: OnSelectionChangeParams) => { - if ( - !( - isDefined(workflowVisualizerWorkflowId) && - isDefined(workflowVisualizerWorkflowVersionId) - ) - ) { - return; - } - - const selectedNode = nodes[0] as WorkflowDiagramNode | undefined; - - if (!isDefined(selectedNode)) { - return; - } - - setWorkflowSelectedNode(selectedNode.id); - - const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData; - - openWorkflowViewStepInCommandMenu({ - workflowId: workflowVisualizerWorkflowId, - workflowVersionId: workflowVisualizerWorkflowVersionId, - title: selectedNodeData.name, - icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)), - }); - }, - [ - setWorkflowSelectedNode, - openWorkflowViewStepInCommandMenu, - workflowVisualizerWorkflowId, - workflowVisualizerWorkflowVersionId, - getIcon, - ], - ); - - useOnSelectionChange({ - onChange: handleSelectionChange, - }); - - useTriggerNodeSelection(); - - return null; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeEditable.tsx index 1a51318de09..6e49213c09f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeEditable.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeEditable.tsx @@ -1,19 +1,23 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseEdge'; +import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeButtonGroup'; import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container'; import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer'; import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId'; +import { useEdgeHovered } from '@/workflow/workflow-diagram/hooks/useEdgeHovered'; import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu'; import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation'; import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep'; -import { useEdgeHovered } from '@/workflow/workflow-diagram/hooks/useEdgeHovered'; import { EdgeLabelRenderer, EdgeProps, getBezierPath } from '@xyflow/react'; +import { useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconFilter, IconPlus } from 'twenty-ui/display'; -import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseEdge'; -import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeButtonGroup'; type WorkflowDiagramDefaultEdgeEditableProps = EdgeProps; @@ -28,6 +32,8 @@ export const WorkflowDiagramDefaultEdgeEditable = ({ markerStart, markerEnd, }: WorkflowDiagramDefaultEdgeEditableProps) => { + const { isInRightDrawer } = useContext(ActionMenuContext); + const { isEdgeHovered } = useEdgeHovered(); const [edgePath, labelX, labelY] = getBezierPath({ @@ -51,6 +57,10 @@ export const WorkflowDiagramDefaultEdgeEditable = ({ nextStepId: target, }); + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + const { openWorkflowEditFilterInCommandMenu } = useOpenWorkflowEditFilterInCommandMenu(); @@ -65,6 +75,10 @@ export const WorkflowDiagramDefaultEdgeEditable = ({ return; } + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + openWorkflowEditFilterInCommandMenu({ stepId: createdStep.id, stepName: createdStep.name, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx deleted file mode 100644 index 1dd4710f498..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; -import styled from '@emotion/styled'; - -const StyledStepNodeLabelIconContainer = styled.div` - align-items: center; - background: ${({ theme }) => theme.background.transparent.light}; - border-radius: ${({ theme }) => theme.spacing(1)}; - display: flex; - justify-content: center; - padding: ${({ theme }) => theme.spacing(3)}; -`; - -export const WorkflowDiagramEmptyTrigger = () => { - return ( - } - /> - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerEditable.tsx new file mode 100644 index 00000000000..b3f2fd5da7a --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerEditable.tsx @@ -0,0 +1,57 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; +import styled from '@emotion/styled'; +import { useLingui } from '@lingui/react/macro'; +import { useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; + +const StyledStepNodeLabelIconContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.transparent.light}; + border-radius: ${({ theme }) => theme.spacing(1)}; + display: flex; + justify-content: center; + padding: ${({ theme }) => theme.spacing(3)}; +`; + +export const WorkflowDiagramEmptyTriggerEditable = () => { + const { t } = useLingui(); + + const { openWorkflowTriggerTypeInCommandMenu } = useWorkflowCommandMenu(); + + const workflowVisualizerWorkflowId = useRecoilComponentValue( + workflowVisualizerWorkflowIdComponentState, + ); + + const { isInRightDrawer } = useContext(ActionMenuContext); + + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + + return ( + } + onClick={() => { + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + + if (!isDefined(workflowVisualizerWorkflowId)) { + return; + } + + openWorkflowTriggerTypeInCommandMenu(workflowVisualizerWorkflowId); + }} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx new file mode 100644 index 00000000000..1a26f065ccf --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx @@ -0,0 +1,82 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState'; +import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; +import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; +import styled from '@emotion/styled'; +import { useLingui } from '@lingui/react/macro'; +import { useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { useIcons } from 'twenty-ui/display'; + +const StyledStepNodeLabelIconContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.transparent.light}; + border-radius: ${({ theme }) => theme.spacing(1)}; + display: flex; + justify-content: center; + padding: ${({ theme }) => theme.spacing(3)}; +`; + +export const WorkflowDiagramEmptyTriggerReadonly = () => { + const { getIcon } = useIcons(); + const { t } = useLingui(); + + const workflowVisualizerWorkflowId = useRecoilComponentValue( + workflowVisualizerWorkflowIdComponentState, + ); + const workflowVisualizerWorkflowVersionId = useRecoilComponentValue( + workflowVisualizerWorkflowVersionIdComponentState, + ); + + const { isInRightDrawer } = useContext(ActionMenuContext); + + const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu(); + + const setWorkflowSelectedNode = useSetRecoilComponentState( + workflowSelectedNodeComponentState, + ); + + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + + return ( + } + onClick={() => { + if ( + !isDefined(workflowVisualizerWorkflowId) || + !isDefined(workflowVisualizerWorkflowVersionId) + ) { + throw new Error( + 'Workflow ID and Version ID must be defined to open the command menu.', + ); + } + + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + + setWorkflowSelectedNode(TRIGGER_STEP_ID); + + openWorkflowViewStepInCommandMenu({ + workflowId: workflowVisualizerWorkflowId, + workflowVersionId: workflowVisualizerWorkflowVersionId, + title: t`Add a Trigger`, + icon: getIcon(null), + }); + }} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeEditable.tsx index 42ded5cd9b8..7f17362082c 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeEditable.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeEditable.tsx @@ -1,3 +1,5 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -9,9 +11,12 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseEdge'; +import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeButtonGroup'; import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container'; import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer'; import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId'; +import { useEdgeHovered } from '@/workflow/workflow-diagram/hooks/useEdgeHovered'; import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu'; import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation'; import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState'; @@ -26,6 +31,8 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { isNonEmptyString } from '@sniptt/guards'; import { EdgeLabelRenderer, EdgeProps, getBezierPath } from '@xyflow/react'; +import { useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconDotsVertical, @@ -35,9 +42,6 @@ import { } from 'twenty-ui/display'; import { IconButtonGroup } from 'twenty-ui/input'; import { MenuItem } from 'twenty-ui/navigation'; -import { useEdgeHovered } from '@/workflow/workflow-diagram/hooks/useEdgeHovered'; -import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseEdge'; -import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeButtonGroup'; type WorkflowDiagramFilterEdgeEditableProps = EdgeProps; @@ -94,10 +98,15 @@ export const WorkflowDiagramFilterEdgeEditable = ({ workflowVisualizerWorkflowIdComponentState, ); const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId); + const { isInRightDrawer } = useContext(ActionMenuContext); const { deleteStep } = useDeleteStep({ workflow }); const { startNodeCreation, isNodeCreationStarted } = useStartNodeCreation(); + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + const { openDropdown } = useOpenDropdown(); const { closeDropdown } = useCloseDropdown(); @@ -130,6 +139,10 @@ export const WorkflowDiagramFilterEdgeEditable = ({ useOpenWorkflowEditFilterInCommandMenu(); const handleFilterButtonClick = () => { + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + openWorkflowEditFilterInCommandMenu({ stepId: data.stepId, stepName: data.name, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeReadonly.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeReadonly.tsx index e98a34b9d35..d2d44a1ae53 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeReadonly.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeReadonly.tsx @@ -1,3 +1,5 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container'; import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer'; @@ -18,6 +20,8 @@ import { EdgeProps, getBezierPath, } from '@xyflow/react'; +import { useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; import { IconFilter } from 'twenty-ui/display'; import { IconButtonGroup } from 'twenty-ui/input'; @@ -62,6 +66,8 @@ export const WorkflowDiagramFilterEdgeReadonly = ({ }: WorkflowDiagramFilterEdgeReadonlyProps) => { assertFilterEdgeDataOrThrow(data); + const { isInRightDrawer } = useContext(ActionMenuContext); + const theme = useTheme(); const [edgePath, labelX, labelY] = getBezierPath({ @@ -81,7 +87,15 @@ export const WorkflowDiagramFilterEdgeReadonly = ({ const { openWorkflowViewFilterInCommandMenu } = useOpenWorkflowViewFilterInCommandMenu(); + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + const handleFilterButtonClick = () => { + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + openWorkflowViewFilterInCommandMenu({ stepId: data.stepId, stepName: data.name, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx index 24878eeb320..2d71aa06e12 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx @@ -150,6 +150,7 @@ export const WorkflowDiagramStepNodeBase = ({ RightFloatingElement, BottomHoverFloatingElement, displayHandle = true, + onClick, onMouseEnter, onMouseLeave, }: { @@ -161,6 +162,7 @@ export const WorkflowDiagramStepNodeBase = ({ RightFloatingElement?: React.ReactNode; BottomHoverFloatingElement?: React.ReactNode; displayHandle?: boolean; + onClick?: () => void; onMouseEnter?: (event: MouseEvent) => void; onMouseLeave?: (event: MouseEvent) => void; }) => { @@ -172,6 +174,7 @@ export const WorkflowDiagramStepNodeBase = ({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx index 323f40e9d01..c8b0a06bc11 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx @@ -1,10 +1,20 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined'; import { WorkflowDiagramStepNodeEditableContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; +import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep'; +import { useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { useIcons } from 'twenty-ui/display'; export const WorkflowDiagramStepNodeEditable = ({ id, @@ -15,6 +25,8 @@ export const WorkflowDiagramStepNodeEditable = ({ data: WorkflowDiagramStepNodeData; selected?: boolean; }) => { + const { getIcon } = useIcons(); + const workflowVisualizerWorkflowId = useRecoilComponentValue( workflowVisualizerWorkflowIdComponentState, ); @@ -28,12 +40,41 @@ export const WorkflowDiagramStepNodeEditable = ({ workflow: workflowWithCurrentVersion, }); + const setWorkflowSelectedNode = useSetRecoilComponentState( + workflowSelectedNodeComponentState, + ); + + const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu(); + + const { isInRightDrawer } = useContext(ActionMenuContext); + + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + return ( { + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + + setWorkflowSelectedNode(id); + + if (isDefined(workflowVisualizerWorkflowId)) { + openWorkflowEditStepInCommandMenu( + workflowVisualizerWorkflowId, + data.name, + getIcon(getWorkflowNodeIconKey(data)), + ); + + return; + } + }} onDelete={() => { deleteStep(id); }} diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx index 862ca7c126b..2c46b78cc98 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx @@ -1,13 +1,13 @@ +import { WorkflowDiagramCreateStepElement } from '@/workflow/workflow-diagram/components/WorkflowDiagramCreateStepElement'; import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; import { WorkflowDiagramStepNodeIcon } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeIcon'; +import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation'; import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant'; -import { FloatingIconButton } from 'twenty-ui/input'; -import { IconTrash } from 'twenty-ui/display'; -import { WorkflowDiagramCreateStepElement } from '@/workflow/workflow-diagram/components/WorkflowDiagramCreateStepElement'; -import React, { useState } from 'react'; import styled from '@emotion/styled'; -import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation'; +import { useState } from 'react'; +import { IconTrash } from 'twenty-ui/display'; +import { FloatingIconButton } from 'twenty-ui/input'; const StyledDeleteButtonContainer = styled.div` display: flex; @@ -39,12 +39,14 @@ export const WorkflowDiagramStepNodeEditableContent = ({ selected, variant, onDelete, + onClick, }: { id: string; data: WorkflowDiagramStepNodeData; variant: WorkflowDiagramNodeVariant; selected: boolean; onDelete: () => void; + onClick?: () => void; }) => { const [isHovered, setIsHovered] = useState(false); @@ -60,6 +62,7 @@ export const WorkflowDiagramStepNodeEditableContent = ({ { + const { getIcon } = useIcons(); + + const workflowVisualizerWorkflowId = useRecoilComponentValue( + workflowVisualizerWorkflowIdComponentState, + ); + const workflowVisualizerWorkflowVersionId = useRecoilComponentValue( + workflowVisualizerWorkflowVersionIdComponentState, + ); + + const setWorkflowSelectedNode = useSetRecoilComponentState( + workflowSelectedNodeComponentState, + ); + + const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu(); + + const { isInRightDrawer } = useContext(ActionMenuContext); + + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + return ( } displayHandle={false} + onClick={() => { + if ( + !isDefined(workflowVisualizerWorkflowId) || + !isDefined(workflowVisualizerWorkflowVersionId) + ) { + throw new Error('Workflow ID and Version ID must be defined'); + } + + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + + setWorkflowSelectedNode(id); + + openWorkflowViewStepInCommandMenu({ + workflowId: workflowVisualizerWorkflowId, + workflowVersionId: workflowVisualizerWorkflowVersionId, + title: data.name, + icon: getIcon(getWorkflowNodeIconKey(data)), + }); + }} /> ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas.tsx index ecb37618ba0..dc6a679082a 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas.tsx @@ -3,9 +3,7 @@ import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/component import { WorkflowDiagramDefaultEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeRun'; import { WorkflowDiagramFilterEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeRun'; import { WorkflowDiagramFilteringDisabledEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilteringDisabledEdgeRun'; -import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly'; -import { WorkflowRunDiagramCanvasEffect } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect'; -import { useHandleWorkflowRunDiagramCanvasInit } from '@/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit'; +import { WorkflowRunDiagramStepNode } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramStepNode'; import { getWorkflowRunStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowRunStatusTagProps'; import { ReactFlowProvider } from '@xyflow/react'; @@ -18,14 +16,11 @@ export const WorkflowRunDiagramCanvas = ({ workflowRunStatus, }); - const { handleWorkflowRunDiagramCanvasInit } = - useHandleWorkflowRunDiagramCanvasInit(); - return ( - - ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx deleted file mode 100644 index e2f20479510..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; -import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; -import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState'; -import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; -import { WorkflowRunDiagramNode } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; -import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; -import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; -import { useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; -import { useIcons } from 'twenty-ui/display'; - -export const WorkflowRunDiagramCanvasEffect = () => { - const { getIcon } = useIcons(); - - const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu(); - - const workflowRunId = useWorkflowRunIdOrThrow(); - - const workflowVisualizerWorkflowIdState = useRecoilComponentCallbackState( - workflowVisualizerWorkflowIdComponentState, - ); - const workflowDiagramStatusState = useRecoilComponentCallbackState( - workflowDiagramStatusComponentState, - ); - const workflowSelectedNodeState = useRecoilComponentCallbackState( - workflowSelectedNodeComponentState, - ); - - const handleSelectionChange = useRecoilCallback( - ({ snapshot, set }) => - ({ nodes }: OnSelectionChangeParams) => { - const workflowId = getSnapshotValue( - snapshot, - workflowVisualizerWorkflowIdState, - ); - - if (!isDefined(workflowId)) { - throw new Error('Expected the workflowId to be defined.'); - } - - const workflowDiagramStatus = getSnapshotValue( - snapshot, - workflowDiagramStatusState, - ); - - // The `handleSelectionChange` function is called when the diagram initializes and - // a node is selected. In this case, we don't want to execute the rest of this function. - // We open the Side PanelĀ® synchronously after ReactFlow is initialized and a node is selected, - // animations perform better that way. - if (workflowDiagramStatus !== 'done') { - return; - } - - const selectedNode = nodes[0] as WorkflowRunDiagramNode | undefined; - - if (!isDefined(selectedNode)) { - return; - } - - set(workflowSelectedNodeState, selectedNode.id); - - const selectedNodeData = selectedNode.data; - - openWorkflowRunViewStepInCommandMenu({ - workflowId, - workflowRunId, - title: selectedNodeData.name, - icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)), - workflowSelectedNode: selectedNode.id, - stepExecutionStatus: selectedNodeData.runStatus, - }); - }, - [ - workflowVisualizerWorkflowIdState, - workflowDiagramStatusState, - workflowSelectedNodeState, - openWorkflowRunViewStepInCommandMenu, - workflowRunId, - getIcon, - ], - ); - - useOnSelectionChange({ - onChange: handleSelectionChange, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramStepNode.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramStepNode.tsx new file mode 100644 index 00000000000..791bba9fefa --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramStepNode.tsx @@ -0,0 +1,75 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; +import { WorkflowDiagramStepNodeIcon } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeIcon'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; +import { WorkflowRunDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; +import { getNodeVariantFromStepRunStatus } from '@/workflow/workflow-diagram/utils/getNodeVariantFromStepRunStatus'; +import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; +import { useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { useIcons } from 'twenty-ui/display'; + +export const WorkflowRunDiagramStepNode = ({ + id, + data, +}: { + id: string; + data: WorkflowRunDiagramStepNodeData; +}) => { + const { getIcon } = useIcons(); + + const workflowId = useRecoilComponentValue( + workflowVisualizerWorkflowIdComponentState, + ); + const workflowRunId = useWorkflowRunIdOrThrow(); + + const setWorkflowSelectedNode = useSetRecoilComponentState( + workflowSelectedNodeComponentState, + ); + + const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu(); + + const { isInRightDrawer } = useContext(ActionMenuContext); + + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + + return ( + } + displayHandle={false} + onClick={() => { + if (!isDefined(workflowId)) { + throw new Error('Workflow ID must be defined'); + } + + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + + setWorkflowSelectedNode(id); + + openWorkflowRunViewStepInCommandMenu({ + workflowId, + workflowRunId, + title: data.name, + icon: getIcon(getWorkflowNodeIconKey(data)), + workflowSelectedNode: id, + stepExecutionStatus: data.runStatus, + }); + }} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx index 446706d8c84..47320367a12 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx @@ -13,7 +13,6 @@ import { WorkflowRunState } from '@/workflow/types/Workflow'; import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState'; import { workflowRunDiagramAutomaticallyOpenedStepsComponentState } from '@/workflow/workflow-diagram/states/workflowRunDiagramAutomaticallyOpenedStepsComponentState'; -import { workflowRunStepToOpenByDefaultComponentState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram'; import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; @@ -52,9 +51,6 @@ export const WorkflowRunVisualizerEffect = ({ const workflowDiagramStatusState = useRecoilComponentCallbackState( workflowDiagramStatusComponentState, ); - const workflowRunStepToOpenByDefaultState = useRecoilComponentCallbackState( - workflowRunStepToOpenByDefaultComponentState, - ); const workflowSelectedNodeState = useRecoilComponentCallbackState( workflowSelectedNodeComponentState, ); @@ -126,67 +122,70 @@ export const WorkflowRunVisualizerEffect = ({ isWorkflowFilteringEnabled, }); - if (isDefined(stepToOpenByDefault)) { - if (isInRightDrawer) { - set(workflowDiagramState, baseWorkflowRunDiagram); - - const workflowRunDiagramAutomaticallyOpenedSteps = getSnapshotValue( - snapshot, - workflowRunDiagramAutomaticallyOpenedStepsState, - ); - const hasStepAlreadyBeenOpenedAutomatically = - workflowRunDiagramAutomaticallyOpenedSteps.includes( - stepToOpenByDefault.id, - ); - - if ( - workflowDiagramStatus === 'done' && - !hasStepAlreadyBeenOpenedAutomatically - ) { - set(workflowSelectedNodeState, stepToOpenByDefault.id); - - const workflowVisualizerWorkflowId = getSnapshotValue( - snapshot, - workflowVisualizerWorkflowIdState, - ); - if (!isDefined(workflowVisualizerWorkflowId)) { - throw new Error( - 'The workflow id must be set; ensure the workflow id is always set before rendering the workflow diagram.', - ); - } - - set(workflowRunDiagramAutomaticallyOpenedStepsState, [ - ...workflowRunDiagramAutomaticallyOpenedSteps, - stepToOpenByDefault.id, - ]); - openWorkflowRunViewStepInCommandMenu({ - workflowId: workflowVisualizerWorkflowId, - workflowRunId, - title: stepToOpenByDefault.data.name, - icon: getIcon(getWorkflowNodeIconKey(stepToOpenByDefault.data)), - workflowSelectedNode: stepToOpenByDefault.id, - stepExecutionStatus: stepToOpenByDefault.data.runStatus, - }); - } - } else { - const workflowRunDiagram = selectWorkflowDiagramNode({ - diagram: baseWorkflowRunDiagram, - nodeIdToSelect: stepToOpenByDefault.id, - }); - - set(workflowDiagramState, workflowRunDiagram); - set(workflowRunStepToOpenByDefaultState, { - id: stepToOpenByDefault.id, - data: stepToOpenByDefault.data, - }); - } - } else { - set(workflowDiagramState, baseWorkflowRunDiagram); - } - if (workflowDiagramStatus !== 'done') { set(workflowDiagramStatusState, 'computing-dimensions'); } + + if (!isDefined(stepToOpenByDefault)) { + set(workflowDiagramState, baseWorkflowRunDiagram); + + return; + } + + const workflowRunDiagramAutomaticallyOpenedSteps = getSnapshotValue( + snapshot, + workflowRunDiagramAutomaticallyOpenedStepsState, + ); + const hasStepAlreadyBeenOpenedAutomatically = + workflowRunDiagramAutomaticallyOpenedSteps.some( + (step) => + step.stepId === stepToOpenByDefault.id && + step.isInRightDrawer === isInRightDrawer, + ); + + const workflowVisualizerWorkflowId = getSnapshotValue( + snapshot, + workflowVisualizerWorkflowIdState, + ); + if (!isDefined(workflowVisualizerWorkflowId)) { + throw new Error( + 'The workflow id must be set; ensure the workflow id is always set before rendering the workflow diagram.', + ); + } + + if (isInRightDrawer) { + set(workflowDiagramState, baseWorkflowRunDiagram); + } else { + const workflowRunDiagram = selectWorkflowDiagramNode({ + diagram: baseWorkflowRunDiagram, + nodeIdToSelect: stepToOpenByDefault.id, + }); + + set(workflowDiagramState, workflowRunDiagram); + } + + if (hasStepAlreadyBeenOpenedAutomatically) { + return; + } + + set(workflowSelectedNodeState, stepToOpenByDefault.id); + + set(workflowRunDiagramAutomaticallyOpenedStepsState, [ + ...workflowRunDiagramAutomaticallyOpenedSteps, + { + stepId: stepToOpenByDefault.id, + isInRightDrawer, + }, + ]); + + openWorkflowRunViewStepInCommandMenu({ + workflowId: workflowVisualizerWorkflowId, + workflowRunId, + title: stepToOpenByDefault.data.name, + icon: getIcon(getWorkflowNodeIconKey(stepToOpenByDefault.data)), + workflowSelectedNode: stepToOpenByDefault.id, + stepExecutionStatus: stepToOpenByDefault.data.runStatus, + }); }, [ flowState, @@ -197,7 +196,6 @@ export const WorkflowRunVisualizerEffect = ({ workflowDiagramStatusState, workflowRunDiagramAutomaticallyOpenedStepsState, workflowRunId, - workflowRunStepToOpenByDefaultState, workflowSelectedNodeState, workflowVisualizerWorkflowIdState, ], diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTrigger.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTriggerEditable.stories.tsx similarity index 76% rename from packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTrigger.stories.tsx rename to packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTriggerEditable.stories.tsx index 0960cf80542..9b2ada63777 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTrigger.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTriggerEditable.stories.tsx @@ -1,24 +1,26 @@ import { Meta, StoryObj } from '@storybook/react'; -import '@xyflow/react/dist/style.css'; -import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator'; -import { WorkflowDiagramEmptyTrigger } from '../WorkflowDiagramEmptyTrigger'; -import { ComponentDecorator } from 'twenty-ui/testing'; -import { RecoilRoot } from 'recoil'; import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; +import '@xyflow/react/dist/style.css'; +import { RecoilRoot } from 'recoil'; +import { ComponentDecorator } from 'twenty-ui/testing'; +import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; +import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator'; +import { WorkflowDiagramEmptyTriggerEditable } from '../WorkflowDiagramEmptyTriggerEditable'; -const meta: Meta = { - title: 'Modules/Workflow/WorkflowDiagramEmptyTrigger', - component: WorkflowDiagramEmptyTrigger, +const meta: Meta = { + title: 'Modules/Workflow/WorkflowDiagramEmptyTriggerEditable', + component: WorkflowDiagramEmptyTriggerEditable, args: { data: { nodeType: 'empty-trigger', }, }, + decorators: [I18nFrontDecorator], }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { decorators: [ diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/CreateStepNodeWidth.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/CreateStepNodeWidth.ts deleted file mode 100644 index c03e587e727..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/CreateStepNodeWidth.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NODE_BORDER_WIDTH } from '@/workflow/workflow-diagram/constants/NodeBorderWidth'; - -export const STEP_ICON_WIDTH = 24; - -const STEP_PADDING = 8; - -export const CREATE_STEP_NODE_WIDTH = - STEP_ICON_WIDTH + STEP_PADDING + NODE_BORDER_WIDTH; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/__tests__/useTriggerNodeSelection.test.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/__tests__/useTriggerNodeSelection.test.tsx deleted file mode 100644 index 40fb6ca1a35..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/__tests__/useTriggerNodeSelection.test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; -import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection'; -import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; -import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState'; -import { act, renderHook } from '@testing-library/react'; -import { useReactFlow } from '@xyflow/react'; -import { RecoilRoot } from 'recoil'; - -jest.mock('@xyflow/react', () => ({ - useReactFlow: jest.fn(), -})); - -const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - {children} - - -); - -describe('useTriggerNodeSelection', () => { - const mockSetNodes = jest.fn(); - - beforeEach(() => { - (useReactFlow as jest.Mock).mockReturnValue({ - setNodes: mockSetNodes, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should trigger node selection', () => { - const { result } = renderHook( - () => { - const [ - workflowDiagramTriggerNodeSelection, - setWorkflowDiagramTriggerNodeSelection, - ] = useRecoilComponentState( - workflowDiagramTriggerNodeSelectionComponentState, - ); - - useTriggerNodeSelection(); - - return { - workflowDiagramTriggerNodeSelection, - setWorkflowDiagramTriggerNodeSelection, - }; - }, - { - wrapper, - }, - ); - - const mockNodeId = 'test-node-id'; - - act(() => { - result.current.setWorkflowDiagramTriggerNodeSelection(mockNodeId); - }); - - expect(result.current.workflowDiagramTriggerNodeSelection).toBeUndefined(); - - expect(mockSetNodes).toHaveBeenCalled(); - }); - - it('should not trigger update if state is not defined', () => { - renderHook(() => useTriggerNodeSelection(), { - wrapper, - }); - - // Ensure updateNode is not called when state is undefined - expect(mockSetNodes).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit.ts deleted file mode 100644 index 5167931319a..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; -import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; -import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState'; -import { workflowRunDiagramAutomaticallyOpenedStepsComponentState } from '@/workflow/workflow-diagram/states/workflowRunDiagramAutomaticallyOpenedStepsComponentState'; -import { workflowRunStepToOpenByDefaultComponentState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState'; -import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; -import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; -import { useContext } from 'react'; -import { useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; -import { useIcons } from 'twenty-ui/display'; - -export const useHandleWorkflowRunDiagramCanvasInit = () => { - const { getIcon } = useIcons(); - - const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu(); - const { isInRightDrawer } = useContext(ActionMenuContext); - - const workflowRunId = useWorkflowRunIdOrThrow(); - - const workflowVisualizerWorkflowIdState = useRecoilComponentCallbackState( - workflowVisualizerWorkflowIdComponentState, - ); - const workflowDiagramStatusState = useRecoilComponentCallbackState( - workflowDiagramStatusComponentState, - ); - const workflowRunStepToOpenByDefaultState = useRecoilComponentCallbackState( - workflowRunStepToOpenByDefaultComponentState, - ); - const workflowSelectedNodeState = useRecoilComponentCallbackState( - workflowSelectedNodeComponentState, - ); - const workflowRunDiagramAutomaticallyOpenedStepsState = - useRecoilComponentCallbackState( - workflowRunDiagramAutomaticallyOpenedStepsComponentState, - ); - - const handleWorkflowRunDiagramCanvasInit = useRecoilCallback( - ({ snapshot, set }) => - () => { - const workflowDiagramStatus = getSnapshotValue( - snapshot, - workflowDiagramStatusState, - ); - - if (workflowDiagramStatus !== 'computing-dimensions') { - throw new Error( - 'Sequence error: reactflow should be considered initialized only when the workflow diagram status is computing-dimensions.', - ); - } - - set(workflowDiagramStatusState, 'done'); - - if (isInRightDrawer) { - return; - } - - const workflowStepToOpenByDefault = getSnapshotValue( - snapshot, - workflowRunStepToOpenByDefaultState, - ); - - if (isDefined(workflowStepToOpenByDefault)) { - const workflowVisualizerWorkflowId = getSnapshotValue( - snapshot, - workflowVisualizerWorkflowIdState, - ); - if (!isDefined(workflowVisualizerWorkflowId)) { - throw new Error( - 'The workflow id must be set; ensure the workflow id is always set before rendering the workflow diagram.', - ); - } - - set(workflowSelectedNodeState, workflowStepToOpenByDefault.id); - - const workflowRunDiagramAutomaticallyOpenedSteps = getSnapshotValue( - snapshot, - workflowRunDiagramAutomaticallyOpenedStepsState, - ); - const hasStepAlreadyBeenOpenedAutomatically = - workflowRunDiagramAutomaticallyOpenedSteps.includes( - workflowStepToOpenByDefault.id, - ); - - // FIXME: This is a workaround to avoid opening a workflow run step twice when going from the side panel to the fullscreen show page. - // The step is opened in the `handleSelectionChange` function of `WorkflowRunDiagramCanvasEffect`. I think it shouldn't be opened there but - // we should keep opening the step here, in `handleWorkflowRunDiagramCanvasInit`. - if (!hasStepAlreadyBeenOpenedAutomatically) { - openWorkflowRunViewStepInCommandMenu({ - workflowId: workflowVisualizerWorkflowId, - workflowRunId, - title: workflowStepToOpenByDefault.data.name, - icon: getIcon( - getWorkflowNodeIconKey(workflowStepToOpenByDefault.data), - ), - workflowSelectedNode: workflowStepToOpenByDefault.id, - stepExecutionStatus: workflowStepToOpenByDefault.data.runStatus, - }); - } - - set(workflowRunStepToOpenByDefaultState, undefined); - } - }, - [ - workflowDiagramStatusState, - isInRightDrawer, - workflowRunStepToOpenByDefaultState, - workflowVisualizerWorkflowIdState, - workflowSelectedNodeState, - workflowRunDiagramAutomaticallyOpenedStepsState, - openWorkflowRunViewStepInCommandMenu, - workflowRunId, - getIcon, - ], - ); - - return { - handleWorkflowRunDiagramCanvasInit, - }; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts index ea9c17811f0..9c7bfe443d6 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useStartNodeCreation.ts @@ -1,13 +1,17 @@ -import { useCallback } from 'react'; - +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +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'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState'; +import { useCallback, useContext } from 'react'; +import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const useStartNodeCreation = () => { + const { isInRightDrawer } = useContext(ActionMenuContext); + const [workflowInsertStepIds, setWorkflowInsertStepIds] = useRecoilComponentState(workflowInsertStepIdsComponentState); @@ -17,6 +21,10 @@ export const useStartNodeCreation = () => { workflowVisualizerWorkflowIdComponentState, ); + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, + ); + /** * This function is used in a context where dependencies shouldn't change much. * That's why its wrapped in a `useCallback` hook. Removing memoization might break the app unexpectedly. @@ -33,15 +41,22 @@ export const useStartNodeCreation = () => { }) => { setWorkflowInsertStepIds({ parentStepId, nextStepId, position }); - if (isDefined(workflowVisualizerWorkflowId)) { - openStepSelectInCommandMenu(workflowVisualizerWorkflowId); + if (!isDefined(workflowVisualizerWorkflowId)) { return; } + + if (!isInRightDrawer) { + setCommandMenuNavigationStack([]); + } + + openStepSelectInCommandMenu(workflowVisualizerWorkflowId); }, [ setWorkflowInsertStepIds, workflowVisualizerWorkflowId, + isInRightDrawer, openStepSelectInCommandMenu, + setCommandMenuNavigationStack, ], ); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useTriggerNodeSelection.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useTriggerNodeSelection.ts deleted file mode 100644 index 2574829d295..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useTriggerNodeSelection.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; -import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState'; -import { - WorkflowDiagramEdge, - WorkflowDiagramNode, -} from '@/workflow/workflow-diagram/types/WorkflowDiagram'; -import { useReactFlow } from '@xyflow/react'; -import { useEffect } from 'react'; -import { isDefined } from 'twenty-shared/utils'; - -export const useTriggerNodeSelection = () => { - const reactflow = useReactFlow(); - - const [ - workflowDiagramTriggerNodeSelection, - setWorkflowDiagramTriggerNodeSelection, - ] = useRecoilComponentState( - workflowDiagramTriggerNodeSelectionComponentState, - ); - - useEffect(() => { - if (!isDefined(workflowDiagramTriggerNodeSelection)) { - return; - } - - reactflow.setNodes((nodes) => - nodes.map((node) => ({ - ...node, - selected: workflowDiagramTriggerNodeSelection === node.id, - })), - ); - - setWorkflowDiagramTriggerNodeSelection(undefined); - }, [ - reactflow, - setWorkflowDiagramTriggerNodeSelection, - workflowDiagramTriggerNodeSelection, - ]); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState.ts deleted file mode 100644 index d99710a29a1..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; -import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; - -export const workflowDiagramTriggerNodeSelectionComponentState = - createComponentState({ - key: 'workflowDiagramTriggerNodeSelectionComponentState', - defaultValue: undefined, - componentInstanceContext: WorkflowVisualizerComponentInstanceContext, - }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunDiagramAutomaticallyOpenedStepsComponentState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunDiagramAutomaticallyOpenedStepsComponentState.ts index 1851b1f0295..9cf49e83bfa 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunDiagramAutomaticallyOpenedStepsComponentState.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunDiagramAutomaticallyOpenedStepsComponentState.ts @@ -2,7 +2,7 @@ import { createComponentState } from '@/ui/utilities/state/component-state/utils import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; export const workflowRunDiagramAutomaticallyOpenedStepsComponentState = - createComponentState({ + createComponentState<{ stepId: string; isInRightDrawer: boolean }[]>({ key: 'workflowRunDiagramAutomaticallyOpenedStepsComponentState', defaultValue: [], componentInstanceContext: WorkflowVisualizerComponentInstanceContext, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState.ts deleted file mode 100644 index 60e560f9094..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; -import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; -import { WorkflowRunDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; - -export const workflowRunStepToOpenByDefaultComponentState = - createComponentState< - | { - id: string; - data: WorkflowRunDiagramStepNodeData; - } - | undefined - >({ - key: 'workflowRunStepToOpenByDefaultComponentState', - defaultValue: undefined, - componentInstanceContext: WorkflowVisualizerComponentInstanceContext, - }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useDeleteStep.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useDeleteStep.ts index 6c70c319f17..c1e7e856832 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useDeleteStep.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useDeleteStep.ts @@ -30,8 +30,6 @@ export const useDeleteStep = ({ const deleteStep = async (stepId: string) => { assertWorkflowWithCurrentVersionIsDefined(workflow); - closeCommandMenu(); - const workflowVersionId = await getUpdatableWorkflowVersion(workflow); if (!isDefined(workflowVersionId)) { @@ -51,6 +49,9 @@ export const useDeleteStep = ({ stepId, }); } + + closeCommandMenu(); + deleteStepOutputSchema({ stepId, workflowVersionId, diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts index 57ea4ab78a6..2c8eed8874d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useVariableDropdown.ts @@ -1,10 +1,16 @@ +import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; -import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { getVariableTemplateFromPath } from '@/workflow/workflow-variables/utils/getVariableTemplateFromPath'; import { useState } from 'react'; +import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; +import { useIcons } from 'twenty-ui/display'; import { BaseOutputSchema, LinkOutputSchema, @@ -35,9 +41,17 @@ export const useVariableDropdown = ({ onSelect, onBack, }: UseVariableDropdownProps): UseVariableDropdownReturn => { + const { getIcon } = useIcons(); + const [currentPath, setCurrentPath] = useState([]); const [searchInputValue, setSearchInputValue] = useState(''); + const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu(); + + const workflowVisualizerWorkflowId = useRecoilComponentValue( + workflowVisualizerWorkflowIdComponentState, + ); + const setWorkflowSelectedNode = useSetRecoilComponentState( workflowSelectedNodeComponentState, ); @@ -45,8 +59,11 @@ export const useVariableDropdown = ({ activeTabIdComponentState, 'workflow-serverless-function-tab-list-component-id', ); - const setWorkflowDiagramTriggerNodeSelection = useSetRecoilComponentState( - workflowDiagramTriggerNodeSelectionComponentState, + const setWorkflowDiagram = useSetRecoilComponentState( + workflowDiagramComponentState, + ); + const setCommandMenuNavigationStack = useSetRecoilState( + commandMenuNavigationStackState, ); const getDisplayedSubStepFields = () => { @@ -83,8 +100,34 @@ export const useVariableDropdown = ({ const handleSelectLinkOutputSchema = ( linkOutputSchema: LinkOutputSchema, ) => { + if (!isDefined(workflowVisualizerWorkflowId)) { + throw new Error('Workflow ID must be configured'); + } + setWorkflowSelectedNode(step.id); - setWorkflowDiagramTriggerNodeSelection(step.id); + + setWorkflowDiagram((diagram) => { + if (!isDefined(diagram)) { + throw new Error('Workflow diagram must be defined'); + } + + return { + ...diagram, + nodes: diagram.nodes.map((node) => ({ + ...node, + selected: node.id === step.id, + })), + }; + }); + + setCommandMenuNavigationStack([]); + + openWorkflowEditStepInCommandMenu( + workflowVisualizerWorkflowId, + step.name, + getIcon(step.icon), + ); + if (isDefined(linkOutputSchema.link.tab)) { setActiveTabId(linkOutputSchema.link.tab); }