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
@@ -1,9 +1,10 @@
import { flowState } from '@/workflow/states/flowState';
import { useRecoilValue } from 'recoil';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { flowComponentState } from '@/workflow/states/flowComponentState';
import { isDefined } from 'twenty-shared/utils';
export const useFlowOrThrow = () => {
const flow = useRecoilValue(flowState);
const flow = useRecoilComponentValueV2(flowComponentState);
if (!isDefined(flow)) {
throw new Error('Expected the flow to be defined');
}
@@ -3,11 +3,12 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { flowState } from '@/workflow/states/flowState';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState';
import { WorkflowRun } from '@/workflow/types/Workflow';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { useApolloClient } from '@apollo/client';
@@ -58,17 +59,46 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => {
return;
}
set(workflowRunIdState, workflowRunRecord.id);
set(workflowIdState, workflowRunRecord.workflowId);
set(flowState, {
workflowVersionId: workflowRunRecord.workflowVersionId,
trigger: workflowRunRecord.output.flow.trigger,
steps: workflowRunRecord.output.flow.steps,
});
set(workflowSelectedNodeState, stepToOpenByDefault.id);
set(
workflowVisualizerWorkflowRunIdComponentState.atomFamily({
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId,
}),
}),
workflowRunRecord.id,
);
set(
workflowVisualizerWorkflowIdComponentState.atomFamily({
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId,
}),
}),
workflowRunRecord.workflowId,
);
set(
flowComponentState.atomFamily({
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId,
}),
}),
{
workflowVersionId: workflowRunRecord.workflowVersionId,
trigger: workflowRunRecord.output.flow.trigger,
steps: workflowRunRecord.output.flow.steps,
},
);
set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: getWorkflowVisualizerComponentInstanceId({
recordId,
}),
}),
stepToOpenByDefault.id,
);
openWorkflowRunViewStepInCommandMenu({
workflowId: workflowRunRecord.workflowId,
workflowRunId: workflowRunRecord.id,
title: stepToOpenByDefault.data.name,
icon: getIcon(getWorkflowNodeIconKey(stepToOpenByDefault.data)),
workflowSelectedNode: stepToOpenByDefault.id,
@@ -1,9 +1,12 @@
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
import { useRecoilValue } from 'recoil';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState';
import { isDefined } from 'twenty-shared/utils';
export const useWorkflowRunIdOrThrow = () => {
const workflowRunId = useRecoilValue(workflowRunIdState);
const workflowRunId = useRecoilComponentValueV2(
workflowVisualizerWorkflowRunIdComponentState,
);
if (!isDefined(workflowRunId)) {
throw new Error('Expected the workflow run ID to be defined');
}
@@ -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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/components/WorkflowDiagramCustomMarkers';
import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import {
WorkflowDiagramEdge,
WorkflowDiagramEdgeType,
@@ -26,7 +27,6 @@ import {
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import React, { useEffect, useMemo, useRef } from 'react';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { Tag, TagColor } from 'twenty-ui/components';
import { THEME_COMMON } from 'twenty-ui/theme';
@@ -121,7 +121,9 @@ export const WorkflowDiagramCanvasBase = ({
const reactflow = useReactFlow();
const workflowDiagram = useRecoilValue(workflowDiagramState);
const workflowDiagram = useRecoilComponentValueV2(
workflowDiagramComponentState,
);
const { nodes, edges } = useMemo(
() =>
@@ -137,14 +139,8 @@ export const WorkflowDiagramCanvasBase = ({
THEME_COMMON.rightDrawerWidth.replace('px', ''),
);
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState);
const setWorkflowReactFlowRef = useRecoilCallback(
({ set }) =>
(node: HTMLDivElement | null) => {
set(workflowReactFlowRefState, { current: node });
},
[],
const setWorkflowDiagram = useSetRecoilComponentStateV2(
workflowDiagramComponentState,
);
const handleEdgesChange = (
@@ -199,24 +195,18 @@ export const WorkflowDiagramCanvasBase = ({
);
}, [reactflow, rightDrawerState, rightDrawerWidth]);
const handleNodesChanges = useRecoilCallback(
({ set }) =>
(changes: NodeChange<WorkflowDiagramNode>[]) => {
set(workflowDiagramState, (diagram) => {
if (!isDefined(diagram)) {
throw new Error(
'It must be impossible for the nodes to be updated if the diagram is not defined yet. Be sure the diagram is rendered only when defined.',
);
}
const handleNodesChanges = (changes: NodeChange<WorkflowDiagramNode>[]) => {
setWorkflowDiagram((diagram) => {
if (!isDefined(diagram)) {
return diagram;
}
return {
...diagram,
nodes: applyNodeChanges(changes, diagram.nodes),
};
});
},
[],
);
return {
...diagram,
nodes: applyNodeChanges(changes, diagram.nodes),
};
});
};
const handleInit = () => {
if (!isDefined(containerRef.current)) {
@@ -239,7 +229,6 @@ export const WorkflowDiagramCanvasBase = ({
<WorkflowDiagramCustomMarkers />
<ReactFlow
ref={setWorkflowReactFlowRef}
onInit={handleInit}
minZoom={defaultFitViewOptions.minZoom}
maxZoom={defaultFitViewOptions.maxZoom}
@@ -1,15 +1,17 @@
import { useCallback, useContext } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useSetRecoilState } from 'recoil';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
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 { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import {
WorkflowDiagramNode,
WorkflowDiagramStepNodeData,
@@ -29,7 +31,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
openWorkflowEditStepInCommandMenu,
} = useWorkflowCommandMenu();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const setCommandMenuNavigationStack = useSetRecoilState(
commandMenuNavigationStackState,
@@ -37,7 +41,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const { isInRightDrawer } = useContext(ActionMenuContext);
const workflowId = useRecoilValue(workflowIdState);
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => {
@@ -53,8 +59,8 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const isEmptyTriggerNode = selectedNode.type === EMPTY_TRIGGER_STEP_ID;
if (isEmptyTriggerNode) {
if (isDefined(workflowId)) {
openWorkflowTriggerTypeInCommandMenu(workflowId);
if (isDefined(workflowVisualizerWorkflowId)) {
openWorkflowTriggerTypeInCommandMenu(workflowVisualizerWorkflowId);
return;
}
@@ -71,9 +77,9 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
setWorkflowSelectedNode(selectedNode.id);
if (isDefined(workflowId)) {
if (isDefined(workflowVisualizerWorkflowId)) {
openWorkflowEditStepInCommandMenu(
workflowId,
workflowVisualizerWorkflowId,
selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
);
@@ -84,7 +90,7 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
[
isInRightDrawer,
setCommandMenuNavigationStack,
workflowId,
workflowVisualizerWorkflowId,
openWorkflowTriggerTypeInCommandMenu,
startNodeCreation,
openWorkflowEditStepInCommandMenu,
@@ -1,7 +1,10 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import {
WorkflowDiagramNode,
WorkflowDiagramStepNodeData,
@@ -9,19 +12,34 @@ import {
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
export const WorkflowDiagramCanvasReadonlyEffect = () => {
const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflowVisualizerWorkflowVersionId = useRecoilComponentValueV2(
workflowVisualizerWorkflowVersionIdComponentState,
);
const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => {
if (
!(
isDefined(workflowVisualizerWorkflowId) &&
isDefined(workflowVisualizerWorkflowVersionId)
)
) {
return;
}
const selectedNode = nodes[0] as WorkflowDiagramNode | undefined;
if (!isDefined(selectedNode)) {
@@ -32,18 +50,18 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
if (isDefined(workflowId)) {
openWorkflowViewStepInCommandMenu(
workflowId,
selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
);
}
openWorkflowViewStepInCommandMenu({
workflowId: workflowVisualizerWorkflowId,
workflowVersionId: workflowVisualizerWorkflowVersionId,
title: selectedNodeData.name,
icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
});
},
[
setWorkflowSelectedNode,
openWorkflowViewStepInCommandMenu,
workflowId,
workflowVisualizerWorkflowId,
workflowVisualizerWorkflowVersionId,
getIcon,
],
);
@@ -1,18 +1,20 @@
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema';
import { flowState } from '@/workflow/states/flowState';
import { workflowLastCreatedStepIdState } from '@/workflow/states/workflowLastCreatedStepIdState';
import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState';
import {
WorkflowVersion,
WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { addCreateStepNodes } from '@/workflow/workflow-diagram/utils/addCreateStepNodes';
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
import { mergeWorkflowDiagrams } from '@/workflow/workflow-diagram/utils/mergeWorkflowDiagrams';
import { useEffect } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const WorkflowDiagramEffect = ({
@@ -20,10 +22,19 @@ export const WorkflowDiagramEffect = ({
}: {
workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined;
}) => {
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState);
const setFlow = useSetRecoilState(flowState);
const workflowDiagramState = useRecoilComponentCallbackStateV2(
workflowDiagramComponentState,
);
const setWorkflowDiagram = useSetRecoilComponentStateV2(
workflowDiagramComponentState,
);
const setFlow = useSetRecoilComponentStateV2(flowComponentState);
const { populateStepsOutputSchema } = useStepsOutputSchema();
const workflowLastCreatedStepIdState = useRecoilComponentCallbackStateV2(
workflowLastCreatedStepIdComponentState,
);
const computeAndMergeNewWorkflowDiagram = useRecoilCallback(
({ snapshot, set }) => {
return (currentVersion: WorkflowVersion) => {
@@ -64,7 +75,7 @@ export const WorkflowDiagramEffect = ({
set(workflowDiagramState, mergedWorkflowDiagram);
};
},
[],
[workflowLastCreatedStepIdState, workflowDiagramState],
);
const currentVersion = workflowWithCurrentVersion?.currentVersion;
@@ -1,10 +1,10 @@
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined';
import { WorkflowDiagramStepNodeEditableContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent';
import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { useRecoilValue } from 'recoil';
export const WorkflowDiagramStepNodeEditable = ({
id,
@@ -15,9 +15,13 @@ export const WorkflowDiagramStepNodeEditable = ({
data: WorkflowDiagramStepNodeData;
selected?: boolean;
}) => {
const workflowId = useRecoilValue(workflowIdState);
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(
workflowVisualizerWorkflowId,
);
assertWorkflowWithCurrentVersionIsDefined(workflowWithCurrentVersion);
const { deleteStep } = useDeleteStep({
@@ -1,8 +1,10 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { WorkflowRunDiagramNode } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
@@ -15,10 +17,25 @@ export const WorkflowRunDiagramCanvasEffect = () => {
const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowVisualizerWorkflowIdState = useRecoilComponentCallbackStateV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflowDiagramStatusState = useRecoilComponentCallbackStateV2(
workflowDiagramStatusComponentState,
);
const workflowSelectedNodeState = useRecoilComponentCallbackStateV2(
workflowSelectedNodeComponentState,
);
const handleSelectionChange = useRecoilCallback(
({ snapshot, set }) =>
({ nodes }: OnSelectionChangeParams) => {
const workflowId = getSnapshotValue(snapshot, workflowIdState);
const workflowId = getSnapshotValue(
snapshot,
workflowVisualizerWorkflowIdState,
);
if (!isDefined(workflowId)) {
throw new Error('Expected the workflowId to be defined.');
@@ -49,13 +66,21 @@ export const WorkflowRunDiagramCanvasEffect = () => {
openWorkflowRunViewStepInCommandMenu({
workflowId,
workflowRunId,
title: selectedNodeData.name,
icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
workflowSelectedNode: selectedNode.id,
stepExecutionStatus: selectedNodeData.runStatus,
});
},
[getIcon, openWorkflowRunViewStepInCommandMenu],
[
workflowVisualizerWorkflowIdState,
workflowDiagramStatusState,
workflowSelectedNodeState,
openWorkflowRunViewStepInCommandMenu,
workflowRunId,
getIcon,
],
);
useOnSelectionChange({
@@ -1,8 +1,8 @@
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas';
import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState';
import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div`
@@ -15,7 +15,9 @@ export const WorkflowRunVisualizer = ({
workflowRunId: string;
}) => {
const workflowRun = useWorkflowRun({ workflowRunId });
const workflowDiagramStatus = useRecoilValue(workflowDiagramStatusState);
const workflowDiagramStatus = useRecoilComponentValueV2(
workflowDiagramStatusComponentState,
);
if (
!isDefined(workflowRun) ||
@@ -1,18 +1,21 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
import { flowState } from '@/workflow/states/flowState';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState';
import { WorkflowRunOutput } from '@/workflow/types/Workflow';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState';
import { workflowRunStepToOpenByDefaultState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState';
import { workflowRunStepToOpenByDefaultComponentState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState';
import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
import { selectWorkflowDiagramNode } from '@/workflow/workflow-diagram/utils/selectWorkflowDiagramNode';
import { useContext, useEffect } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const WorkflowRunVisualizerEffect = ({
@@ -23,8 +26,24 @@ export const WorkflowRunVisualizerEffect = ({
const workflowRun = useWorkflowRun({ workflowRunId });
const workflowVersion = useWorkflowVersion(workflowRun?.workflowVersionId);
const setWorkflowRunId = useSetRecoilState(workflowRunIdState);
const setWorkflowId = useSetRecoilState(workflowIdState);
const setWorkflowRunId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowRunIdComponentState,
);
const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowIdComponentState,
);
const flowState = useRecoilComponentCallbackStateV2(flowComponentState);
const workflowDiagramState = useRecoilComponentCallbackStateV2(
workflowDiagramComponentState,
);
const workflowDiagramStatusState = useRecoilComponentCallbackStateV2(
workflowDiagramStatusComponentState,
);
const workflowRunStepToOpenByDefaultState = useRecoilComponentCallbackStateV2(
workflowRunStepToOpenByDefaultComponentState,
);
const { populateStepsOutputSchema } = useStepsOutputSchema();
const { isInRightDrawer } = useContext(ActionMenuContext);
@@ -38,11 +57,11 @@ export const WorkflowRunVisualizerEffect = ({
return;
}
setWorkflowId(workflowRun.workflowId);
}, [setWorkflowId, workflowRun]);
setWorkflowVisualizerWorkflowId(workflowRun.workflowId);
}, [setWorkflowVisualizerWorkflowId, workflowRun]);
const handleWorkflowRunDiagramGeneration = useRecoilCallback(
({ set }) =>
({ snapshot, set }) =>
({
workflowRunOutput,
workflowVersionId,
@@ -59,7 +78,14 @@ export const WorkflowRunVisualizerEffect = ({
return;
}
set(workflowDiagramStatusState, 'computing-diagram');
const workflowDiagramStatus = getSnapshotValue(
snapshot,
workflowDiagramStatusState,
);
if (workflowDiagramStatus !== 'done') {
set(workflowDiagramStatusState, 'computing-diagram');
}
set(flowState, {
workflowVersionId,
@@ -89,9 +115,16 @@ export const WorkflowRunVisualizerEffect = ({
set(workflowDiagramState, baseWorkflowRunDiagram);
}
set(workflowDiagramStatusState, 'computing-dimensions');
if (workflowDiagramStatus !== 'done') {
set(workflowDiagramStatusState, 'computing-dimensions');
}
},
[],
[
flowState,
workflowDiagramState,
workflowDiagramStatusState,
workflowRunStepToOpenByDefaultState,
],
);
useEffect(() => {
@@ -1,11 +1,12 @@
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useStepsOutputSchema } from '@/workflow/hooks/useStepsOutputSchema';
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
import { flowState } from '@/workflow/states/flowState';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import { flowComponentState } from '@/workflow/states/flowComponentState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const WorkflowVersionVisualizerEffect = ({
@@ -15,10 +16,19 @@ export const WorkflowVersionVisualizerEffect = ({
}) => {
const workflowVersion = useWorkflowVersion(workflowVersionId);
const setFlow = useSetRecoilState(flowState);
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState);
const setWorkflowId = useSetRecoilState(workflowIdState);
const setFlow = useSetRecoilComponentStateV2(flowComponentState);
const setWorkflowDiagram = useSetRecoilComponentStateV2(
workflowDiagramComponentState,
);
const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowIdComponentState,
);
const setWorkflowVisualizerWorkflowVersionId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowVersionIdComponentState,
);
const { populateStepsOutputSchema } = useStepsOutputSchema();
useEffect(() => {
if (!isDefined(workflowVersion)) {
setFlow(undefined);
@@ -32,8 +42,14 @@ export const WorkflowVersionVisualizerEffect = ({
steps: workflowVersion.steps,
});
setWorkflowId(workflowVersion.workflowId);
}, [setFlow, setWorkflowId, workflowVersion]);
setWorkflowVisualizerWorkflowId(workflowVersion.workflowId);
setWorkflowVisualizerWorkflowVersionId(workflowVersion.id);
}, [
setFlow,
setWorkflowVisualizerWorkflowId,
setWorkflowVisualizerWorkflowVersionId,
workflowVersion,
]);
useEffect(() => {
if (!isDefined(workflowVersion)) {
@@ -1,17 +1,19 @@
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
export const WorkflowVisualizerEffect = ({
workflowId,
}: {
workflowId: string;
}) => {
const setWorkflowId = useSetRecoilState(workflowIdState);
const setWorkflowVisualizerWorkflowId = useSetRecoilComponentStateV2(
workflowVisualizerWorkflowIdComponentState,
);
useEffect(() => {
setWorkflowId(workflowId);
}, [setWorkflowId, workflowId]);
setWorkflowVisualizerWorkflowId(workflowId);
}, [setWorkflowVisualizerWorkflowId, workflowId]);
return null;
};
@@ -9,11 +9,12 @@ import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/com
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeDefaultConfiguration';
import { WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeSuccessConfiguration';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator';
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { workflowDiagramState } from '../../states/workflowDiagramState';
import { workflowDiagramComponentState } from '../../states/workflowDiagramComponentState';
import { WorkflowDiagramCanvasBase } from '../WorkflowDiagramCanvasBase';
const StyledContainer = styled.div`
@@ -51,63 +52,79 @@ export const DefaultEdge: Story = {
},
},
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(workflowDiagramState, {
nodes: [
(Story) => {
const workflowVisualizerComponentInstanceId =
'workflow-visualizer-test-id';
return (
<RecoilRoot
initializeState={({ set }) => {
set(
workflowDiagramComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
{
id: 'trigger-1',
type: 'default',
position: { x: 100, y: 100 },
data: {
nodeType: 'trigger',
triggerType: 'DATABASE_EVENT',
name: 'When record is created',
},
nodes: [
{
id: 'trigger-1',
type: 'default',
position: { x: 100, y: 100 },
data: {
nodeType: 'trigger',
triggerType: 'DATABASE_EVENT',
name: 'When record is created',
},
},
{
id: 'action-1',
type: 'default',
position: { x: 300, y: 100 },
data: {
nodeType: 'action',
actionType: 'CREATE_RECORD',
name: 'Create record',
},
},
{
id: 'create-step-1',
type: 'create-step',
position: { x: 500, y: 100 },
data: {
nodeType: 'create-step',
parentNodeId: 'action-1',
},
},
],
edges: [
{
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION,
id: 'edge-1',
source: 'trigger-1',
target: 'action-1',
},
{
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION,
id: 'edge-2',
source: 'action-1',
target: 'create-step-1',
},
],
},
{
id: 'action-1',
type: 'default',
position: { x: 300, y: 100 },
data: {
nodeType: 'action',
actionType: 'CREATE_RECORD',
name: 'Create record',
},
},
{
id: 'create-step-1',
type: 'create-step',
position: { x: 500, y: 100 },
data: {
nodeType: 'create-step',
parentNodeId: 'action-1',
},
},
],
edges: [
{
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION,
id: 'edge-1',
source: 'trigger-1',
target: 'action-1',
},
{
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION,
id: 'edge-2',
source: 'action-1',
target: 'create-step-1',
},
],
});
}}
>
<StyledContainer>
<Story />
</StyledContainer>
</RecoilRoot>
),
);
}}
>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
<StyledContainer>
<Story />
</StyledContainer>
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
);
},
],
};
@@ -124,49 +141,65 @@ export const SuccessEdge: Story = {
},
},
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(workflowDiagramState, {
nodes: [
(Story) => {
const workflowVisualizerComponentInstanceId =
'workflow-visualizer-test-id';
return (
<RecoilRoot
initializeState={({ set }) => {
set(
workflowDiagramComponentState.atomFamily({
instanceId: workflowVisualizerComponentInstanceId,
}),
{
id: 'trigger-1',
type: 'default',
position: { x: 100, y: 100 },
data: {
nodeType: 'trigger',
triggerType: 'DATABASE_EVENT',
name: 'When record is created',
},
nodes: [
{
id: 'trigger-1',
type: 'default',
position: { x: 100, y: 100 },
data: {
nodeType: 'trigger',
triggerType: 'DATABASE_EVENT',
name: 'When record is created',
},
},
{
id: 'action-1',
type: 'default',
position: { x: 300, y: 100 },
data: {
nodeType: 'action',
actionType: 'CREATE_RECORD',
name: 'Create record',
},
},
],
edges: [
{
...WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION,
id: 'edge-1',
source: 'trigger-1',
target: 'action-1',
type: 'success',
label: '1 item',
},
],
},
{
id: 'action-1',
type: 'default',
position: { x: 300, y: 100 },
data: {
nodeType: 'action',
actionType: 'CREATE_RECORD',
name: 'Create record',
},
},
],
edges: [
{
...WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION,
id: 'edge-1',
source: 'trigger-1',
target: 'action-1',
type: 'success',
label: '1 item',
},
],
});
}}
>
<StyledContainer>
<Story />
</StyledContainer>
</RecoilRoot>
),
);
}}
>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
<StyledContainer>
<Story />
</StyledContainer>
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
);
},
],
};
@@ -1,15 +1,25 @@
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState';
import { act, renderHook } from '@testing-library/react';
import { useReactFlow } from '@xyflow/react';
import { RecoilRoot, useRecoilState } from 'recoil';
import { RecoilRoot } from 'recoil';
jest.mock('@xyflow/react', () => ({
useReactFlow: jest.fn(),
}));
const wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot>
<RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: 'test-instance-id',
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
);
describe('useTriggerNodeSelection', () => {
@@ -31,7 +41,9 @@ describe('useTriggerNodeSelection', () => {
const [
workflowDiagramTriggerNodeSelection,
setWorkflowDiagramTriggerNodeSelection,
] = useRecoilState(workflowDiagramTriggerNodeSelectionState);
] = useRecoilComponentStateV2(
workflowDiagramTriggerNodeSelectionComponentState,
);
useTriggerNodeSelection();
@@ -1,10 +1,12 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowDiagramStatusState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusState';
import { workflowRunStepToOpenByDefaultState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState';
import { workflowRunStepToOpenByDefaultComponentState } from '@/workflow/workflow-diagram/states/workflowRunStepToOpenByDefaultComponentState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil';
@@ -17,6 +19,21 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => {
const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu();
const { isInRightDrawer } = useContext(ActionMenuContext);
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowVisualizerWorkflowIdState = useRecoilComponentCallbackStateV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflowDiagramStatusState = useRecoilComponentCallbackStateV2(
workflowDiagramStatusComponentState,
);
const workflowRunStepToOpenByDefaultState = useRecoilComponentCallbackStateV2(
workflowRunStepToOpenByDefaultComponentState,
);
const workflowSelectedNodeState = useRecoilComponentCallbackStateV2(
workflowSelectedNodeComponentState,
);
const handleWorkflowRunDiagramCanvasInit = useRecoilCallback(
({ snapshot, set }) =>
() => {
@@ -43,8 +60,11 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => {
);
if (isDefined(workflowStepToOpenByDefault)) {
const workflowId = getSnapshotValue(snapshot, workflowIdState);
if (!isDefined(workflowId)) {
const workflowVisualizerWorkflowId = getSnapshotValue(
snapshot,
workflowVisualizerWorkflowIdState,
);
if (!isDefined(workflowVisualizerWorkflowId)) {
throw new Error(
'The workflow id must be set; ensure the workflow id is always set before rendering the workflow diagram.',
);
@@ -53,7 +73,8 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => {
set(workflowSelectedNodeState, workflowStepToOpenByDefault.id);
openWorkflowRunViewStepInCommandMenu({
workflowId,
workflowId: workflowVisualizerWorkflowId,
workflowRunId,
title: workflowStepToOpenByDefault.data.name,
icon: getIcon(
getWorkflowNodeIconKey(workflowStepToOpenByDefault.data),
@@ -65,7 +86,16 @@ export const useHandleWorkflowRunDiagramCanvasInit = () => {
set(workflowRunStepToOpenByDefaultState, undefined);
}
},
[getIcon, isInRightDrawer, openWorkflowRunViewStepInCommandMenu],
[
workflowDiagramStatusState,
isInRightDrawer,
workflowRunStepToOpenByDefaultState,
workflowVisualizerWorkflowIdState,
workflowSelectedNodeState,
openWorkflowRunViewStepInCommandMenu,
workflowRunId,
getIcon,
],
);
return {
@@ -1,18 +1,21 @@
import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowCreateStepFromParentStepIdComponentState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState';
import { isDefined } from 'twenty-shared/utils';
export const useStartNodeCreation = () => {
const setWorkflowCreateStepFromParentStepId = useSetRecoilState(
workflowCreateStepFromParentStepIdState,
const setWorkflowCreateStepFromParentStepId = useSetRecoilComponentStateV2(
workflowCreateStepFromParentStepIdComponentState,
);
const { openStepSelectInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
/**
* This function is used in a context where dependencies shouldn't change much.
@@ -22,14 +25,14 @@ export const useStartNodeCreation = () => {
(parentNodeId: string) => {
setWorkflowCreateStepFromParentStepId(parentNodeId);
if (isDefined(workflowId)) {
openStepSelectInCommandMenu(workflowId);
if (isDefined(workflowVisualizerWorkflowId)) {
openStepSelectInCommandMenu(workflowVisualizerWorkflowId);
return;
}
},
[
setWorkflowCreateStepFromParentStepId,
workflowId,
workflowVisualizerWorkflowId,
openStepSelectInCommandMenu,
],
);
@@ -1,11 +1,11 @@
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState';
import {
WorkflowDiagramEdge,
WorkflowDiagramNode,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { useReactFlow } from '@xyflow/react';
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const useTriggerNodeSelection = () => {
@@ -14,7 +14,9 @@ export const useTriggerNodeSelection = () => {
const [
workflowDiagramTriggerNodeSelection,
setWorkflowDiagramTriggerNodeSelection,
] = useRecoilState(workflowDiagramTriggerNodeSelectionState);
] = useRecoilComponentStateV2(
workflowDiagramTriggerNodeSelectionComponentState,
);
useEffect(() => {
if (!isDefined(workflowDiagramTriggerNodeSelection)) {
@@ -1,9 +1,11 @@
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { useRecoilValue } from 'recoil';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { isDefined } from 'twenty-shared/utils';
export const useWorkflowSelectedNodeOrThrow = () => {
const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState);
const workflowSelectedNode = useRecoilComponentValueV2(
workflowSelectedNodeComponentState,
);
if (!isDefined(workflowSelectedNode)) {
throw new Error(
@@ -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 { workflowLastCreatedStepIdState } from '@/workflow/states/workflowLastCreatedStepIdState';
import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState';
import {
WorkflowStepType,
WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { useCreateWorkflowVersionStep } from '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionStep';
import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { workflowCreateStepFromParentStepIdComponentState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdComponentState';
import { isDefined } from 'twenty-shared/utils';
export const useCreateStep = ({
@@ -16,13 +17,15 @@ export const useCreateStep = ({
workflow: WorkflowWithCurrentVersion;
}) => {
const { createWorkflowVersionStep } = useCreateWorkflowVersionStep();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setWorkflowLastCreatedStepId = useSetRecoilState(
workflowLastCreatedStepIdState,
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const setWorkflowLastCreatedStepId = useSetRecoilComponentStateV2(
workflowLastCreatedStepIdComponentState,
);
const workflowCreateStepFromParentStepId = useRecoilValue(
workflowCreateStepFromParentStepIdState,
const workflowCreateStepFromParentStepId = useRecoilComponentValueV2(
workflowCreateStepFromParentStepIdComponentState,
);
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
@@ -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 { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { WorkflowCodeAction } from '@/workflow/types/Workflow';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue';
@@ -36,11 +36,11 @@ import { Monaco } from '@monaco-editor/react';
import { editor } from 'monaco-editor';
import { AutoTypings } from 'monaco-editor-auto-typings';
import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useDebouncedCallback } from 'use-debounce';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { CodeEditor } from 'twenty-ui/input';
import { IconCode, IconPlayerPlay, useIcons } from 'twenty-ui/display';
import { CodeEditor } from 'twenty-ui/input';
import { useDebouncedCallback } from 'use-debounce';
const StyledCodeEditorContainer = styled.div`
display: flex;
@@ -82,8 +82,10 @@ export const WorkflowEditActionServerlessFunction = ({
useUpdateOneServerlessFunction(serverlessFunctionId);
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
const workflowId = useRecoilValue(workflowIdState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
const { availablePackages } = useGetAvailablePackages({
id: serverlessFunctionId,
});
@@ -8,7 +8,8 @@ import { FormTextFieldInput } from '@/object-record/record-field/form-types/comp
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
import { SettingsPath } from '@/types/SettingsPath';
import { Select } from '@/ui/input/components/Select';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { WorkflowSendEmailAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
@@ -20,11 +21,11 @@ import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
import { IconPlus, useIcons } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
import { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { IconPlus, useIcons } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
type WorkflowEditActionSendEmailProps = {
action: WorkflowSendEmailAction;
@@ -53,8 +54,10 @@ export const WorkflowEditActionSendEmail = ({
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { triggerApisOAuth } = useTriggerApisOAuth();
const workflowId = useRecoilValue(workflowIdState);
const redirectUrl = `/object/workflow/${workflowId}`;
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const redirectUrl = `/object/workflow/${workflowVisualizerWorkflowId}`;
const [formData, setFormData] = useState<SendEmailFormData>({
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 { Select } from '@/ui/input/components/Select';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { WorkflowWebhookTrigger } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
@@ -54,7 +55,9 @@ export const WorkflowEditTriggerWebhookForm = ({
const [errorMessages, setErrorMessages] = useState<FormErrorMessages>({});
const [errorMessagesVisible, setErrorMessagesVisible] = useState(false);
const { getIcon } = useIcons();
const workflowId = useRecoilValue(workflowIdState);
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
workflowVisualizerWorkflowIdComponentState,
);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const onBlur = () => {
@@ -66,7 +69,7 @@ export const WorkflowEditTriggerWebhookForm = ({
const headerIcon = getTriggerIcon(trigger);
const headerType = getTriggerHeaderType(trigger);
const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowId}`;
const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowVisualizerWorkflowId}`;
const displayWebhookUrl = webhookUrl.replace(/^(https?:\/\/)?(www\.)?/, '');
const copyToClipboard = async () => {
@@ -13,13 +13,12 @@ import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isReco
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { workflowDiagramTriggerNodeSelectionComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionComponentState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath';
import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema';
import { useState } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import {
IconChevronLeft,
@@ -42,13 +41,15 @@ export const WorkflowVariablesDropdownFieldItems = ({
const [currentPath, setCurrentPath] = useState<string[]>([]);
const [searchInputValue, setSearchInputValue] = useState('');
const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
workflowSelectedNodeComponentState,
);
const setActiveTabId = useSetRecoilComponentStateV2(
activeTabIdComponentState,
'workflow-serverless-function-tab-list-component-id',
);
const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState(
workflowDiagramTriggerNodeSelectionState,
const setWorkflowDiagramTriggerNodeSelection = useSetRecoilComponentStateV2(
workflowDiagramTriggerNodeSelectionComponentState,
);
const getDisplayedSubStepFields = () => {