diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled.ts
index 0d5067218e7..99ee5dc4a94 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled.ts
@@ -6,6 +6,8 @@ export const getIsOutputTabDisabled = ({
stepExecutionStatus: WorkflowRunStepStatus;
}) => {
return (
- stepExecutionStatus === 'RUNNING' || stepExecutionStatus === 'NOT_STARTED'
+ stepExecutionStatus === 'RUNNING' ||
+ stepExecutionStatus === 'NOT_STARTED' ||
+ stepExecutionStatus === 'SKIPPED'
);
};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeEditable.tsx
index 0bbf18f1641..7facd18aae1 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeEditable.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeEditable.tsx
@@ -41,7 +41,7 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
const {
segments,
- labelPosition: [labelX, labelY],
+ overlayPosition: [labelX, labelY],
} = getEdgePath({
sourceX,
sourceY,
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/utils/getEdgePath.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/utils/getEdgePath.ts
index eedd7c15492..8d002d4a515 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/utils/getEdgePath.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-edges/utils/getEdgePath.ts
@@ -32,7 +32,7 @@ export const getEdgePath = ({
strategy,
}: GetEdgePathParams) => {
if (strategy === 'smooth-step-path-to-target') {
- const [path, labelX, labelY] = getSmoothStepPath({
+ const [path] = getSmoothStepPath({
sourceX,
sourceY,
sourcePosition,
@@ -43,6 +43,12 @@ export const getEdgePath = ({
offset: EDGE_PADDING_X,
});
+ const overlayY =
+ targetY - sourceY > EDGE_PADDING_BOTTOM &&
+ sourceX + EDGE_PADDING_X < targetX
+ ? targetY - (targetY - sourceY) / 2
+ : targetY - EDGE_PADDING_BOTTOM / 2;
+
return {
segments: [
{
@@ -51,7 +57,7 @@ export const getEdgePath = ({
markerEnd,
},
],
- labelPosition: [labelX, labelY],
+ overlayPosition: [targetX, overlayY],
};
}
@@ -73,7 +79,7 @@ export const getEdgePath = ({
markerEnd,
},
],
- labelPosition: [labelX, labelY],
+ overlayPosition: [labelX, labelY],
};
}
@@ -117,6 +123,9 @@ export const getEdgePath = ({
markerEnd,
},
],
- labelPosition: [firstSegmentTargetX, firstSegmentTargetY],
+ overlayPosition:
+ strategy === 'bypass-source-node-on-right-side'
+ ? [sourceX, sourceY + EDGE_PADDING_BOTTOM]
+ : [firstSegmentTargetX, firstSegmentTargetY],
};
};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowRunDiagramStepNode.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowRunDiagramStepNode.tsx
index 4204ae1a788..1d732ffbb8c 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowRunDiagramStepNode.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowRunDiagramStepNode.tsx
@@ -3,11 +3,14 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandM
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
+import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
+import { type WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramStepNodeClickOutsideId';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { type WorkflowRunDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
+import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
@@ -19,7 +22,8 @@ import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/wo
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
-import { useTheme } from '@emotion/react';
+import { getNodeIterationCount } from '@/workflow/workflow-diagram/workflow-nodes/utils/getNodeIterationCount';
+import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Position } from '@xyflow/react';
import { useContext } from 'react';
@@ -33,7 +37,7 @@ const StyledNodeLabelWithCounterPart = styled(WorkflowNodeLabelWithCounterPart)`
column-gap: ${({ theme }) => theme.spacing(2)};
`;
-const StyledNodeCounter = styled.div`
+const StyledStatusIconsContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
@@ -54,6 +58,25 @@ const StyledColorIcon = styled.div<{
background: ${({ color }) => color};
`;
+const StyledIterationCounter = styled.div<{
+ runStatus?: WorkflowRunStepStatus;
+}>`
+ font-size: ${({ theme }) => theme.font.size.sm};
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+
+ ${({ theme, runStatus }) => {
+ const colors = getWorkflowDiagramColors({ theme, runStatus });
+ return css`
+ color: ${colors.unselected.color};
+ `;
+ }}
+`;
+
+const StyledRightPartContainer = styled.div`
+ display: flex;
+ gap: ${({ theme }) => theme.spacing(1)};
+`;
+
export const WorkflowRunDiagramStepNode = ({
id,
data,
@@ -83,6 +106,15 @@ export const WorkflowRunDiagramStepNode = ({
commandMenuNavigationStackState,
);
+ const workflowRun = useWorkflowRun({ workflowRunId });
+
+ const stepInfo = workflowRun?.state?.stepInfos[data.stepId];
+
+ const iterationCount =
+ data.nodeType === 'action' && isDefined(stepInfo)
+ ? getNodeIterationCount({ stepInfo })
+ : 0;
+
const handleClick = () => {
if (!isDefined(workflowId)) {
throw new Error('Workflow ID must be defined');
@@ -122,29 +154,37 @@ export const WorkflowRunDiagramStepNode = ({
{capitalize(data.nodeType)}
- {(data.runStatus === StepStatus.SUCCESS ||
- data.runStatus === StepStatus.STOPPED) && (
-
-
-
-
-
- )}
+
+ {iterationCount > 0 && (
+
+ {iterationCount}
+
+ )}
- {data.runStatus === StepStatus.FAILED && (
-
-
-
-
-
- )}
+ {(data.runStatus === StepStatus.SUCCESS ||
+ data.runStatus === StepStatus.STOPPED) && (
+
+
+
+
+
+ )}
- {(data.runStatus === StepStatus.RUNNING ||
- data.runStatus === StepStatus.PENDING) && (
-
-
-
- )}
+ {data.runStatus === StepStatus.FAILED && (
+
+
+
+
+
+ )}
+
+ {(data.runStatus === StepStatus.RUNNING ||
+ data.runStatus === StepStatus.PENDING) && (
+
+
+
+ )}
+
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/utils/getNodeIterationCount.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/utils/getNodeIterationCount.ts
new file mode 100644
index 00000000000..3af1ee26dfa
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/utils/getNodeIterationCount.ts
@@ -0,0 +1,15 @@
+import { getWorkflowRunAllStepInfoHistory } from '@/workflow/workflow-steps/utils/getWorkflowRunAllStepInfoHistory';
+import { StepStatus, type WorkflowRunStepInfo } from 'twenty-shared/workflow';
+export const getNodeIterationCount = ({
+ stepInfo,
+}: {
+ stepInfo: WorkflowRunStepInfo;
+}) => {
+ const stepInfoHistory = getWorkflowRunAllStepInfoHistory({ stepInfo });
+
+ return stepInfoHistory.filter(
+ (stepInfo) =>
+ stepInfo.status === StepStatus.SUCCESS ||
+ stepInfo.status === StepStatus.STOPPED,
+ ).length;
+};
diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts
index 086f1ca0432..bcb250d9b16 100644
--- a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts
+++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts
@@ -224,10 +224,6 @@ export class WorkflowExecutorWorkspaceService {
const steps = workflowRun.state.flow.steps;
- if (workflowShouldKeepRunning({ stepInfos, steps })) {
- return;
- }
-
if (workflowShouldFail({ stepInfos, steps })) {
await this.workflowRunWorkspaceService.endWorkflowRun({
workflowRunId,
@@ -239,6 +235,10 @@ export class WorkflowExecutorWorkspaceService {
return;
}
+ if (workflowShouldKeepRunning({ stepInfos, steps })) {
+ return;
+ }
+
await this.workflowRunWorkspaceService.endWorkflowRun({
workflowRunId,
workspaceId,