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
@@ -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,
}) => { }) => {
@@ -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,
@@ -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,
@@ -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>
);
}; };
@@ -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 { 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',
@@ -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 { 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>
); );
}; };
@@ -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>
); );
}; };
@@ -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 { 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>
); );
}; };
@@ -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 { 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>
);
}; };
@@ -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 = ({
@@ -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>
);
},
}; };
@@ -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');
} }
@@ -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,
@@ -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');
} }
@@ -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,
});
@@ -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,
});
@@ -1,5 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowIdState = createState<string | undefined>({
key: 'workflowIdState',
defaultValue: undefined,
});
@@ -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,
});
@@ -1,5 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowLastCreatedStepIdState = createState<string | undefined>({
key: 'workflowLastCreatedStepIdState',
defaultValue: undefined,
});
@@ -1,6 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowRunIdState = createState<string | undefined>({
key: 'workflowRunIdState',
defaultValue: undefined,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -0,0 +1,7 @@
export const getWorkflowVisualizerComponentInstanceId = ({
recordId,
}: {
recordId: string;
}) => {
return recordId;
};
@@ -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}
@@ -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,
@@ -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,
], ],
); );
@@ -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;
@@ -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({
@@ -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({
@@ -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) ||
@@ -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(() => {
@@ -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)) {
@@ -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;
}; };
@@ -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>
),
], ],
}; };
@@ -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();
@@ -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 {
@@ -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,
], ],
); );
@@ -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)) {
@@ -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(
@@ -0,0 +1,6 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const WorkflowRunVisualizerComponentInstanceContext =
createComponentInstanceContext({
instanceId: '',
});
@@ -0,0 +1,6 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const WorkflowVisualizerComponentInstanceContext =
createComponentInstanceContext({
instanceId: '',
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -1,8 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowDiagramStatusState = createState<
'computing-diagram' | 'computing-dimensions' | 'done'
>({
key: 'workflowDiagramStatusState',
defaultValue: 'computing-diagram',
});
@@ -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,
});
@@ -1,7 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowDiagramTriggerNodeSelectionState = createState<
string | undefined
>({
key: 'workflowDiagramTriggerNodeSelectionState',
defaultValue: undefined,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -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,
});
@@ -1,6 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowSelectedNodeState = createState<string | undefined>({
key: 'workflowSelectedNodeState',
defaultValue: undefined,
});
@@ -0,0 +1,4 @@
export type WorkflowDiagramStatus =
| 'computing-diagram'
| 'computing-dimensions'
| 'done';
@@ -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();
});
});
@@ -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();
});
});
@@ -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();
@@ -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,
});
@@ -1,7 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const workflowCreateStepFromParentStepIdState = createState<
string | undefined
>({
key: 'workflowCreateStepFromParentStepId',
defaultValue: undefined,
});
@@ -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,
}); });
@@ -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,
@@ -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} />;
};
@@ -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>
);
};
@@ -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 () => {
@@ -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 = () => {
@@ -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>
); );
}; };