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 `<Reactflow />` 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.
This commit is contained in:
Baptiste Devessier
2025-05-05 10:58:11 +02:00
committed by GitHub
parent 8b68dce795
commit 1543c900ae
73 changed files with 1209 additions and 752 deletions

View File

@ -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('Workflow Runs with a pending form step can be opened in the side panel and then in full screen', async ({
test.fail('Workflow Runs with a pending form step can be opened in the side panel and then in full screen', async ({
workflowVisualizer, workflowVisualizer,
page, page,
}) => { }) => {

View File

@ -4,7 +4,8 @@ import { useRecoilValue } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId'; import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState'; import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState'; 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 { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { act } from 'react'; import { act } from 'react';
import { IconBolt, IconSettingsAutomation, useIcons } from 'twenty-ui/display';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { useWorkflowCommandMenu } from '../useWorkflowCommandMenu'; import { useWorkflowCommandMenu } from '../useWorkflowCommandMenu';
import { IconBolt, IconSettingsAutomation, useIcons } from 'twenty-ui/display';
jest.mock('uuid', () => ({ jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('mocked-uuid'), v4: jest.fn().mockReturnValue('mocked-uuid'),
@ -95,7 +96,11 @@ const renderHooks = () => {
'mocked-uuid', 'mocked-uuid',
); );
const workflowId = useRecoilComponentValueV2( const workflowId = useRecoilComponentValueV2(
workflowIdComponentState, commandMenuWorkflowIdComponentState,
'mocked-uuid',
);
const workflowVersionId = useRecoilComponentValueV2(
commandMenuWorkflowVersionIdComponentState,
'mocked-uuid', 'mocked-uuid',
); );
const { getIcon } = useIcons(); const { getIcon } = useIcons();
@ -106,6 +111,7 @@ const renderHooks = () => {
openWorkflowEditStepInCommandMenu, openWorkflowEditStepInCommandMenu,
openWorkflowViewStepInCommandMenu, openWorkflowViewStepInCommandMenu,
workflowId, workflowId,
workflowVersionId,
viewableRecordId, viewableRecordId,
commandMenuPage, commandMenuPage,
commandMenuNavigationMorphItemByPage, commandMenuNavigationMorphItemByPage,
@ -188,14 +194,16 @@ describe('useWorkflowCommandMenu', () => {
const { result } = renderHooks(); const { result } = renderHooks();
act(() => { act(() => {
result.current.openWorkflowViewStepInCommandMenu( result.current.openWorkflowViewStepInCommandMenu({
'test-workflow-id', workflowId: 'test-workflow-id',
'View Step', workflowVersionId: 'test-workflow-version-id',
IconSettingsAutomation, icon: IconSettingsAutomation,
); title: 'View Step',
});
}); });
expect(result.current.workflowId).toBe('test-workflow-id'); expect(result.current.workflowId).toBe('test-workflow-id');
expect(result.current.workflowVersionId).toBe('test-workflow-version-id');
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({ expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.WorkflowStepView, page: CommandMenuPages.WorkflowStepView,

View File

@ -1,5 +1,7 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; 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 { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useSetInitialWorkflowRunRightDrawerTab } from '@/workflow/workflow-diagram/hooks/useSetInitialWorkflowRunRightDrawerTab'; import { useSetInitialWorkflowRunRightDrawerTab } from '@/workflow/workflow-diagram/hooks/useSetInitialWorkflowRunRightDrawerTab';
import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
@ -23,7 +25,9 @@ export const useWorkflowCommandMenu = () => {
const pageId = v4(); const pageId = v4();
set( set(
workflowIdComponentState.atomFamily({ instanceId: pageId }), commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId, workflowId,
); );
@ -44,7 +48,9 @@ export const useWorkflowCommandMenu = () => {
const pageId = v4(); const pageId = v4();
set( set(
workflowIdComponentState.atomFamily({ instanceId: pageId }), commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId, workflowId,
); );
@ -65,7 +71,9 @@ export const useWorkflowCommandMenu = () => {
const pageId = v4(); const pageId = v4();
set( set(
workflowIdComponentState.atomFamily({ instanceId: pageId }), commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId, workflowId,
); );
@ -82,13 +90,31 @@ export const useWorkflowCommandMenu = () => {
const openWorkflowViewStepInCommandMenu = useRecoilCallback( const openWorkflowViewStepInCommandMenu = useRecoilCallback(
({ set }) => { ({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => { return ({
workflowId,
workflowVersionId,
title,
icon,
}: {
workflowId: string;
workflowVersionId: string;
title: string;
icon: IconComponent;
}) => {
const pageId = v4(); const pageId = v4();
set( set(
workflowIdComponentState.atomFamily({ instanceId: pageId }), commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId, workflowId,
); );
set(
commandMenuWorkflowVersionIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowVersionId,
);
navigateCommandMenu({ navigateCommandMenu({
page: CommandMenuPages.WorkflowStepView, page: CommandMenuPages.WorkflowStepView,
@ -105,12 +131,14 @@ export const useWorkflowCommandMenu = () => {
({ set }) => { ({ set }) => {
return ({ return ({
workflowId, workflowId,
workflowRunId,
title, title,
icon, icon,
workflowSelectedNode, workflowSelectedNode,
stepExecutionStatus, stepExecutionStatus,
}: { }: {
workflowId: string; workflowId: string;
workflowRunId: string;
title: string; title: string;
icon: IconComponent; icon: IconComponent;
workflowSelectedNode: string; workflowSelectedNode: string;
@ -119,9 +147,17 @@ export const useWorkflowCommandMenu = () => {
const pageId = v4(); const pageId = v4();
set( set(
workflowIdComponentState.atomFamily({ instanceId: pageId }), commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId, workflowId,
); );
set(
commandMenuWorkflowRunIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowRunId,
);
navigateCommandMenu({ navigateCommandMenu({
page: CommandMenuPages.WorkflowRunStepView, page: CommandMenuPages.WorkflowRunStepView,

View File

@ -1,16 +1,27 @@
import { CommandMenuWorkflowSelectActionContent } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent'; import { CommandMenuWorkflowSelectActionContent } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent';
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState'; import { useCommandMenuWorkflowIdOrThrow } from '@/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; 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'; import { isDefined } from 'twenty-shared/utils';
export const CommandMenuWorkflowSelectAction = () => { export const CommandMenuWorkflowSelectAction = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState); const workflowId = useCommandMenuWorkflowIdOrThrow();
const workflow = useWorkflowWithCurrentVersion(workflowId); const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) { if (!isDefined(workflow)) {
return null; return null;
} }
return <CommandMenuWorkflowSelectActionContent workflow={workflow} />; return (
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId: workflowId,
}),
}}
>
<CommandMenuWorkflowSelectActionContent workflow={workflow} />
</WorkflowVisualizerComponentInstanceContext.Provider>
);
}; };

View File

@ -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;
};

View File

@ -1,7 +1,7 @@
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const workflowIdComponentState = createComponentStateV2< export const commandMenuWorkflowIdComponentState = createComponentStateV2<
string | undefined string | undefined
>({ >({
key: 'command-menu/workflow-id', key: 'command-menu/workflow-id',

View File

@ -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,
});

View File

@ -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<string | undefined>({
key: 'command-menu/workflow-version-id',
defaultValue: undefined,
componentInstanceContext: CommandMenuPageComponentInstanceContext,
});

View File

@ -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 { 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 { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; 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'; import { isDefined } from 'twenty-shared/utils';
export const CommandMenuWorkflowEditStep = () => { export const CommandMenuWorkflowEditStep = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState); const workflowId = useCommandMenuWorkflowIdOrThrow();
const workflow = useWorkflowWithCurrentVersion(workflowId); const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) { if (!isDefined(workflow)) {
@ -14,10 +15,18 @@ export const CommandMenuWorkflowEditStep = () => {
} }
return ( return (
<WorkflowStepContextProvider <WorkflowVisualizerComponentInstanceContext.Provider
value={{ workflowVersionId: workflow.currentVersion.id }} value={{
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId: workflowId,
}),
}}
> >
<CommandMenuWorkflowEditStepContent workflow={workflow} /> <WorkflowStepContextProvider
</WorkflowStepContextProvider> value={{ workflowVersionId: workflow.currentVersion.id }}
>
<CommandMenuWorkflowEditStepContent workflow={workflow} />
</WorkflowStepContextProvider>
</WorkflowVisualizerComponentInstanceContext.Provider>
); );
}; };

View File

@ -1,132 +1,20 @@
import { getIsInputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled'; import { CommandMenuWorkflowRunViewStepContent } from '@/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent';
import { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled'; import { useCommandMenuWorkflowRunIdOrThrow } from '@/command-menu/pages/workflow/step/view-run/hooks/useCommandMenuWorkflowRunIdOrThrow';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList'; import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
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 CommandMenuWorkflowRunViewStep = () => { export const CommandMenuWorkflowRunViewStep = () => {
const flow = useFlowOrThrow(); const workflowRunId = useCommandMenuWorkflowRunIdOrThrow();
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<TabId>[] = [
{
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 ( return (
<WorkflowStepContextProvider <WorkflowVisualizerComponentInstanceContext.Provider
value={{ value={{
workflowVersionId: workflowRun.workflowVersionId, instanceId: getWorkflowVisualizerComponentInstanceId({
workflowRunId: workflowRun.id, recordId: workflowRunId,
}),
}} }}
> >
<StyledContainer> <CommandMenuWorkflowRunViewStepContent />
<StyledTabList </WorkflowVisualizerComponentInstanceContext.Provider>
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={commandMenuPageComponentInstance.instanceId}
/>
{activeTabId === WorkflowRunTabId.OUTPUT ? (
<WorkflowRunStepOutputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}
{activeTabId === WorkflowRunTabId.NODE ? (
<WorkflowRunStepNodeDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
stepExecutionStatus={stepExecutionStatus}
/>
) : null}
{activeTabId === WorkflowRunTabId.INPUT ? (
<WorkflowRunStepInputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}
</StyledContainer>
</WorkflowStepContextProvider>
); );
}; };

View File

@ -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<TabId>[] = [
{
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 (
<WorkflowStepContextProvider
value={{
workflowVersionId: workflowRun.workflowVersionId,
workflowRunId: workflowRun.id,
}}
>
<StyledContainer>
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={commandMenuPageComponentInstance.instanceId}
/>
{activeTabId === WorkflowRunTabId.OUTPUT ? (
<WorkflowRunStepOutputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}
{activeTabId === WorkflowRunTabId.NODE ? (
<WorkflowRunStepNodeDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
stepExecutionStatus={stepExecutionStatus}
/>
) : null}
{activeTabId === WorkflowRunTabId.INPUT ? (
<WorkflowRunStepInputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}
</StyledContainer>
</WorkflowStepContextProvider>
);
};

View File

@ -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;
};

View File

@ -1,31 +1,20 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; import { useCommandMenuWorkflowVersionIdOrThrow } from '@/command-menu/pages/workflow/step/view/hooks/useCommandMenuWorkflowVersionIdOrThrow';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; import { CommandMenuWorkflowViewStepContent } from '@/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStepContent';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import styled from '@emotion/styled';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
export const CommandMenuWorkflowViewStep = () => { export const CommandMenuWorkflowViewStep = () => {
const flow = useFlowOrThrow(); const workflowVersionId = useCommandMenuWorkflowVersionIdOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
return ( return (
<WorkflowStepContextProvider <WorkflowVisualizerComponentInstanceContext.Provider
value={{ workflowVersionId: flow.workflowVersionId }} value={{
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId: workflowVersionId,
}),
}}
> >
<StyledContainer> <CommandMenuWorkflowViewStepContent />
<WorkflowStepDetail </WorkflowVisualizerComponentInstanceContext.Provider>
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
readonly
/>
</StyledContainer>
</WorkflowStepContextProvider>
); );
}; };

View File

@ -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 (
<WorkflowStepContextProvider
value={{ workflowVersionId: flow.workflowVersionId }}
>
<StyledContainer>
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
readonly
/>
</StyledContainer>
</WorkflowStepContextProvider>
);
};

View File

@ -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;
};

View File

@ -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 { 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 { 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'; import { isDefined } from 'twenty-shared/utils';
export const CommandMenuWorkflowSelectTriggerType = () => { export const CommandMenuWorkflowSelectTriggerType = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState); const workflowId = useCommandMenuWorkflowIdOrThrow();
const workflow = useWorkflowWithCurrentVersion(workflowId); const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) { if (!isDefined(workflow)) {
return null; return null;
} }
return <CommandMenuWorkflowSelectTriggerTypeContent workflow={workflow} />; return (
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId: workflowId,
}),
}}
>
<CommandMenuWorkflowSelectTriggerTypeContent workflow={workflow} />
</WorkflowVisualizerComponentInstanceContext.Provider>
);
}; };

View File

@ -1,10 +1,11 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { import {
WorkflowTriggerType, WorkflowTriggerType,
WorkflowWithCurrentVersion, WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow'; } 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 { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle'; import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes'; 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 { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition'; 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 { useIcons } from 'twenty-ui/display';
import { MenuItemCommand } from 'twenty-ui/navigation';
export const CommandMenuWorkflowSelectTriggerTypeContent = ({ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
workflow, workflow,
@ -26,7 +26,9 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu(); const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const handleTriggerTypeClick = ({ const handleTriggerTypeClick = ({

View File

@ -7,15 +7,19 @@ import { TimelineActivities } from '@/activities/timeline-activities/components/
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard'; import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
import { CardType } from '@/object-record/record-show/types/CardType'; 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 { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { WorkflowRunVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizer'; import { WorkflowRunVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizer';
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect'; import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer'; import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer';
import { WorkflowVersionVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect'; import { WorkflowVersionVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect';
import { WorkflowVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVisualizer'; import { WorkflowVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVisualizer';
import { WorkflowVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVisualizerEffect'; 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 styled from '@emotion/styled';
import { ListenRecordUpdatesEffect } from '@/subscription/components/ListenUpdatesEffect'; import { useId } from 'react';
const StyledGreyBox = styled.div<{ isInRightDrawer?: boolean }>` const StyledGreyBox = styled.div<{ isInRightDrawer?: boolean }>`
background: ${({ theme, isInRightDrawer }) => background: ${({ theme, isInRightDrawer }) =>
@ -81,32 +85,63 @@ export const CardComponents: Record<CardType, CardComponentType> = {
<Calendar targetableObject={targetableObject} /> <Calendar targetableObject={targetableObject} />
), ),
[CardType.WorkflowCard]: ({ targetableObject }) => ( [CardType.WorkflowCard]: ({ targetableObject }) => {
<> return (
<WorkflowVisualizerEffect workflowId={targetableObject.id} /> <WorkflowVisualizerComponentInstanceContext.Provider
<WorkflowVisualizer workflowId={targetableObject.id} /> value={{
</> instanceId: getWorkflowVisualizerComponentInstanceId({
), recordId: targetableObject.id,
}),
}}
>
<WorkflowVisualizerEffect workflowId={targetableObject.id} />
<WorkflowVisualizer workflowId={targetableObject.id} />
</WorkflowVisualizerComponentInstanceContext.Provider>
);
},
[CardType.WorkflowVersionCard]: ({ targetableObject }) => ( [CardType.WorkflowVersionCard]: ({ targetableObject }) => {
<> return (
<WorkflowVersionVisualizerEffect <WorkflowVisualizerComponentInstanceContext.Provider
workflowVersionId={targetableObject.id} value={{
/> instanceId: getWorkflowVisualizerComponentInstanceId({
<WorkflowVersionVisualizer workflowVersionId={targetableObject.id} /> recordId: targetableObject.id,
</> }),
), }}
>
<WorkflowVersionVisualizerEffect
workflowVersionId={targetableObject.id}
/>
<WorkflowVersionVisualizer workflowVersionId={targetableObject.id} />
</WorkflowVisualizerComponentInstanceContext.Provider>
);
},
[CardType.WorkflowRunCard]: ({ targetableObject }) => ( [CardType.WorkflowRunCard]: ({ targetableObject }) => {
<> const componentId = useId();
<WorkflowRunVisualizerEffect workflowRunId={targetableObject.id} />
<ListenRecordUpdatesEffect
objectNameSingular={targetableObject.targetObjectNameSingular}
recordId={targetableObject.id}
listenedFields={['status', 'output']}
/>
<WorkflowRunVisualizer workflowRunId={targetableObject.id} /> return (
</> <WorkflowVisualizerComponentInstanceContext.Provider
), value={{
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId: targetableObject.id,
}),
}}
>
<WorkflowRunVisualizerComponentInstanceContext.Provider
value={{
instanceId: componentId,
}}
>
<WorkflowRunVisualizerEffect workflowRunId={targetableObject.id} />
<ListenRecordUpdatesEffect
objectNameSingular={targetableObject.targetObjectNameSingular}
recordId={targetableObject.id}
listenedFields={['status', 'output']}
/>
<WorkflowRunVisualizer workflowRunId={targetableObject.id} />
</WorkflowRunVisualizerComponentInstanceContext.Provider>
</WorkflowVisualizerComponentInstanceContext.Provider>
);
},
}; };

View File

@ -1,9 +1,10 @@
import { flowState } from '@/workflow/states/flowState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilValue } from 'recoil'; import { flowComponentState } from '@/workflow/states/flowComponentState';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useFlowOrThrow = () => { export const useFlowOrThrow = () => {
const flow = useRecoilValue(flowState); const flow = useRecoilComponentValueV2(flowComponentState);
if (!isDefined(flow)) { if (!isDefined(flow)) {
throw new Error('Expected the flow to be defined'); throw new Error('Expected the flow to be defined');
} }

View File

@ -3,11 +3,12 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { flowState } from '@/workflow/states/flowState'; import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowIdState } from '@/workflow/states/workflowIdState'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState'; import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState';
import { WorkflowRun } from '@/workflow/types/Workflow'; 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 { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
@ -58,17 +59,46 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => {
return; return;
} }
set(workflowRunIdState, workflowRunRecord.id); set(
set(workflowIdState, workflowRunRecord.workflowId); workflowVisualizerWorkflowRunIdComponentState.atomFamily({
set(flowState, { instanceId: getWorkflowVisualizerComponentInstanceId({
workflowVersionId: workflowRunRecord.workflowVersionId, recordId,
trigger: workflowRunRecord.output.flow.trigger, }),
steps: workflowRunRecord.output.flow.steps, }),
}); workflowRunRecord.id,
set(workflowSelectedNodeState, stepToOpenByDefault.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({ openWorkflowRunViewStepInCommandMenu({
workflowId: workflowRunRecord.workflowId, workflowId: workflowRunRecord.workflowId,
workflowRunId: workflowRunRecord.id,
title: stepToOpenByDefault.data.name, title: stepToOpenByDefault.data.name,
icon: getIcon(getWorkflowNodeIconKey(stepToOpenByDefault.data)), icon: getIcon(getWorkflowNodeIconKey(stepToOpenByDefault.data)),
workflowSelectedNode: stepToOpenByDefault.id, workflowSelectedNode: stepToOpenByDefault.id,

View File

@ -1,9 +1,12 @@
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilValue } from 'recoil'; import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useWorkflowRunIdOrThrow = () => { export const useWorkflowRunIdOrThrow = () => {
const workflowRunId = useRecoilValue(workflowRunIdState); const workflowRunId = useRecoilComponentValueV2(
workflowVisualizerWorkflowRunIdComponentState,
);
if (!isDefined(workflowRunId)) { if (!isDefined(workflowRunId)) {
throw new Error('Expected the workflow run ID to be defined'); throw new Error('Expected the workflow run ID to be defined');
} }

View File

@ -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,
});

View File

@ -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,
});

View File

@ -1,5 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowIdState = createState<string | undefined>({
key: 'workflowIdState',
defaultValue: undefined,
});

View File

@ -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,
});

View File

@ -1,5 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowLastCreatedStepIdState = createState<string | undefined>({
key: 'workflowLastCreatedStepIdState',
defaultValue: undefined,
});

View File

@ -1,6 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowRunIdState = createState<string | undefined>({
key: 'workflowRunIdState',
defaultValue: undefined,
});

View File

@ -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<string | undefined>({
key: 'workflowVisualizerWorkflowIdComponentState',
defaultValue: undefined,
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
});

View File

@ -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<string | undefined>({
key: 'workflowVisualizerWorkflowRunIdComponentState',
defaultValue: undefined,
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
});

View File

@ -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<string | undefined>({
key: 'workflowVisualizerWorkflowVersionIdComponentState',
defaultValue: undefined,
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
});

View File

@ -0,0 +1,7 @@
export const getWorkflowVisualizerComponentInstanceId = ({
recordId,
}: {
recordId: string;
}) => {
return recordId;
};

View File

@ -1,8 +1,9 @@
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; 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 { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/components/WorkflowDiagramCustomMarkers';
import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState'; import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
import { import {
WorkflowDiagramEdge, WorkflowDiagramEdge,
WorkflowDiagramEdgeType, WorkflowDiagramEdgeType,
@ -26,7 +27,6 @@ import {
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { Tag, TagColor } from 'twenty-ui/components'; import { Tag, TagColor } from 'twenty-ui/components';
import { THEME_COMMON } from 'twenty-ui/theme'; import { THEME_COMMON } from 'twenty-ui/theme';
@ -121,7 +121,9 @@ export const WorkflowDiagramCanvasBase = ({
const reactflow = useReactFlow(); const reactflow = useReactFlow();
const workflowDiagram = useRecoilValue(workflowDiagramState); const workflowDiagram = useRecoilComponentValueV2(
workflowDiagramComponentState,
);
const { nodes, edges } = useMemo( const { nodes, edges } = useMemo(
() => () =>
@ -137,14 +139,8 @@ export const WorkflowDiagramCanvasBase = ({
THEME_COMMON.rightDrawerWidth.replace('px', ''), THEME_COMMON.rightDrawerWidth.replace('px', ''),
); );
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); const setWorkflowDiagram = useSetRecoilComponentStateV2(
workflowDiagramComponentState,
const setWorkflowReactFlowRef = useRecoilCallback(
({ set }) =>
(node: HTMLDivElement | null) => {
set(workflowReactFlowRefState, { current: node });
},
[],
); );
const handleEdgesChange = ( const handleEdgesChange = (
@ -199,24 +195,18 @@ export const WorkflowDiagramCanvasBase = ({
); );
}, [reactflow, rightDrawerState, rightDrawerWidth]); }, [reactflow, rightDrawerState, rightDrawerWidth]);
const handleNodesChanges = useRecoilCallback( const handleNodesChanges = (changes: NodeChange<WorkflowDiagramNode>[]) => {
({ set }) => setWorkflowDiagram((diagram) => {
(changes: NodeChange<WorkflowDiagramNode>[]) => { if (!isDefined(diagram)) {
set(workflowDiagramState, (diagram) => { return 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.',
);
}
return { return {
...diagram, ...diagram,
nodes: applyNodeChanges(changes, diagram.nodes), nodes: applyNodeChanges(changes, diagram.nodes),
}; };
}); });
}, };
[],
);
const handleInit = () => { const handleInit = () => {
if (!isDefined(containerRef.current)) { if (!isDefined(containerRef.current)) {
@ -239,7 +229,6 @@ export const WorkflowDiagramCanvasBase = ({
<WorkflowDiagramCustomMarkers /> <WorkflowDiagramCustomMarkers />
<ReactFlow <ReactFlow
ref={setWorkflowReactFlowRef}
onInit={handleInit} onInit={handleInit}
minZoom={defaultFitViewOptions.minZoom} minZoom={defaultFitViewOptions.minZoom}
maxZoom={defaultFitViewOptions.maxZoom} maxZoom={defaultFitViewOptions.maxZoom}

View File

@ -1,15 +1,17 @@
import { useCallback, useContext } from 'react'; import { useCallback, useContext } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; 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 { EMPTY_TRIGGER_STEP_ID } from '@/workflow/workflow-diagram/constants/EmptyTriggerStepId'; import { EMPTY_TRIGGER_STEP_ID } from '@/workflow/workflow-diagram/constants/EmptyTriggerStepId';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation'; import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection'; 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 { import {
WorkflowDiagramNode, WorkflowDiagramNode,
WorkflowDiagramStepNodeData, WorkflowDiagramStepNodeData,
@ -29,7 +31,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
openWorkflowEditStepInCommandMenu, openWorkflowEditStepInCommandMenu,
} = useWorkflowCommandMenu(); } = useWorkflowCommandMenu();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const setCommandMenuNavigationStack = useSetRecoilState( const setCommandMenuNavigationStack = useSetRecoilState(
commandMenuNavigationStackState, commandMenuNavigationStackState,
@ -37,7 +41,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const { isInRightDrawer } = useContext(ActionMenuContext); const { isInRightDrawer } = useContext(ActionMenuContext);
const workflowId = useRecoilValue(workflowIdState); const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const handleSelectionChange = useCallback( const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => { ({ nodes }: OnSelectionChangeParams) => {
@ -53,8 +59,8 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const isEmptyTriggerNode = selectedNode.type === EMPTY_TRIGGER_STEP_ID; const isEmptyTriggerNode = selectedNode.type === EMPTY_TRIGGER_STEP_ID;
if (isEmptyTriggerNode) { if (isEmptyTriggerNode) {
if (isDefined(workflowId)) { if (isDefined(workflowVisualizerWorkflowId)) {
openWorkflowTriggerTypeInCommandMenu(workflowId); openWorkflowTriggerTypeInCommandMenu(workflowVisualizerWorkflowId);
return; return;
} }
@ -71,9 +77,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
setWorkflowSelectedNode(selectedNode.id); setWorkflowSelectedNode(selectedNode.id);
if (isDefined(workflowId)) { if (isDefined(workflowVisualizerWorkflowId)) {
openWorkflowEditStepInCommandMenu( openWorkflowEditStepInCommandMenu(
workflowId, workflowVisualizerWorkflowId,
selectedNodeData.name, selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)), getIcon(getWorkflowNodeIconKey(selectedNodeData)),
); );
@ -84,7 +90,7 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
[ [
isInRightDrawer, isInRightDrawer,
setCommandMenuNavigationStack, setCommandMenuNavigationStack,
workflowId, workflowVisualizerWorkflowId,
openWorkflowTriggerTypeInCommandMenu, openWorkflowTriggerTypeInCommandMenu,
startNodeCreation, startNodeCreation,
openWorkflowEditStepInCommandMenu, openWorkflowEditStepInCommandMenu,

View File

@ -1,7 +1,10 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; 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 { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { import {
WorkflowDiagramNode, WorkflowDiagramNode,
WorkflowDiagramStepNodeData, WorkflowDiagramStepNodeData,
@ -9,19 +12,34 @@ import {
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display'; import { useIcons } from 'twenty-ui/display';
export const WorkflowDiagramCanvasReadonlyEffect = () => { export const WorkflowDiagramCanvasReadonlyEffect = () => {
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu(); const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState); const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflowVisualizerWorkflowVersionId = useRecoilComponentValueV2(
workflowVisualizerWorkflowVersionIdComponentState,
);
const handleSelectionChange = useCallback( const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => { ({ nodes }: OnSelectionChangeParams) => {
if (
!(
isDefined(workflowVisualizerWorkflowId) &&
isDefined(workflowVisualizerWorkflowVersionId)
)
) {
return;
}
const selectedNode = nodes[0] as WorkflowDiagramNode | undefined; const selectedNode = nodes[0] as WorkflowDiagramNode | undefined;
if (!isDefined(selectedNode)) { if (!isDefined(selectedNode)) {
@ -32,18 +50,18 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData; const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
if (isDefined(workflowId)) { openWorkflowViewStepInCommandMenu({
openWorkflowViewStepInCommandMenu( workflowId: workflowVisualizerWorkflowId,
workflowId, workflowVersionId: workflowVisualizerWorkflowVersionId,
selectedNodeData.name, title: selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)), icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
); });
}
}, },
[ [
setWorkflowSelectedNode, setWorkflowSelectedNode,
openWorkflowViewStepInCommandMenu, openWorkflowViewStepInCommandMenu,
workflowId, workflowVisualizerWorkflowId,
workflowVisualizerWorkflowVersionId,
getIcon, getIcon,
], ],
); );

View File

@ -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 { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema';
import { flowState } from '@/workflow/states/flowState'; import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowLastCreatedStepIdState } from '@/workflow/states/workflowLastCreatedStepIdState'; import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState';
import { import {
WorkflowVersion, WorkflowVersion,
WorkflowWithCurrentVersion, WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow'; } 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 { addCreateStepNodes } from '@/workflow/workflow-diagram/utils/addCreateStepNodes';
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram'; import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
import { mergeWorkflowDiagrams } from '@/workflow/workflow-diagram/utils/mergeWorkflowDiagrams'; import { mergeWorkflowDiagrams } from '@/workflow/workflow-diagram/utils/mergeWorkflowDiagrams';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const WorkflowDiagramEffect = ({ export const WorkflowDiagramEffect = ({
@ -20,10 +22,19 @@ export const WorkflowDiagramEffect = ({
}: { }: {
workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined; workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined;
}) => { }) => {
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); const workflowDiagramState = useRecoilComponentCallbackStateV2(
const setFlow = useSetRecoilState(flowState); workflowDiagramComponentState,
);
const setWorkflowDiagram = useSetRecoilComponentStateV2(
workflowDiagramComponentState,
);
const setFlow = useSetRecoilComponentStateV2(flowComponentState);
const { populateStepsOutputSchema } = useStepsOutputSchema(); const { populateStepsOutputSchema } = useStepsOutputSchema();
const workflowLastCreatedStepIdState = useRecoilComponentCallbackStateV2(
workflowLastCreatedStepIdComponentState,
);
const computeAndMergeNewWorkflowDiagram = useRecoilCallback( const computeAndMergeNewWorkflowDiagram = useRecoilCallback(
({ snapshot, set }) => { ({ snapshot, set }) => {
return (currentVersion: WorkflowVersion) => { return (currentVersion: WorkflowVersion) => {
@ -64,7 +75,7 @@ export const WorkflowDiagramEffect = ({
set(workflowDiagramState, mergedWorkflowDiagram); set(workflowDiagramState, mergedWorkflowDiagram);
}; };
}, },
[], [workflowLastCreatedStepIdState, workflowDiagramState],
); );
const currentVersion = workflowWithCurrentVersion?.currentVersion; const currentVersion = workflowWithCurrentVersion?.currentVersion;

View File

@ -1,10 +1,10 @@
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; 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 { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined';
import { WorkflowDiagramStepNodeEditableContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent'; import { WorkflowDiagramStepNodeEditableContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent';
import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep'; import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { useRecoilValue } from 'recoil';
export const WorkflowDiagramStepNodeEditable = ({ export const WorkflowDiagramStepNodeEditable = ({
id, id,
@ -15,9 +15,13 @@ export const WorkflowDiagramStepNodeEditable = ({
data: WorkflowDiagramStepNodeData; data: WorkflowDiagramStepNodeData;
selected?: boolean; selected?: boolean;
}) => { }) => {
const workflowId = useRecoilValue(workflowIdState); const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(
workflowVisualizerWorkflowId,
);
assertWorkflowWithCurrentVersionIsDefined(workflowWithCurrentVersion); assertWorkflowWithCurrentVersionIsDefined(workflowWithCurrentVersion);
const { deleteStep } = useDeleteStep({ const { deleteStep } = useDeleteStep({

View File

@ -1,8 +1,10 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; 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 { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState'; import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; 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 { WorkflowRunDiagramNode } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
@ -15,10 +17,25 @@ export const WorkflowRunDiagramCanvasEffect = () => {
const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu(); const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowVisualizerWorkflowIdState = useRecoilComponentCallbackStateV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflowDiagramStatusState = useRecoilComponentCallbackStateV2(
workflowDiagramStatusComponentState,
);
const workflowSelectedNodeState = useRecoilComponentCallbackStateV2(
workflowSelectedNodeComponentState,
);
const handleSelectionChange = useRecoilCallback( const handleSelectionChange = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
({ nodes }: OnSelectionChangeParams) => { ({ nodes }: OnSelectionChangeParams) => {
const workflowId = getSnapshotValue(snapshot, workflowIdState); const workflowId = getSnapshotValue(
snapshot,
workflowVisualizerWorkflowIdState,
);
if (!isDefined(workflowId)) { if (!isDefined(workflowId)) {
throw new Error('Expected the workflowId to be defined.'); throw new Error('Expected the workflowId to be defined.');
@ -49,13 +66,21 @@ export const WorkflowRunDiagramCanvasEffect = () => {
openWorkflowRunViewStepInCommandMenu({ openWorkflowRunViewStepInCommandMenu({
workflowId, workflowId,
workflowRunId,
title: selectedNodeData.name, title: selectedNodeData.name,
icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)), icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
workflowSelectedNode: selectedNode.id, workflowSelectedNode: selectedNode.id,
stepExecutionStatus: selectedNodeData.runStatus, stepExecutionStatus: selectedNodeData.runStatus,
}); });
}, },
[getIcon, openWorkflowRunViewStepInCommandMenu], [
workflowVisualizerWorkflowIdState,
workflowDiagramStatusState,
workflowSelectedNodeState,
openWorkflowRunViewStepInCommandMenu,
workflowRunId,
getIcon,
],
); );
useOnSelectionChange({ useOnSelectionChange({

View File

@ -1,8 +1,8 @@
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas'; 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 styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -15,7 +15,9 @@ export const WorkflowRunVisualizer = ({
workflowRunId: string; workflowRunId: string;
}) => { }) => {
const workflowRun = useWorkflowRun({ workflowRunId }); const workflowRun = useWorkflowRun({ workflowRunId });
const workflowDiagramStatus = useRecoilValue(workflowDiagramStatusState); const workflowDiagramStatus = useRecoilComponentValueV2(
workflowDiagramStatusComponentState,
);
if ( if (
!isDefined(workflowRun) || !isDefined(workflowRun) ||

View File

@ -1,18 +1,21 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; 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 { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
import { flowState } from '@/workflow/states/flowState'; import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowIdState } from '@/workflow/states/workflowIdState'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState'; import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState';
import { WorkflowRunOutput } from '@/workflow/types/Workflow'; import { WorkflowRunOutput } from '@/workflow/types/Workflow';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState'; import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState';
import { workflowRunStepToOpenByDefaultState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState'; import { workflowRunStepToOpenByDefaultComponentState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState';
import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram'; import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
import { selectWorkflowDiagramNode } from '@/workflow/workflow-diagram/utils/selectWorkflowDiagramNode'; import { selectWorkflowDiagramNode } from '@/workflow/workflow-diagram/utils/selectWorkflowDiagramNode';
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const WorkflowRunVisualizerEffect = ({ export const WorkflowRunVisualizerEffect = ({
@ -23,8 +26,24 @@ export const WorkflowRunVisualizerEffect = ({
const workflowRun = useWorkflowRun({ workflowRunId }); const workflowRun = useWorkflowRun({ workflowRunId });
const workflowVersion = useWorkflowVersion(workflowRun?.workflowVersionId); const workflowVersion = useWorkflowVersion(workflowRun?.workflowVersionId);
const setWorkflowRunId = useSetRecoilState(workflowRunIdState); const setWorkflowRunId = useSetRecoilComponentStateV2(
const setWorkflowId = useSetRecoilState(workflowIdState); 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 { populateStepsOutputSchema } = useStepsOutputSchema();
const { isInRightDrawer } = useContext(ActionMenuContext); const { isInRightDrawer } = useContext(ActionMenuContext);
@ -38,11 +57,11 @@ export const WorkflowRunVisualizerEffect = ({
return; return;
} }
setWorkflowId(workflowRun.workflowId); setWorkflowVisualizerWorkflowId(workflowRun.workflowId);
}, [setWorkflowId, workflowRun]); }, [setWorkflowVisualizerWorkflowId, workflowRun]);
const handleWorkflowRunDiagramGeneration = useRecoilCallback( const handleWorkflowRunDiagramGeneration = useRecoilCallback(
({ set }) => ({ snapshot, set }) =>
({ ({
workflowRunOutput, workflowRunOutput,
workflowVersionId, workflowVersionId,
@ -59,7 +78,14 @@ export const WorkflowRunVisualizerEffect = ({
return; return;
} }
set(workflowDiagramStatusState, 'computing-diagram'); const workflowDiagramStatus = getSnapshotValue(
snapshot,
workflowDiagramStatusState,
);
if (workflowDiagramStatus !== 'done') {
set(workflowDiagramStatusState, 'computing-diagram');
}
set(flowState, { set(flowState, {
workflowVersionId, workflowVersionId,
@ -89,9 +115,16 @@ export const WorkflowRunVisualizerEffect = ({
set(workflowDiagramState, baseWorkflowRunDiagram); set(workflowDiagramState, baseWorkflowRunDiagram);
} }
set(workflowDiagramStatusState, 'computing-dimensions'); if (workflowDiagramStatus !== 'done') {
set(workflowDiagramStatusState, 'computing-dimensions');
}
}, },
[], [
flowState,
workflowDiagramState,
workflowDiagramStatusState,
workflowRunStepToOpenByDefaultState,
],
); );
useEffect(() => { useEffect(() => {

View File

@ -1,11 +1,12 @@
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema';
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
import { flowState } from '@/workflow/states/flowState'; import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowIdState } from '@/workflow/states/workflowIdState'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram'; import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const WorkflowVersionVisualizerEffect = ({ export const WorkflowVersionVisualizerEffect = ({
@ -15,10 +16,19 @@ export const WorkflowVersionVisualizerEffect = ({
}) => { }) => {
const workflowVersion = useWorkflowVersion(workflowVersionId); const workflowVersion = useWorkflowVersion(workflowVersionId);
const setFlow = useSetRecoilState(flowState); const setFlow = useSetRecoilComponentStateV2(flowComponentState);
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); const setWorkflowDiagram = useSetRecoilComponentStateV2(
const setWorkflowId = useSetRecoilState(workflowIdState); workflowDiagramComponentState,
);
const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowIdComponentState,
);
const setWorkflowVisualizerWorkflowVersionId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowVersionIdComponentState,
);
const { populateStepsOutputSchema } = useStepsOutputSchema(); const { populateStepsOutputSchema } = useStepsOutputSchema();
useEffect(() => { useEffect(() => {
if (!isDefined(workflowVersion)) { if (!isDefined(workflowVersion)) {
setFlow(undefined); setFlow(undefined);
@ -32,8 +42,14 @@ export const WorkflowVersionVisualizerEffect = ({
steps: workflowVersion.steps, steps: workflowVersion.steps,
}); });
setWorkflowId(workflowVersion.workflowId); setWorkflowVisualizerWorkflowId(workflowVersion.workflowId);
}, [setFlow, setWorkflowId, workflowVersion]); setWorkflowVisualizerWorkflowVersionId(workflowVersion.id);
}, [
setFlow,
setWorkflowVisualizerWorkflowId,
setWorkflowVisualizerWorkflowVersionId,
workflowVersion,
]);
useEffect(() => { useEffect(() => {
if (!isDefined(workflowVersion)) { if (!isDefined(workflowVersion)) {

View File

@ -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 { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
export const WorkflowVisualizerEffect = ({ export const WorkflowVisualizerEffect = ({
workflowId, workflowId,
}: { }: {
workflowId: string; workflowId: string;
}) => { }) => {
const setWorkflowId = useSetRecoilState(workflowIdState); const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowIdComponentState,
);
useEffect(() => { useEffect(() => {
setWorkflowId(workflowId); setWorkflowVisualizerWorkflowId(workflowId);
}, [setWorkflowId, workflowId]); }, [setWorkflowVisualizerWorkflowId, workflowId]);
return null; return null;
}; };

View File

@ -9,11 +9,12 @@ import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/com
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge'; import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeDefaultConfiguration'; 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 { 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 { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator'; import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator';
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { workflowDiagramState } from '../../states/workflowDiagramState'; import { workflowDiagramComponentState } from '../../states/workflowDiagramComponentState';
import { WorkflowDiagramCanvasBase } from '../WorkflowDiagramCanvasBase'; import { WorkflowDiagramCanvasBase } from '../WorkflowDiagramCanvasBase';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -51,63 +52,79 @@ export const DefaultEdge: Story = {
}, },
}, },
decorators: [ decorators: [
(Story) => ( (Story) => {
<RecoilRoot const workflowVisualizerComponentInstanceId =
initializeState={({ set }) => { 'workflow-visualizer-test-id';
set(workflowDiagramState, {
nodes: [ return (
<RecoilRoot
initializeState={({ set }) => {
set(
workflowDiagramComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
{ {
id: 'trigger-1', nodes: [
type: 'default', {
position: { x: 100, y: 100 }, id: 'trigger-1',
data: { type: 'default',
nodeType: 'trigger', position: { x: 100, y: 100 },
triggerType: 'DATABASE_EVENT', data: {
name: 'When record is created', 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 }, <WorkflowVisualizerComponentInstanceContext.Provider
data: { value={{
nodeType: 'action', instanceId: workflowVisualizerComponentInstanceId,
actionType: 'CREATE_RECORD', }}
name: 'Create record', >
}, <StyledContainer>
}, <Story />
{ </StyledContainer>
id: 'create-step-1', </WorkflowVisualizerComponentInstanceContext.Provider>
type: 'create-step', </RecoilRoot>
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',
},
],
});
}}
>
<StyledContainer>
<Story />
</StyledContainer>
</RecoilRoot>
),
], ],
}; };
@ -124,49 +141,65 @@ export const SuccessEdge: Story = {
}, },
}, },
decorators: [ decorators: [
(Story) => ( (Story) => {
<RecoilRoot const workflowVisualizerComponentInstanceId =
initializeState={({ set }) => { 'workflow-visualizer-test-id';
set(workflowDiagramState, {
nodes: [ return (
<RecoilRoot
initializeState={({ set }) => {
set(
workflowDiagramComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
{ {
id: 'trigger-1', nodes: [
type: 'default', {
position: { x: 100, y: 100 }, id: 'trigger-1',
data: { type: 'default',
nodeType: 'trigger', position: { x: 100, y: 100 },
triggerType: 'DATABASE_EVENT', data: {
name: 'When record is created', 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 }, <WorkflowVisualizerComponentInstanceContext.Provider
data: { value={{
nodeType: 'action', instanceId: workflowVisualizerComponentInstanceId,
actionType: 'CREATE_RECORD', }}
name: 'Create record', >
}, <StyledContainer>
}, <Story />
], </StyledContainer>
edges: [ </WorkflowVisualizerComponentInstanceContext.Provider>
{ </RecoilRoot>
...WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION, );
id: 'edge-1', },
source: 'trigger-1',
target: 'action-1',
type: 'success',
label: '1 item',
},
],
});
}}
>
<StyledContainer>
<Story />
</StyledContainer>
</RecoilRoot>
),
], ],
}; };

View File

@ -1,15 +1,25 @@
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection'; 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 { act, renderHook } from '@testing-library/react';
import { useReactFlow } from '@xyflow/react'; import { useReactFlow } from '@xyflow/react';
import { RecoilRoot, useRecoilState } from 'recoil'; import { RecoilRoot } from 'recoil';
jest.mock('@xyflow/react', () => ({ jest.mock('@xyflow/react', () => ({
useReactFlow: jest.fn(), useReactFlow: jest.fn(),
})); }));
const wrapper = ({ children }: { children: React.ReactNode }) => ( const wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot> <RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: 'test-instance-id',
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
); );
describe('useTriggerNodeSelection', () => { describe('useTriggerNodeSelection', () => {
@ -31,7 +41,9 @@ describe('useTriggerNodeSelection', () => {
const [ const [
workflowDiagramTriggerNodeSelection, workflowDiagramTriggerNodeSelection,
setWorkflowDiagramTriggerNodeSelection, setWorkflowDiagramTriggerNodeSelection,
] = useRecoilState(workflowDiagramTriggerNodeSelectionState); ] = useRecoilComponentStateV2(
workflowDiagramTriggerNodeSelectionComponentState,
);
useTriggerNodeSelection(); useTriggerNodeSelection();

View File

@ -1,10 +1,12 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState'; import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { workflowRunStepToOpenByDefaultState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; 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 { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
@ -17,6 +19,21 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => {
const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu(); const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu();
const { isInRightDrawer } = useContext(ActionMenuContext); 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( const handleWorkflowRunDiagramCanvasInit = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
() => { () => {
@ -43,8 +60,11 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => {
); );
if (isDefined(workflowStepToOpenByDefault)) { if (isDefined(workflowStepToOpenByDefault)) {
const workflowId = getSnapshotValue(snapshot, workflowIdState); const workflowVisualizerWorkflowId = getSnapshotValue(
if (!isDefined(workflowId)) { snapshot,
workflowVisualizerWorkflowIdState,
);
if (!isDefined(workflowVisualizerWorkflowId)) {
throw new Error( throw new Error(
'The workflow id must be set; ensure the workflow id is always set before rendering the workflow diagram.', '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); set(workflowSelectedNodeState, workflowStepToOpenByDefault.id);
openWorkflowRunViewStepInCommandMenu({ openWorkflowRunViewStepInCommandMenu({
workflowId, workflowId: workflowVisualizerWorkflowId,
workflowRunId,
title: workflowStepToOpenByDefault.data.name, title: workflowStepToOpenByDefault.data.name,
icon: getIcon( icon: getIcon(
getWorkflowNodeIconKey(workflowStepToOpenByDefault.data), getWorkflowNodeIconKey(workflowStepToOpenByDefault.data),
@ -65,7 +86,16 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => {
set(workflowRunStepToOpenByDefaultState, undefined); set(workflowRunStepToOpenByDefaultState, undefined);
} }
}, },
[getIcon, isInRightDrawer, openWorkflowRunViewStepInCommandMenu], [
workflowDiagramStatusState,
isInRightDrawer,
workflowRunStepToOpenByDefaultState,
workflowVisualizerWorkflowIdState,
workflowSelectedNodeState,
openWorkflowRunViewStepInCommandMenu,
workflowRunId,
getIcon,
],
); );
return { return {

View File

@ -1,18 +1,21 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState'; 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'; import { isDefined } from 'twenty-shared/utils';
export const useStartNodeCreation = () => { export const useStartNodeCreation = () => {
const setWorkflowCreateStepFromParentStepId = useSetRecoilState( const setWorkflowCreateStepFromParentStepId = useSetRecoilComponentStateV2(
workflowCreateStepFromParentStepIdState, workflowCreateStepFromParentStepIdComponentState,
); );
const { openStepSelectInCommandMenu } = useWorkflowCommandMenu(); const { openStepSelectInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState); const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
/** /**
* This function is used in a context where dependencies shouldn't change much. * This function is used in a context where dependencies shouldn't change much.
@ -22,14 +25,14 @@ export const useStartNodeCreation = () => {
(parentNodeId: string) => { (parentNodeId: string) => {
setWorkflowCreateStepFromParentStepId(parentNodeId); setWorkflowCreateStepFromParentStepId(parentNodeId);
if (isDefined(workflowId)) { if (isDefined(workflowVisualizerWorkflowId)) {
openStepSelectInCommandMenu(workflowId); openStepSelectInCommandMenu(workflowVisualizerWorkflowId);
return; return;
} }
}, },
[ [
setWorkflowCreateStepFromParentStepId, setWorkflowCreateStepFromParentStepId,
workflowId, workflowVisualizerWorkflowId,
openStepSelectInCommandMenu, openStepSelectInCommandMenu,
], ],
); );

View File

@ -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 { import {
WorkflowDiagramEdge, WorkflowDiagramEdge,
WorkflowDiagramNode, WorkflowDiagramNode,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram'; } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { useReactFlow } from '@xyflow/react'; import { useReactFlow } from '@xyflow/react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useTriggerNodeSelection = () => { export const useTriggerNodeSelection = () => {
@ -14,7 +14,9 @@ export const useTriggerNodeSelection = () => {
const [ const [
workflowDiagramTriggerNodeSelection, workflowDiagramTriggerNodeSelection,
setWorkflowDiagramTriggerNodeSelection, setWorkflowDiagramTriggerNodeSelection,
] = useRecoilState(workflowDiagramTriggerNodeSelectionState); ] = useRecoilComponentStateV2(
workflowDiagramTriggerNodeSelectionComponentState,
);
useEffect(() => { useEffect(() => {
if (!isDefined(workflowDiagramTriggerNodeSelection)) { if (!isDefined(workflowDiagramTriggerNodeSelection)) {

View File

@ -1,9 +1,11 @@
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilValue } from 'recoil'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useWorkflowSelectedNodeOrThrow = () => { export const useWorkflowSelectedNodeOrThrow = () => {
const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState); const workflowSelectedNode = useRecoilComponentValueV2(
workflowSelectedNodeComponentState,
);
if (!isDefined(workflowSelectedNode)) { if (!isDefined(workflowSelectedNode)) {
throw new Error( throw new Error(

View File

@ -0,0 +1,6 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const WorkflowRunVisualizerComponentInstanceContext =
createComponentInstanceContext({
instanceId: '',
});

View File

@ -0,0 +1,6 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const WorkflowVisualizerComponentInstanceContext =
createComponentInstanceContext({
instanceId: '',
});

View File

@ -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,
});

View File

@ -1,7 +0,0 @@
import { WorkflowDiagram } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { createState } from 'twenty-ui/utilities';
export const workflowDiagramState = createState<WorkflowDiagram | undefined>({
key: 'workflowDiagramState',
defaultValue: undefined,
});

View File

@ -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<WorkflowDiagramStatus>({
key: 'workflowDiagramStatusComponentState',
defaultValue: 'computing-diagram',
componentInstanceContext: WorkflowRunVisualizerComponentInstanceContext,
});

View File

@ -1,8 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowDiagramStatusState = createState<
'computing-diagram' | 'computing-dimensions' | 'done'
>({
key: 'workflowDiagramStatusState',
defaultValue: 'computing-diagram',
});

View File

@ -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<string | undefined>({
key: 'workflowDiagramTriggerNodeSelectionComponentState',
defaultValue: undefined,
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
});

View File

@ -1,7 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowDiagramTriggerNodeSelectionState = createState<
string | undefined
>({
key: 'workflowDiagramTriggerNodeSelectionState',
defaultValue: undefined,
});

View File

@ -1,8 +0,0 @@
import { RefObject } from 'react';
import { createState } from 'twenty-ui/utilities';
export const workflowReactFlowRefState =
createState<RefObject<HTMLDivElement> | null>({
key: 'workflowReactFlowRefState',
defaultValue: null,
});

View File

@ -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,
});

View File

@ -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,
});

View File

@ -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,
});

View File

@ -1,6 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowSelectedNodeState = createState<string | undefined>({
key: 'workflowSelectedNodeState',
defaultValue: undefined,
});

View File

@ -0,0 +1,4 @@
export type WorkflowDiagramStatus =
| 'computing-diagram'
| 'computing-dimensions'
| 'done';

View File

@ -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();
});
});

View File

@ -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 (
<RecoilRoot
initializeState={({ set }) => {
set(
workflowCreateStepFromParentStepIdComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
'parent-step-id',
);
}}
>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
);
};
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();
});
});

View File

@ -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 { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
import { workflowLastCreatedStepIdState } from '@/workflow/states/workflowLastCreatedStepIdState'; import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState';
import { import {
WorkflowStepType, WorkflowStepType,
WorkflowWithCurrentVersion, WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow'; } 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 { useCreateWorkflowVersionStep } from '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionStep';
import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState'; import { workflowCreateStepFromParentStepIdComponentState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useCreateStep = ({ export const useCreateStep = ({
@ -16,13 +17,15 @@ export const useCreateStep = ({
workflow: WorkflowWithCurrentVersion; workflow: WorkflowWithCurrentVersion;
}) => { }) => {
const { createWorkflowVersionStep } = useCreateWorkflowVersionStep(); const { createWorkflowVersionStep } = useCreateWorkflowVersionStep();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
const setWorkflowLastCreatedStepId = useSetRecoilState( workflowSelectedNodeComponentState,
workflowLastCreatedStepIdState, );
const setWorkflowLastCreatedStepId = useSetRecoilComponentStateV2(
workflowLastCreatedStepIdComponentState,
); );
const workflowCreateStepFromParentStepId = useRecoilValue( const workflowCreateStepFromParentStepId = useRecoilComponentValueV2(
workflowCreateStepFromParentStepIdState, workflowCreateStepFromParentStepIdComponentState,
); );
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion(); const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();

View File

@ -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<string | undefined>({
key: 'workflowCreateStepFromParentStepIdComponentState',
defaultValue: undefined,
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
});

View File

@ -1,7 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowCreateStepFromParentStepIdState = createState<
string | undefined
>({
key: 'workflowCreateStepFromParentStepId',
defaultValue: undefined,
});

View File

@ -3,7 +3,7 @@ import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-func
import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction'; import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction';
import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion'; import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; 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 { WorkflowCodeAction } from '@/workflow/types/Workflow';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue'; 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 { editor } from 'monaco-editor';
import { AutoTypings } from 'monaco-editor-auto-typings'; import { AutoTypings } from 'monaco-editor-auto-typings';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState } from 'recoil';
import { useDebouncedCallback } from 'use-debounce';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { CodeEditor } from 'twenty-ui/input';
import { IconCode, IconPlayerPlay, useIcons } from 'twenty-ui/display'; import { IconCode, IconPlayerPlay, useIcons } from 'twenty-ui/display';
import { CodeEditor } from 'twenty-ui/input';
import { useDebouncedCallback } from 'use-debounce';
const StyledCodeEditorContainer = styled.div` const StyledCodeEditorContainer = styled.div`
display: flex; display: flex;
@ -82,8 +82,10 @@ export const WorkflowEditActionServerlessFunction = ({
useUpdateOneServerlessFunction(serverlessFunctionId); useUpdateOneServerlessFunction(serverlessFunctionId);
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion(); const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
const workflowId = useRecoilValue(workflowIdState); const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
const workflow = useWorkflowWithCurrentVersion(workflowId); workflowVisualizerWorkflowIdComponentState,
);
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
const { availablePackages } = useGetAvailablePackages({ const { availablePackages } = useGetAvailablePackages({
id: serverlessFunctionId, id: serverlessFunctionId,
}); });

View File

@ -8,7 +8,8 @@ import { FormTextFieldInput } from '@/object-record/record-field/form-types/comp
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { Select } from '@/ui/input/components/Select'; 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 { WorkflowSendEmailAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
@ -20,11 +21,11 @@ import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountProvider } from 'twenty-shared/types';
import { assertUnreachable, isDefined } from 'twenty-shared/utils'; 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 { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { IconPlus, useIcons } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
type WorkflowEditActionSendEmailProps = { type WorkflowEditActionSendEmailProps = {
action: WorkflowSendEmailAction; action: WorkflowSendEmailAction;
@ -53,8 +54,10 @@ export const WorkflowEditActionSendEmail = ({
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { triggerApisOAuth } = useTriggerApisOAuth(); const { triggerApisOAuth } = useTriggerApisOAuth();
const workflowId = useRecoilValue(workflowIdState); const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
const redirectUrl = `/object/workflow/${workflowId}`; workflowVisualizerWorkflowIdComponentState,
);
const redirectUrl = `/object/workflow/${workflowVisualizerWorkflowId}`;
const [formData, setFormData] = useState<SendEmailFormData>({ const [formData, setFormData] = useState<SendEmailFormData>({
connectedAccountId: action.settings.input.connectedAccountId, connectedAccountId: action.settings.input.connectedAccountId,

View File

@ -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 <RightDrawerWorkflowSelectTriggerTypeContent workflow={workflow} />;
};

View File

@ -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 (
<RightDrawerStepListContainer>
<RightDrawerWorkflowSelectStepTitle>
Data
</RightDrawerWorkflowSelectStepTitle>
{DATABASE_TRIGGER_TYPES.map((action) => (
<MenuItemCommand
key={action.defaultLabel}
LeftIcon={getIcon(action.icon)}
text={action.defaultLabel}
onClick={handleTriggerTypeClick(action)}
/>
))}
<RightDrawerWorkflowSelectStepTitle>
Others
</RightDrawerWorkflowSelectStepTitle>
{OTHER_TRIGGER_TYPES.map((action) => (
<MenuItemCommand
key={action.defaultLabel}
LeftIcon={getIcon(action.icon)}
text={action.defaultLabel}
onClick={handleTriggerTypeClick(action)}
/>
))}
</RightDrawerStepListContainer>
);
};

View File

@ -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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Select } from '@/ui/input/components/Select'; import { Select } from '@/ui/input/components/Select';
import { TextInputV2 } from '@/ui/input/components/TextInputV2'; 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 { WorkflowWebhookTrigger } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
@ -54,7 +55,9 @@ export const WorkflowEditTriggerWebhookForm = ({
const [errorMessages, setErrorMessages] = useState<FormErrorMessages>({}); const [errorMessages, setErrorMessages] = useState<FormErrorMessages>({});
const [errorMessagesVisible, setErrorMessagesVisible] = useState(false); const [errorMessagesVisible, setErrorMessagesVisible] = useState(false);
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const workflowId = useRecoilValue(workflowIdState); const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const currentWorkspace = useRecoilValue(currentWorkspaceState); const currentWorkspace = useRecoilValue(currentWorkspaceState);
const onBlur = () => { const onBlur = () => {
@ -66,7 +69,7 @@ export const WorkflowEditTriggerWebhookForm = ({
const headerIcon = getTriggerIcon(trigger); const headerIcon = getTriggerIcon(trigger);
const headerType = getTriggerHeaderType(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 displayWebhookUrl = webhookUrl.replace(/^(https?:\/\/)?(www\.)?/, '');
const copyToClipboard = async () => { const copyToClipboard = async () => {

View File

@ -13,13 +13,12 @@ import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isReco
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState'; import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath'; import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath';
import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel'; import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema'; import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema';
import { useState } from 'react'; import { useState } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { import {
IconChevronLeft, IconChevronLeft,
@ -42,13 +41,15 @@ export const WorkflowVariablesDropdownFieldItems = ({
const [currentPath, setCurrentPath] = useState<string[]>([]); const [currentPath, setCurrentPath] = useState<string[]>([]);
const [searchInputValue, setSearchInputValue] = useState(''); const [searchInputValue, setSearchInputValue] = useState('');
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const setActiveTabId = useSetRecoilComponentStateV2( const setActiveTabId = useSetRecoilComponentStateV2(
activeTabIdComponentState, activeTabIdComponentState,
'workflow-serverless-function-tab-list-component-id', 'workflow-serverless-function-tab-list-component-id',
); );
const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState( const setWorkflowDiagramTriggerNodeSelection = useSetRecoilComponentStateV2(
workflowDiagramTriggerNodeSelectionState, workflowDiagramTriggerNodeSelectionComponentState,
); );
const getDisplayedSubStepFields = () => { const getDisplayedSubStepFields = () => {

View File

@ -1,52 +1,75 @@
import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema'; import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
import { flowState } from '@/workflow/states/flowState'; import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowIdState } from '@/workflow/states/workflowIdState'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { WorkflowVersion } from '@/workflow/types/Workflow'; 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 { Decorator } from '@storybook/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSetRecoilState } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { import {
getWorkflowMock, getWorkflowMock,
getWorkflowNodeIdMock, getWorkflowNodeIdMock,
} from '~/testing/mock-data/workflow'; } from '~/testing/mock-data/workflow';
export const WorkflowStepDecorator: Decorator = (Story) => { export const WorkflowStepDecorator: Decorator = (Story) => {
const setWorkflowId = useSetRecoilState(workflowIdState); const workflowVisualizerComponentInstanceId = 'workflow-visualizer-test-id';
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setFlow = useSetRecoilState(flowState);
const workflowVersion = getWorkflowMock().versions.edges[0] const workflowVersion = getWorkflowMock().versions.edges[0]
.node as WorkflowVersion; .node as WorkflowVersion;
const { populateStepsOutputSchema } = useStepsOutputSchema(); const { populateStepsOutputSchema } = useStepsOutputSchema();
const [ready, setReady] = useState(false); 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(() => { useEffect(() => {
setWorkflowId(getWorkflowMock().id); handleMount();
setWorkflowSelectedNode(getWorkflowNodeIdMock()); }, [handleMount]);
setFlow({
workflowVersionId: workflowVersion.id,
trigger: workflowVersion.trigger,
steps: workflowVersion.steps,
});
populateStepsOutputSchema(workflowVersion);
setReady(true);
}, [
setWorkflowId,
setWorkflowSelectedNode,
populateStepsOutputSchema,
workflowVersion,
setFlow,
]);
return ( return (
<WorkflowStepContextProvider <WorkflowVisualizerComponentInstanceContext.Provider
value={{ value={{
workflowVersionId: workflowVersion.id, instanceId: workflowVisualizerComponentInstanceId,
workflowRunId: '123',
}} }}
> >
{ready && <Story />} <WorkflowStepContextProvider
</WorkflowStepContextProvider> value={{
workflowVersionId: workflowVersion.id,
workflowRunId: '123',
}}
>
{ready && <Story />}
</WorkflowStepContextProvider>
</WorkflowVisualizerComponentInstanceContext.Provider>
); );
}; };