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={
-
-
-
-
-
-
- }
- />
- );
- };
+ 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;