From 1543c900aeec72e854d88a6282becb88dbf18ad4 Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Mon, 5 May 2025 10:58:11 +0200 Subject: [PATCH] Migrate workflow states to component states (#11773) - Migrated all workflow Recoil states to component states to isolate each workflow visualizer instance. The use case of having two workflow visualizers displayed at the same time appeared recently and will grow in the near future. - We chose to use the `recordId` as the value for the `instanceId` of the component states. Currently, there are a few cases where two workflows or two workflow runs are rendered at the same time. As a consequence, relying on the `recordId` is enough for the moment. - However, there is one case where it's necessary to have a component state scoped to a workflow visualizer instance: the `workflowVisualizerStatusState`. This component is tightly coupled to the `` component instance rendered in the workflow visualizer, and it must be set to its default value when the component first renders. I achieved that by using another component instance context whose instanceId is an identifier returned by the `useId()` hook in the Workflow Run Card component. --- .../tests/workflow-run.spec.ts | 3 +- .../__tests__/useWorkflowCommandMenu.test.tsx | 24 +- .../hooks/useWorkflowCommandMenu.ts | 50 +++- .../CommandMenuWorkflowSelectAction.tsx | 19 +- .../hooks/useCommandMenuWorkflowIdOrThrow.ts | 16 ++ ...=> commandMenuWorkflowIdComponentState.ts} | 2 +- .../commandMenuWorkflowRunIdComponentState.ts | 10 + ...mandMenuWorkflowVersionIdComponentState.ts | 9 + .../CommandMenuWorkflowEditStep.tsx | 23 +- .../CommandMenuWorkflowRunViewStep.tsx | 134 +--------- .../CommandMenuWorkflowRunViewStepContent.tsx | 132 ++++++++++ .../useCommandMenuWorkflowRunIdOrThrow.ts | 16 ++ .../CommandMenuWorkflowViewStep.tsx | 37 +-- .../CommandMenuWorkflowViewStepContent.tsx | 31 +++ .../useCommandMenuWorkflowVersionIdOrThrow.ts | 16 ++ .../CommandMenuWorkflowSelectTriggerType.tsx | 19 +- ...ndMenuWorkflowSelectTriggerTypeContent.tsx | 10 +- .../record-show/components/CardComponents.tsx | 87 +++++-- .../modules/workflow/hooks/useFlowOrThrow.ts | 7 +- ...kflowRunOpeningInCommandMenuSideEffects.ts | 54 ++++- .../workflow/hooks/useWorkflowRunIdOrThrow.ts | 9 +- .../workflow/states/flowComponentState.ts | 16 ++ .../src/modules/workflow/states/flowState.ts | 14 -- .../workflow/states/workflowIdState.ts | 5 - ...workflowLastCreatedStepIdComponentState.ts | 10 + .../states/workflowLastCreatedStepIdState.ts | 5 - .../workflow/states/workflowRunIdState.ts | 6 - ...kflowVisualizerWorkflowIdComponentState.ts | 9 + ...owVisualizerWorkflowRunIdComponentState.ts | 9 + ...sualizerWorkflowVersionIdComponentState.ts | 9 + ...etWorkflowVisualizerComponentInstanceId.ts | 7 + .../components/WorkflowDiagramCanvasBase.tsx | 49 ++-- .../WorkflowDiagramCanvasEditableEffect.tsx | 26 +- .../WorkflowDiagramCanvasReadonlyEffect.tsx | 44 +++- .../components/WorkflowDiagramEffect.tsx | 25 +- .../WorkflowDiagramStepNodeEditable.tsx | 12 +- .../WorkflowRunDiagramCanvasEffect.tsx | 35 ++- .../components/WorkflowRunVisualizer.tsx | 8 +- .../WorkflowRunVisualizerEffect.tsx | 63 +++-- .../WorkflowVersionVisualizerEffect.tsx | 34 ++- .../components/WorkflowVisualizerEffect.tsx | 12 +- .../WorkflowDiagramCustomMarkers.stories.tsx | 229 ++++++++++-------- .../useTriggerNodeSelection.test.tsx | 20 +- .../useHandleWorkflowRunDiagramCanvasInit.ts | 46 +++- .../hooks/useStartNodeCreation.ts | 21 +- .../hooks/useTriggerNodeSelection.ts | 8 +- .../hooks/useWorkflowSelectedNodeOrThrow.ts | 8 +- ...wRunVisualizerComponentInstanceContext.tsx | 6 + ...flowVisualizerComponentInstanceContext.tsx | 6 + .../states/workflowDiagramComponentState.ts | 11 + .../states/workflowDiagramState.ts | 7 - .../workflowDiagramStatusComponentState.ts | 12 + .../states/workflowDiagramStatusState.ts | 8 - ...agramTriggerNodeSelectionComponentState.ts | 9 + ...orkflowDiagramTriggerNodeSelectionState.ts | 7 - .../states/workflowReactFlowRefState.ts | 8 - ...lowRunStepToOpenByDefaultComponentState.ts | 16 ++ .../workflowRunStepToOpenByDefaultState.ts | 13 - .../workflowSelectedNodeComponentState.ts | 10 + .../states/workflowSelectedNodeState.ts | 6 - .../types/WorkflowDiagramStatus.ts | 4 + .../hooks/__tests__/useCreateStep.test.ts | 53 ---- .../hooks/__tests__/useCreateStep.test.tsx | 80 ++++++ .../workflow-steps/hooks/useCreateStep.ts | 21 +- ...reateStepFromParentStepIdComponentState.ts | 9 + ...workflowCreateStepFromParentStepIdState.ts | 7 - .../WorkflowEditActionServerlessFunction.tsx | 14 +- .../WorkflowEditActionSendEmail.tsx | 13 +- .../RightDrawerWorkflowSelectTriggerType.tsx | 16 -- ...DrawerWorkflowSelectTriggerTypeContent.tsx | 86 ------- .../WorkflowEditTriggerWebhookForm.tsx | 9 +- .../WorkflowVariablesDropdownFieldItems.tsx | 13 +- .../decorators/WorkflowStepDecorator.tsx | 79 +++--- 73 files changed, 1209 insertions(+), 752 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow.ts rename packages/twenty-front/src/modules/command-menu/pages/workflow/states/{workflowIdComponentState.ts => commandMenuWorkflowIdComponentState.ts} (84%) create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowRunIdComponentState.ts create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState.ts create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/hooks/useCommandMenuWorkflowRunIdOrThrow.ts create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStepContent.tsx create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/hooks/useCommandMenuWorkflowVersionIdOrThrow.ts create mode 100644 packages/twenty-front/src/modules/workflow/states/flowComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/states/flowState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/states/workflowIdState.ts create mode 100644 packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/states/workflowRunIdState.ts create mode 100644 packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowIdComponentState.ts create mode 100644 packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowRunIdComponentState.ts create mode 100644 packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowVersionIdComponentState.ts create mode 100644 packages/twenty-front/src/modules/workflow/utils/getWorkflowVisualizerComponentInstanceId.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowRunVisualizerComponentInstanceContext.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramState.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusState.ts create 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/workflowDiagramTriggerNodeSelectionState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowReactFlowRefState.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeState.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagramStatus.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx diff --git a/packages/twenty-e2e-testing/tests/workflow-run.spec.ts b/packages/twenty-e2e-testing/tests/workflow-run.spec.ts index fd7a3a323..ed5b565cd 100644 --- a/packages/twenty-e2e-testing/tests/workflow-run.spec.ts +++ b/packages/twenty-e2e-testing/tests/workflow-run.spec.ts @@ -57,8 +57,7 @@ test('The workflow run visualizer shows the executed draft version without the l ); }); -// FIXME: Documented bug. See https://github.com/twentyhq/core-team-issues/issues/921 -test.fail('Workflow Runs with a pending form step can be opened in the side panel and then in full screen', async ({ +test('Workflow Runs with a pending form step can be opened in the side panel and then in full screen', async ({ workflowVisualizer, page, }) => { diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx index 4e00fe3aa..9e53b1ed9 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx +++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useWorkflowCommandMenu.test.tsx @@ -4,7 +4,8 @@ import { useRecoilValue } from 'recoil'; import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId'; import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState'; import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState'; -import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState'; +import { commandMenuWorkflowIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowIdComponentState'; +import { commandMenuWorkflowVersionIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState'; import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; @@ -16,10 +17,10 @@ import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { t } from '@lingui/core/macro'; import { act } from 'react'; +import { IconBolt, IconSettingsAutomation, useIcons } from 'twenty-ui/display'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { useWorkflowCommandMenu } from '../useWorkflowCommandMenu'; -import { IconBolt, IconSettingsAutomation, useIcons } from 'twenty-ui/display'; jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('mocked-uuid'), @@ -95,7 +96,11 @@ const renderHooks = () => { 'mocked-uuid', ); const workflowId = useRecoilComponentValueV2( - workflowIdComponentState, + commandMenuWorkflowIdComponentState, + 'mocked-uuid', + ); + const workflowVersionId = useRecoilComponentValueV2( + commandMenuWorkflowVersionIdComponentState, 'mocked-uuid', ); const { getIcon } = useIcons(); @@ -106,6 +111,7 @@ const renderHooks = () => { openWorkflowEditStepInCommandMenu, openWorkflowViewStepInCommandMenu, workflowId, + workflowVersionId, viewableRecordId, commandMenuPage, commandMenuNavigationMorphItemByPage, @@ -188,14 +194,16 @@ describe('useWorkflowCommandMenu', () => { const { result } = renderHooks(); act(() => { - result.current.openWorkflowViewStepInCommandMenu( - 'test-workflow-id', - 'View Step', - IconSettingsAutomation, - ); + result.current.openWorkflowViewStepInCommandMenu({ + workflowId: 'test-workflow-id', + workflowVersionId: 'test-workflow-version-id', + icon: IconSettingsAutomation, + title: 'View Step', + }); }); expect(result.current.workflowId).toBe('test-workflow-id'); + expect(result.current.workflowVersionId).toBe('test-workflow-version-id'); expect(mockNavigateCommandMenu).toHaveBeenCalledWith({ page: CommandMenuPages.WorkflowStepView, diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts index 0ecedbc0e..4e0997ddf 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useWorkflowCommandMenu.ts @@ -1,5 +1,7 @@ import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; -import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState'; +import { commandMenuWorkflowIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowIdComponentState'; +import { commandMenuWorkflowRunIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowRunIdComponentState'; +import { commandMenuWorkflowVersionIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { useSetInitialWorkflowRunRightDrawerTab } from '@/workflow/workflow-diagram/hooks/useSetInitialWorkflowRunRightDrawerTab'; import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; @@ -23,7 +25,9 @@ export const useWorkflowCommandMenu = () => { const pageId = v4(); set( - workflowIdComponentState.atomFamily({ instanceId: pageId }), + commandMenuWorkflowIdComponentState.atomFamily({ + instanceId: pageId, + }), workflowId, ); @@ -44,7 +48,9 @@ export const useWorkflowCommandMenu = () => { const pageId = v4(); set( - workflowIdComponentState.atomFamily({ instanceId: pageId }), + commandMenuWorkflowIdComponentState.atomFamily({ + instanceId: pageId, + }), workflowId, ); @@ -65,7 +71,9 @@ export const useWorkflowCommandMenu = () => { const pageId = v4(); set( - workflowIdComponentState.atomFamily({ instanceId: pageId }), + commandMenuWorkflowIdComponentState.atomFamily({ + instanceId: pageId, + }), workflowId, ); @@ -82,13 +90,31 @@ export const useWorkflowCommandMenu = () => { const openWorkflowViewStepInCommandMenu = useRecoilCallback( ({ set }) => { - return (workflowId: string, title: string, icon: IconComponent) => { + return ({ + workflowId, + workflowVersionId, + title, + icon, + }: { + workflowId: string; + workflowVersionId: string; + title: string; + icon: IconComponent; + }) => { const pageId = v4(); set( - workflowIdComponentState.atomFamily({ instanceId: pageId }), + commandMenuWorkflowIdComponentState.atomFamily({ + instanceId: pageId, + }), workflowId, ); + set( + commandMenuWorkflowVersionIdComponentState.atomFamily({ + instanceId: pageId, + }), + workflowVersionId, + ); navigateCommandMenu({ page: CommandMenuPages.WorkflowStepView, @@ -105,12 +131,14 @@ export const useWorkflowCommandMenu = () => { ({ set }) => { return ({ workflowId, + workflowRunId, title, icon, workflowSelectedNode, stepExecutionStatus, }: { workflowId: string; + workflowRunId: string; title: string; icon: IconComponent; workflowSelectedNode: string; @@ -119,9 +147,17 @@ export const useWorkflowCommandMenu = () => { const pageId = v4(); set( - workflowIdComponentState.atomFamily({ instanceId: pageId }), + commandMenuWorkflowIdComponentState.atomFamily({ + instanceId: pageId, + }), workflowId, ); + set( + commandMenuWorkflowRunIdComponentState.atomFamily({ + instanceId: pageId, + }), + workflowRunId, + ); navigateCommandMenu({ page: CommandMenuPages.WorkflowRunStepView, diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction.tsx index 45ca049d0..1b9613a3b 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction.tsx @@ -1,16 +1,27 @@ import { CommandMenuWorkflowSelectActionContent } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent'; -import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useCommandMenuWorkflowIdOrThrow } from '@/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; import { isDefined } from 'twenty-shared/utils'; export const CommandMenuWorkflowSelectAction = () => { - const workflowId = useRecoilComponentValueV2(workflowIdComponentState); + const workflowId = useCommandMenuWorkflowIdOrThrow(); const workflow = useWorkflowWithCurrentVersion(workflowId); if (!isDefined(workflow)) { return null; } - return ; + return ( + + + + ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow.ts new file mode 100644 index 000000000..047435f05 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow.ts @@ -0,0 +1,16 @@ +import { commandMenuWorkflowIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isDefined } from 'twenty-shared/utils'; + +export const useCommandMenuWorkflowIdOrThrow = () => { + const workflowId = useRecoilComponentValueV2( + commandMenuWorkflowIdComponentState, + ); + if (!isDefined(workflowId)) { + throw new Error( + 'Expected commandMenuWorkflowIdComponentState to be defined', + ); + } + + return workflowId; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/states/workflowIdComponentState.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowIdComponentState.ts similarity index 84% rename from packages/twenty-front/src/modules/command-menu/pages/workflow/states/workflowIdComponentState.ts rename to packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowIdComponentState.ts index 35bb2d051..19e2b6a6a 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/states/workflowIdComponentState.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowIdComponentState.ts @@ -1,7 +1,7 @@ import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -export const workflowIdComponentState = createComponentStateV2< +export const commandMenuWorkflowIdComponentState = createComponentStateV2< string | undefined >({ key: 'command-menu/workflow-id', diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowRunIdComponentState.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowRunIdComponentState.ts new file mode 100644 index 000000000..55a39168a --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowRunIdComponentState.ts @@ -0,0 +1,10 @@ +import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const commandMenuWorkflowRunIdComponentState = createComponentStateV2< + string | undefined +>({ + key: 'command-menu/workflow-run-id', + defaultValue: undefined, + componentInstanceContext: CommandMenuPageComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState.ts new file mode 100644 index 000000000..cf0f5c15b --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState.ts @@ -0,0 +1,9 @@ +import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const commandMenuWorkflowVersionIdComponentState = + createComponentStateV2({ + key: 'command-menu/workflow-version-id', + defaultValue: undefined, + componentInstanceContext: CommandMenuPageComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStep.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStep.tsx index 217e50504..27e889273 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStep.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStep.tsx @@ -1,12 +1,13 @@ -import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState'; +import { useCommandMenuWorkflowIdOrThrow } from '@/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow'; import { CommandMenuWorkflowEditStepContent } from '@/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStepContent'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; +import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; import { isDefined } from 'twenty-shared/utils'; export const CommandMenuWorkflowEditStep = () => { - const workflowId = useRecoilComponentValueV2(workflowIdComponentState); + const workflowId = useCommandMenuWorkflowIdOrThrow(); const workflow = useWorkflowWithCurrentVersion(workflowId); if (!isDefined(workflow)) { @@ -14,10 +15,18 @@ export const CommandMenuWorkflowEditStep = () => { } return ( - - - + + + + ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx index 6f34c1fc1..8bac2a3e3 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx @@ -1,132 +1,20 @@ -import { getIsInputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled'; -import { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled'; -import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; -import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList'; -import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; -import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; -import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; -import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; -import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; -import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; -import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail'; -import { WorkflowRunStepNodeDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepNodeDetail'; -import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail'; -import { - WorkflowRunTabId, - WorkflowRunTabIdType, -} from '@/workflow/workflow-steps/types/WorkflowRunTabId'; -import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus'; -import styled from '@emotion/styled'; -import { isNull } from '@sniptt/guards'; -import { isDefined } from 'twenty-shared/utils'; -import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui/display'; - -const StyledContainer = styled.div` - display: flex; - flex-direction: column; - height: 100%; -`; - -const StyledTabList = styled(TabList)` - background-color: ${({ theme }) => theme.background.secondary}; - padding-left: ${({ theme }) => theme.spacing(2)}; -`; - -type TabId = WorkflowRunTabIdType; +import { CommandMenuWorkflowRunViewStepContent } from '@/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent'; +import { useCommandMenuWorkflowRunIdOrThrow } from '@/command-menu/pages/workflow/step/view-run/hooks/useCommandMenuWorkflowRunIdOrThrow'; +import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; export const CommandMenuWorkflowRunViewStep = () => { - const flow = useFlowOrThrow(); - const workflowSelectedNode = useWorkflowSelectedNodeOrThrow(); - const workflowRunId = useWorkflowRunIdOrThrow(); - - const workflowRun = useWorkflowRun({ workflowRunId }); - - const commandMenuPageComponentInstance = useComponentInstanceStateContext( - CommandMenuPageComponentInstanceContext, - ); - if (isNull(commandMenuPageComponentInstance)) { - throw new Error( - 'CommandMenuPageComponentInstanceContext is not defined. This component should be used within CommandMenuPageComponentInstanceContext.', - ); - } - - const activeTabId = useRecoilComponentValueV2( - activeTabIdComponentState, - commandMenuPageComponentInstance.instanceId, - ); - - if (!isDefined(workflowRun)) { - return null; - } - - const stepExecutionStatus = getWorkflowRunStepExecutionStatus({ - workflowRunOutput: workflowRun.output, - stepId: workflowSelectedNode, - }); - - const isInputTabDisabled = getIsInputTabDisabled({ - stepExecutionStatus, - workflowSelectedNode, - }); - const isOutputTabDisabled = getIsOutputTabDisabled({ - stepExecutionStatus, - }); - - const tabs: SingleTabProps[] = [ - { - id: WorkflowRunTabId.OUTPUT, - title: 'Output', - Icon: IconLogout, - disabled: isOutputTabDisabled, - }, - { id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto }, - { - id: WorkflowRunTabId.INPUT, - title: 'Input', - Icon: IconLogin2, - disabled: isInputTabDisabled, - }, - ]; + const workflowRunId = useCommandMenuWorkflowRunIdOrThrow(); return ( - - - - - {activeTabId === WorkflowRunTabId.OUTPUT ? ( - - ) : null} - - {activeTabId === WorkflowRunTabId.NODE ? ( - - ) : null} - - {activeTabId === WorkflowRunTabId.INPUT ? ( - - ) : null} - - + + ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx new file mode 100644 index 000000000..32e278b14 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx @@ -0,0 +1,132 @@ +import { getIsInputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled'; +import { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled'; +import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; +import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; +import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; +import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; +import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; +import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; +import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; +import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail'; +import { WorkflowRunStepNodeDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepNodeDetail'; +import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail'; +import { + WorkflowRunTabId, + WorkflowRunTabIdType, +} from '@/workflow/workflow-steps/types/WorkflowRunTabId'; +import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus'; +import styled from '@emotion/styled'; +import { isNull } from '@sniptt/guards'; +import { isDefined } from 'twenty-shared/utils'; +import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui/display'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + height: 100%; +`; + +const StyledTabList = styled(TabList)` + background-color: ${({ theme }) => theme.background.secondary}; + padding-left: ${({ theme }) => theme.spacing(2)}; +`; + +type TabId = WorkflowRunTabIdType; + +export const CommandMenuWorkflowRunViewStepContent = () => { + const flow = useFlowOrThrow(); + const workflowSelectedNode = useWorkflowSelectedNodeOrThrow(); + const workflowRunId = useWorkflowRunIdOrThrow(); + + const workflowRun = useWorkflowRun({ workflowRunId }); + + const commandMenuPageComponentInstance = useComponentInstanceStateContext( + CommandMenuPageComponentInstanceContext, + ); + if (isNull(commandMenuPageComponentInstance)) { + throw new Error( + 'CommandMenuPageComponentInstanceContext is not defined. This component should be used within CommandMenuPageComponentInstanceContext.', + ); + } + + const activeTabId = useRecoilComponentValueV2( + activeTabIdComponentState, + commandMenuPageComponentInstance.instanceId, + ); + + if (!isDefined(workflowRun)) { + return null; + } + + const stepExecutionStatus = getWorkflowRunStepExecutionStatus({ + workflowRunOutput: workflowRun.output, + stepId: workflowSelectedNode, + }); + + const isInputTabDisabled = getIsInputTabDisabled({ + stepExecutionStatus, + workflowSelectedNode, + }); + const isOutputTabDisabled = getIsOutputTabDisabled({ + stepExecutionStatus, + }); + + const tabs: SingleTabProps[] = [ + { + id: WorkflowRunTabId.OUTPUT, + title: 'Output', + Icon: IconLogout, + disabled: isOutputTabDisabled, + }, + { id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto }, + { + id: WorkflowRunTabId.INPUT, + title: 'Input', + Icon: IconLogin2, + disabled: isInputTabDisabled, + }, + ]; + + return ( + + + + + {activeTabId === WorkflowRunTabId.OUTPUT ? ( + + ) : null} + + {activeTabId === WorkflowRunTabId.NODE ? ( + + ) : null} + + {activeTabId === WorkflowRunTabId.INPUT ? ( + + ) : null} + + + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/hooks/useCommandMenuWorkflowRunIdOrThrow.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/hooks/useCommandMenuWorkflowRunIdOrThrow.ts new file mode 100644 index 000000000..6afcf4e71 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/hooks/useCommandMenuWorkflowRunIdOrThrow.ts @@ -0,0 +1,16 @@ +import { commandMenuWorkflowRunIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowRunIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isDefined } from 'twenty-shared/utils'; + +export const useCommandMenuWorkflowRunIdOrThrow = () => { + const workflowRunId = useRecoilComponentValueV2( + commandMenuWorkflowRunIdComponentState, + ); + if (!isDefined(workflowRunId)) { + throw new Error( + 'Expected the commandMenuWorkflowRunIdComponentState to be defined.', + ); + } + + return workflowRunId; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStep.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStep.tsx index 659b17808..4e644f0e6 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStep.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStep.tsx @@ -1,31 +1,20 @@ -import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; -import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; -import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; -import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; -import styled from '@emotion/styled'; - -const StyledContainer = styled.div` - display: flex; - flex-direction: column; - height: 100%; -`; +import { useCommandMenuWorkflowVersionIdOrThrow } from '@/command-menu/pages/workflow/step/view/hooks/useCommandMenuWorkflowVersionIdOrThrow'; +import { CommandMenuWorkflowViewStepContent } from '@/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStepContent'; +import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; export const CommandMenuWorkflowViewStep = () => { - const flow = useFlowOrThrow(); - const workflowSelectedNode = useWorkflowSelectedNodeOrThrow(); + const workflowVersionId = useCommandMenuWorkflowVersionIdOrThrow(); return ( - - - - - + + ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStepContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStepContent.tsx new file mode 100644 index 000000000..2742cf763 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStepContent.tsx @@ -0,0 +1,31 @@ +import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; +import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; +import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; +import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; +import styled from '@emotion/styled'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + height: 100%; +`; + +export const CommandMenuWorkflowViewStepContent = () => { + const flow = useFlowOrThrow(); + const workflowSelectedNode = useWorkflowSelectedNodeOrThrow(); + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/hooks/useCommandMenuWorkflowVersionIdOrThrow.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/hooks/useCommandMenuWorkflowVersionIdOrThrow.ts new file mode 100644 index 000000000..230c61710 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view/hooks/useCommandMenuWorkflowVersionIdOrThrow.ts @@ -0,0 +1,16 @@ +import { commandMenuWorkflowVersionIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isDefined } from 'twenty-shared/utils'; + +export const useCommandMenuWorkflowVersionIdOrThrow = () => { + const workflowVersionId = useRecoilComponentValueV2( + commandMenuWorkflowVersionIdComponentState, + ); + if (!isDefined(workflowVersionId)) { + throw new Error( + 'Expected commandMenuWorkflowVersionIdComponentState to be defined', + ); + } + + return workflowVersionId; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerType.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerType.tsx index 2e4bb6bc1..4877cfeaf 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerType.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerType.tsx @@ -1,16 +1,27 @@ -import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState'; +import { useCommandMenuWorkflowIdOrThrow } from '@/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow'; import { CommandMenuWorkflowSelectTriggerTypeContent } from '@/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; import { isDefined } from 'twenty-shared/utils'; export const CommandMenuWorkflowSelectTriggerType = () => { - const workflowId = useRecoilComponentValueV2(workflowIdComponentState); + const workflowId = useCommandMenuWorkflowIdOrThrow(); const workflow = useWorkflowWithCurrentVersion(workflowId); if (!isDefined(workflow)) { return null; } - return ; + return ( + + + + ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx index 92799918d..3abd377ad 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx @@ -1,10 +1,11 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { WorkflowTriggerType, WorkflowWithCurrentVersion, } from '@/workflow/types/Workflow'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer'; import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle'; import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes'; @@ -12,9 +13,8 @@ import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/Other import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition'; -import { useSetRecoilState } from 'recoil'; -import { MenuItemCommand } from 'twenty-ui/navigation'; import { useIcons } from 'twenty-ui/display'; +import { MenuItemCommand } from 'twenty-ui/navigation'; export const CommandMenuWorkflowSelectTriggerTypeContent = ({ workflow, @@ -26,7 +26,9 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({ const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); - const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); + const setWorkflowSelectedNode = useSetRecoilComponentStateV2( + workflowSelectedNodeComponentState, + ); const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu(); const handleTriggerTypeClick = ({ diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx index 62ddeba26..5fab1d8b4 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx @@ -7,15 +7,19 @@ import { TimelineActivities } from '@/activities/timeline-activities/components/ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { FieldsCard } from '@/object-record/record-show/components/FieldsCard'; import { CardType } from '@/object-record/record-show/types/CardType'; +import { ListenRecordUpdatesEffect } from '@/subscription/components/ListenUpdatesEffect'; import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer'; +import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; import { WorkflowRunVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizer'; import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect'; import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer'; import { WorkflowVersionVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect'; import { WorkflowVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVisualizer'; import { WorkflowVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVisualizerEffect'; +import { WorkflowRunVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowRunVisualizerComponentInstanceContext'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; import styled from '@emotion/styled'; -import { ListenRecordUpdatesEffect } from '@/subscription/components/ListenUpdatesEffect'; +import { useId } from 'react'; const StyledGreyBox = styled.div<{ isInRightDrawer?: boolean }>` background: ${({ theme, isInRightDrawer }) => @@ -81,32 +85,63 @@ export const CardComponents: Record = { ), - [CardType.WorkflowCard]: ({ targetableObject }) => ( - <> - - - - ), + [CardType.WorkflowCard]: ({ targetableObject }) => { + return ( + + + + + ); + }, - [CardType.WorkflowVersionCard]: ({ targetableObject }) => ( - <> - - - - ), + [CardType.WorkflowVersionCard]: ({ targetableObject }) => { + return ( + + + + + ); + }, - [CardType.WorkflowRunCard]: ({ targetableObject }) => ( - <> - - + [CardType.WorkflowRunCard]: ({ targetableObject }) => { + const componentId = useId(); - - - ), + return ( + + + + + + + + ); + }, }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useFlowOrThrow.ts b/packages/twenty-front/src/modules/workflow/hooks/useFlowOrThrow.ts index f5f99a409..537781083 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useFlowOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useFlowOrThrow.ts @@ -1,9 +1,10 @@ -import { flowState } from '@/workflow/states/flowState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { flowComponentState } from '@/workflow/states/flowComponentState'; import { isDefined } from 'twenty-shared/utils'; export const useFlowOrThrow = () => { - const flow = useRecoilValue(flowState); + const flow = useRecoilComponentValueV2(flowComponentState); + if (!isDefined(flow)) { throw new Error('Expected the flow to be defined'); } diff --git a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts index 9a233a042..e34106778 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts @@ -3,11 +3,12 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { flowState } from '@/workflow/states/flowState'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { workflowRunIdState } from '@/workflow/states/workflowRunIdState'; +import { flowComponentState } from '@/workflow/states/flowComponentState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState'; import { WorkflowRun } from '@/workflow/types/Workflow'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram'; import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; import { useApolloClient } from '@apollo/client'; @@ -58,17 +59,46 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => { return; } - set(workflowRunIdState, workflowRunRecord.id); - set(workflowIdState, workflowRunRecord.workflowId); - set(flowState, { - workflowVersionId: workflowRunRecord.workflowVersionId, - trigger: workflowRunRecord.output.flow.trigger, - steps: workflowRunRecord.output.flow.steps, - }); - set(workflowSelectedNodeState, stepToOpenByDefault.id); + set( + workflowVisualizerWorkflowRunIdComponentState.atomFamily({ + instanceId: getWorkflowVisualizerComponentInstanceId({ + recordId, + }), + }), + workflowRunRecord.id, + ); + set( + workflowVisualizerWorkflowIdComponentState.atomFamily({ + instanceId: getWorkflowVisualizerComponentInstanceId({ + recordId, + }), + }), + workflowRunRecord.workflowId, + ); + set( + flowComponentState.atomFamily({ + instanceId: getWorkflowVisualizerComponentInstanceId({ + recordId, + }), + }), + { + workflowVersionId: workflowRunRecord.workflowVersionId, + trigger: workflowRunRecord.output.flow.trigger, + steps: workflowRunRecord.output.flow.steps, + }, + ); + set( + workflowSelectedNodeComponentState.atomFamily({ + instanceId: getWorkflowVisualizerComponentInstanceId({ + recordId, + }), + }), + stepToOpenByDefault.id, + ); openWorkflowRunViewStepInCommandMenu({ workflowId: workflowRunRecord.workflowId, + workflowRunId: workflowRunRecord.id, title: stepToOpenByDefault.data.name, icon: getIcon(getWorkflowNodeIconKey(stepToOpenByDefault.data)), workflowSelectedNode: stepToOpenByDefault.id, diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunIdOrThrow.ts b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunIdOrThrow.ts index d76132dcc..98f383790 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunIdOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunIdOrThrow.ts @@ -1,9 +1,12 @@ -import { workflowRunIdState } from '@/workflow/states/workflowRunIdState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState'; import { isDefined } from 'twenty-shared/utils'; export const useWorkflowRunIdOrThrow = () => { - const workflowRunId = useRecoilValue(workflowRunIdState); + const workflowRunId = useRecoilComponentValueV2( + workflowVisualizerWorkflowRunIdComponentState, + ); + if (!isDefined(workflowRunId)) { throw new Error('Expected the workflow run ID to be defined'); } diff --git a/packages/twenty-front/src/modules/workflow/states/flowComponentState.ts b/packages/twenty-front/src/modules/workflow/states/flowComponentState.ts new file mode 100644 index 000000000..1b5b9ddb6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/states/flowComponentState.ts @@ -0,0 +1,16 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowAction, WorkflowTrigger } from '@/workflow/types/Workflow'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const flowComponentState = createComponentStateV2< + | { + workflowVersionId: string; + trigger: WorkflowTrigger | null; + steps: WorkflowAction[] | null; + } + | undefined +>({ + key: 'flowComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/workflow/states/flowState.ts b/packages/twenty-front/src/modules/workflow/states/flowState.ts deleted file mode 100644 index 9cdd51b00..000000000 --- a/packages/twenty-front/src/modules/workflow/states/flowState.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { WorkflowAction, WorkflowTrigger } from '@/workflow/types/Workflow'; -import { createState } from 'twenty-ui/utilities'; - -export const flowState = createState< - | { - workflowVersionId: string; - trigger: WorkflowTrigger | null; - steps: WorkflowAction[] | null; - } - | undefined ->({ - key: 'flowState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/states/workflowIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowIdState.ts deleted file mode 100644 index 0851b1044..000000000 --- a/packages/twenty-front/src/modules/workflow/states/workflowIdState.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; -export const workflowIdState = createState({ - key: 'workflowIdState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdComponentState.ts b/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdComponentState.ts new file mode 100644 index 000000000..20b09c41e --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdComponentState.ts @@ -0,0 +1,10 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const workflowLastCreatedStepIdComponentState = createComponentStateV2< + string | undefined +>({ + key: 'workflowLastCreatedStepIdComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts deleted file mode 100644 index 14252388f..000000000 --- a/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; -export const workflowLastCreatedStepIdState = createState({ - key: 'workflowLastCreatedStepIdState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/states/workflowRunIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowRunIdState.ts deleted file mode 100644 index f1460ff00..000000000 --- a/packages/twenty-front/src/modules/workflow/states/workflowRunIdState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; - -export const workflowRunIdState = createState({ - key: 'workflowRunIdState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowIdComponentState.ts b/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowIdComponentState.ts new file mode 100644 index 000000000..cbd75d932 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowIdComponentState.ts @@ -0,0 +1,9 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const workflowVisualizerWorkflowIdComponentState = + createComponentStateV2({ + key: 'workflowVisualizerWorkflowIdComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowRunIdComponentState.ts b/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowRunIdComponentState.ts new file mode 100644 index 000000000..905c9fec5 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowRunIdComponentState.ts @@ -0,0 +1,9 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const workflowVisualizerWorkflowRunIdComponentState = + createComponentStateV2({ + key: 'workflowVisualizerWorkflowRunIdComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowVersionIdComponentState.ts b/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowVersionIdComponentState.ts new file mode 100644 index 000000000..e60141130 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/states/workflowVisualizerWorkflowVersionIdComponentState.ts @@ -0,0 +1,9 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const workflowVisualizerWorkflowVersionIdComponentState = + createComponentStateV2({ + key: 'workflowVisualizerWorkflowVersionIdComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/utils/getWorkflowVisualizerComponentInstanceId.ts b/packages/twenty-front/src/modules/workflow/utils/getWorkflowVisualizerComponentInstanceId.ts new file mode 100644 index 000000000..b8a930e0e --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/getWorkflowVisualizerComponentInstanceId.ts @@ -0,0 +1,7 @@ +export const getWorkflowVisualizerComponentInstanceId = ({ + recordId, +}: { + recordId: string; +}) => { + return recordId; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx index 5d517e832..ba69cea7c 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx @@ -1,8 +1,9 @@ import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/components/WorkflowDiagramCustomMarkers'; import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState'; -import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; -import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState'; +import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; import { WorkflowDiagramEdge, WorkflowDiagramEdgeType, @@ -26,7 +27,6 @@ import { } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import React, { useEffect, useMemo, useRef } from 'react'; -import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { Tag, TagColor } from 'twenty-ui/components'; import { THEME_COMMON } from 'twenty-ui/theme'; @@ -121,7 +121,9 @@ export const WorkflowDiagramCanvasBase = ({ const reactflow = useReactFlow(); - const workflowDiagram = useRecoilValue(workflowDiagramState); + const workflowDiagram = useRecoilComponentValueV2( + workflowDiagramComponentState, + ); const { nodes, edges } = useMemo( () => @@ -137,14 +139,8 @@ export const WorkflowDiagramCanvasBase = ({ THEME_COMMON.rightDrawerWidth.replace('px', ''), ); - const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); - - const setWorkflowReactFlowRef = useRecoilCallback( - ({ set }) => - (node: HTMLDivElement | null) => { - set(workflowReactFlowRefState, { current: node }); - }, - [], + const setWorkflowDiagram = useSetRecoilComponentStateV2( + workflowDiagramComponentState, ); const handleEdgesChange = ( @@ -199,24 +195,18 @@ export const WorkflowDiagramCanvasBase = ({ ); }, [reactflow, rightDrawerState, rightDrawerWidth]); - const handleNodesChanges = useRecoilCallback( - ({ set }) => - (changes: NodeChange[]) => { - set(workflowDiagramState, (diagram) => { - if (!isDefined(diagram)) { - throw new Error( - 'It must be impossible for the nodes to be updated if the diagram is not defined yet. Be sure the diagram is rendered only when defined.', - ); - } + const handleNodesChanges = (changes: NodeChange[]) => { + setWorkflowDiagram((diagram) => { + if (!isDefined(diagram)) { + return diagram; + } - return { - ...diagram, - nodes: applyNodeChanges(changes, diagram.nodes), - }; - }); - }, - [], - ); + return { + ...diagram, + nodes: applyNodeChanges(changes, diagram.nodes), + }; + }); + }; const handleInit = () => { if (!isDefined(containerRef.current)) { @@ -239,7 +229,6 @@ export const WorkflowDiagramCanvasBase = ({ { openWorkflowEditStepInCommandMenu, } = useWorkflowCommandMenu(); - const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); + const setWorkflowSelectedNode = useSetRecoilComponentStateV2( + workflowSelectedNodeComponentState, + ); const setCommandMenuNavigationStack = useSetRecoilState( commandMenuNavigationStackState, @@ -37,7 +41,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => { const { isInRightDrawer } = useContext(ActionMenuContext); - const workflowId = useRecoilValue(workflowIdState); + const workflowVisualizerWorkflowId = useRecoilComponentValueV2( + workflowVisualizerWorkflowIdComponentState, + ); const handleSelectionChange = useCallback( ({ nodes }: OnSelectionChangeParams) => { @@ -53,8 +59,8 @@ export const WorkflowDiagramCanvasEditableEffect = () => { const isEmptyTriggerNode = selectedNode.type === EMPTY_TRIGGER_STEP_ID; if (isEmptyTriggerNode) { - if (isDefined(workflowId)) { - openWorkflowTriggerTypeInCommandMenu(workflowId); + if (isDefined(workflowVisualizerWorkflowId)) { + openWorkflowTriggerTypeInCommandMenu(workflowVisualizerWorkflowId); return; } @@ -71,9 +77,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => { setWorkflowSelectedNode(selectedNode.id); - if (isDefined(workflowId)) { + if (isDefined(workflowVisualizerWorkflowId)) { openWorkflowEditStepInCommandMenu( - workflowId, + workflowVisualizerWorkflowId, selectedNodeData.name, getIcon(getWorkflowNodeIconKey(selectedNodeData)), ); @@ -84,7 +90,7 @@ export const WorkflowDiagramCanvasEditableEffect = () => { [ isInRightDrawer, setCommandMenuNavigationStack, - workflowId, + workflowVisualizerWorkflowId, openWorkflowTriggerTypeInCommandMenu, startNodeCreation, openWorkflowEditStepInCommandMenu, 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 index 023f1a78a..9f1252cd3 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect.tsx @@ -1,7 +1,10 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState'; import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { WorkflowDiagramNode, WorkflowDiagramStepNodeData, @@ -9,19 +12,34 @@ import { import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; import { useCallback } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; export const WorkflowDiagramCanvasReadonlyEffect = () => { const { getIcon } = useIcons(); - const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); + const setWorkflowSelectedNode = useSetRecoilComponentStateV2( + workflowSelectedNodeComponentState, + ); const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu(); - const workflowId = useRecoilValue(workflowIdState); + const workflowVisualizerWorkflowId = useRecoilComponentValueV2( + workflowVisualizerWorkflowIdComponentState, + ); + const workflowVisualizerWorkflowVersionId = useRecoilComponentValueV2( + workflowVisualizerWorkflowVersionIdComponentState, + ); const handleSelectionChange = useCallback( ({ nodes }: OnSelectionChangeParams) => { + if ( + !( + isDefined(workflowVisualizerWorkflowId) && + isDefined(workflowVisualizerWorkflowVersionId) + ) + ) { + return; + } + const selectedNode = nodes[0] as WorkflowDiagramNode | undefined; if (!isDefined(selectedNode)) { @@ -32,18 +50,18 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => { const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData; - if (isDefined(workflowId)) { - openWorkflowViewStepInCommandMenu( - workflowId, - selectedNodeData.name, - getIcon(getWorkflowNodeIconKey(selectedNodeData)), - ); - } + openWorkflowViewStepInCommandMenu({ + workflowId: workflowVisualizerWorkflowId, + workflowVersionId: workflowVisualizerWorkflowVersionId, + title: selectedNodeData.name, + icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)), + }); }, [ setWorkflowSelectedNode, openWorkflowViewStepInCommandMenu, - workflowId, + workflowVisualizerWorkflowId, + workflowVisualizerWorkflowVersionId, getIcon, ], ); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx index ec652bbad..16c6a2da4 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx @@ -1,18 +1,20 @@ +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; -import { flowState } from '@/workflow/states/flowState'; -import { workflowLastCreatedStepIdState } from '@/workflow/states/workflowLastCreatedStepIdState'; +import { flowComponentState } from '@/workflow/states/flowComponentState'; +import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState'; import { WorkflowVersion, WorkflowWithCurrentVersion, } from '@/workflow/types/Workflow'; -import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; +import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; import { addCreateStepNodes } from '@/workflow/workflow-diagram/utils/addCreateStepNodes'; import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram'; import { mergeWorkflowDiagrams } from '@/workflow/workflow-diagram/utils/mergeWorkflowDiagrams'; import { useEffect } from 'react'; -import { useRecoilCallback, useSetRecoilState } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const WorkflowDiagramEffect = ({ @@ -20,10 +22,19 @@ export const WorkflowDiagramEffect = ({ }: { workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined; }) => { - const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); - const setFlow = useSetRecoilState(flowState); + const workflowDiagramState = useRecoilComponentCallbackStateV2( + workflowDiagramComponentState, + ); + const setWorkflowDiagram = useSetRecoilComponentStateV2( + workflowDiagramComponentState, + ); + const setFlow = useSetRecoilComponentStateV2(flowComponentState); const { populateStepsOutputSchema } = useStepsOutputSchema(); + const workflowLastCreatedStepIdState = useRecoilComponentCallbackStateV2( + workflowLastCreatedStepIdComponentState, + ); + const computeAndMergeNewWorkflowDiagram = useRecoilCallback( ({ snapshot, set }) => { return (currentVersion: WorkflowVersion) => { @@ -64,7 +75,7 @@ export const WorkflowDiagramEffect = ({ set(workflowDiagramState, mergedWorkflowDiagram); }; }, - [], + [workflowLastCreatedStepIdState, workflowDiagramState], ); const currentVersion = workflowWithCurrentVersion?.currentVersion; 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 d5f9bd18f..08c188a80 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,10 @@ +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined'; import { WorkflowDiagramStepNodeEditableContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent'; import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep'; -import { useRecoilValue } from 'recoil'; export const WorkflowDiagramStepNodeEditable = ({ id, @@ -15,9 +15,13 @@ export const WorkflowDiagramStepNodeEditable = ({ data: WorkflowDiagramStepNodeData; selected?: boolean; }) => { - const workflowId = useRecoilValue(workflowIdState); + const workflowVisualizerWorkflowId = useRecoilComponentValueV2( + workflowVisualizerWorkflowIdComponentState, + ); - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( + workflowVisualizerWorkflowId, + ); assertWorkflowWithCurrentVersionIsDefined(workflowWithCurrentVersion); const { deleteStep } = useDeleteStep({ 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 index c524fa985..22042b0e3 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx @@ -1,8 +1,10 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +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'; @@ -15,10 +17,25 @@ export const WorkflowRunDiagramCanvasEffect = () => { const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu(); + const workflowRunId = useWorkflowRunIdOrThrow(); + + const workflowVisualizerWorkflowIdState = useRecoilComponentCallbackStateV2( + workflowVisualizerWorkflowIdComponentState, + ); + const workflowDiagramStatusState = useRecoilComponentCallbackStateV2( + workflowDiagramStatusComponentState, + ); + const workflowSelectedNodeState = useRecoilComponentCallbackStateV2( + workflowSelectedNodeComponentState, + ); + const handleSelectionChange = useRecoilCallback( ({ snapshot, set }) => ({ nodes }: OnSelectionChangeParams) => { - const workflowId = getSnapshotValue(snapshot, workflowIdState); + const workflowId = getSnapshotValue( + snapshot, + workflowVisualizerWorkflowIdState, + ); if (!isDefined(workflowId)) { throw new Error('Expected the workflowId to be defined.'); @@ -49,13 +66,21 @@ export const WorkflowRunDiagramCanvasEffect = () => { openWorkflowRunViewStepInCommandMenu({ workflowId, + workflowRunId, title: selectedNodeData.name, icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)), workflowSelectedNode: selectedNode.id, stepExecutionStatus: selectedNodeData.runStatus, }); }, - [getIcon, openWorkflowRunViewStepInCommandMenu], + [ + workflowVisualizerWorkflowIdState, + workflowDiagramStatusState, + workflowSelectedNodeState, + openWorkflowRunViewStepInCommandMenu, + workflowRunId, + getIcon, + ], ); useOnSelectionChange({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx index a46d913c7..c5a66d597 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx @@ -1,8 +1,8 @@ +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas'; -import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState'; +import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; const StyledContainer = styled.div` @@ -15,7 +15,9 @@ export const WorkflowRunVisualizer = ({ workflowRunId: string; }) => { const workflowRun = useWorkflowRun({ workflowRunId }); - const workflowDiagramStatus = useRecoilValue(workflowDiagramStatusState); + const workflowDiagramStatus = useRecoilComponentValueV2( + workflowDiagramStatusComponentState, + ); if ( !isDefined(workflowRun) || 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 c49777bc9..a404d8bae 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 @@ -1,18 +1,21 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; -import { flowState } from '@/workflow/states/flowState'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { workflowRunIdState } from '@/workflow/states/workflowRunIdState'; +import { flowComponentState } from '@/workflow/states/flowComponentState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState'; import { WorkflowRunOutput } from '@/workflow/types/Workflow'; -import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; -import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState'; -import { workflowRunStepToOpenByDefaultState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState'; +import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; +import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState'; +import { workflowRunStepToOpenByDefaultComponentState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState'; import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram'; import { selectWorkflowDiagramNode } from '@/workflow/workflow-diagram/utils/selectWorkflowDiagramNode'; import { useContext, useEffect } from 'react'; -import { useRecoilCallback, useSetRecoilState } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const WorkflowRunVisualizerEffect = ({ @@ -23,8 +26,24 @@ export const WorkflowRunVisualizerEffect = ({ const workflowRun = useWorkflowRun({ workflowRunId }); const workflowVersion = useWorkflowVersion(workflowRun?.workflowVersionId); - const setWorkflowRunId = useSetRecoilState(workflowRunIdState); - const setWorkflowId = useSetRecoilState(workflowIdState); + const setWorkflowRunId = useSetRecoilComponentStateV2( + workflowVisualizerWorkflowRunIdComponentState, + ); + const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2( + workflowVisualizerWorkflowIdComponentState, + ); + + const flowState = useRecoilComponentCallbackStateV2(flowComponentState); + const workflowDiagramState = useRecoilComponentCallbackStateV2( + workflowDiagramComponentState, + ); + const workflowDiagramStatusState = useRecoilComponentCallbackStateV2( + workflowDiagramStatusComponentState, + ); + const workflowRunStepToOpenByDefaultState = useRecoilComponentCallbackStateV2( + workflowRunStepToOpenByDefaultComponentState, + ); + const { populateStepsOutputSchema } = useStepsOutputSchema(); const { isInRightDrawer } = useContext(ActionMenuContext); @@ -38,11 +57,11 @@ export const WorkflowRunVisualizerEffect = ({ return; } - setWorkflowId(workflowRun.workflowId); - }, [setWorkflowId, workflowRun]); + setWorkflowVisualizerWorkflowId(workflowRun.workflowId); + }, [setWorkflowVisualizerWorkflowId, workflowRun]); const handleWorkflowRunDiagramGeneration = useRecoilCallback( - ({ set }) => + ({ snapshot, set }) => ({ workflowRunOutput, workflowVersionId, @@ -59,7 +78,14 @@ export const WorkflowRunVisualizerEffect = ({ return; } - set(workflowDiagramStatusState, 'computing-diagram'); + const workflowDiagramStatus = getSnapshotValue( + snapshot, + workflowDiagramStatusState, + ); + + if (workflowDiagramStatus !== 'done') { + set(workflowDiagramStatusState, 'computing-diagram'); + } set(flowState, { workflowVersionId, @@ -89,9 +115,16 @@ export const WorkflowRunVisualizerEffect = ({ set(workflowDiagramState, baseWorkflowRunDiagram); } - set(workflowDiagramStatusState, 'computing-dimensions'); + if (workflowDiagramStatus !== 'done') { + set(workflowDiagramStatusState, 'computing-dimensions'); + } }, - [], + [ + flowState, + workflowDiagramState, + workflowDiagramStatusState, + workflowRunStepToOpenByDefaultState, + ], ); useEffect(() => { diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect.tsx index 7f73f34f1..084fc5037 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect.tsx @@ -1,11 +1,12 @@ +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; -import { flowState } from '@/workflow/states/flowState'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; +import { flowComponentState } from '@/workflow/states/flowComponentState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState'; +import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const WorkflowVersionVisualizerEffect = ({ @@ -15,10 +16,19 @@ export const WorkflowVersionVisualizerEffect = ({ }) => { const workflowVersion = useWorkflowVersion(workflowVersionId); - const setFlow = useSetRecoilState(flowState); - const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); - const setWorkflowId = useSetRecoilState(workflowIdState); + const setFlow = useSetRecoilComponentStateV2(flowComponentState); + const setWorkflowDiagram = useSetRecoilComponentStateV2( + workflowDiagramComponentState, + ); + const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2( + workflowVisualizerWorkflowIdComponentState, + ); + const setWorkflowVisualizerWorkflowVersionId = useSetRecoilComponentStateV2( + workflowVisualizerWorkflowVersionIdComponentState, + ); + const { populateStepsOutputSchema } = useStepsOutputSchema(); + useEffect(() => { if (!isDefined(workflowVersion)) { setFlow(undefined); @@ -32,8 +42,14 @@ export const WorkflowVersionVisualizerEffect = ({ steps: workflowVersion.steps, }); - setWorkflowId(workflowVersion.workflowId); - }, [setFlow, setWorkflowId, workflowVersion]); + setWorkflowVisualizerWorkflowId(workflowVersion.workflowId); + setWorkflowVisualizerWorkflowVersionId(workflowVersion.id); + }, [ + setFlow, + setWorkflowVisualizerWorkflowId, + setWorkflowVisualizerWorkflowVersionId, + workflowVersion, + ]); useEffect(() => { if (!isDefined(workflowVersion)) { diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVisualizerEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVisualizerEffect.tsx index f3f7ed097..3d5fa311f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVisualizerEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowVisualizerEffect.tsx @@ -1,17 +1,19 @@ -import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; export const WorkflowVisualizerEffect = ({ workflowId, }: { workflowId: string; }) => { - const setWorkflowId = useSetRecoilState(workflowIdState); + const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2( + workflowVisualizerWorkflowIdComponentState, + ); useEffect(() => { - setWorkflowId(workflowId); - }, [setWorkflowId, workflowId]); + setWorkflowVisualizerWorkflowId(workflowId); + }, [setWorkflowVisualizerWorkflowId, workflowId]); return null; }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCustomMarkers.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCustomMarkers.stories.tsx index 8bb3f74b9..8a5eec03c 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCustomMarkers.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCustomMarkers.stories.tsx @@ -9,11 +9,12 @@ import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/com import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge'; import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeDefaultConfiguration'; import { WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeSuccessConfiguration'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator'; import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { workflowDiagramState } from '../../states/workflowDiagramState'; +import { workflowDiagramComponentState } from '../../states/workflowDiagramComponentState'; import { WorkflowDiagramCanvasBase } from '../WorkflowDiagramCanvasBase'; const StyledContainer = styled.div` @@ -51,63 +52,79 @@ export const DefaultEdge: Story = { }, }, decorators: [ - (Story) => ( - { - set(workflowDiagramState, { - nodes: [ + (Story) => { + const workflowVisualizerComponentInstanceId = + 'workflow-visualizer-test-id'; + + return ( + { + set( + workflowDiagramComponentState.atomFamily({ + instanceId: workflowVisualizerComponentInstanceId, + }), { - id: 'trigger-1', - type: 'default', - position: { x: 100, y: 100 }, - data: { - nodeType: 'trigger', - triggerType: 'DATABASE_EVENT', - name: 'When record is created', - }, + nodes: [ + { + id: 'trigger-1', + type: 'default', + position: { x: 100, y: 100 }, + data: { + nodeType: 'trigger', + triggerType: 'DATABASE_EVENT', + name: 'When record is created', + }, + }, + { + id: 'action-1', + type: 'default', + position: { x: 300, y: 100 }, + data: { + nodeType: 'action', + actionType: 'CREATE_RECORD', + name: 'Create record', + }, + }, + { + id: 'create-step-1', + type: 'create-step', + position: { x: 500, y: 100 }, + data: { + nodeType: 'create-step', + parentNodeId: 'action-1', + }, + }, + ], + edges: [ + { + ...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION, + id: 'edge-1', + source: 'trigger-1', + target: 'action-1', + }, + { + ...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION, + id: 'edge-2', + source: 'action-1', + target: 'create-step-1', + }, + ], }, - { - id: 'action-1', - type: 'default', - position: { x: 300, y: 100 }, - data: { - nodeType: 'action', - actionType: 'CREATE_RECORD', - name: 'Create record', - }, - }, - { - id: 'create-step-1', - type: 'create-step', - position: { x: 500, y: 100 }, - data: { - nodeType: 'create-step', - parentNodeId: 'action-1', - }, - }, - ], - edges: [ - { - ...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION, - id: 'edge-1', - source: 'trigger-1', - target: 'action-1', - }, - { - ...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION, - id: 'edge-2', - source: 'action-1', - target: 'create-step-1', - }, - ], - }); - }} - > - - - - - ), + ); + }} + > + + + + + + + ); + }, ], }; @@ -124,49 +141,65 @@ export const SuccessEdge: Story = { }, }, decorators: [ - (Story) => ( - { - set(workflowDiagramState, { - nodes: [ + (Story) => { + const workflowVisualizerComponentInstanceId = + 'workflow-visualizer-test-id'; + + return ( + { + set( + workflowDiagramComponentState.atomFamily({ + instanceId: workflowVisualizerComponentInstanceId, + }), { - id: 'trigger-1', - type: 'default', - position: { x: 100, y: 100 }, - data: { - nodeType: 'trigger', - triggerType: 'DATABASE_EVENT', - name: 'When record is created', - }, + nodes: [ + { + id: 'trigger-1', + type: 'default', + position: { x: 100, y: 100 }, + data: { + nodeType: 'trigger', + triggerType: 'DATABASE_EVENT', + name: 'When record is created', + }, + }, + { + id: 'action-1', + type: 'default', + position: { x: 300, y: 100 }, + data: { + nodeType: 'action', + actionType: 'CREATE_RECORD', + name: 'Create record', + }, + }, + ], + edges: [ + { + ...WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION, + id: 'edge-1', + source: 'trigger-1', + target: 'action-1', + type: 'success', + label: '1 item', + }, + ], }, - { - id: 'action-1', - type: 'default', - position: { x: 300, y: 100 }, - data: { - nodeType: 'action', - actionType: 'CREATE_RECORD', - name: 'Create record', - }, - }, - ], - edges: [ - { - ...WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION, - id: 'edge-1', - source: 'trigger-1', - target: 'action-1', - type: 'success', - label: '1 item', - }, - ], - }); - }} - > - - - - - ), + ); + }} + > + + + + + + + ); + }, ], }; 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 index d28c33ba5..bade0f15d 100644 --- 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 @@ -1,15 +1,25 @@ +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection'; -import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState'; +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, useRecoilState } from 'recoil'; +import { RecoilRoot } from 'recoil'; jest.mock('@xyflow/react', () => ({ useReactFlow: jest.fn(), })); const wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} + + + {children} + + ); describe('useTriggerNodeSelection', () => { @@ -31,7 +41,9 @@ describe('useTriggerNodeSelection', () => { const [ workflowDiagramTriggerNodeSelection, setWorkflowDiagramTriggerNodeSelection, - ] = useRecoilState(workflowDiagramTriggerNodeSelectionState); + ] = useRecoilComponentStateV2( + workflowDiagramTriggerNodeSelectionComponentState, + ); useTriggerNodeSelection(); 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 index ab8849c84..4b52dd43c 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit.ts @@ -1,10 +1,12 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState'; -import { workflowRunStepToOpenByDefaultState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState'; +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'; @@ -17,6 +19,21 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => { const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu(); const { isInRightDrawer } = useContext(ActionMenuContext); + const workflowRunId = useWorkflowRunIdOrThrow(); + + const workflowVisualizerWorkflowIdState = useRecoilComponentCallbackStateV2( + workflowVisualizerWorkflowIdComponentState, + ); + const workflowDiagramStatusState = useRecoilComponentCallbackStateV2( + workflowDiagramStatusComponentState, + ); + const workflowRunStepToOpenByDefaultState = useRecoilComponentCallbackStateV2( + workflowRunStepToOpenByDefaultComponentState, + ); + const workflowSelectedNodeState = useRecoilComponentCallbackStateV2( + workflowSelectedNodeComponentState, + ); + const handleWorkflowRunDiagramCanvasInit = useRecoilCallback( ({ snapshot, set }) => () => { @@ -43,8 +60,11 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => { ); if (isDefined(workflowStepToOpenByDefault)) { - const workflowId = getSnapshotValue(snapshot, workflowIdState); - if (!isDefined(workflowId)) { + 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.', ); @@ -53,7 +73,8 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => { set(workflowSelectedNodeState, workflowStepToOpenByDefault.id); openWorkflowRunViewStepInCommandMenu({ - workflowId, + workflowId: workflowVisualizerWorkflowId, + workflowRunId, title: workflowStepToOpenByDefault.data.name, icon: getIcon( getWorkflowNodeIconKey(workflowStepToOpenByDefault.data), @@ -65,7 +86,16 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => { set(workflowRunStepToOpenByDefaultState, undefined); } }, - [getIcon, isInRightDrawer, openWorkflowRunViewStepInCommandMenu], + [ + workflowDiagramStatusState, + isInRightDrawer, + workflowRunStepToOpenByDefaultState, + workflowVisualizerWorkflowIdState, + workflowSelectedNodeState, + openWorkflowRunViewStepInCommandMenu, + workflowRunId, + getIcon, + ], ); return { 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 218205f5b..1487c835a 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,18 +1,21 @@ import { useCallback } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; +import { workflowCreateStepFromParentStepIdComponentState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState'; import { isDefined } from 'twenty-shared/utils'; export const useStartNodeCreation = () => { - const setWorkflowCreateStepFromParentStepId = useSetRecoilState( - workflowCreateStepFromParentStepIdState, + const setWorkflowCreateStepFromParentStepId = useSetRecoilComponentStateV2( + workflowCreateStepFromParentStepIdComponentState, ); const { openStepSelectInCommandMenu } = useWorkflowCommandMenu(); - const workflowId = useRecoilValue(workflowIdState); + const workflowVisualizerWorkflowId = useRecoilComponentValueV2( + workflowVisualizerWorkflowIdComponentState, + ); /** * This function is used in a context where dependencies shouldn't change much. @@ -22,14 +25,14 @@ export const useStartNodeCreation = () => { (parentNodeId: string) => { setWorkflowCreateStepFromParentStepId(parentNodeId); - if (isDefined(workflowId)) { - openStepSelectInCommandMenu(workflowId); + if (isDefined(workflowVisualizerWorkflowId)) { + openStepSelectInCommandMenu(workflowVisualizerWorkflowId); return; } }, [ setWorkflowCreateStepFromParentStepId, - workflowId, + workflowVisualizerWorkflowId, openStepSelectInCommandMenu, ], ); 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 index fe8a87c3f..bddbaaf12 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useTriggerNodeSelection.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useTriggerNodeSelection.ts @@ -1,11 +1,11 @@ -import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; +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 { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const useTriggerNodeSelection = () => { @@ -14,7 +14,9 @@ export const useTriggerNodeSelection = () => { const [ workflowDiagramTriggerNodeSelection, setWorkflowDiagramTriggerNodeSelection, - ] = useRecoilState(workflowDiagramTriggerNodeSelectionState); + ] = useRecoilComponentStateV2( + workflowDiagramTriggerNodeSelectionComponentState, + ); useEffect(() => { if (!isDefined(workflowDiagramTriggerNodeSelection)) { diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow.ts index 10a62c6c1..b36fb2985 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow.ts @@ -1,9 +1,11 @@ -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { isDefined } from 'twenty-shared/utils'; export const useWorkflowSelectedNodeOrThrow = () => { - const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState); + const workflowSelectedNode = useRecoilComponentValueV2( + workflowSelectedNodeComponentState, + ); if (!isDefined(workflowSelectedNode)) { throw new Error( diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowRunVisualizerComponentInstanceContext.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowRunVisualizerComponentInstanceContext.tsx new file mode 100644 index 000000000..e9787f3ab --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowRunVisualizerComponentInstanceContext.tsx @@ -0,0 +1,6 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const WorkflowRunVisualizerComponentInstanceContext = + createComponentInstanceContext({ + instanceId: '', + }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext.tsx new file mode 100644 index 000000000..64493b8e2 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext.tsx @@ -0,0 +1,6 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const WorkflowVisualizerComponentInstanceContext = + createComponentInstanceContext({ + instanceId: '', + }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramComponentState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramComponentState.ts new file mode 100644 index 000000000..f861397f4 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramComponentState.ts @@ -0,0 +1,11 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; +import { WorkflowDiagram } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; + +export const workflowDiagramComponentState = createComponentStateV2< + WorkflowDiagram | undefined +>({ + key: 'workflowDiagramComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramState.ts deleted file mode 100644 index fc535453b..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramState.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { WorkflowDiagram } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; -import { createState } from 'twenty-ui/utilities'; - -export const workflowDiagramState = createState({ - key: 'workflowDiagramState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusComponentState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusComponentState.ts new file mode 100644 index 000000000..f63685f25 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusComponentState.ts @@ -0,0 +1,12 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowRunVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowRunVisualizerComponentInstanceContext'; +import { WorkflowDiagramStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagramStatus'; + +// This state must be fresh every time the Reactflow component is mounted. +// We use another instance context whose instanceId is an id unique to the component hierarchy. +export const workflowDiagramStatusComponentState = + createComponentStateV2({ + key: 'workflowDiagramStatusComponentState', + defaultValue: 'computing-diagram', + componentInstanceContext: WorkflowRunVisualizerComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusState.ts deleted file mode 100644 index 6a43fac9f..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramStatusState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; - -export const workflowDiagramStatusState = createState< - 'computing-diagram' | 'computing-dimensions' | 'done' ->({ - key: 'workflowDiagramStatusState', - defaultValue: 'computing-diagram', -}); 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 new file mode 100644 index 000000000..341dba6d7 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState.ts @@ -0,0 +1,9 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const workflowDiagramTriggerNodeSelectionComponentState = + createComponentStateV2({ + key: 'workflowDiagramTriggerNodeSelectionComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState.ts deleted file mode 100644 index a1df2bab9..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; -export const workflowDiagramTriggerNodeSelectionState = createState< - string | undefined ->({ - key: 'workflowDiagramTriggerNodeSelectionState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowReactFlowRefState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowReactFlowRefState.ts deleted file mode 100644 index 3f16972b7..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowReactFlowRefState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { RefObject } from 'react'; -import { createState } from 'twenty-ui/utilities'; - -export const workflowReactFlowRefState = - createState | null>({ - key: 'workflowReactFlowRefState', - defaultValue: null, - }); 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 new file mode 100644 index 000000000..f0830b445 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState.ts @@ -0,0 +1,16 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; +import { WorkflowRunDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; + +export const workflowRunStepToOpenByDefaultComponentState = + createComponentStateV2< + | { + id: string; + data: WorkflowRunDiagramStepNodeData; + } + | undefined + >({ + key: 'workflowRunStepToOpenByDefaultComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState.ts deleted file mode 100644 index d17465faf..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { WorkflowRunDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; -import { createState } from 'twenty-ui/utilities'; - -export const workflowRunStepToOpenByDefaultState = createState< - | { - id: string; - data: WorkflowRunDiagramStepNodeData; - } - | undefined ->({ - key: 'workflowStepIdToOpenByDefaultState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeComponentState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeComponentState.ts new file mode 100644 index 000000000..0ffad0a3b --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeComponentState.ts @@ -0,0 +1,10 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const workflowSelectedNodeComponentState = createComponentStateV2< + string | undefined +>({ + key: 'workflowSelectedNodeComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeState.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeState.ts deleted file mode 100644 index 6046be903..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/states/workflowSelectedNodeState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; - -export const workflowSelectedNodeState = createState({ - key: 'workflowSelectedNodeState', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagramStatus.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagramStatus.ts new file mode 100644 index 000000000..5b5d7aede --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagramStatus.ts @@ -0,0 +1,4 @@ +export type WorkflowDiagramStatus = + | 'computing-diagram' + | 'computing-dimensions' + | 'done'; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.ts deleted file mode 100644 index 3d9694c2c..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; -import { renderHook } from '@testing-library/react'; -import { useCreateStep } from '../useCreateStep'; - -const mockCreateDraftFromWorkflowVersion = jest.fn().mockResolvedValue('457'); -const mockCreateWorkflowVersionStep = jest.fn().mockResolvedValue({ - data: { createWorkflowVersionStep: { id: '1', type: 'CODE' } }, -}); - -jest.mock('recoil', () => ({ - useRecoilValue: () => 'parent-step-id', - useSetRecoilState: () => jest.fn(), - atom: (params: any) => params, -})); - -jest.mock( - '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionStep', - () => ({ - useCreateWorkflowVersionStep: () => ({ - createWorkflowVersionStep: mockCreateWorkflowVersionStep, - }), - }), -); - -jest.mock('@/workflow/hooks/useCreateDraftFromWorkflowVersion', () => ({ - useCreateDraftFromWorkflowVersion: () => ({ - createDraftFromWorkflowVersion: mockCreateDraftFromWorkflowVersion, - }), -})); - -describe('useCreateStep', () => { - const mockWorkflow = { - id: '123', - currentVersion: { - id: '456', - status: 'DRAFT', - steps: [], - trigger: { type: 'manual' }, - }, - versions: [], - }; - - it('should create step in draft version', async () => { - const { result } = renderHook(() => - useCreateStep({ - workflow: mockWorkflow as unknown as WorkflowWithCurrentVersion, - }), - ); - await result.current.createStep('CODE'); - - expect(mockCreateWorkflowVersionStep).toHaveBeenCalled(); - }); -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.tsx new file mode 100644 index 000000000..c00e8c934 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/__tests__/useCreateStep.test.tsx @@ -0,0 +1,80 @@ +import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; +import { workflowCreateStepFromParentStepIdComponentState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState'; +import { renderHook } from '@testing-library/react'; +import { RecoilRoot } from 'recoil'; +import { WorkflowVisualizerComponentInstanceContext } from '../../../workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; +import { useCreateStep } from '../useCreateStep'; + +const mockCreateDraftFromWorkflowVersion = jest.fn().mockResolvedValue('457'); +const mockCreateWorkflowVersionStep = jest.fn().mockResolvedValue({ + data: { createWorkflowVersionStep: { id: '1', type: 'CODE' } }, +}); + +jest.mock( + '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionStep', + () => ({ + useCreateWorkflowVersionStep: () => ({ + createWorkflowVersionStep: mockCreateWorkflowVersionStep, + }), + }), +); + +jest.mock('@/workflow/hooks/useCreateDraftFromWorkflowVersion', () => ({ + useCreateDraftFromWorkflowVersion: () => ({ + createDraftFromWorkflowVersion: mockCreateDraftFromWorkflowVersion, + }), +})); + +const wrapper = ({ children }: { children: React.ReactNode }) => { + const workflowVisualizerComponentInstanceId = + 'workflow-visualizer-instance-id'; + + return ( + { + set( + workflowCreateStepFromParentStepIdComponentState.atomFamily({ + instanceId: workflowVisualizerComponentInstanceId, + }), + 'parent-step-id', + ); + }} + > + + {children} + + + ); +}; + +describe('useCreateStep', () => { + const mockWorkflow = { + id: '123', + currentVersion: { + id: '456', + status: 'DRAFT', + steps: [], + trigger: { type: 'manual' }, + }, + versions: [], + }; + + it('should create step in draft version', async () => { + const { result } = renderHook( + () => + useCreateStep({ + workflow: mockWorkflow as unknown as WorkflowWithCurrentVersion, + }), + { + wrapper, + }, + ); + await result.current.createStep('CODE'); + + expect(mockCreateWorkflowVersionStep).toHaveBeenCalled(); + }); +}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useCreateStep.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useCreateStep.ts index 674ca3a0f..d41459a1e 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useCreateStep.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/hooks/useCreateStep.ts @@ -1,13 +1,14 @@ +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion'; -import { workflowLastCreatedStepIdState } from '@/workflow/states/workflowLastCreatedStepIdState'; +import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState'; import { WorkflowStepType, WorkflowWithCurrentVersion, } from '@/workflow/types/Workflow'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { useCreateWorkflowVersionStep } from '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionStep'; -import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { workflowCreateStepFromParentStepIdComponentState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState'; import { isDefined } from 'twenty-shared/utils'; export const useCreateStep = ({ @@ -16,13 +17,15 @@ export const useCreateStep = ({ workflow: WorkflowWithCurrentVersion; }) => { const { createWorkflowVersionStep } = useCreateWorkflowVersionStep(); - const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); - const setWorkflowLastCreatedStepId = useSetRecoilState( - workflowLastCreatedStepIdState, + const setWorkflowSelectedNode = useSetRecoilComponentStateV2( + workflowSelectedNodeComponentState, + ); + const setWorkflowLastCreatedStepId = useSetRecoilComponentStateV2( + workflowLastCreatedStepIdComponentState, ); - const workflowCreateStepFromParentStepId = useRecoilValue( - workflowCreateStepFromParentStepIdState, + const workflowCreateStepFromParentStepId = useRecoilComponentValueV2( + workflowCreateStepFromParentStepIdComponentState, ); const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState.ts new file mode 100644 index 000000000..98153b858 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState.ts @@ -0,0 +1,9 @@ +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; + +export const workflowCreateStepFromParentStepIdComponentState = + createComponentStateV2({ + key: 'workflowCreateStepFromParentStepIdComponentState', + defaultValue: undefined, + componentInstanceContext: WorkflowVisualizerComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState.ts deleted file mode 100644 index e8f52d0a6..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; -export const workflowCreateStepFromParentStepIdState = createState< - string | undefined ->({ - key: 'workflowCreateStepFromParentStepId', - defaultValue: undefined, -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunction.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunction.tsx index 8de526e9a..2b43b89cd 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunction.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunction.tsx @@ -3,7 +3,7 @@ import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-func import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction'; import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { WorkflowCodeAction } from '@/workflow/types/Workflow'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue'; @@ -36,11 +36,11 @@ import { Monaco } from '@monaco-editor/react'; import { editor } from 'monaco-editor'; import { AutoTypings } from 'monaco-editor-auto-typings'; import { useEffect, useState } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; -import { useDebouncedCallback } from 'use-debounce'; +import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; -import { CodeEditor } from 'twenty-ui/input'; import { IconCode, IconPlayerPlay, useIcons } from 'twenty-ui/display'; +import { CodeEditor } from 'twenty-ui/input'; +import { useDebouncedCallback } from 'use-debounce'; const StyledCodeEditorContainer = styled.div` display: flex; @@ -82,8 +82,10 @@ export const WorkflowEditActionServerlessFunction = ({ useUpdateOneServerlessFunction(serverlessFunctionId); const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion(); - const workflowId = useRecoilValue(workflowIdState); - const workflow = useWorkflowWithCurrentVersion(workflowId); + const workflowVisualizerWorkflowId = useRecoilComponentValueV2( + workflowVisualizerWorkflowIdComponentState, + ); + const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId); const { availablePackages } = useGetAvailablePackages({ id: serverlessFunctionId, }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx index 0869263f8..bfb6efd0e 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx @@ -8,7 +8,8 @@ import { FormTextFieldInput } from '@/object-record/record-field/form-types/comp import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; import { SettingsPath } from '@/types/SettingsPath'; import { Select } from '@/ui/input/components/Select'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { WorkflowSendEmailAction } from '@/workflow/types/Workflow'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; @@ -20,11 +21,11 @@ import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; +import { IconPlus, useIcons } from 'twenty-ui/display'; +import { SelectOption } from 'twenty-ui/input'; import { JsonValue } from 'type-fest'; import { useDebouncedCallback } from 'use-debounce'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; -import { IconPlus, useIcons } from 'twenty-ui/display'; -import { SelectOption } from 'twenty-ui/input'; type WorkflowEditActionSendEmailProps = { action: WorkflowSendEmailAction; @@ -53,8 +54,10 @@ export const WorkflowEditActionSendEmail = ({ const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const { triggerApisOAuth } = useTriggerApisOAuth(); - const workflowId = useRecoilValue(workflowIdState); - const redirectUrl = `/object/workflow/${workflowId}`; + const workflowVisualizerWorkflowId = useRecoilComponentValueV2( + workflowVisualizerWorkflowIdComponentState, + ); + const redirectUrl = `/object/workflow/${workflowVisualizerWorkflowId}`; const [formData, setFormData] = useState({ connectedAccountId: action.settings.input.connectedAccountId, diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType.tsx deleted file mode 100644 index c41a8f3fd..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { RightDrawerWorkflowSelectTriggerTypeContent } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent'; -import { useRecoilValue } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; - -export const RightDrawerWorkflowSelectTriggerType = () => { - const workflowId = useRecoilValue(workflowIdState); - const workflow = useWorkflowWithCurrentVersion(workflowId); - - if (!isDefined(workflow)) { - return null; - } - - return ; -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx deleted file mode 100644 index 20ea25d13..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; -import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; -import { - WorkflowTriggerType, - WorkflowWithCurrentVersion, -} from '@/workflow/types/Workflow'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; -import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer'; -import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle'; -import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes'; -import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; -import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; -import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition'; -import { useSetRecoilState } from 'recoil'; -import { MenuItemCommand } from 'twenty-ui/navigation'; -import { useIcons } from 'twenty-ui/display'; - -export const RightDrawerWorkflowSelectTriggerTypeContent = ({ - workflow, -}: { - workflow: WorkflowWithCurrentVersion; -}) => { - const { getIcon } = useIcons(); - const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow }); - - const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); - - const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); - const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu(); - - const handleTriggerTypeClick = ({ - type, - defaultLabel, - icon, - }: { - type: WorkflowTriggerType; - defaultLabel: string; - icon: string; - }) => { - return async () => { - await updateTrigger( - getTriggerDefaultDefinition({ - defaultLabel, - type, - activeObjectMetadataItems, - }), - ); - - setWorkflowSelectedNode(TRIGGER_STEP_ID); - - openWorkflowEditStepInCommandMenu( - workflow.id, - defaultLabel, - getIcon(icon), - ); - }; - }; - - return ( - - - Data - - {DATABASE_TRIGGER_TYPES.map((action) => ( - - ))} - - Others - - {OTHER_TRIGGER_TYPES.map((action) => ( - - ))} - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx index 0b39d9d53..159be6634 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx @@ -5,7 +5,8 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Select } from '@/ui/input/components/Select'; import { TextInputV2 } from '@/ui/input/components/TextInputV2'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { WorkflowWebhookTrigger } from '@/workflow/types/Workflow'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; @@ -54,7 +55,9 @@ export const WorkflowEditTriggerWebhookForm = ({ const [errorMessages, setErrorMessages] = useState({}); const [errorMessagesVisible, setErrorMessagesVisible] = useState(false); const { getIcon } = useIcons(); - const workflowId = useRecoilValue(workflowIdState); + const workflowVisualizerWorkflowId = useRecoilComponentValueV2( + workflowVisualizerWorkflowIdComponentState, + ); const currentWorkspace = useRecoilValue(currentWorkspaceState); const onBlur = () => { @@ -66,7 +69,7 @@ export const WorkflowEditTriggerWebhookForm = ({ const headerIcon = getTriggerIcon(trigger); const headerType = getTriggerHeaderType(trigger); - const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowId}`; + const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowVisualizerWorkflowId}`; const displayWebhookUrl = webhookUrl.replace(/^(https?:\/\/)?(www\.)?/, ''); const copyToClipboard = async () => { diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx index 5fa9e0a3f..d25d2a057 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems.tsx @@ -13,13 +13,12 @@ import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isReco import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath'; import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel'; import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema'; import { useState } from 'react'; -import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconChevronLeft, @@ -42,13 +41,15 @@ export const WorkflowVariablesDropdownFieldItems = ({ const [currentPath, setCurrentPath] = useState([]); const [searchInputValue, setSearchInputValue] = useState(''); const { getIcon } = useIcons(); - const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); + const setWorkflowSelectedNode = useSetRecoilComponentStateV2( + workflowSelectedNodeComponentState, + ); const setActiveTabId = useSetRecoilComponentStateV2( activeTabIdComponentState, 'workflow-serverless-function-tab-list-component-id', ); - const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState( - workflowDiagramTriggerNodeSelectionState, + const setWorkflowDiagramTriggerNodeSelection = useSetRecoilComponentStateV2( + workflowDiagramTriggerNodeSelectionComponentState, ); const getDisplayedSubStepFields = () => { diff --git a/packages/twenty-front/src/testing/decorators/WorkflowStepDecorator.tsx b/packages/twenty-front/src/testing/decorators/WorkflowStepDecorator.tsx index 943e8bfd4..71c639bcd 100644 --- a/packages/twenty-front/src/testing/decorators/WorkflowStepDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/WorkflowStepDecorator.tsx @@ -1,52 +1,75 @@ import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; -import { flowState } from '@/workflow/states/flowState'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { flowComponentState } from '@/workflow/states/flowComponentState'; +import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { WorkflowVersion } from '@/workflow/types/Workflow'; -import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; +import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext'; +import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { Decorator } from '@storybook/react'; import { useEffect, useState } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { getWorkflowMock, getWorkflowNodeIdMock, } from '~/testing/mock-data/workflow'; export const WorkflowStepDecorator: Decorator = (Story) => { - const setWorkflowId = useSetRecoilState(workflowIdState); - const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); - const setFlow = useSetRecoilState(flowState); + const workflowVisualizerComponentInstanceId = 'workflow-visualizer-test-id'; + const workflowVersion = getWorkflowMock().versions.edges[0] .node as WorkflowVersion; const { populateStepsOutputSchema } = useStepsOutputSchema(); const [ready, setReady] = useState(false); + const handleMount = useRecoilCallback( + ({ set }) => + () => { + set( + workflowVisualizerWorkflowIdComponentState.atomFamily({ + instanceId: workflowVisualizerComponentInstanceId, + }), + getWorkflowMock().id, + ); + set( + workflowSelectedNodeComponentState.atomFamily({ + instanceId: workflowVisualizerComponentInstanceId, + }), + getWorkflowNodeIdMock(), + ); + set( + flowComponentState.atomFamily({ + instanceId: workflowVisualizerComponentInstanceId, + }), + { + workflowVersionId: workflowVersion.id, + trigger: workflowVersion.trigger, + steps: workflowVersion.steps, + }, + ); + populateStepsOutputSchema(workflowVersion); + setReady(true); + }, + [populateStepsOutputSchema, workflowVersion], + ); + useEffect(() => { - setWorkflowId(getWorkflowMock().id); - setWorkflowSelectedNode(getWorkflowNodeIdMock()); - setFlow({ - workflowVersionId: workflowVersion.id, - trigger: workflowVersion.trigger, - steps: workflowVersion.steps, - }); - populateStepsOutputSchema(workflowVersion); - setReady(true); - }, [ - setWorkflowId, - setWorkflowSelectedNode, - populateStepsOutputSchema, - workflowVersion, - setFlow, - ]); + handleMount(); + }, [handleMount]); return ( - - {ready && } - + + {ready && } + + ); };