Visualize workflow run step input (#10677)

- Compute the context the selected step had access to during its
execution and display it with the `<JsonNestedNode />` component
- Ensure several steps with the same name can be displayed in order
- Prevent access to the input tab in a few cases
- Hide the input tab when the trigger node is selected as this node
takes no input
- Hide the input tab when the selected node has not been executed yet or
is currently executed
- Fallback to the Node tab when the Input tab can't be accessed

## Successful workflow execution


https://github.com/user-attachments/assets/4a2bb5f5-450c-46ed-b2d7-a14d3b1e5c1f

## Failed workflow execution


https://github.com/user-attachments/assets/3be2784e-e76c-48ab-aef5-17f63410898e

Closes https://github.com/twentyhq/core-team-issues/issues/433
This commit is contained in:
Baptiste Devessier
2025-03-06 17:49:10 +01:00
committed by GitHub
parent 9d78dc322d
commit cb5f4820d7
22 changed files with 12943 additions and 74 deletions

View File

@ -2,16 +2,21 @@ 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 { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
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 { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
import { WorkflowRunTabId } from '@/workflow/workflow-steps/types/WorkflowRunTabId';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useIcons } from 'twenty-ui';
@ -22,6 +27,26 @@ export const WorkflowRunDiagramCanvasEffect = () => {
const setHotkeyScope = useSetHotkeyScope();
const { closeCommandMenu } = useCommandMenu();
const { activeTabIdState: workflowRunRightDrawerListActiveTabIdState } =
useTabListStates({
tabListScopeId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
});
const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback(
({ snapshot, set }) =>
() => {
const activeWorkflowRunRightDrawerTab = getSnapshotValue(
snapshot,
workflowRunRightDrawerListActiveTabIdState,
) as WorkflowRunTabId | null;
if (activeWorkflowRunRightDrawerTab === 'input') {
set(workflowRunRightDrawerListActiveTabIdState, 'node');
}
},
[workflowRunRightDrawerListActiveTabIdState],
);
const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => {
const selectedNode = nodes[0] as WorkflowDiagramNode;
@ -38,6 +63,14 @@ export const WorkflowRunDiagramCanvasEffect = () => {
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
if (
selectedNode.id === TRIGGER_STEP_ID ||
selectedNodeData.runStatus === 'not-executed' ||
selectedNodeData.runStatus === 'running'
) {
goBackToFirstWorkflowRunRightDrawerTabIfNeeded();
}
openRightDrawer(RightDrawerPages.WorkflowRunStepView, {
title: selectedNodeData.name,
Icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
@ -47,9 +80,10 @@ export const WorkflowRunDiagramCanvasEffect = () => {
setWorkflowSelectedNode,
setHotkeyScope,
openRightDrawer,
getIcon,
closeRightDrawer,
closeCommandMenu,
getIcon,
goBackToFirstWorkflowRunRightDrawerTabIfNeeded,
],
);

View File

@ -1,5 +1,6 @@
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { flowState } from '@/workflow/states/flowState';
import { WorkflowRun } from '@/workflow/types/Workflow';
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
import { useEffect } from 'react';
@ -7,16 +8,24 @@ import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
export const WorkflowRunVisualizerEffect = ({
workflowRun,
workflowRunId,
}: {
workflowRun: WorkflowRun;
workflowRunId: string;
}) => {
const workflowRun = useWorkflowRun({ workflowRunId });
const setWorkflowRunId = useSetRecoilState(workflowRunIdState);
const setFlow = useSetRecoilState(flowState);
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState);
useEffect(() => {
if (!isDefined(workflowRun.output)) {
setWorkflowRunId(workflowRunId);
}, [setWorkflowRunId, workflowRunId]);
useEffect(() => {
if (!isDefined(workflowRun?.output)) {
setFlow(undefined);
setWorkflowDiagram(undefined);
return;
}
@ -25,14 +34,6 @@ export const WorkflowRunVisualizerEffect = ({
trigger: workflowRun.output.flow.trigger,
steps: workflowRun.output.flow.steps,
});
}, [setFlow, workflowRun.output]);
useEffect(() => {
if (!isDefined(workflowRun.output)) {
setWorkflowDiagram(undefined);
return;
}
const nextWorkflowDiagram = generateWorkflowRunDiagram({
trigger: workflowRun.output.flow.trigger,
@ -41,7 +42,7 @@ export const WorkflowRunVisualizerEffect = ({
});
setWorkflowDiagram(nextWorkflowDiagram);
}, [setWorkflowDiagram, workflowRun.output]);
}, [setFlow, setWorkflowDiagram, workflowRun?.output]);
return null;
};