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
@@ -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,
@@ -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,
@@ -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>
);
};
@@ -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;
};
@@ -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',
@@ -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,
});
@@ -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,
});
@@ -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>
);
};
@@ -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>
);
};
@@ -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>
);
};
@@ -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;
};
@@ -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>
);
};
@@ -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>
);
};
@@ -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;
};
@@ -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>
);
};
@@ -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 = ({