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

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

View File

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

View File

@ -1,16 +1,27 @@
import { CommandMenuWorkflowSelectActionContent } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent';
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCommandMenuWorkflowIdOrThrow } from '@/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuWorkflowSelectAction = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState);
const workflowId = useCommandMenuWorkflowIdOrThrow();
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return <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 { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const workflowIdComponentState = createComponentStateV2<
export const commandMenuWorkflowIdComponentState = createComponentStateV2<
string | undefined
>({
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuWorkflowEditStep = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState);
const workflowId = useCommandMenuWorkflowIdOrThrow();
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
@ -14,10 +15,18 @@ export const CommandMenuWorkflowEditStep = () => {
}
return (
<WorkflowStepContextProvider
value={{ workflowVersionId: workflow.currentVersion.id }}
<WorkflowVisualizerComponentInstanceContext.Provider
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 { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
import { WorkflowRunStepNodeDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepNodeDetail';
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
import {
WorkflowRunTabId,
WorkflowRunTabIdType,
} from '@/workflow/workflow-steps/types/WorkflowRunTabId';
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
import styled from '@emotion/styled';
import { isNull } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui/display';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
const StyledTabList = styled(TabList)`
background-color: ${({ theme }) => theme.background.secondary};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
type TabId = WorkflowRunTabIdType;
import { CommandMenuWorkflowRunViewStepContent } from '@/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent';
import { useCommandMenuWorkflowRunIdOrThrow } from '@/command-menu/pages/workflow/step/view-run/hooks/useCommandMenuWorkflowRunIdOrThrow';
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
export const CommandMenuWorkflowRunViewStep = () => {
const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId });
const commandMenuPageComponentInstance = useComponentInstanceStateContext(
CommandMenuPageComponentInstanceContext,
);
if (isNull(commandMenuPageComponentInstance)) {
throw new Error(
'CommandMenuPageComponentInstanceContext is not defined. This component should be used within CommandMenuPageComponentInstanceContext.',
);
}
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
commandMenuPageComponentInstance.instanceId,
);
if (!isDefined(workflowRun)) {
return null;
}
const stepExecutionStatus = getWorkflowRunStepExecutionStatus({
workflowRunOutput: workflowRun.output,
stepId: workflowSelectedNode,
});
const isInputTabDisabled = getIsInputTabDisabled({
stepExecutionStatus,
workflowSelectedNode,
});
const isOutputTabDisabled = getIsOutputTabDisabled({
stepExecutionStatus,
});
const tabs: SingleTabProps<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,
},
];
const workflowRunId = useCommandMenuWorkflowRunIdOrThrow();
return (
<WorkflowStepContextProvider
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
workflowVersionId: workflowRun.workflowVersionId,
workflowRunId: workflowRun.id,
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId: workflowRunId,
}),
}}
>
<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>
<CommandMenuWorkflowRunViewStepContent />
</WorkflowVisualizerComponentInstanceContext.Provider>
);
};

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

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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuWorkflowSelectTriggerType = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState);
const workflowId = useCommandMenuWorkflowIdOrThrow();
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return <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 { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import {
WorkflowTriggerType,
WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes';
@ -12,9 +13,8 @@ import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/Other
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition';
import { useSetRecoilState } from 'recoil';
import { MenuItemCommand } from 'twenty-ui/navigation';
import { useIcons } from 'twenty-ui/display';
import { MenuItemCommand } from 'twenty-ui/navigation';
export const CommandMenuWorkflowSelectTriggerTypeContent = ({
workflow,
@ -26,7 +26,9 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const handleTriggerTypeClick = ({