Create a right drawer for viewing steps in workflow runs (#10366)

- Improve the type-safety of the objects mapping the id of a right
drawer or side panel view to a React component
- Improve the types of the `useTabList` hook to type the available tab
identifiers strictly
- Create a specialized `WorkflowRunDiagramCanvas` component to render a
`WorkflowRunDiagramCanvasEffect` component that opens
`RightDrawerPages.WorkflowRunStepView` when a step is selected
- Create a new side panel view specifically for workflow run step
details
- Create tab list in the new side panel; all the tabs are `Node`,
`Input` and `Output`
- Create a hook `useWorkflowSelectedNodeOrThrow` not to duplicate
throwing mechanisms

Closes https://github.com/twentyhq/core-team-issues/issues/432

## Demo


https://github.com/user-attachments/assets/8d5df7dc-0b99-49a2-9a54-d3eaee80a8e6
This commit is contained in:
Baptiste Devessier
2025-02-26 16:48:24 +01:00
committed by GitHub
parent 694553608b
commit f74e4bedc4
28 changed files with 418 additions and 148 deletions

View File

@ -0,0 +1,30 @@
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
import { WorkflowRunDiagramCanvasEffect } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect';
import { ReactFlowProvider } from '@xyflow/react';
export const WorkflowRunDiagramCanvas = ({
versionStatus,
}: {
versionStatus: WorkflowVersionStatus;
}) => {
return (
<ReactFlowProvider>
<WorkflowDiagramCanvasBase
status={versionStatus}
nodeTypes={{
default: WorkflowDiagramStepNodeReadonly,
}}
edgeTypes={{
default: WorkflowDiagramDefaultEdge,
success: WorkflowDiagramSuccessEdge,
}}
/>
<WorkflowRunDiagramCanvasEffect />
</ReactFlowProvider>
);
};

View File

@ -0,0 +1,61 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import {
WorkflowDiagramNode,
WorkflowDiagramStepNodeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useIcons } from 'twenty-ui';
export const WorkflowRunDiagramCanvasEffect = () => {
const { getIcon } = useIcons();
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setHotkeyScope = useSetHotkeyScope();
const { closeCommandMenu } = useCommandMenu();
const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => {
const selectedNode = nodes[0] as WorkflowDiagramNode;
const isClosingStep = isDefined(selectedNode) === false;
if (isClosingStep) {
closeRightDrawer();
closeCommandMenu();
return;
}
setWorkflowSelectedNode(selectedNode.id);
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
openRightDrawer(RightDrawerPages.WorkflowRunStepView, {
title: selectedNodeData.name,
Icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
});
},
[
setWorkflowSelectedNode,
setHotkeyScope,
openRightDrawer,
closeRightDrawer,
closeCommandMenu,
getIcon,
],
);
useOnSelectionChange({
onChange: handleSelectionChange,
});
return null;
};

View File

@ -0,0 +1,15 @@
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
export const useWorkflowSelectedNodeOrThrow = () => {
const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState);
if (!isDefined(workflowSelectedNode)) {
throw new Error(
'Expected a node to be selected. A node must have been selected before running this code.',
);
}
return workflowSelectedNode;
};