diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx index 349973db73..aa14667ae9 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStepContent.tsx @@ -1,3 +1,4 @@ +import { CommandMenuWorkflowRunStepContentComponentInstanceContext } from '@/command-menu/pages/workflow/step/view-run/states/contexts/CommandMenuWorkflowRunStepContentComponentInstanceContext'; import { getIsInputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled'; import { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled'; import { getShouldFocusNodeTab } from '@/command-menu/pages/workflow/step/view-run/utils/getShouldFocusNodeTab'; @@ -12,6 +13,7 @@ import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; +import { WorkflowIteratorSubStepSwitcher } from '@/workflow/workflow-steps/components/WorkflowIteratorSubStepSwitcher'; import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail'; import { WorkflowRunStepNodeDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepNodeDetail'; import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail'; @@ -112,46 +114,54 @@ export const CommandMenuWorkflowRunViewStepContent = () => { ]; return ( - - {shouldFocusNodeTab ? ( - - ) : ( - <> - + + {shouldFocusNodeTab ? ( + - - {activeTabId === WorkflowRunTabId.OUTPUT ? ( - + - ) : null} - {activeTabId === WorkflowRunTabId.NODE ? ( - - ) : null} + {activeTabId === WorkflowRunTabId.OUTPUT ? ( + + ) : null} - {activeTabId === WorkflowRunTabId.INPUT ? ( - - ) : null} - - )} - + {activeTabId === WorkflowRunTabId.NODE ? ( + + ) : null} + + {activeTabId === WorkflowRunTabId.INPUT ? ( + + ) : null} + + + + )} + + ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/states/contexts/CommandMenuWorkflowRunStepContentComponentInstanceContext.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/states/contexts/CommandMenuWorkflowRunStepContentComponentInstanceContext.ts new file mode 100644 index 0000000000..8c4410cb7e --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/states/contexts/CommandMenuWorkflowRunStepContentComponentInstanceContext.ts @@ -0,0 +1,6 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const CommandMenuWorkflowRunStepContentComponentInstanceContext = + createComponentInstanceContext({ + instanceId: '', + }); diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/states/workflowRunIteratorSubStepIterationIndexComponentState.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/states/workflowRunIteratorSubStepIterationIndexComponentState.ts new file mode 100644 index 0000000000..a65db1293e --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/states/workflowRunIteratorSubStepIterationIndexComponentState.ts @@ -0,0 +1,10 @@ +import { CommandMenuWorkflowRunStepContentComponentInstanceContext } from '@/command-menu/pages/workflow/step/view-run/states/contexts/CommandMenuWorkflowRunStepContentComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const workflowRunIteratorSubStepIterationIndexComponentState = + createComponentState({ + key: 'workflowRunIteratorSubStepIterationIndexComponentState', + defaultValue: 0, + componentInstanceContext: + CommandMenuWorkflowRunStepContentComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/isParentStep.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/isParentStep.ts new file mode 100644 index 0000000000..6f6b2b54c6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/isParentStep.ts @@ -0,0 +1,33 @@ +import { type WorkflowStep } from '@/workflow/types/Workflow'; +import { isLastStepOfLoop } from '@/workflow/workflow-diagram/utils/isLastStepOfLoop'; + +export const isParentStep = ({ + currentStep, + potentialParentStep, + steps, +}: { + currentStep: WorkflowStep; + potentialParentStep: WorkflowStep; + steps: WorkflowStep[]; +}): boolean => { + if (potentialParentStep.type === 'ITERATOR') { + return !!( + potentialParentStep.settings.input.initialLoopStepIds?.includes( + currentStep.id, + ) || potentialParentStep.nextStepIds?.includes(currentStep.id) + ); + } + + if (currentStep.type === 'ITERATOR') { + return !!( + potentialParentStep.nextStepIds?.includes(currentStep.id) && + !isLastStepOfLoop({ + iterator: currentStep, + stepId: potentialParentStep.id, + steps, + }) + ); + } + + return !!potentialParentStep.nextStepIds?.includes(currentStep.id); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowActionFooter.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowActionFooter.tsx index 58bef2e8c7..ee7b42e815 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowActionFooter.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowActionFooter.tsx @@ -30,58 +30,53 @@ export const WorkflowActionFooter = ({ const workflowId = useCommandMenuWorkflowIdOrThrow(); const { openWorkflowEditStepTypeInCommandMenu } = useWorkflowCommandMenu(); - const OptionsDropdown = () => { - return ( - - } - dropdownPlacement="top-end" - dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }} - globalHotkeysConfig={{ - enableGlobalHotkeysWithModifiers: true, - enableGlobalHotkeysConflictingWithKeyboard: false, - }} - dropdownComponents={ - - - - { - closeDropdown(dropdownId); - openWorkflowEditStepTypeInCommandMenu(workflowId); - }} - text={t`Change node type`} - LeftIcon={IconPencil} - /> - { - closeDropdown(dropdownId); - duplicateStep({ stepId }); - }} - text={t`Duplicate node`} - LeftIcon={IconCopyPlus} - /> - - - - } - /> - ); - }; + const OptionsDropdown = ( + + } + dropdownPlacement="top-end" + dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }} + globalHotkeysConfig={{ + enableGlobalHotkeysWithModifiers: true, + enableGlobalHotkeysConflictingWithKeyboard: false, + }} + dropdownComponents={ + + + + { + closeDropdown(dropdownId); + openWorkflowEditStepTypeInCommandMenu(workflowId); + }} + text={t`Change node type`} + LeftIcon={IconPencil} + /> + { + closeDropdown(dropdownId); + duplicateStep({ stepId }); + }} + text={t`Duplicate node`} + LeftIcon={IconCopyPlus} + /> + + + + } + /> + ); return ( , - ...(additionalActions ?? []), - ]} + actions={[OptionsDropdown, ...(additionalActions ?? [])]} /> ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowIteratorSubStepSwitcher.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowIteratorSubStepSwitcher.tsx new file mode 100644 index 0000000000..78eacb8859 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowIteratorSubStepSwitcher.tsx @@ -0,0 +1,126 @@ +import { workflowRunIteratorSubStepIterationIndexComponentState } from '@/command-menu/pages/workflow/step/view-run/states/workflowRunIteratorSubStepIterationIndexComponentState'; +import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow'; +import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; +import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; +import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; +import { getIsDescendantOfIterator } from '@/workflow/workflow-steps/utils/getIsDescendantOfIterator'; +import { getWorkflowRunAllStepInfoHistory } from '@/workflow/workflow-steps/utils/getWorkflowRunAllStepInfoHistory'; +import styled from '@emotion/styled'; +import { plural } from '@lingui/core/macro'; +import { isDefined } from 'twenty-shared/utils'; +import { IconChevronLeft, IconChevronRight } from 'twenty-ui/display'; +import { IconButton } from 'twenty-ui/input'; + +const StyledContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding-block: ${({ theme }) => theme.spacing(2)}; + padding-inline: ${({ theme }) => theme.spacing(3)}; +`; + +const StyledCounter = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + font-weight: ${({ theme }) => theme.font.weight.medium}; +`; + +export const WorkflowIteratorSubStepSwitcher = ({ + stepId, +}: { + stepId: string; +}) => { + const flow = useFlowOrThrow(); + const workflowRunId = useWorkflowRunIdOrThrow(); + const workflowRun = useWorkflowRun({ workflowRunId }); + + const [ + workflowRunIteratorSubStepIterationIndex, + setWorkflowRunIteratorSubStepIterationIndex, + ] = useRecoilComponentState( + workflowRunIteratorSubStepIterationIndexComponentState, + ); + + const stepDefinition = getStepDefinitionOrThrow({ + stepId, + trigger: flow.trigger, + steps: flow.steps, + }); + + const stepInfo = workflowRun?.state?.stepInfos[stepId]; + + if ( + !isDefined(stepInfo) || + !isDefined(workflowRun?.state) || + !isDefined(flow.steps) || + stepDefinition?.type !== 'action' + ) { + return null; + } + + const allStepInfos = getWorkflowRunAllStepInfoHistory({ + stepInfo, + }); + + const isDescendantOfIterator = getIsDescendantOfIterator({ + stepId, + steps: flow.steps, + }); + + const workflowRunIteratorSubStepIterationsCount = allStepInfos.length; + + const canGoToPreviousIndex = workflowRunIteratorSubStepIterationIndex > 0; + const canGoToNextIndex = + workflowRunIteratorSubStepIterationIndex < + workflowRunIteratorSubStepIterationsCount - 1; + + const handleDecrementIndex = () => { + if (!canGoToPreviousIndex) { + return; + } + + setWorkflowRunIteratorSubStepIterationIndex( + workflowRunIteratorSubStepIterationIndex - 1, + ); + }; + + const handleIncrementIndex = () => { + if (!canGoToNextIndex) { + return; + } + + setWorkflowRunIteratorSubStepIterationIndex( + workflowRunIteratorSubStepIterationIndex + 1, + ); + }; + + if (!isDescendantOfIterator) { + return null; + } + + return ( + + + + + {workflowRunIteratorSubStepIterationIndex + 1}/ + {plural(workflowRunIteratorSubStepIterationsCount, { + one: '# item', + other: '# items', + })} + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx index b1d555acca..eb627824dc 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx @@ -3,6 +3,8 @@ import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThro import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; +import { useWorkflowRunStepInfo } from '@/workflow/workflow-steps/hooks/useWorkflowRunStepInfo'; +import { getWorkflowRunStepInfoToDisplayAsOutput } from '@/workflow/workflow-steps/utils/getWorkflowRunStepInfoToDisplayAsOutput'; import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow'; @@ -29,13 +31,15 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => { const workflowRunId = useWorkflowRunIdOrThrow(); const workflowRun = useWorkflowRun({ workflowRunId }); - if (!isDefined(workflowRun?.state?.stepInfos)) { + const stepInfo = useWorkflowRunStepInfo({ stepId }); + + if (!isDefined(workflowRun?.state) || !isDefined(stepInfo)) { return null; } - const stepInfo = workflowRun.state.stepInfos[stepId]; - - const { status: _, ...stepInfoWithoutStatus } = stepInfo ?? {}; + const stepInfoToDisplay = getWorkflowRunStepInfoToDisplayAsOutput({ + stepInfo, + }); const stepDefinition = getStepDefinitionOrThrow({ stepId, @@ -89,7 +93,7 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => { { + const workflowRunId = useWorkflowRunIdOrThrow(); + const workflowRun = useWorkflowRun({ workflowRunId }); + + const workflowRunIteratorSubStepIterationIndex = useRecoilComponentValue( + workflowRunIteratorSubStepIterationIndexComponentState, + ); + + const stepInfo = workflowRun?.state?.stepInfos[stepId]; + + if (!isDefined(stepInfo)) { + return undefined; + } + + const allStepInfoHistory = getWorkflowRunAllStepInfoHistory({ stepInfo }); + + return allStepInfoHistory[workflowRunIteratorSubStepIterationIndex]; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getIsDescendantOfIterator.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getIsDescendantOfIterator.test.ts new file mode 100644 index 0000000000..630cc94dfc --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getIsDescendantOfIterator.test.ts @@ -0,0 +1,113 @@ +import { type WorkflowStep } from '@/workflow/types/Workflow'; +import { getIsDescendantOfIterator } from '../getIsDescendantOfIterator'; + +describe('getIsDescendantOfIterator', () => { + const iteratorStep: WorkflowStep = { + id: 'iterator1', + name: 'Iterator Step', + type: 'ITERATOR', + valid: true, + nextStepIds: ['step3'], + settings: { + input: { + initialLoopStepIds: ['step2'], + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: true }, + }, + }, + }; + + const codeStep2: WorkflowStep = { + id: 'step2', + name: 'Second Step', + type: 'CODE', + valid: true, + nextStepIds: ['iterator1'], + settings: { + input: { + serverlessFunctionId: 'func2', + serverlessFunctionVersion: '1.0.0', + serverlessFunctionInput: {}, + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: true }, + }, + }, + }; + + const codeStep3: WorkflowStep = { + id: 'step3', + name: 'Third Step', + type: 'CODE', + valid: true, + nextStepIds: [], + settings: { + input: { + serverlessFunctionId: 'func3', + serverlessFunctionVersion: '1.0.0', + serverlessFunctionInput: {}, + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: true }, + }, + }, + }; + + const workflow: WorkflowStep[] = [iteratorStep, codeStep2, codeStep3]; + + it('returns true for direct loop step descendant', () => { + expect( + getIsDescendantOfIterator({ stepId: codeStep2.id, steps: workflow }), + ).toBe(true); + }); + + it('returns true for indirect descendant', () => { + // step3 is pointed to by iterator1.nextStepIds + expect( + getIsDescendantOfIterator({ stepId: codeStep3.id, steps: workflow }), + ).toBe(true); + }); + + it('returns false for iterator itself', () => { + expect( + getIsDescendantOfIterator({ stepId: iteratorStep.id, steps: workflow }), + ).toBe(false); + }); + + it('returns false for step not connected to iterator', () => { + const unrelatedStep: WorkflowStep = { + id: 'step4', + name: 'Unrelated Step', + type: 'CODE', + valid: true, + nextStepIds: [], + settings: { + input: { + serverlessFunctionId: 'func4', + serverlessFunctionVersion: '1.0.0', + serverlessFunctionInput: {}, + }, + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: true }, + }, + }, + }; + expect( + getIsDescendantOfIterator({ + stepId: unrelatedStep.id, + steps: [...workflow, unrelatedStep], + }), + ).toBe(false); + }); +}); + +// ...existing test code... diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getIsDescendantOfIterator.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getIsDescendantOfIterator.ts new file mode 100644 index 0000000000..32c4ce10f2 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getIsDescendantOfIterator.ts @@ -0,0 +1,46 @@ +import { type WorkflowStep } from '@/workflow/types/Workflow'; +import { isParentStep } from '@/workflow/workflow-diagram/utils/isParentStep'; + +export const getIsDescendantOfIterator = ({ + stepId, + steps, +}: { + stepId: string; + steps: WorkflowStep[]; +}): boolean => { + const hasIteratorAncestor = ( + currentStepId: string, + visited = new Set(), + ): boolean => { + const currentStep = steps.find((step) => step.id === currentStepId); + if (!currentStep) { + throw new Error(`Step with ID ${currentStepId} not found`); + } + + const parentSteps = steps + .filter((step) => + isParentStep({ + currentStep, + potentialParentStep: step, + steps, + }), + ) + .filter((step) => !visited.has(step.id)); + + for (const parent of parentSteps) { + visited.add(currentStepId); + + if (parent.type === 'ITERATOR') { + return true; + } + + if (hasIteratorAncestor(parent.id, visited)) { + return true; + } + } + + return false; + }; + + return hasIteratorAncestor(stepId); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousSteps.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousSteps.ts index af1f33f6a3..574ec3091d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousSteps.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousSteps.ts @@ -1,36 +1,5 @@ import { type WorkflowStep } from '@/workflow/types/Workflow'; -import { isLastStepOfLoop } from '@/workflow/workflow-diagram/utils/isLastStepOfLoop'; - -const isParentStep = ({ - currentStep, - potentialParentStep, - steps, -}: { - currentStep: WorkflowStep; - potentialParentStep: WorkflowStep; - steps: WorkflowStep[]; -}) => { - if (potentialParentStep.type === 'ITERATOR') { - return ( - potentialParentStep.settings.input.initialLoopStepIds?.includes( - currentStep.id, - ) || potentialParentStep.nextStepIds?.includes(currentStep.id) - ); - } - - if (currentStep.type === 'ITERATOR') { - return ( - potentialParentStep.nextStepIds?.includes(currentStep.id) && - !isLastStepOfLoop({ - iterator: currentStep, - stepId: potentialParentStep.id, - steps, - }) - ); - } - - return potentialParentStep.nextStepIds?.includes(currentStep.id); -}; +import { isParentStep } from '@/workflow/workflow-diagram/utils/isParentStep'; export const getPreviousSteps = ({ steps, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunAllStepInfoHistory.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunAllStepInfoHistory.ts new file mode 100644 index 0000000000..82d42627b4 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunAllStepInfoHistory.ts @@ -0,0 +1,15 @@ +import { isArray } from '@sniptt/guards'; +import { type WorkflowRunStepInfo } from 'twenty-shared/workflow'; + +export const getWorkflowRunAllStepInfoHistory = ({ + stepInfo, +}: { + stepInfo: WorkflowRunStepInfo; +}) => { + const allStepInfoHistory: WorkflowRunStepInfo[] = [ + ...(isArray(stepInfo?.history) ? stepInfo.history : []), + stepInfo, + ]; + + return allStepInfoHistory; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepInfoToDisplayAsOutput.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepInfoToDisplayAsOutput.ts new file mode 100644 index 0000000000..f391b4300b --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepInfoToDisplayAsOutput.ts @@ -0,0 +1,11 @@ +import { type WorkflowRunStepInfo } from 'twenty-shared/workflow'; + +export const getWorkflowRunStepInfoToDisplayAsOutput = ({ + stepInfo, +}: { + stepInfo: WorkflowRunStepInfo; +}) => { + const { status: _status, history: _history, ...infoToDisplay } = stepInfo; + + return infoToDisplay; +}; diff --git a/packages/twenty-shared/src/workflow/schemas/workflow-run-state-step-info-schema.ts b/packages/twenty-shared/src/workflow/schemas/workflow-run-state-step-info-schema.ts index 5419f7009d..913ffa5acf 100644 --- a/packages/twenty-shared/src/workflow/schemas/workflow-run-state-step-info-schema.ts +++ b/packages/twenty-shared/src/workflow/schemas/workflow-run-state-step-info-schema.ts @@ -3,6 +3,15 @@ import { workflowRunStepStatusSchema } from './workflow-run-step-status-schema'; export const workflowRunStateStepInfoSchema = z.object({ result: z.any().optional(), - error: z.any().optional(), + error: z.string().optional(), status: workflowRunStepStatusSchema, + get history() { + return z.array( + workflowRunStateStepInfoSchema.pick({ + result: true, + status: true, + error: true, + }) + ).optional(); + } }); diff --git a/packages/twenty-shared/src/workflow/types/WorkflowRunStateStepInfos.ts b/packages/twenty-shared/src/workflow/types/WorkflowRunStateStepInfos.ts index c1e9642b95..e0e07178a0 100644 --- a/packages/twenty-shared/src/workflow/types/WorkflowRunStateStepInfos.ts +++ b/packages/twenty-shared/src/workflow/types/WorkflowRunStateStepInfos.ts @@ -1,3 +1,7 @@ +import { type workflowRunStateStepInfoSchema } from "@/workflow/schemas/workflow-run-state-step-info-schema"; +import { type workflowRunStateStepInfosSchema } from "@/workflow/schemas/workflow-run-state-step-infos-schema"; +import type z from "zod"; + export enum StepStatus { NOT_STARTED = 'NOT_STARTED', RUNNING = 'RUNNING', @@ -7,15 +11,6 @@ export enum StepStatus { PENDING = 'PENDING', } -export type WorkflowRunStepInfo = { - result?: object; - error?: string; - status: StepStatus; - history?: { - status: StepStatus; - result?: object; - error?: string; - }[]; -}; +export type WorkflowRunStepInfo = z.infer -export type WorkflowRunStepInfos = Record; +export type WorkflowRunStepInfos = z.infer;