From 8bd9bc9d31337f489da171ec0706d265c116df38 Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Thu, 27 Feb 2025 10:36:19 +0100 Subject: [PATCH] Make the frontend resilient to old workflow run output formats (#10522) - Create zod schemas for everything related to a workflow run - Update the types to be inferred from the zod schemas - Improper workflow run outputs will render a blank screen; we could show an error in the future https://github.com/user-attachments/assets/8e666c3e-82b0-4ab5-8804-2f70130ea257 --- .../WorkflowRunOutputVisualizer.tsx | 5 +- .../modules/workflow/hooks/useWorkflowRun.ts | 11 +- .../workflow/hooks/useWorkflowRunUnsafe.ts | 16 ++ .../src/modules/workflow/types/Workflow.ts | 246 +++++------------- .../validation-schemas/workflowSchema.ts | 205 +++++++++++++++ .../generateWorkflowRunDiagram.test.ts | 245 ++--------------- .../utils/generateWorkflowRunDiagram.ts | 7 +- 7 files changed, 324 insertions(+), 411 deletions(-) create mode 100644 packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunUnsafe.ts create mode 100644 packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowRunOutputVisualizer.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowRunOutputVisualizer.tsx index 836d3bea1..4e5ccb3d4 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowRunOutputVisualizer.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowRunOutputVisualizer.tsx @@ -1,4 +1,4 @@ -import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; +import { useWorkflowRunUnsafe } from '@/workflow/hooks/useWorkflowRunUnsafe'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared'; import { CodeEditor } from 'twenty-ui'; @@ -12,7 +12,8 @@ export const WorkflowRunOutputVisualizer = ({ }: { workflowRunId: string; }) => { - const workflowRun = useWorkflowRun({ workflowRunId }); + const workflowRun = useWorkflowRunUnsafe({ workflowRunId }); + if (!isDefined(workflowRun)) { return null; } diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRun.ts b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRun.ts index 9bb6fa564..7b5dff362 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRun.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRun.ts @@ -1,16 +1,23 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { WorkflowRun } from '@/workflow/types/Workflow'; +import { workflowRunSchema } from '@/workflow/validation-schemas/workflowSchema'; export const useWorkflowRun = ({ workflowRunId, }: { workflowRunId: string; -}) => { - const { record } = useFindOneRecord({ +}): WorkflowRun | undefined => { + const { record: rawRecord } = useFindOneRecord({ objectNameSingular: CoreObjectNameSingular.WorkflowRun, objectRecordId: workflowRunId, }); + const { success, data: record } = workflowRunSchema.safeParse(rawRecord); + + if (!success) { + return undefined; + } + return record; }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunUnsafe.ts b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunUnsafe.ts new file mode 100644 index 000000000..2d44c8af6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRunUnsafe.ts @@ -0,0 +1,16 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { WorkflowRun } from '@/workflow/types/Workflow'; + +export const useWorkflowRunUnsafe = ({ + workflowRunId, +}: { + workflowRunId: string; +}) => { + const { record } = useFindOneRecord({ + objectNameSingular: CoreObjectNameSingular.WorkflowRun, + objectRecordId: workflowRunId, + }); + + return record; +}; diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts index acc1d0772..56af95bef 100644 --- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts +++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts @@ -1,179 +1,89 @@ -type BaseWorkflowActionSettings = { - input: object; - outputSchema: object; - errorHandlingOptions: { - retryOnFailure: { - value: boolean; - }; - continueOnFailure: { - value: boolean; - }; - }; -}; +import { + workflowActionSchema, + workflowCodeActionSchema, + workflowCodeActionSettingsSchema, + workflowCreateRecordActionSchema, + workflowCreateRecordActionSettingsSchema, + workflowCronTriggerSchema, + workflowDatabaseEventTriggerSchema, + workflowDeleteRecordActionSchema, + workflowDeleteRecordActionSettingsSchema, + workflowFindRecordsActionSchema, + workflowFindRecordsActionSettingsSchema, + workflowManualTriggerSchema, + workflowRunOutputSchema, + workflowRunSchema, + workflowSendEmailActionSchema, + workflowSendEmailActionSettingsSchema, + workflowTriggerSchema, + workflowUpdateRecordActionSchema, + workflowUpdateRecordActionSettingsSchema, +} from '@/workflow/validation-schemas/workflowSchema'; +import { z } from 'zod'; -export type WorkflowCodeActionSettings = BaseWorkflowActionSettings & { - input: { - serverlessFunctionId: string; - serverlessFunctionVersion: string; - serverlessFunctionInput: { - [key: string]: any; - }; - }; -}; +export type WorkflowCodeActionSettings = z.infer< + typeof workflowCodeActionSettingsSchema +>; +export type WorkflowSendEmailActionSettings = z.infer< + typeof workflowSendEmailActionSettingsSchema +>; +export type WorkflowCreateRecordActionSettings = z.infer< + typeof workflowCreateRecordActionSettingsSchema +>; +export type WorkflowUpdateRecordActionSettings = z.infer< + typeof workflowUpdateRecordActionSettingsSchema +>; +export type WorkflowDeleteRecordActionSettings = z.infer< + typeof workflowDeleteRecordActionSettingsSchema +>; +export type WorkflowFindRecordsActionSettings = z.infer< + typeof workflowFindRecordsActionSettingsSchema +>; -export type WorkflowSendEmailActionSettings = BaseWorkflowActionSettings & { - input: { - connectedAccountId: string; - email: string; - subject?: string; - body?: string; - }; -}; - -type ObjectRecord = Record; - -export type WorkflowCreateRecordActionSettings = BaseWorkflowActionSettings & { - input: { - objectName: string; - objectRecord: ObjectRecord; - }; -}; - -export type WorkflowUpdateRecordActionSettings = BaseWorkflowActionSettings & { - input: { - objectName: string; - objectRecord: ObjectRecord; - objectRecordId: string; - fieldsToUpdate: string[]; - }; -}; - -export type WorkflowDeleteRecordActionSettings = BaseWorkflowActionSettings & { - input: { - objectName: string; - objectRecordId: string; - }; -}; - -export type WorkflowFindRecordsActionSettings = BaseWorkflowActionSettings & { - input: { - objectName: string; - limit?: number; - }; -}; - -type BaseWorkflowAction = { - id: string; - name: string; - valid: boolean; -}; - -export type WorkflowCodeAction = BaseWorkflowAction & { - type: 'CODE'; - settings: WorkflowCodeActionSettings; -}; - -export type WorkflowSendEmailAction = BaseWorkflowAction & { - type: 'SEND_EMAIL'; - settings: WorkflowSendEmailActionSettings; -}; - -export type WorkflowCreateRecordAction = BaseWorkflowAction & { - type: 'CREATE_RECORD'; - settings: WorkflowCreateRecordActionSettings; -}; - -export type WorkflowUpdateRecordAction = BaseWorkflowAction & { - type: 'UPDATE_RECORD'; - settings: WorkflowUpdateRecordActionSettings; -}; - -export type WorkflowDeleteRecordAction = BaseWorkflowAction & { - type: 'DELETE_RECORD'; - settings: WorkflowDeleteRecordActionSettings; -}; - -export type WorkflowFindRecordsAction = BaseWorkflowAction & { - type: 'FIND_RECORDS'; - settings: WorkflowFindRecordsActionSettings; -}; - -export type WorkflowAction = - | WorkflowCodeAction - | WorkflowSendEmailAction - | WorkflowCreateRecordAction - | WorkflowUpdateRecordAction - | WorkflowDeleteRecordAction - | WorkflowFindRecordsAction; +export type WorkflowCodeAction = z.infer; +export type WorkflowSendEmailAction = z.infer< + typeof workflowSendEmailActionSchema +>; +export type WorkflowCreateRecordAction = z.infer< + typeof workflowCreateRecordActionSchema +>; +export type WorkflowUpdateRecordAction = z.infer< + typeof workflowUpdateRecordActionSchema +>; +export type WorkflowDeleteRecordAction = z.infer< + typeof workflowDeleteRecordActionSchema +>; +export type WorkflowFindRecordsAction = z.infer< + typeof workflowFindRecordsActionSchema +>; +export type WorkflowAction = z.infer; export type WorkflowActionType = WorkflowAction['type']; - export type WorkflowStep = WorkflowAction; - export type WorkflowStepType = WorkflowStep['type']; -type BaseTrigger = { - name?: string; - type: string; -}; - -export type WorkflowDatabaseEventTrigger = BaseTrigger & { - type: 'DATABASE_EVENT'; - settings: { - eventName: string; - input?: object; - outputSchema: object; - objectType?: string; - }; -}; - -export type WorkflowManualTrigger = BaseTrigger & { - type: 'MANUAL'; - settings: { - objectType?: string; - outputSchema: object; - }; -}; - -export type WorkflowCronTrigger = BaseTrigger & { - type: 'CRON'; - settings: ( - | { - type: 'HOURS'; - schedule: { hour: number; minute: number }; - } - | { - type: 'MINUTES'; - schedule: { minute: number }; - } - | { - type: 'CUSTOM'; - pattern: string; - } - ) & { outputSchema: object }; -}; +export type WorkflowDatabaseEventTrigger = z.infer< + typeof workflowDatabaseEventTriggerSchema +>; +export type WorkflowManualTrigger = z.infer; +export type WorkflowCronTrigger = z.infer; export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings']; - export type WorkflowManualTriggerAvailability = | 'EVERYWHERE' | 'WHEN_RECORD_SELECTED'; -export type WorkflowTrigger = - | WorkflowDatabaseEventTrigger - | WorkflowManualTrigger - | WorkflowCronTrigger; - +export type WorkflowTrigger = z.infer; export type WorkflowTriggerType = WorkflowTrigger['type']; export type WorkflowStatus = 'DRAFT' | 'ACTIVE' | 'DEACTIVATED'; - export type WorkflowVersionStatus = | 'DRAFT' | 'ACTIVE' | 'DEACTIVATED' | 'ARCHIVED'; +// Keep existing types that are not covered by schemas export type WorkflowVersion = { id: string; name: string; @@ -186,32 +96,10 @@ export type WorkflowVersion = { __typename: 'WorkflowVersion'; }; -type StepRunOutput = { - id: string; - outputs: { - attemptCount: number; - result: object | undefined; - error: string | undefined; - }[]; -}; +export type WorkflowRunOutput = z.infer; +export type WorkflowRunOutputStepsOutput = WorkflowRunOutput['stepsOutput']; -export type WorkflowRunOutputStepsOutput = Record; - -export type WorkflowRunOutput = { - flow: { - trigger: WorkflowTrigger; - steps: WorkflowAction[]; - }; - stepsOutput?: WorkflowRunOutputStepsOutput; - error?: string; -}; - -export type WorkflowRun = { - __typename: 'WorkflowRun'; - id: string; - workflowVersionId: string; - output: WorkflowRunOutput | null; -}; +export type WorkflowRun = z.infer; export type Workflow = { __typename: 'Workflow'; diff --git a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts new file mode 100644 index 000000000..26628665d --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts @@ -0,0 +1,205 @@ +import { z } from 'zod'; + +// Base schemas +export const objectRecordSchema = z.record(z.any()); + +export const baseWorkflowActionSettingsSchema = z.object({ + input: z.object({}).passthrough(), + outputSchema: z.object({}).passthrough(), + errorHandlingOptions: z.object({ + retryOnFailure: z.object({ + value: z.boolean(), + }), + continueOnFailure: z.object({ + value: z.boolean(), + }), + }), +}); + +export const baseWorkflowActionSchema = z.object({ + id: z.string(), + name: z.string(), + valid: z.boolean(), +}); + +export const baseTriggerSchema = z.object({ + name: z.string().optional(), + type: z.string(), +}); + +// Action settings schemas +export const workflowCodeActionSettingsSchema = + baseWorkflowActionSettingsSchema.extend({ + input: z.object({ + serverlessFunctionId: z.string(), + serverlessFunctionVersion: z.string(), + serverlessFunctionInput: z.record(z.any()), + }), + }); + +export const workflowSendEmailActionSettingsSchema = + baseWorkflowActionSettingsSchema.extend({ + input: z.object({ + connectedAccountId: z.string(), + email: z.string(), + subject: z.string().optional(), + body: z.string().optional(), + }), + }); + +export const workflowCreateRecordActionSettingsSchema = + baseWorkflowActionSettingsSchema.extend({ + input: z.object({ + objectName: z.string(), + objectRecord: objectRecordSchema, + }), + }); + +export const workflowUpdateRecordActionSettingsSchema = + baseWorkflowActionSettingsSchema.extend({ + input: z.object({ + objectName: z.string(), + objectRecord: objectRecordSchema, + objectRecordId: z.string(), + fieldsToUpdate: z.array(z.string()), + }), + }); + +export const workflowDeleteRecordActionSettingsSchema = + baseWorkflowActionSettingsSchema.extend({ + input: z.object({ + objectName: z.string(), + objectRecordId: z.string(), + }), + }); + +export const workflowFindRecordsActionSettingsSchema = + baseWorkflowActionSettingsSchema.extend({ + input: z.object({ + objectName: z.string(), + limit: z.number().optional(), + }), + }); + +// Action schemas +export const workflowCodeActionSchema = baseWorkflowActionSchema.extend({ + type: z.literal('CODE'), + settings: workflowCodeActionSettingsSchema, +}); + +export const workflowSendEmailActionSchema = baseWorkflowActionSchema.extend({ + type: z.literal('SEND_EMAIL'), + settings: workflowSendEmailActionSettingsSchema, +}); + +export const workflowCreateRecordActionSchema = baseWorkflowActionSchema.extend( + { + type: z.literal('CREATE_RECORD'), + settings: workflowCreateRecordActionSettingsSchema, + }, +); + +export const workflowUpdateRecordActionSchema = baseWorkflowActionSchema.extend( + { + type: z.literal('UPDATE_RECORD'), + settings: workflowUpdateRecordActionSettingsSchema, + }, +); + +export const workflowDeleteRecordActionSchema = baseWorkflowActionSchema.extend( + { + type: z.literal('DELETE_RECORD'), + settings: workflowDeleteRecordActionSettingsSchema, + }, +); + +export const workflowFindRecordsActionSchema = baseWorkflowActionSchema.extend({ + type: z.literal('FIND_RECORDS'), + settings: workflowFindRecordsActionSettingsSchema, +}); + +// Combined action schema +export const workflowActionSchema = z.discriminatedUnion('type', [ + workflowCodeActionSchema, + workflowSendEmailActionSchema, + workflowCreateRecordActionSchema, + workflowUpdateRecordActionSchema, + workflowDeleteRecordActionSchema, + workflowFindRecordsActionSchema, +]); + +// Trigger schemas +export const workflowDatabaseEventTriggerSchema = baseTriggerSchema.extend({ + type: z.literal('DATABASE_EVENT'), + settings: z.object({ + eventName: z.string(), + input: z.object({}).passthrough().optional(), + outputSchema: z.object({}).passthrough(), + objectType: z.string().optional(), + }), +}); + +export const workflowManualTriggerSchema = baseTriggerSchema.extend({ + type: z.literal('MANUAL'), + settings: z.object({ + objectType: z.string().optional(), + outputSchema: z.object({}).passthrough(), + }), +}); + +export const workflowCronTriggerSchema = baseTriggerSchema.extend({ + type: z.literal('CRON'), + settings: z.discriminatedUnion('type', [ + z.object({ + type: z.literal('HOURS'), + schedule: z.object({ hour: z.number(), minute: z.number() }), + outputSchema: z.object({}).passthrough(), + }), + z.object({ + type: z.literal('MINUTES'), + schedule: z.object({ minute: z.number() }), + outputSchema: z.object({}).passthrough(), + }), + z.object({ + type: z.literal('CUSTOM'), + pattern: z.string(), + outputSchema: z.object({}).passthrough(), + }), + ]), +}); + +// Combined trigger schema +export const workflowTriggerSchema = z.discriminatedUnion('type', [ + workflowDatabaseEventTriggerSchema, + workflowManualTriggerSchema, + workflowCronTriggerSchema, +]); + +// Step output schemas +const workflowExecutorOutputSchema = z.object({ + result: z.any().optional(), + error: z.string().optional(), +}); + +const workflowRunOutputStepsOutputSchema = z.record( + workflowExecutorOutputSchema, +); + +// Final workflow run output schema +export const workflowRunOutputSchema = z.object({ + flow: z.object({ + trigger: workflowTriggerSchema, + steps: z.array(workflowActionSchema), + }), + stepsOutput: workflowRunOutputStepsOutputSchema.optional(), + error: z.string().optional(), +}); + +export const workflowRunSchema = z.object({ + __typename: z.literal('WorkflowRun'), + id: z.string(), + workflowVersionId: z.string(), + output: workflowRunOutputSchema.nullable(), +}); + +export type WorkflowRunOutput = z.infer; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts index 4e220a54c..4fef5c445 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts @@ -11,175 +11,6 @@ jest.mock('uuid', () => ({ })); describe('generateWorkflowRunDiagram', () => { - it('marks node as failed when not at least one attempt is in output', () => { - const trigger: WorkflowTrigger = { - name: 'Company created', - type: 'DATABASE_EVENT', - settings: { - eventName: 'company.created', - outputSchema: {}, - }, - }; - const steps: WorkflowStep[] = [ - { - id: 'step1', - name: 'Step 1', - type: 'CODE', - valid: true, - settings: { - errorHandlingOptions: { - retryOnFailure: { value: true }, - continueOnFailure: { value: false }, - }, - input: { - serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997', - serverlessFunctionVersion: '1', - serverlessFunctionInput: {}, - }, - outputSchema: {}, - }, - }, - { - id: 'step2', - name: 'Step 2', - type: 'CODE', - valid: true, - settings: { - errorHandlingOptions: { - retryOnFailure: { value: true }, - continueOnFailure: { value: false }, - }, - input: { - serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997', - serverlessFunctionVersion: '1', - serverlessFunctionInput: {}, - }, - outputSchema: {}, - }, - }, - { - id: 'step3', - name: 'Step 3', - type: 'CODE', - valid: true, - settings: { - errorHandlingOptions: { - retryOnFailure: { value: true }, - continueOnFailure: { value: false }, - }, - input: { - serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997', - serverlessFunctionVersion: '1', - serverlessFunctionInput: {}, - }, - outputSchema: {}, - }, - }, - ]; - const stepsOutput: WorkflowRunOutputStepsOutput = { - step1: { - id: 'step1', - outputs: [], - }, - }; - - const result = generateWorkflowRunDiagram({ trigger, steps, stepsOutput }); - - 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", - "isLeafNode": false, - "name": "Company created", - "nodeType": "trigger", - "runStatus": "success", - "triggerType": "DATABASE_EVENT", - }, - "id": "trigger", - "position": { - "x": 0, - "y": 0, - }, - }, - { - "data": { - "actionType": "CODE", - "isLeafNode": false, - "name": "Step 1", - "nodeType": "action", - "runStatus": "failure", - }, - "id": "step1", - "position": { - "x": 0, - "y": 0, - }, - }, - { - "data": { - "actionType": "CODE", - "isLeafNode": false, - "name": "Step 2", - "nodeType": "action", - "runStatus": "not-executed", - }, - "id": "step2", - "position": { - "x": 0, - "y": 150, - }, - }, - { - "data": { - "actionType": "CODE", - "isLeafNode": false, - "name": "Step 3", - "nodeType": "action", - "runStatus": "not-executed", - }, - "id": "step3", - "position": { - "x": 0, - "y": 300, - }, - }, - ], -} -`); - }); - it('marks node as failed when the last attempt failed', () => { const trigger: WorkflowTrigger = { name: 'Company created', @@ -247,14 +78,8 @@ describe('generateWorkflowRunDiagram', () => { ]; const stepsOutput: WorkflowRunOutputStepsOutput = { step1: { - id: 'step1', - outputs: [ - { - attemptCount: 1, - result: undefined, - error: '', - }, - ], + result: undefined, + error: '', }, }; @@ -265,7 +90,7 @@ describe('generateWorkflowRunDiagram', () => { "edges": [ { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-3", + "id": "8f3b2121-f194-4ba4-9fbf-0", "markerEnd": "workflow-edge-green-arrow-rounded", "markerStart": "workflow-edge-green-circle", "selectable": false, @@ -275,7 +100,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-4", + "id": "8f3b2121-f194-4ba4-9fbf-1", "markerEnd": "workflow-edge-arrow-rounded", "markerStart": "workflow-edge-gray-circle", "selectable": false, @@ -284,7 +109,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-5", + "id": "8f3b2121-f194-4ba4-9fbf-2", "markerEnd": "workflow-edge-arrow-rounded", "markerStart": "workflow-edge-gray-circle", "selectable": false, @@ -422,34 +247,16 @@ describe('generateWorkflowRunDiagram', () => { ]; const stepsOutput: WorkflowRunOutputStepsOutput = { step1: { - id: 'step1', - outputs: [ - { - attemptCount: 1, - result: {}, - error: undefined, - }, - ], + result: {}, + error: undefined, }, step2: { - id: 'step2', - outputs: [ - { - attemptCount: 1, - result: {}, - error: undefined, - }, - ], + result: {}, + error: undefined, }, step3: { - id: 'step3', - outputs: [ - { - attemptCount: 1, - result: {}, - error: undefined, - }, - ], + result: {}, + error: undefined, }, }; @@ -460,7 +267,7 @@ describe('generateWorkflowRunDiagram', () => { "edges": [ { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-6", + "id": "8f3b2121-f194-4ba4-9fbf-3", "markerEnd": "workflow-edge-green-arrow-rounded", "markerStart": "workflow-edge-green-circle", "selectable": false, @@ -470,7 +277,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-7", + "id": "8f3b2121-f194-4ba4-9fbf-4", "markerEnd": "workflow-edge-green-arrow-rounded", "markerStart": "workflow-edge-green-circle", "selectable": false, @@ -480,7 +287,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-8", + "id": "8f3b2121-f194-4ba4-9fbf-5", "markerEnd": "workflow-edge-green-arrow-rounded", "markerStart": "workflow-edge-green-circle", "selectable": false, @@ -626,7 +433,7 @@ describe('generateWorkflowRunDiagram', () => { "edges": [ { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-9", + "id": "8f3b2121-f194-4ba4-9fbf-6", "markerEnd": "workflow-edge-green-arrow-rounded", "markerStart": "workflow-edge-green-circle", "selectable": false, @@ -636,7 +443,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-10", + "id": "8f3b2121-f194-4ba4-9fbf-7", "markerEnd": "workflow-edge-arrow-rounded", "markerStart": "workflow-edge-gray-circle", "selectable": false, @@ -645,7 +452,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-11", + "id": "8f3b2121-f194-4ba4-9fbf-8", "markerEnd": "workflow-edge-arrow-rounded", "markerStart": "workflow-edge-gray-circle", "selectable": false, @@ -801,14 +608,8 @@ describe('generateWorkflowRunDiagram', () => { ]; const stepsOutput: WorkflowRunOutputStepsOutput = { step1: { - id: 'step1', - outputs: [ - { - attemptCount: 1, - result: {}, - error: undefined, - }, - ], + result: {}, + error: undefined, }, }; @@ -819,7 +620,7 @@ describe('generateWorkflowRunDiagram', () => { "edges": [ { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-12", + "id": "8f3b2121-f194-4ba4-9fbf-9", "markerEnd": "workflow-edge-green-arrow-rounded", "markerStart": "workflow-edge-green-circle", "selectable": false, @@ -829,7 +630,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-13", + "id": "8f3b2121-f194-4ba4-9fbf-10", "markerEnd": "workflow-edge-green-arrow-rounded", "markerStart": "workflow-edge-green-circle", "selectable": false, @@ -839,7 +640,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-14", + "id": "8f3b2121-f194-4ba4-9fbf-11", "markerEnd": "workflow-edge-arrow-rounded", "markerStart": "workflow-edge-gray-circle", "selectable": false, @@ -848,7 +649,7 @@ describe('generateWorkflowRunDiagram', () => { }, { "deletable": false, - "id": "8f3b2121-f194-4ba4-9fbf-15", + "id": "8f3b2121-f194-4ba4-9fbf-12", "markerEnd": "workflow-edge-arrow-rounded", "markerStart": "workflow-edge-gray-circle", "selectable": false, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts index b9b62d4bd..478204010 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts @@ -86,12 +86,7 @@ export const generateWorkflowRunDiagram = ({ } else if (!isDefined(runResult)) { runStatus = 'running'; } else { - const lastAttempt = runResult.outputs.at(-1); - - if (!isDefined(lastAttempt)) { - // Should never happen. Should we throw instead? - runStatus = 'failure'; - } else if (isDefined(lastAttempt.error)) { + if (isDefined(runResult.error)) { runStatus = 'failure'; } else { runStatus = 'success';