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:
committed by
GitHub
parent
9d78dc322d
commit
cb5f4820d7
@ -10,6 +10,7 @@ import { CardType } from '@/object-record/record-show/types/CardType';
|
||||
import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
|
||||
import { WorkflowRunOutputVisualizer } from '@/workflow/components/WorkflowRunOutputVisualizer';
|
||||
import { WorkflowRunVisualizer } from '@/workflow/components/WorkflowRunVisualizer';
|
||||
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
|
||||
import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer';
|
||||
import { WorkflowVersionVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizerEffect';
|
||||
import { WorkflowVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVisualizer';
|
||||
@ -94,7 +95,11 @@ export const CardComponents: Record<CardType, CardComponentType> = {
|
||||
),
|
||||
|
||||
[CardType.WorkflowRunCard]: ({ targetableObject }) => (
|
||||
<WorkflowRunVisualizer workflowRunId={targetableObject.id} />
|
||||
<>
|
||||
<WorkflowRunVisualizerEffect workflowRunId={targetableObject.id} />
|
||||
|
||||
<WorkflowRunVisualizer workflowRunId={targetableObject.id} />
|
||||
</>
|
||||
),
|
||||
[CardType.WorkflowRunOutputCard]: ({ targetableObject }) => (
|
||||
<WorkflowRunOutputVisualizer workflowRunId={targetableObject.id} />
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
|
||||
import { WorkflowRun } from '@/workflow/types/Workflow';
|
||||
import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas';
|
||||
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const WorkflowRunVisualizerContent = ({
|
||||
@ -14,11 +13,5 @@ export const WorkflowRunVisualizerContent = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkflowRunVisualizerEffect workflowRun={workflowRun} />
|
||||
|
||||
<WorkflowRunDiagramCanvas versionStatus={workflowVersion.status} />
|
||||
</>
|
||||
);
|
||||
return <WorkflowRunDiagramCanvas versionStatus={workflowVersion.status} />;
|
||||
};
|
||||
|
||||
@ -14,7 +14,8 @@ export const JsonArrayNode = ({
|
||||
return (
|
||||
<JsonNestedNode
|
||||
elements={[...value.entries()].map(([key, value]) => ({
|
||||
key: String(key),
|
||||
id: key,
|
||||
label: String(key),
|
||||
value,
|
||||
}))}
|
||||
label={label}
|
||||
|
||||
@ -28,7 +28,7 @@ export const JsonNestedNode = ({
|
||||
}: {
|
||||
label?: string;
|
||||
Icon: IconComponent;
|
||||
elements: Array<{ key: string; value: JsonValue }>;
|
||||
elements: Array<{ id: string | number; label: string; value: JsonValue }>;
|
||||
depth: number;
|
||||
}) => {
|
||||
const hideRoot = !isDefined(label);
|
||||
@ -37,8 +37,8 @@ export const JsonNestedNode = ({
|
||||
|
||||
const renderedChildren = (
|
||||
<JsonList depth={depth}>
|
||||
{elements.map(({ key, value }) => (
|
||||
<JsonNode key={key} label={key} value={value} depth={depth + 1} />
|
||||
{elements.map(({ id, label, value }) => (
|
||||
<JsonNode key={id} label={label} value={value} depth={depth + 1} />
|
||||
))}
|
||||
</JsonList>
|
||||
);
|
||||
|
||||
@ -14,7 +14,8 @@ export const JsonObjectNode = ({
|
||||
return (
|
||||
<JsonNestedNode
|
||||
elements={Object.entries(value).map(([key, value]) => ({
|
||||
key,
|
||||
id: key,
|
||||
label: key,
|
||||
value,
|
||||
}))}
|
||||
label={label}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useWorkflowRunIdOrThrow = () => {
|
||||
const workflowRunId = useRecoilValue(workflowRunIdState);
|
||||
if (!isDefined(workflowRunId)) {
|
||||
throw new Error('Expected the workflow run ID to be defined');
|
||||
}
|
||||
|
||||
return workflowRunId;
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const workflowRunIdState = createState<string | undefined>({
|
||||
key: 'workflowRunIdState',
|
||||
defaultValue: undefined,
|
||||
});
|
||||
@ -11,7 +11,9 @@ import {
|
||||
workflowFindRecordsActionSchema,
|
||||
workflowFindRecordsActionSettingsSchema,
|
||||
workflowManualTriggerSchema,
|
||||
workflowRunContextSchema,
|
||||
workflowRunOutputSchema,
|
||||
workflowRunOutputStepsOutputSchema,
|
||||
workflowRunSchema,
|
||||
workflowSendEmailActionSchema,
|
||||
workflowSendEmailActionSettingsSchema,
|
||||
@ -97,7 +99,13 @@ export type WorkflowVersion = {
|
||||
};
|
||||
|
||||
export type WorkflowRunOutput = z.infer<typeof workflowRunOutputSchema>;
|
||||
export type WorkflowRunOutputStepsOutput = WorkflowRunOutput['stepsOutput'];
|
||||
export type WorkflowRunOutputStepsOutput = z.infer<
|
||||
typeof workflowRunOutputStepsOutputSchema
|
||||
>;
|
||||
|
||||
export type WorkflowRunContext = z.infer<typeof workflowRunContextSchema>;
|
||||
|
||||
export type WorkflowRunFlow = WorkflowRunOutput['flow'];
|
||||
|
||||
export type WorkflowRun = z.infer<typeof workflowRunSchema>;
|
||||
|
||||
|
||||
@ -181,7 +181,7 @@ const workflowExecutorOutputSchema = z.object({
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
const workflowRunOutputStepsOutputSchema = z.record(
|
||||
export const workflowRunOutputStepsOutputSchema = z.record(
|
||||
workflowExecutorOutputSchema,
|
||||
);
|
||||
|
||||
@ -195,11 +195,18 @@ export const workflowRunOutputSchema = z.object({
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
export const workflowRunSchema = z.object({
|
||||
__typename: z.literal('WorkflowRun'),
|
||||
id: z.string(),
|
||||
workflowVersionId: z.string(),
|
||||
output: workflowRunOutputSchema.nullable(),
|
||||
});
|
||||
export const workflowRunContextSchema = z.record(z.any());
|
||||
|
||||
export type WorkflowRunOutput = z.infer<typeof workflowRunOutputSchema>;
|
||||
export const workflowRunSchema = z
|
||||
.object({
|
||||
__typename: z.literal('WorkflowRun'),
|
||||
id: z.string(),
|
||||
workflowVersionId: z.string(),
|
||||
output: workflowRunOutputSchema.nullable(),
|
||||
context: workflowRunContextSchema.nullable(),
|
||||
createdAt: z.string(),
|
||||
deletedAt: z.string().nullable(),
|
||||
endedAt: z.string().nullable(),
|
||||
name: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -2,10 +2,16 @@ import { ShowPageSubContainerTabListContainer } from '@/ui/layout/show-page/comp
|
||||
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
|
||||
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 { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui';
|
||||
|
||||
const StyledTabListContainer = styled(ShowPageSubContainerTabListContainer)`
|
||||
@ -17,17 +23,41 @@ type TabId = 'node' | 'input' | 'output';
|
||||
export const RightDrawerWorkflowRunViewStep = () => {
|
||||
const flow = useFlowOrThrow();
|
||||
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||
|
||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||
|
||||
const { activeTabId } = useTabList<TabId>(
|
||||
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
|
||||
const stepExecutionStatus = isDefined(workflowRun)
|
||||
? getWorkflowRunStepExecutionStatus({
|
||||
workflowRunOutput: workflowRun.output,
|
||||
stepId: workflowSelectedNode,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const isInputTabDisabled =
|
||||
workflowSelectedNode === TRIGGER_STEP_ID ||
|
||||
stepExecutionStatus === 'running' ||
|
||||
stepExecutionStatus === 'not-executed';
|
||||
|
||||
const tabs: SingleTabProps<TabId>[] = [
|
||||
{ id: 'node', title: 'Node', Icon: IconStepInto },
|
||||
{ id: 'input', title: 'Input', Icon: IconLogin2 },
|
||||
{
|
||||
id: 'input',
|
||||
title: 'Input',
|
||||
Icon: IconLogin2,
|
||||
disabled: isInputTabDisabled,
|
||||
},
|
||||
{ id: 'output', title: 'Output', Icon: IconLogout },
|
||||
];
|
||||
|
||||
if (!isDefined(workflowRun)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledTabListContainer>
|
||||
@ -46,6 +76,10 @@ export const RightDrawerWorkflowRunViewStep = () => {
|
||||
steps={flow.steps}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{activeTabId === 'input' ? (
|
||||
<WorkflowRunStepInputDetail stepId={workflowSelectedNode} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import { JsonNestedNode } from '@/workflow/components/json-visualizer/components/JsonNestedNode';
|
||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconBrackets } from 'twenty-ui';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
padding-block: ${({ theme }) => theme.spacing(4)};
|
||||
padding-inline: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||
|
||||
if (
|
||||
!(
|
||||
isDefined(workflowRun) &&
|
||||
isDefined(workflowRun.context) &&
|
||||
isDefined(workflowRun.output?.flow)
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stepContext = getWorkflowRunStepContext({
|
||||
context: workflowRun.context,
|
||||
flow: workflowRun.output.flow,
|
||||
stepId,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<JsonNestedNode
|
||||
elements={stepContext.map(({ id, name, context }) => ({
|
||||
id,
|
||||
label: name,
|
||||
value: context,
|
||||
}))}
|
||||
Icon={IconBrackets}
|
||||
depth={0}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,142 @@
|
||||
import { flowState } from '@/workflow/states/flowState';
|
||||
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
|
||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import styled from '@emotion/styled';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
||||
import { graphql, HttpResponse } from 'msw';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { oneFailedWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run';
|
||||
import { RightDrawerWorkflowRunViewStep } from '../RightDrawerWorkflowRunViewStep';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 500px;
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof RightDrawerWorkflowRunViewStep> = {
|
||||
title: 'Modules/Workflow/RightDrawerWorkflowRunViewStep',
|
||||
component: RightDrawerWorkflowRunViewStep,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<StyledWrapper>
|
||||
<Story />
|
||||
</StyledWrapper>
|
||||
),
|
||||
I18nFrontDecorator,
|
||||
ComponentDecorator,
|
||||
(Story) => {
|
||||
const setFlow = useSetRecoilState(flowState);
|
||||
const setWorkflowSelectedNode = useSetRecoilState(
|
||||
workflowSelectedNodeState,
|
||||
);
|
||||
const setWorkflowRunId = useSetRecoilState(workflowRunIdState);
|
||||
|
||||
setFlow(oneFailedWorkflowRunQueryResult.workflowRun.output.flow);
|
||||
setWorkflowSelectedNode(
|
||||
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps[0].id,
|
||||
);
|
||||
setWorkflowRunId(oneFailedWorkflowRunQueryResult.workflowRun.id);
|
||||
|
||||
return <Story />;
|
||||
},
|
||||
RouterDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
WorkspaceDecorator,
|
||||
],
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
graphql.query('FindOneWorkflowRun', () => {
|
||||
return HttpResponse.json({
|
||||
data: oneFailedWorkflowRunQueryResult,
|
||||
});
|
||||
}),
|
||||
...graphqlMocks.handlers,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof RightDrawerWorkflowRunViewStep>;
|
||||
|
||||
export const NodeTab: Story = {};
|
||||
|
||||
export const InputTab: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await userEvent.click(await canvas.findByRole('button', { name: 'Input' }));
|
||||
|
||||
expect(await canvas.findByText('Trigger')).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const InputTabDisabledForTrigger: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const setWorkflowSelectedNode = useSetRecoilState(
|
||||
workflowSelectedNodeState,
|
||||
);
|
||||
|
||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||
|
||||
return <Story />;
|
||||
},
|
||||
],
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const inputTab = await canvas.findByRole('button', { name: 'Input' });
|
||||
|
||||
expect(inputTab).toBeDisabled();
|
||||
},
|
||||
};
|
||||
|
||||
export const InputTabNotExecutedStep: 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 inputTab = await canvas.findByRole('button', { name: 'Input' });
|
||||
|
||||
expect(inputTab).toBeDisabled();
|
||||
},
|
||||
};
|
||||
|
||||
export const OutputTab: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await userEvent.click(
|
||||
await canvas.findByRole('button', { name: 'Output' }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.queryByText('Create Record')).not.toBeInTheDocument();
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export type WorkflowRunTabId = 'node' | 'input' | 'output';
|
||||
@ -0,0 +1,266 @@
|
||||
import { WorkflowRunFlow } from '@/workflow/types/Workflow';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import { getWorkflowRunStepContext } from '../getWorkflowRunStepContext';
|
||||
|
||||
describe('getWorkflowRunStepContext', () => {
|
||||
it('should return an empty array for trigger step', () => {
|
||||
const flow = {
|
||||
trigger: {
|
||||
name: 'Company Created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
steps: [],
|
||||
} satisfies WorkflowRunFlow;
|
||||
const context = {
|
||||
[TRIGGER_STEP_ID]: { company: { id: '123' } },
|
||||
};
|
||||
|
||||
const result = getWorkflowRunStepContext({
|
||||
stepId: TRIGGER_STEP_ID,
|
||||
flow,
|
||||
context,
|
||||
});
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should include previous steps context', () => {
|
||||
const flow = {
|
||||
trigger: {
|
||||
name: 'Company Created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Create company',
|
||||
type: 'CREATE_RECORD',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
objectName: 'Company',
|
||||
objectRecord: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
name: 'Send Email',
|
||||
type: 'SEND_EMAIL',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
connectedAccountId: '123',
|
||||
email: '',
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
} satisfies WorkflowRunFlow;
|
||||
const context = {
|
||||
[TRIGGER_STEP_ID]: { company: { id: '123' } },
|
||||
step1: { taskId: '456' },
|
||||
};
|
||||
|
||||
const result = getWorkflowRunStepContext({
|
||||
stepId: 'step2',
|
||||
flow,
|
||||
context,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: TRIGGER_STEP_ID,
|
||||
name: 'Company Created',
|
||||
context: { company: { id: '123' } },
|
||||
},
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Create company',
|
||||
context: { taskId: '456' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not include subsequent steps context', () => {
|
||||
const flow = {
|
||||
trigger: {
|
||||
name: 'Company Created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Create company',
|
||||
type: 'CREATE_RECORD',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
objectName: 'Company',
|
||||
objectRecord: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
name: 'Send Email',
|
||||
type: 'SEND_EMAIL',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
connectedAccountId: '123',
|
||||
email: '',
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
} satisfies WorkflowRunFlow;
|
||||
const context = {
|
||||
[TRIGGER_STEP_ID]: { company: { id: '123' } },
|
||||
step1: { taskId: '456' },
|
||||
step2: { emailId: '789' },
|
||||
};
|
||||
|
||||
const result = getWorkflowRunStepContext({
|
||||
stepId: 'step1',
|
||||
flow,
|
||||
context,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: TRIGGER_STEP_ID,
|
||||
name: 'Company Created',
|
||||
context: { company: { id: '123' } },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle multiple steps with the same name', () => {
|
||||
const flow = {
|
||||
trigger: {
|
||||
name: 'Company Created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Create Note',
|
||||
type: 'CREATE_RECORD',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
objectName: 'Note',
|
||||
objectRecord: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
name: 'Create Note',
|
||||
type: 'CREATE_RECORD',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
objectName: 'Note',
|
||||
objectRecord: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
id: 'step3',
|
||||
name: 'Create Note',
|
||||
type: 'CREATE_RECORD',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
objectName: 'Note',
|
||||
objectRecord: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
} satisfies WorkflowRunFlow;
|
||||
const context = {
|
||||
[TRIGGER_STEP_ID]: { company: { id: '123' } },
|
||||
step1: { noteId: '456' },
|
||||
step2: { noteId: '789' },
|
||||
step3: { noteId: '101' },
|
||||
};
|
||||
|
||||
const result = getWorkflowRunStepContext({
|
||||
stepId: 'step3',
|
||||
flow,
|
||||
context,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: TRIGGER_STEP_ID,
|
||||
name: 'Company Created',
|
||||
context: { company: { id: '123' } },
|
||||
},
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Create Note',
|
||||
context: { noteId: '456' },
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
name: 'Create Note',
|
||||
context: { noteId: '789' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,230 @@
|
||||
import { getWorkflowRunStepExecutionStatus } from '../getWorkflowRunStepExecutionStatus';
|
||||
|
||||
describe('getWorkflowRunStepExecutionStatus', () => {
|
||||
const stepId = '453e0084-aca2-45b9-8d1c-458a2b8ac70a';
|
||||
|
||||
it('should return not-executed when the output is null', () => {
|
||||
expect(
|
||||
getWorkflowRunStepExecutionStatus({
|
||||
workflowRunOutput: null,
|
||||
stepId,
|
||||
}),
|
||||
).toBe('not-executed');
|
||||
});
|
||||
|
||||
it('should return success when step has result', () => {
|
||||
expect(
|
||||
getWorkflowRunStepExecutionStatus({
|
||||
workflowRunOutput: {
|
||||
flow: {
|
||||
steps: [
|
||||
{
|
||||
id: stepId,
|
||||
name: 'Code - Serverless Function',
|
||||
type: 'CODE',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId:
|
||||
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
|
||||
serverlessFunctionInput: {
|
||||
a: null,
|
||||
b: null,
|
||||
},
|
||||
serverlessFunctionVersion: 'draft',
|
||||
},
|
||||
outputSchema: {
|
||||
link: {
|
||||
tab: 'test',
|
||||
icon: 'IconVariable',
|
||||
label: 'Generate Function Output',
|
||||
isLeaf: true,
|
||||
},
|
||||
_outputSchemaType: 'LINK',
|
||||
},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
stepsOutput: {
|
||||
[stepId]: {
|
||||
result: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
stepId,
|
||||
}),
|
||||
).toBe('success');
|
||||
});
|
||||
|
||||
it('should return failure when workflow has error', () => {
|
||||
const error = 'fn(...).then is not a function';
|
||||
|
||||
expect(
|
||||
getWorkflowRunStepExecutionStatus({
|
||||
workflowRunOutput: {
|
||||
flow: {
|
||||
steps: [
|
||||
{
|
||||
id: stepId,
|
||||
name: 'Code - Serverless Function',
|
||||
type: 'CODE',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId:
|
||||
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
|
||||
serverlessFunctionInput: {
|
||||
a: null,
|
||||
b: null,
|
||||
},
|
||||
serverlessFunctionVersion: 'draft',
|
||||
},
|
||||
outputSchema: {
|
||||
link: {
|
||||
tab: 'test',
|
||||
icon: 'IconVariable',
|
||||
label: 'Generate Function Output',
|
||||
isLeaf: true,
|
||||
},
|
||||
_outputSchemaType: 'LINK',
|
||||
},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
error,
|
||||
stepsOutput: {
|
||||
[stepId]: {
|
||||
error,
|
||||
},
|
||||
},
|
||||
},
|
||||
stepId,
|
||||
}),
|
||||
).toBe('failure');
|
||||
});
|
||||
|
||||
it('should return not-executed when step has no output', () => {
|
||||
const secondStepId = '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c';
|
||||
|
||||
expect(
|
||||
getWorkflowRunStepExecutionStatus({
|
||||
workflowRunOutput: {
|
||||
flow: {
|
||||
steps: [
|
||||
{
|
||||
id: stepId,
|
||||
name: 'Code - Serverless Function',
|
||||
type: 'CODE',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId:
|
||||
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
|
||||
serverlessFunctionInput: {
|
||||
a: null,
|
||||
b: null,
|
||||
},
|
||||
serverlessFunctionVersion: 'draft',
|
||||
},
|
||||
outputSchema: {
|
||||
link: {
|
||||
tab: 'test',
|
||||
icon: 'IconVariable',
|
||||
label: 'Generate Function Output',
|
||||
isLeaf: true,
|
||||
},
|
||||
_outputSchemaType: 'LINK',
|
||||
},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: secondStepId,
|
||||
name: 'Code - Serverless Function',
|
||||
type: 'CODE',
|
||||
valid: false,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId:
|
||||
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
|
||||
serverlessFunctionInput: {
|
||||
a: null,
|
||||
b: null,
|
||||
},
|
||||
serverlessFunctionVersion: 'draft',
|
||||
},
|
||||
outputSchema: {
|
||||
link: {
|
||||
tab: 'test',
|
||||
icon: 'IconVariable',
|
||||
label: 'Generate Function Output',
|
||||
isLeaf: true,
|
||||
},
|
||||
_outputSchemaType: 'LINK',
|
||||
},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
continueOnFailure: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
stepsOutput: {
|
||||
[stepId]: {
|
||||
result: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
stepId: secondStepId,
|
||||
}),
|
||||
).toBe('not-executed');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
import { WorkflowRunContext, WorkflowRunFlow } from '@/workflow/types/Workflow';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
|
||||
export const getWorkflowRunStepContext = ({
|
||||
stepId,
|
||||
flow,
|
||||
context,
|
||||
}: {
|
||||
stepId: string;
|
||||
context: WorkflowRunContext;
|
||||
flow: WorkflowRunFlow;
|
||||
}) => {
|
||||
const stepContext: Array<{ id: string; name: string; context: any }> = [];
|
||||
|
||||
if (stepId === TRIGGER_STEP_ID) {
|
||||
return stepContext;
|
||||
}
|
||||
|
||||
stepContext.push({
|
||||
id: TRIGGER_STEP_ID,
|
||||
name: flow.trigger.name ?? 'Trigger',
|
||||
context: context[TRIGGER_STEP_ID],
|
||||
});
|
||||
|
||||
for (const step of flow.steps) {
|
||||
if (step.id === stepId) {
|
||||
break;
|
||||
}
|
||||
|
||||
stepContext.push({
|
||||
id: step.id,
|
||||
name: step.name,
|
||||
context: context[step.id],
|
||||
});
|
||||
}
|
||||
|
||||
return stepContext;
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
import { WorkflowRunOutput } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const getWorkflowRunStepExecutionStatus = ({
|
||||
workflowRunOutput,
|
||||
stepId,
|
||||
}: {
|
||||
workflowRunOutput: WorkflowRunOutput | null;
|
||||
stepId: string;
|
||||
}): WorkflowDiagramRunStatus => {
|
||||
if (isNull(workflowRunOutput)) {
|
||||
return 'not-executed';
|
||||
}
|
||||
|
||||
const stepOutput = workflowRunOutput.stepsOutput?.[stepId];
|
||||
|
||||
if (isDefined(stepOutput?.error)) {
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
if (isDefined(stepOutput?.result)) {
|
||||
return 'success';
|
||||
}
|
||||
|
||||
return 'not-executed';
|
||||
};
|
||||
@ -1,8 +1,13 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||
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';
|
||||
@ -10,8 +15,6 @@ import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hoo
|
||||
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { MenuItemCommand, useIcons } from 'twenty-ui';
|
||||
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
|
||||
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
|
||||
|
||||
export const RightDrawerWorkflowSelectTriggerTypeContent = ({
|
||||
workflow,
|
||||
@ -26,6 +29,33 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
||||
|
||||
const handleTriggerTypeClick = ({
|
||||
type,
|
||||
defaultLabel,
|
||||
icon,
|
||||
}: {
|
||||
type: WorkflowTriggerType;
|
||||
defaultLabel: string;
|
||||
icon: string;
|
||||
}) => {
|
||||
return async () => {
|
||||
await updateTrigger(
|
||||
getTriggerDefaultDefinition({
|
||||
defaultLabel,
|
||||
type,
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
);
|
||||
|
||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||
|
||||
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
|
||||
title: defaultLabel,
|
||||
Icon: getIcon(icon),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<RightDrawerStepListContainer>
|
||||
<RightDrawerWorkflowSelectStepTitle>
|
||||
@ -36,22 +66,7 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
|
||||
key={action.defaultLabel}
|
||||
LeftIcon={getIcon(action.icon)}
|
||||
text={action.defaultLabel}
|
||||
onClick={async () => {
|
||||
await updateTrigger(
|
||||
getTriggerDefaultDefinition({
|
||||
defaultLabel: action.defaultLabel,
|
||||
type: action.type,
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
);
|
||||
|
||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||
|
||||
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
|
||||
title: action.defaultLabel,
|
||||
Icon: getIcon(action.icon),
|
||||
});
|
||||
}}
|
||||
onClick={handleTriggerTypeClick(action)}
|
||||
/>
|
||||
))}
|
||||
<RightDrawerWorkflowSelectStepTitle>
|
||||
@ -62,22 +77,7 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
|
||||
key={action.defaultLabel}
|
||||
LeftIcon={getIcon(action.icon)}
|
||||
text={action.defaultLabel}
|
||||
onClick={async () => {
|
||||
await updateTrigger(
|
||||
getTriggerDefaultDefinition({
|
||||
defaultLabel: action.defaultLabel,
|
||||
type: action.type,
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
);
|
||||
|
||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||
|
||||
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
|
||||
title: action.defaultLabel,
|
||||
Icon: getIcon(action.icon),
|
||||
});
|
||||
}}
|
||||
onClick={handleTriggerTypeClick(action)}
|
||||
/>
|
||||
))}
|
||||
</RightDrawerStepListContainer>
|
||||
|
||||
@ -30,6 +30,7 @@ import {
|
||||
getWorkflowVersionsMock,
|
||||
workflowQueryResult,
|
||||
} from '~/testing/mock-data/workflow';
|
||||
import { oneSucceededWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run';
|
||||
import { mockedRemoteServers } from './mock-data/remote-servers';
|
||||
import { mockedViewFieldsData } from './mock-data/view-fields';
|
||||
|
||||
@ -714,6 +715,11 @@ export const graphqlMocks = {
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphql.query('FindOneWorkflowRun', () => {
|
||||
return HttpResponse.json({
|
||||
data: oneSucceededWorkflowRunQueryResult,
|
||||
});
|
||||
}),
|
||||
graphql.query('FindManyWorkflowVersions', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
|
||||
12009
packages/twenty-front/src/testing/mock-data/workflow-run.ts
Normal file
12009
packages/twenty-front/src/testing/mock-data/workflow-run.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user