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:
committed by
GitHub
parent
0e1d742f3d
commit
b49ec864b1
@ -83,7 +83,9 @@ export const Tab = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const iconColor = active
|
const iconColor = active
|
||||||
? theme.font.color.primary
|
? theme.font.color.primary
|
||||||
: theme.font.color.secondary;
|
: disabled
|
||||||
|
? theme.font.color.light
|
||||||
|
: theme.font.color.secondary;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTab
|
<StyledTab
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { WorkflowRunVisualizerContent } from '@/workflow/components/WorkflowRunVisualizerContent';
|
|
||||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||||
|
import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export const WorkflowRunVisualizer = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSourceCodeContainer>
|
<StyledSourceCodeContainer>
|
||||||
<WorkflowRunVisualizerContent workflowRun={workflowRun} />
|
<WorkflowRunDiagramCanvas workflowRunStatus={workflowRun.status} />
|
||||||
</StyledSourceCodeContainer>
|
</StyledSourceCodeContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
|
|
||||||
import { WorkflowRun } from '@/workflow/types/Workflow';
|
|
||||||
import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const WorkflowRunVisualizerContent = ({
|
|
||||||
workflowRun,
|
|
||||||
}: {
|
|
||||||
workflowRun: WorkflowRun;
|
|
||||||
}) => {
|
|
||||||
const workflowVersion = useWorkflowVersion(workflowRun.workflowVersionId);
|
|
||||||
if (!isDefined(workflowVersion)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <WorkflowRunDiagramCanvas versionStatus={workflowVersion.status} />;
|
|
||||||
};
|
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
workflowRunOutputSchema,
|
workflowRunOutputSchema,
|
||||||
workflowRunOutputStepsOutputSchema,
|
workflowRunOutputStepsOutputSchema,
|
||||||
workflowRunSchema,
|
workflowRunSchema,
|
||||||
|
workflowRunStatusSchema,
|
||||||
workflowSendEmailActionSchema,
|
workflowSendEmailActionSchema,
|
||||||
workflowSendEmailActionSettingsSchema,
|
workflowSendEmailActionSettingsSchema,
|
||||||
workflowTriggerSchema,
|
workflowTriggerSchema,
|
||||||
@ -107,6 +108,8 @@ export type WorkflowRunContext = z.infer<typeof workflowRunContextSchema>;
|
|||||||
|
|
||||||
export type WorkflowRunFlow = WorkflowRunOutput['flow'];
|
export type WorkflowRunFlow = WorkflowRunOutput['flow'];
|
||||||
|
|
||||||
|
export type WorkflowRunStatus = z.infer<typeof workflowRunStatusSchema>;
|
||||||
|
|
||||||
export type WorkflowRun = z.infer<typeof workflowRunSchema>;
|
export type WorkflowRun = z.infer<typeof workflowRunSchema>;
|
||||||
|
|
||||||
export type Workflow = {
|
export type Workflow = {
|
||||||
|
|||||||
@ -197,6 +197,13 @@ export const workflowRunOutputSchema = z.object({
|
|||||||
|
|
||||||
export const workflowRunContextSchema = z.record(z.any());
|
export const workflowRunContextSchema = z.record(z.any());
|
||||||
|
|
||||||
|
export const workflowRunStatusSchema = z.enum([
|
||||||
|
'NOT_STARTED',
|
||||||
|
'RUNNING',
|
||||||
|
'COMPLETED',
|
||||||
|
'FAILED',
|
||||||
|
]);
|
||||||
|
|
||||||
export const workflowRunSchema = z
|
export const workflowRunSchema = z
|
||||||
.object({
|
.object({
|
||||||
__typename: z.literal('WorkflowRun'),
|
__typename: z.literal('WorkflowRun'),
|
||||||
@ -204,6 +211,7 @@ export const workflowRunSchema = z
|
|||||||
workflowVersionId: z.string(),
|
workflowVersionId: z.string(),
|
||||||
output: workflowRunOutputSchema.nullable(),
|
output: workflowRunOutputSchema.nullable(),
|
||||||
context: workflowRunContextSchema.nullable(),
|
context: workflowRunContextSchema.nullable(),
|
||||||
|
status: workflowRunStatusSchema,
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
deletedAt: z.string().nullable(),
|
deletedAt: z.string().nullable(),
|
||||||
endedAt: z.string().nullable(),
|
endedAt: z.string().nullable(),
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
|
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
|
||||||
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
|
|
||||||
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/components/WorkflowDiagramCustomMarkers';
|
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 { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
|
||||||
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
|
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
|
||||||
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
|
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 { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {
|
import {
|
||||||
|
applyEdgeChanges,
|
||||||
|
applyNodeChanges,
|
||||||
Background,
|
Background,
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
EdgeProps,
|
EdgeProps,
|
||||||
@ -23,15 +23,13 @@ import {
|
|||||||
NodeChange,
|
NodeChange,
|
||||||
NodeProps,
|
NodeProps,
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
applyEdgeChanges,
|
|
||||||
applyNodeChanges,
|
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import '@xyflow/react/dist/style.css';
|
import '@xyflow/react/dist/style.css';
|
||||||
import React, { useEffect, useMemo, useRef } from 'react';
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { THEME_COMMON } from 'twenty-ui';
|
import { Tag, TagColor, THEME_COMMON } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledResetReactflowStyles = styled.div`
|
const StyledResetReactflowStyles = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -83,12 +81,13 @@ const defaultFitViewOptions = {
|
|||||||
} satisfies FitViewOptions;
|
} satisfies FitViewOptions;
|
||||||
|
|
||||||
export const WorkflowDiagramCanvasBase = ({
|
export const WorkflowDiagramCanvasBase = ({
|
||||||
status,
|
|
||||||
nodeTypes,
|
nodeTypes,
|
||||||
edgeTypes,
|
edgeTypes,
|
||||||
children,
|
children,
|
||||||
|
tagContainerTestId,
|
||||||
|
tagColor,
|
||||||
|
tagText,
|
||||||
}: {
|
}: {
|
||||||
status: WorkflowVersionStatus;
|
|
||||||
nodeTypes: Partial<
|
nodeTypes: Partial<
|
||||||
Record<
|
Record<
|
||||||
WorkflowDiagramNodeType,
|
WorkflowDiagramNodeType,
|
||||||
@ -112,6 +111,9 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
tagContainerTestId: string;
|
||||||
|
tagColor: TagColor;
|
||||||
|
tagText: string;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -258,8 +260,8 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
{children}
|
{children}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|
||||||
<StyledStatusTagContainer data-testid="workflow-visualizer-status">
|
<StyledStatusTagContainer data-testid={tagContainerTestId}>
|
||||||
<WorkflowVersionStatusTag versionStatus={status} />
|
<Tag color={tagColor} text={tagText} />
|
||||||
</StyledStatusTagContainer>
|
</StyledStatusTagContainer>
|
||||||
</StyledResetReactflowStyles>
|
</StyledResetReactflowStyles>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { WorkflowDiagramCreateStepNode } from '@/workflow/workflow-diagram/compo
|
|||||||
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
||||||
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
|
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
|
||||||
import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable';
|
import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable';
|
||||||
|
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
|
||||||
import { ReactFlowProvider } from '@xyflow/react';
|
import { ReactFlowProvider } from '@xyflow/react';
|
||||||
|
|
||||||
export const WorkflowDiagramCanvasEditable = ({
|
export const WorkflowDiagramCanvasEditable = ({
|
||||||
@ -12,10 +13,13 @@ export const WorkflowDiagramCanvasEditable = ({
|
|||||||
}: {
|
}: {
|
||||||
versionStatus: WorkflowVersionStatus;
|
versionStatus: WorkflowVersionStatus;
|
||||||
}) => {
|
}) => {
|
||||||
|
const tagProps = getWorkflowVersionStatusTagProps({
|
||||||
|
workflowVersionStatus: versionStatus,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<WorkflowDiagramCanvasBase
|
<WorkflowDiagramCanvasBase
|
||||||
status={versionStatus}
|
|
||||||
nodeTypes={{
|
nodeTypes={{
|
||||||
default: WorkflowDiagramStepNodeEditable,
|
default: WorkflowDiagramStepNodeEditable,
|
||||||
'create-step': WorkflowDiagramCreateStepNode,
|
'create-step': WorkflowDiagramCreateStepNode,
|
||||||
@ -24,7 +28,11 @@ export const WorkflowDiagramCanvasEditable = ({
|
|||||||
edgeTypes={{
|
edgeTypes={{
|
||||||
default: WorkflowDiagramDefaultEdge,
|
default: WorkflowDiagramDefaultEdge,
|
||||||
}}
|
}}
|
||||||
|
tagContainerTestId="workflow-visualizer-status"
|
||||||
|
tagColor={tagProps.color}
|
||||||
|
tagText={tagProps.text}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WorkflowDiagramCanvasEditableEffect />
|
<WorkflowDiagramCanvasEditableEffect />
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/componen
|
|||||||
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
|
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
|
||||||
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
||||||
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
||||||
|
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
|
||||||
import { ReactFlowProvider } from '@xyflow/react';
|
import { ReactFlowProvider } from '@xyflow/react';
|
||||||
|
|
||||||
export const WorkflowDiagramCanvasReadonly = ({
|
export const WorkflowDiagramCanvasReadonly = ({
|
||||||
@ -12,10 +13,13 @@ export const WorkflowDiagramCanvasReadonly = ({
|
|||||||
}: {
|
}: {
|
||||||
versionStatus: WorkflowVersionStatus;
|
versionStatus: WorkflowVersionStatus;
|
||||||
}) => {
|
}) => {
|
||||||
|
const tagProps = getWorkflowVersionStatusTagProps({
|
||||||
|
workflowVersionStatus: versionStatus,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<WorkflowDiagramCanvasBase
|
<WorkflowDiagramCanvasBase
|
||||||
status={versionStatus}
|
|
||||||
nodeTypes={{
|
nodeTypes={{
|
||||||
default: WorkflowDiagramStepNodeReadonly,
|
default: WorkflowDiagramStepNodeReadonly,
|
||||||
'empty-trigger': WorkflowDiagramEmptyTrigger,
|
'empty-trigger': WorkflowDiagramEmptyTrigger,
|
||||||
@ -24,7 +28,11 @@ export const WorkflowDiagramCanvasReadonly = ({
|
|||||||
default: WorkflowDiagramDefaultEdge,
|
default: WorkflowDiagramDefaultEdge,
|
||||||
success: WorkflowDiagramSuccessEdge,
|
success: WorkflowDiagramSuccessEdge,
|
||||||
}}
|
}}
|
||||||
|
tagContainerTestId="workflow-visualizer-status"
|
||||||
|
tagColor={tagProps.color}
|
||||||
|
tagText={tagProps.text}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WorkflowDiagramCanvasReadonlyEffect />
|
<WorkflowDiagramCanvasReadonlyEffect />
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||||
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
||||||
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
||||||
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
||||||
import { WorkflowRunDiagramCanvasEffect } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect';
|
import { WorkflowRunDiagramCanvasEffect } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect';
|
||||||
|
import { getWorkflowRunStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowRunStatusTagProps';
|
||||||
import { ReactFlowProvider } from '@xyflow/react';
|
import { ReactFlowProvider } from '@xyflow/react';
|
||||||
|
|
||||||
export const WorkflowRunDiagramCanvas = ({
|
export const WorkflowRunDiagramCanvas = ({
|
||||||
versionStatus,
|
workflowRunStatus,
|
||||||
}: {
|
}: {
|
||||||
versionStatus: WorkflowVersionStatus;
|
workflowRunStatus: WorkflowRunStatus;
|
||||||
}) => {
|
}) => {
|
||||||
|
const tagProps = getWorkflowRunStatusTagProps({
|
||||||
|
workflowRunStatus,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<WorkflowDiagramCanvasBase
|
<WorkflowDiagramCanvasBase
|
||||||
status={versionStatus}
|
|
||||||
nodeTypes={{
|
nodeTypes={{
|
||||||
default: WorkflowDiagramStepNodeReadonly,
|
default: WorkflowDiagramStepNodeReadonly,
|
||||||
}}
|
}}
|
||||||
@ -22,6 +26,9 @@ export const WorkflowRunDiagramCanvas = ({
|
|||||||
default: WorkflowDiagramDefaultEdge,
|
default: WorkflowDiagramDefaultEdge,
|
||||||
success: WorkflowDiagramSuccessEdge,
|
success: WorkflowDiagramSuccessEdge,
|
||||||
}}
|
}}
|
||||||
|
tagContainerTestId="workflow-run-status"
|
||||||
|
tagColor={tagProps.color}
|
||||||
|
tagText={tagProps.text}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WorkflowRunDiagramCanvasEffect />
|
<WorkflowRunDiagramCanvasEffect />
|
||||||
|
|||||||
@ -40,7 +40,10 @@ export const WorkflowRunDiagramCanvasEffect = () => {
|
|||||||
workflowRunRightDrawerListActiveTabIdState,
|
workflowRunRightDrawerListActiveTabIdState,
|
||||||
) as WorkflowRunTabId | null;
|
) as WorkflowRunTabId | null;
|
||||||
|
|
||||||
if (activeWorkflowRunRightDrawerTab === 'input') {
|
if (
|
||||||
|
activeWorkflowRunRightDrawerTab === 'input' ||
|
||||||
|
activeWorkflowRunRightDrawerTab === 'output'
|
||||||
|
) {
|
||||||
set(workflowRunRightDrawerListActiveTabIdState, 'node');
|
set(workflowRunRightDrawerListActiveTabIdState, 'node');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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" />;
|
|
||||||
};
|
|
||||||
@ -41,7 +41,6 @@ type Story = StoryObj<typeof WorkflowDiagramCanvasBase>;
|
|||||||
|
|
||||||
export const DefaultEdge: Story = {
|
export const DefaultEdge: Story = {
|
||||||
args: {
|
args: {
|
||||||
status: 'DRAFT',
|
|
||||||
nodeTypes: {
|
nodeTypes: {
|
||||||
default: WorkflowDiagramStepNodeReadonly,
|
default: WorkflowDiagramStepNodeReadonly,
|
||||||
'create-step': WorkflowDiagramCreateStepNode,
|
'create-step': WorkflowDiagramCreateStepNode,
|
||||||
@ -116,7 +115,6 @@ export const DefaultEdge: Story = {
|
|||||||
|
|
||||||
export const SuccessEdge: Story = {
|
export const SuccessEdge: Story = {
|
||||||
args: {
|
args: {
|
||||||
status: 'DRAFT',
|
|
||||||
nodeTypes: {
|
nodeTypes: {
|
||||||
default: WorkflowDiagramStepNodeReadonly,
|
default: WorkflowDiagramStepNodeReadonly,
|
||||||
'create-step': WorkflowDiagramCreateStepNode,
|
'create-step': WorkflowDiagramCreateStepNode,
|
||||||
|
|||||||
@ -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],
|
|
||||||
};
|
|
||||||
@ -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',
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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',
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -6,6 +6,7 @@ import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
|||||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||||
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||||
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
|
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
|
||||||
|
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
|
||||||
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
||||||
import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
|
import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
|
||||||
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
|
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
|
||||||
@ -38,7 +39,7 @@ export const RightDrawerWorkflowRunViewStep = () => {
|
|||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const isInputTabDisabled =
|
const areInputAndOutputTabsDisabled =
|
||||||
workflowSelectedNode === TRIGGER_STEP_ID ||
|
workflowSelectedNode === TRIGGER_STEP_ID ||
|
||||||
stepExecutionStatus === 'running' ||
|
stepExecutionStatus === 'running' ||
|
||||||
stepExecutionStatus === 'not-executed';
|
stepExecutionStatus === 'not-executed';
|
||||||
@ -49,9 +50,14 @@ export const RightDrawerWorkflowRunViewStep = () => {
|
|||||||
id: 'input',
|
id: 'input',
|
||||||
title: 'Input',
|
title: 'Input',
|
||||||
Icon: IconLogin2,
|
Icon: IconLogin2,
|
||||||
disabled: isInputTabDisabled,
|
disabled: areInputAndOutputTabsDisabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'output',
|
||||||
|
title: 'Output',
|
||||||
|
Icon: IconLogout,
|
||||||
|
disabled: areInputAndOutputTabsDisabled,
|
||||||
},
|
},
|
||||||
{ id: 'output', title: 'Output', Icon: IconLogout },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!isDefined(workflowRun)) {
|
if (!isDefined(workflowRun)) {
|
||||||
@ -80,6 +86,10 @@ export const RightDrawerWorkflowRunViewStep = () => {
|
|||||||
{activeTabId === 'input' ? (
|
{activeTabId === 'input' ? (
|
||||||
<WorkflowRunStepInputDetail stepId={workflowSelectedNode} />
|
<WorkflowRunStepInputDetail stepId={workflowSelectedNode} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{activeTabId === 'output' ? (
|
||||||
|
<WorkflowRunStepOutputDetail stepId={workflowSelectedNode} />
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { JsonTree } from '@/workflow/components/json-visualizer/components/JsonTree';
|
||||||
|
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||||
|
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
padding-block: ${({ theme }) => theme.spacing(4)};
|
||||||
|
padding-inline: ${({ theme }) => theme.spacing(3)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
||||||
|
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||||
|
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||||
|
|
||||||
|
if (!isDefined(workflowRun?.output?.stepsOutput)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepOutput = workflowRun.output.stepsOutput[stepId];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<JsonTree value={stepOutput} />
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -26,6 +26,10 @@ const StyledHeaderTitle = styled.div`
|
|||||||
font-size: ${({ theme }) => theme.font.size.xl};
|
font-size: ${({ theme }) => theme.font.size.xl};
|
||||||
width: 420px;
|
width: 420px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > input:disabled {
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledHeaderType = styled.div`
|
const StyledHeaderType = styled.div`
|
||||||
|
|||||||
@ -117,7 +117,6 @@ export const InputTabNotExecutedStep: Story = {
|
|||||||
return <Story />;
|
return <Story />;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
@ -138,5 +137,52 @@ export const OutputTab: Story = {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(canvas.queryByText('Create Record')).not.toBeInTheDocument();
|
expect(canvas.queryByText('Create Record')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(await canvas.findByText('result')).toBeVisible();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OutputTabDisabledForTrigger: Story = {
|
||||||
|
decorators: [
|
||||||
|
(Story) => {
|
||||||
|
const setWorkflowSelectedNode = useSetRecoilState(
|
||||||
|
workflowSelectedNodeState,
|
||||||
|
);
|
||||||
|
|
||||||
|
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||||
|
|
||||||
|
return <Story />;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const outputTab = await canvas.findByRole('button', { name: 'Output' });
|
||||||
|
|
||||||
|
expect(outputTab).toBeDisabled();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OutputTabNotExecutedStep: Story = {
|
||||||
|
decorators: [
|
||||||
|
(Story) => {
|
||||||
|
const setWorkflowSelectedNode = useSetRecoilState(
|
||||||
|
workflowSelectedNodeState,
|
||||||
|
);
|
||||||
|
|
||||||
|
setWorkflowSelectedNode(
|
||||||
|
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps.at(-1)!
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return <Story />;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const outputTab = await canvas.findByRole('button', { name: 'Output' });
|
||||||
|
|
||||||
|
expect(outputTab).toBeDisabled();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user