Baptiste Devessier
2025-04-24 11:33:17 +02:00
committed by GitHub
parent 0083569606
commit cc211550ae
26 changed files with 1216 additions and 550 deletions
@@ -3,6 +3,7 @@ import {
WorkflowStep,
WorkflowTrigger,
} from '@/workflow/types/Workflow';
import { FieldMetadataType } from 'twenty-shared/types';
import { getUuidV4Mock } from '~/testing/utils/getUuidV4Mock';
import { generateWorkflowRunDiagram } from '../generateWorkflowRunDiagram';
@@ -87,94 +88,94 @@ describe('generateWorkflowRunDiagram', () => {
expect(result).toMatchInlineSnapshot(`
{
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-0",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-1",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step1",
"target": "step2",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-2",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step2",
"target": "step3",
},
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
"diagram": {
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-0",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-1",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step1",
"target": "step2",
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "failure",
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-2",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step2",
"target": "step3",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
},
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "not-executed",
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "failure",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
},
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
},
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "not-executed",
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
"selected": false,
},
],
],
},
"stepToOpenByDefault": undefined,
}
`);
});
@@ -263,96 +264,96 @@ describe('generateWorkflowRunDiagram', () => {
expect(result).toMatchInlineSnapshot(`
{
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-3",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-4",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "step1",
"target": "step2",
"type": "success",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-5",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "step2",
"target": "step3",
"type": "success",
},
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
"diagram": {
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-3",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-4",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "step1",
"target": "step2",
"type": "success",
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "success",
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-5",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "step2",
"target": "step3",
"type": "success",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
},
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "success",
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "success",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
},
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "success",
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
},
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "success",
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "success",
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
"selected": false,
},
],
],
},
"stepToOpenByDefault": undefined,
}
`);
});
@@ -428,94 +429,94 @@ describe('generateWorkflowRunDiagram', () => {
expect(result).toMatchInlineSnapshot(`
{
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-6",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-7",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step1",
"target": "step2",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-8",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step2",
"target": "step3",
},
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
"diagram": {
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-6",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-7",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step1",
"target": "step2",
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "running",
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-8",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step2",
"target": "step3",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
},
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "not-executed",
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "running",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
},
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
},
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "not-executed",
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
"selected": false,
},
],
],
},
"stepToOpenByDefault": undefined,
}
`);
});
@@ -614,118 +615,219 @@ describe('generateWorkflowRunDiagram', () => {
expect(result).toMatchInlineSnapshot(`
{
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-9",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
"diagram": {
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-9",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-10",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "step1",
"target": "step2",
"type": "success",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-11",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step2",
"target": "step3",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-12",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step3",
"target": "step4",
},
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "success",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "running",
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 4",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step4",
"position": {
"x": 0,
"y": 450,
},
},
],
},
"stepToOpenByDefault": undefined,
}
`);
});
it('marks node as running when a Form step is pending and return its data as the stepToOpenByDefault object', () => {
const trigger: WorkflowTrigger = {
name: 'Company created',
type: 'DATABASE_EVENT',
settings: {
eventName: 'company.created',
outputSchema: {},
},
};
const steps: WorkflowStep[] = [
{
id: 'step1',
name: 'Step 1',
type: 'FORM',
valid: true,
settings: {
errorHandlingOptions: {
retryOnFailure: { value: true },
continueOnFailure: { value: false },
},
input: [
{
id: 'field-1',
name: 'text',
label: 'Text Field',
type: FieldMetadataType.TEXT,
placeholder: 'Enter text',
settings: {},
},
],
outputSchema: {},
},
},
];
const stepsOutput = {
step1: {
result: undefined,
error: undefined,
pendingEvent: true,
},
};
const result = generateWorkflowRunDiagram({ trigger, steps, stepsOutput });
expect(result).toMatchInlineSnapshot(`
{
"diagram": {
"edges": [
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-13",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "trigger",
"target": "step1",
"type": "success",
},
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
},
},
{
"data": {
"actionType": "FORM",
"name": "Step 1",
"nodeType": "action",
"runStatus": "running",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
},
},
],
},
"stepToOpenByDefault": {
"data": {
"actionType": "FORM",
"name": "Step 1",
"nodeType": "action",
"runStatus": "running",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-10",
"markerEnd": "workflow-edge-green-arrow-rounded",
"markerStart": "workflow-edge-green-circle",
"selectable": false,
"source": "step1",
"target": "step2",
"type": "success",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-11",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step2",
"target": "step3",
},
{
"deletable": false,
"id": "8f3b2121-f194-4ba4-9fbf-12",
"markerEnd": "workflow-edge-arrow-rounded",
"markerStart": "workflow-edge-gray-circle",
"selectable": false,
"source": "step3",
"target": "step4",
},
],
"nodes": [
{
"data": {
"icon": "IconPlaylistAdd",
"name": "Company created",
"nodeType": "trigger",
"runStatus": "success",
"triggerType": "DATABASE_EVENT",
},
"id": "trigger",
"position": {
"x": 0,
"y": 0,
},
},
{
"data": {
"actionType": "CODE",
"name": "Step 1",
"nodeType": "action",
"runStatus": "success",
},
"id": "step1",
"position": {
"x": 0,
"y": 0,
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 2",
"nodeType": "action",
"runStatus": "running",
},
"id": "step2",
"position": {
"x": 0,
"y": 150,
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 3",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step3",
"position": {
"x": 0,
"y": 300,
},
"selected": false,
},
{
"data": {
"actionType": "CODE",
"name": "Step 4",
"nodeType": "action",
"runStatus": "not-executed",
},
"id": "step4",
"position": {
"x": 0,
"y": 450,
},
"selected": false,
},
],
"id": "step1",
},
}
`);
});
@@ -0,0 +1,91 @@
import { WorkflowDiagram } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { selectWorkflowDiagramNode } from '../selectWorkflowDiagramNode';
describe('selectWorkflowDiagramNode', () => {
it('should select the specified node', () => {
const diagram: WorkflowDiagram = {
nodes: [
{
id: '1',
selected: false,
position: { x: 0, y: 0 },
data: {
name: 'Node 1',
nodeType: 'action',
actionType: 'CODE',
},
},
{
id: '2',
selected: false,
position: { x: 0, y: 150 },
data: {
name: 'Node 2',
nodeType: 'action',
actionType: 'CODE',
},
},
],
edges: [],
};
const result = selectWorkflowDiagramNode({
diagram,
nodeIdToSelect: '1',
});
expect(result.nodes[0].selected).toBe(true);
expect(result.nodes[1].selected).toBe(false);
});
it('should return same diagram when node is not found', () => {
const diagram: WorkflowDiagram = {
nodes: [
{
id: '1',
selected: false,
position: { x: 0, y: 0 },
data: {
name: 'Node 1',
nodeType: 'action',
actionType: 'CODE',
},
},
],
edges: [],
};
const result = selectWorkflowDiagramNode({
diagram,
nodeIdToSelect: 'non-existent',
});
expect(result).toEqual(diagram);
});
it('should not mutate original diagram', () => {
const diagram: WorkflowDiagram = {
nodes: [
{
id: '1',
selected: false,
position: { x: 0, y: 0 },
data: {
name: 'Node 1',
nodeType: 'action',
actionType: 'CODE',
},
},
],
edges: [],
};
const originalDiagram = JSON.parse(JSON.stringify(diagram));
selectWorkflowDiagramNode({
diagram,
nodeIdToSelect: '1',
});
expect(diagram).toEqual(originalDiagram);
});
});
@@ -12,6 +12,8 @@ import {
WorkflowRunDiagram,
WorkflowRunDiagramEdge,
WorkflowRunDiagramNode,
WorkflowRunDiagramNodeData,
WorkflowRunDiagramStepNodeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowDiagramTriggerNode } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
@@ -26,7 +28,22 @@ export const generateWorkflowRunDiagram = ({
trigger: WorkflowTrigger;
steps: Array<WorkflowStep>;
stepsOutput: WorkflowRunOutputStepsOutput | undefined;
}): WorkflowRunDiagram => {
}): {
diagram: WorkflowRunDiagram;
stepToOpenByDefault:
| {
id: string;
data: WorkflowRunDiagramStepNodeData;
}
| undefined;
} => {
let stepToOpenByDefault:
| {
id: string;
data: WorkflowRunDiagramStepNodeData;
}
| undefined = undefined;
const triggerBase = getWorkflowDiagramTriggerNode({ trigger });
const nodes: Array<WorkflowRunDiagramNode> = [
@@ -97,21 +114,29 @@ export const generateWorkflowRunDiagram = ({
}
}
const nodeData: WorkflowRunDiagramNodeData = {
nodeType: 'action',
actionType: step.type,
name: step.name,
runStatus,
};
nodes.push({
id: nodeId,
data: {
nodeType: 'action',
actionType: step.type,
name: step.name,
runStatus,
},
data: nodeData,
position: {
x: xPos,
y: yPos,
},
selected: isPendingFormAction,
});
if (isPendingFormAction) {
stepToOpenByDefault = {
id: nodeId,
data: nodeData,
};
}
processNode({
stepIndex: stepIndex + 1,
parentNodeId: nodeId,
@@ -134,7 +159,10 @@ export const generateWorkflowRunDiagram = ({
});
return {
nodes,
edges,
diagram: {
nodes,
edges,
},
stepToOpenByDefault,
};
};
@@ -0,0 +1,23 @@
import { WorkflowDiagram } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
export const selectWorkflowDiagramNode = <T extends WorkflowDiagram>({
diagram,
nodeIdToSelect,
}: {
diagram: T;
nodeIdToSelect: string;
}): T => {
return {
...diagram,
nodes: diagram.nodes.map((node) => {
if (node.id === nodeIdToSelect) {
return {
...node,
selected: true,
};
}
return node;
}),
};
};