Visualize workflow run step output (#10730)

- Displays the output of the selected step in the `Output` tab
- Access to the `Output` tab is prevented when the selected node is
currently executed or was skipped
- Display the status of the workflow run instead of the status of the
workflow version at the top left corner of the workflow run visualizer
- Fixed the icon's color for disabled tabs
- Use text/primary color for the step's name even when the input is
disabled

## Demo: Successful execution


https://github.com/user-attachments/assets/02e492f3-1589-48e9-926e-7edb031d9210

## Demo: Failed execution


https://github.com/user-attachments/assets/73e5ec86-5f38-4306-aa9a-46b2e73950da

Closes https://github.com/twentyhq/core-team-issues/issues/434
This commit is contained in:
Baptiste Devessier
2025-03-07 17:35:39 +01:00
committed by GitHub
parent 0e1d742f3d
commit b49ec864b1
19 changed files with 219 additions and 107 deletions

View File

@ -1,8 +1,6 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/components/WorkflowDiagramCustomMarkers';
import { WorkflowVersionStatusTag } from '@/workflow/workflow-diagram/components/WorkflowVersionStatusTag';
import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
@ -16,6 +14,8 @@ import { getOrganizedDiagram } from '@/workflow/workflow-diagram/utils/getOrgani
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
applyEdgeChanges,
applyNodeChanges,
Background,
EdgeChange,
EdgeProps,
@ -23,15 +23,13 @@ import {
NodeChange,
NodeProps,
ReactFlow,
applyEdgeChanges,
applyNodeChanges,
useReactFlow,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import React, { useEffect, useMemo, useRef } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { THEME_COMMON } from 'twenty-ui';
import { Tag, TagColor, THEME_COMMON } from 'twenty-ui';
const StyledResetReactflowStyles = styled.div`
height: 100%;
@ -83,12 +81,13 @@ const defaultFitViewOptions = {
} satisfies FitViewOptions;
export const WorkflowDiagramCanvasBase = ({
status,
nodeTypes,
edgeTypes,
children,
tagContainerTestId,
tagColor,
tagText,
}: {
status: WorkflowVersionStatus;
nodeTypes: Partial<
Record<
WorkflowDiagramNodeType,
@ -112,6 +111,9 @@ export const WorkflowDiagramCanvasBase = ({
>
>;
children?: React.ReactNode;
tagContainerTestId: string;
tagColor: TagColor;
tagText: string;
}) => {
const theme = useTheme();
@ -258,8 +260,8 @@ export const WorkflowDiagramCanvasBase = ({
{children}
</ReactFlow>
<StyledStatusTagContainer data-testid="workflow-visualizer-status">
<WorkflowVersionStatusTag versionStatus={status} />
<StyledStatusTagContainer data-testid={tagContainerTestId}>
<Tag color={tagColor} text={tagText} />
</StyledStatusTagContainer>
</StyledResetReactflowStyles>
);

View File

@ -5,6 +5,7 @@ import { WorkflowDiagramCreateStepNode } from '@/workflow/workflow-diagram/compo
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable';
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
import { ReactFlowProvider } from '@xyflow/react';
export const WorkflowDiagramCanvasEditable = ({
@ -12,10 +13,13 @@ export const WorkflowDiagramCanvasEditable = ({
}: {
versionStatus: WorkflowVersionStatus;
}) => {
const tagProps = getWorkflowVersionStatusTagProps({
workflowVersionStatus: versionStatus,
});
return (
<ReactFlowProvider>
<WorkflowDiagramCanvasBase
status={versionStatus}
nodeTypes={{
default: WorkflowDiagramStepNodeEditable,
'create-step': WorkflowDiagramCreateStepNode,
@ -24,7 +28,11 @@ export const WorkflowDiagramCanvasEditable = ({
edgeTypes={{
default: WorkflowDiagramDefaultEdge,
}}
tagContainerTestId="workflow-visualizer-status"
tagColor={tagProps.color}
tagText={tagProps.text}
/>
<WorkflowDiagramCanvasEditableEffect />
</ReactFlowProvider>
);

View File

@ -5,6 +5,7 @@ import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/componen
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
import { ReactFlowProvider } from '@xyflow/react';
export const WorkflowDiagramCanvasReadonly = ({
@ -12,10 +13,13 @@ export const WorkflowDiagramCanvasReadonly = ({
}: {
versionStatus: WorkflowVersionStatus;
}) => {
const tagProps = getWorkflowVersionStatusTagProps({
workflowVersionStatus: versionStatus,
});
return (
<ReactFlowProvider>
<WorkflowDiagramCanvasBase
status={versionStatus}
nodeTypes={{
default: WorkflowDiagramStepNodeReadonly,
'empty-trigger': WorkflowDiagramEmptyTrigger,
@ -24,7 +28,11 @@ export const WorkflowDiagramCanvasReadonly = ({
default: WorkflowDiagramDefaultEdge,
success: WorkflowDiagramSuccessEdge,
}}
tagContainerTestId="workflow-visualizer-status"
tagColor={tagProps.color}
tagText={tagProps.text}
/>
<WorkflowDiagramCanvasReadonlyEffect />
</ReactFlowProvider>
);

View File

@ -1,20 +1,24 @@
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
import { WorkflowRunStatus } 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 { getWorkflowRunStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowRunStatusTagProps';
import { ReactFlowProvider } from '@xyflow/react';
export const WorkflowRunDiagramCanvas = ({
versionStatus,
workflowRunStatus,
}: {
versionStatus: WorkflowVersionStatus;
workflowRunStatus: WorkflowRunStatus;
}) => {
const tagProps = getWorkflowRunStatusTagProps({
workflowRunStatus,
});
return (
<ReactFlowProvider>
<WorkflowDiagramCanvasBase
status={versionStatus}
nodeTypes={{
default: WorkflowDiagramStepNodeReadonly,
}}
@ -22,6 +26,9 @@ export const WorkflowRunDiagramCanvas = ({
default: WorkflowDiagramDefaultEdge,
success: WorkflowDiagramSuccessEdge,
}}
tagContainerTestId="workflow-run-status"
tagColor={tagProps.color}
tagText={tagProps.text}
/>
<WorkflowRunDiagramCanvasEffect />

View File

@ -40,7 +40,10 @@ export const WorkflowRunDiagramCanvasEffect = () => {
workflowRunRightDrawerListActiveTabIdState,
) as WorkflowRunTabId | null;
if (activeWorkflowRunRightDrawerTab === 'input') {
if (
activeWorkflowRunRightDrawerTab === 'input' ||
activeWorkflowRunRightDrawerTab === 'output'
) {
set(workflowRunRightDrawerListActiveTabIdState, 'node');
}
},

View File

@ -1,22 +0,0 @@
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
import { Tag } from 'twenty-ui';
export const WorkflowVersionStatusTag = ({
versionStatus,
}: {
versionStatus: WorkflowVersionStatus;
}) => {
if (versionStatus === 'ACTIVE') {
return <Tag color="green" text="Active" />;
}
if (versionStatus === 'DRAFT') {
return <Tag color="yellow" text="Draft" />;
}
if (versionStatus === 'ARCHIVED') {
return <Tag color="gray" text="Archived" />;
}
return <Tag color="gray" text="Deactivated" />;
};

View File

@ -41,7 +41,6 @@ type Story = StoryObj<typeof WorkflowDiagramCanvasBase>;
export const DefaultEdge: Story = {
args: {
status: 'DRAFT',
nodeTypes: {
default: WorkflowDiagramStepNodeReadonly,
'create-step': WorkflowDiagramCreateStepNode,
@ -116,7 +115,6 @@ export const DefaultEdge: Story = {
export const SuccessEdge: Story = {
args: {
status: 'DRAFT',
nodeTypes: {
default: WorkflowDiagramStepNodeReadonly,
'create-step': WorkflowDiagramCreateStepNode,

View File

@ -1,43 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { CatalogDecorator, CatalogStory, ComponentDecorator } from 'twenty-ui';
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
import { WorkflowVersionStatusTag } from '../WorkflowVersionStatusTag';
const meta: Meta<typeof WorkflowVersionStatusTag> = {
title: 'Modules/Workflow/WorkflowVersionStatusTag',
component: WorkflowVersionStatusTag,
};
export default meta;
type Story = StoryObj<typeof WorkflowVersionStatusTag>;
export const Default: Story = {
args: {
versionStatus: 'DRAFT',
},
decorators: [ComponentDecorator],
};
export const Catalog: CatalogStory<Story, typeof WorkflowVersionStatusTag> = {
argTypes: {
versionStatus: { table: { disable: true } },
},
parameters: {
catalog: {
dimensions: [
{
name: 'version status',
values: [
'DRAFT',
'ACTIVE',
'DEACTIVATED',
'ARCHIVED',
] satisfies WorkflowVersionStatus[],
props: (versionStatus: WorkflowVersionStatus) => ({ versionStatus }),
},
],
},
},
decorators: [CatalogDecorator],
};

View File

@ -0,0 +1,34 @@
import { WorkflowRunStatus } from '@/workflow/types/Workflow';
import { TagColor } from 'twenty-ui';
export const getWorkflowRunStatusTagProps = ({
workflowRunStatus,
}: {
workflowRunStatus: WorkflowRunStatus;
}): { color: TagColor; text: string } => {
if (workflowRunStatus === 'NOT_STARTED') {
return {
color: 'gray',
text: 'Not started',
};
}
if (workflowRunStatus === 'RUNNING') {
return {
color: 'yellow',
text: 'Running',
};
}
if (workflowRunStatus === 'COMPLETED') {
return {
color: 'green',
text: 'Completed',
};
}
return {
color: 'red',
text: 'Failed',
};
};

View File

@ -0,0 +1,34 @@
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
import { TagColor } from 'twenty-ui';
export const getWorkflowVersionStatusTagProps = ({
workflowVersionStatus,
}: {
workflowVersionStatus: WorkflowVersionStatus;
}): { color: TagColor; text: string } => {
if (workflowVersionStatus === 'ARCHIVED') {
return {
color: 'gray',
text: 'Archived',
};
}
if (workflowVersionStatus === 'DRAFT') {
return {
color: 'yellow',
text: 'Draft',
};
}
if (workflowVersionStatus === 'ACTIVE') {
return {
color: 'green',
text: 'Active',
};
}
return {
color: 'gray',
text: 'Deactivated',
};
};