Migrate workflow states to component states (#11773)

- Migrated all workflow Recoil states to component states to isolate
each workflow visualizer instance. The use case of having two workflow
visualizers displayed at the same time appeared recently and will grow
in the near future.
- We chose to use the `recordId` as the value for the `instanceId` of
the component states. Currently, there are a few cases where two
workflows or two workflow runs are rendered at the same time. As a
consequence, relying on the `recordId` is enough for the moment.
- However, there is one case where it's necessary to have a component
state scoped to a workflow visualizer instance: the
`workflowVisualizerStatusState`. This component is tightly coupled to
the `<Reactflow />` component instance rendered in the workflow
visualizer, and it must be set to its default value when the component
first renders. I achieved that by using another component instance
context whose instanceId is an identifier returned by the `useId()` hook
in the Workflow Run Card component.
This commit is contained in:
Baptiste Devessier
2025-05-05 10:58:11 +02:00
committed by GitHub
parent 8b68dce795
commit 1543c900ae
73 changed files with 1209 additions and 752 deletions

View File

@ -1,16 +0,0 @@
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { RightDrawerWorkflowSelectTriggerTypeContent } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const RightDrawerWorkflowSelectTriggerType = () => {
const workflowId = useRecoilValue(workflowIdState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return <RightDrawerWorkflowSelectTriggerTypeContent workflow={workflow} />;
};

View File

@ -1,86 +0,0 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import {
WorkflowTriggerType,
WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes';
import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition';
import { useSetRecoilState } from 'recoil';
import { MenuItemCommand } from 'twenty-ui/navigation';
import { useIcons } from 'twenty-ui/display';
export const RightDrawerWorkflowSelectTriggerTypeContent = ({
workflow,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const { getIcon } = useIcons();
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const handleTriggerTypeClick = ({
type,
defaultLabel,
icon,
}: {
type: WorkflowTriggerType;
defaultLabel: string;
icon: string;
}) => {
return async () => {
await updateTrigger(
getTriggerDefaultDefinition({
defaultLabel,
type,
activeObjectMetadataItems,
}),
);
setWorkflowSelectedNode(TRIGGER_STEP_ID);
openWorkflowEditStepInCommandMenu(
workflow.id,
defaultLabel,
getIcon(icon),
);
};
};
return (
<RightDrawerStepListContainer>
<RightDrawerWorkflowSelectStepTitle>
Data
</RightDrawerWorkflowSelectStepTitle>
{DATABASE_TRIGGER_TYPES.map((action) => (
<MenuItemCommand
key={action.defaultLabel}
LeftIcon={getIcon(action.icon)}
text={action.defaultLabel}
onClick={handleTriggerTypeClick(action)}
/>
))}
<RightDrawerWorkflowSelectStepTitle>
Others
</RightDrawerWorkflowSelectStepTitle>
{OTHER_TRIGGER_TYPES.map((action) => (
<MenuItemCommand
key={action.defaultLabel}
LeftIcon={getIcon(action.icon)}
text={action.defaultLabel}
onClick={handleTriggerTypeClick(action)}
/>
))}
</RightDrawerStepListContainer>
);
};

View File

@ -5,7 +5,8 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { 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 () => {