Fix step output for iterators (#14796)
It also prevents the app from crashing when opening the output of the trigger node. ## Before <img width="3738" height="2442" alt="CleanShot 2025-09-30 at 19 44 39@2x" src="https://github.com/user-attachments/assets/39fa81b1-2c25-402e-9030-770054870f70" /> ## After https://github.com/user-attachments/assets/b7585690-dd3b-4875-9c3e-18b60b3acc3d
This commit is contained in:
committed by
GitHub
parent
bc37a3a9bb
commit
b3153535a6
+8
-5
@@ -2,8 +2,8 @@ import { workflowRunIteratorSubStepIterationIndexComponentState } from '@/comman
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||
import { getWorkflowRunAllStepInfoHistory } from '@/workflow/workflow-steps/utils/getWorkflowRunAllStepInfoHistory';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getStepInfoHistoryItem } from '../utils/getStepInfoHistoryItem';
|
||||
|
||||
export const useWorkflowRunStepInfo = ({ stepId }: { stepId: string }) => {
|
||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||
@@ -15,11 +15,14 @@ export const useWorkflowRunStepInfo = ({ stepId }: { stepId: string }) => {
|
||||
|
||||
const stepInfo = workflowRun?.state?.stepInfos[stepId];
|
||||
|
||||
if (!isDefined(stepInfo)) {
|
||||
if (!isDefined(stepInfo) || !isDefined(workflowRun?.state?.flow)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allStepInfoHistory = getWorkflowRunAllStepInfoHistory({ stepInfo });
|
||||
|
||||
return allStepInfoHistory[workflowRunIteratorSubStepIterationIndex];
|
||||
return getStepInfoHistoryItem({
|
||||
stepInfo,
|
||||
steps: workflowRun.state.flow.steps,
|
||||
stepId,
|
||||
iterationIndex: workflowRunIteratorSubStepIterationIndex,
|
||||
});
|
||||
};
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import { type WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { type WorkflowRunStepInfo, StepStatus } from 'twenty-shared/workflow';
|
||||
import { getStepInfoHistoryItem } from '../getStepInfoHistoryItem';
|
||||
|
||||
describe('getStepInfoHistoryItem', () => {
|
||||
const iteratorStep: WorkflowStep = {
|
||||
id: 'iterator1',
|
||||
name: 'Iterator',
|
||||
type: 'ITERATOR',
|
||||
valid: true,
|
||||
nextStepIds: ['step2'],
|
||||
settings: {
|
||||
input: { initialLoopStepIds: ['step2'] },
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: false },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const codeStep: WorkflowStep = {
|
||||
id: 'step2',
|
||||
name: 'Step 2',
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
nextStepIds: [],
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId: 'func',
|
||||
serverlessFunctionVersion: '1.0.0',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: false },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const steps: WorkflowStep[] = [iteratorStep, codeStep];
|
||||
|
||||
it('returns the correct history item for a descendant of iterator', () => {
|
||||
const stepInfo: WorkflowRunStepInfo = {
|
||||
result: 'final',
|
||||
status: StepStatus.SUCCESS,
|
||||
history: [
|
||||
{ result: 'first', status: StepStatus.SUCCESS },
|
||||
{ result: 'second', status: StepStatus.SUCCESS },
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
getStepInfoHistoryItem({
|
||||
stepInfo,
|
||||
steps,
|
||||
stepId: 'step2',
|
||||
iterationIndex: 0,
|
||||
})?.result,
|
||||
).toBe('first');
|
||||
expect(
|
||||
getStepInfoHistoryItem({
|
||||
stepInfo,
|
||||
steps,
|
||||
stepId: 'step2',
|
||||
iterationIndex: 1,
|
||||
})?.result,
|
||||
).toBe('second');
|
||||
expect(
|
||||
getStepInfoHistoryItem({
|
||||
stepInfo,
|
||||
steps,
|
||||
stepId: 'step2',
|
||||
iterationIndex: 2,
|
||||
})?.result,
|
||||
).toBe('final');
|
||||
});
|
||||
|
||||
it('returns the last history item for a non-descendant', () => {
|
||||
const stepInfo: WorkflowRunStepInfo = {
|
||||
result: 'final',
|
||||
status: StepStatus.SUCCESS,
|
||||
history: [
|
||||
{ result: 'first', status: StepStatus.SUCCESS },
|
||||
{ result: 'second', status: StepStatus.SUCCESS },
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
getStepInfoHistoryItem({
|
||||
stepInfo,
|
||||
steps,
|
||||
stepId: 'iterator1',
|
||||
iterationIndex: 0,
|
||||
})?.result,
|
||||
).toBe('final');
|
||||
});
|
||||
|
||||
it('returns undefined if iterationIndex is out of bounds', () => {
|
||||
const stepInfo: WorkflowRunStepInfo = {
|
||||
result: 'final',
|
||||
status: StepStatus.SUCCESS,
|
||||
history: [{ result: 'first', status: StepStatus.SUCCESS }],
|
||||
};
|
||||
|
||||
expect(
|
||||
getStepInfoHistoryItem({
|
||||
stepInfo,
|
||||
steps,
|
||||
stepId: 'step2',
|
||||
iterationIndex: 5,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns the only item if no history', () => {
|
||||
const stepInfo: WorkflowRunStepInfo = {
|
||||
result: 'only',
|
||||
status: StepStatus.SUCCESS,
|
||||
};
|
||||
|
||||
expect(
|
||||
getStepInfoHistoryItem({
|
||||
stepInfo,
|
||||
steps,
|
||||
stepId: 'step2',
|
||||
iterationIndex: 0,
|
||||
})?.result,
|
||||
).toBe('only');
|
||||
});
|
||||
});
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
import { type WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { isParentStep } from '@/workflow/workflow-diagram/utils/isParentStep';
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
export const getIsDescendantOfIterator = ({
|
||||
stepId,
|
||||
@@ -12,6 +13,10 @@ export const getIsDescendantOfIterator = ({
|
||||
currentStepId: string,
|
||||
visited = new Set<string>(),
|
||||
): boolean => {
|
||||
if (currentStepId === TRIGGER_STEP_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentStep = steps.find((step) => step.id === currentStepId);
|
||||
if (!currentStep) {
|
||||
throw new Error(`Step with ID ${currentStepId} not found`);
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { type WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { type WorkflowRunStepInfo } from 'twenty-shared/workflow';
|
||||
import { getIsDescendantOfIterator } from './getIsDescendantOfIterator';
|
||||
import { getWorkflowRunAllStepInfoHistory } from './getWorkflowRunAllStepInfoHistory';
|
||||
|
||||
export const getStepInfoHistoryItem = ({
|
||||
stepInfo,
|
||||
steps,
|
||||
stepId,
|
||||
iterationIndex,
|
||||
}: {
|
||||
stepInfo: WorkflowRunStepInfo;
|
||||
steps: WorkflowStep[];
|
||||
stepId: string;
|
||||
iterationIndex: number;
|
||||
}) => {
|
||||
const allStepInfoHistory = getWorkflowRunAllStepInfoHistory({ stepInfo });
|
||||
|
||||
const isDescendantOfIterator = getIsDescendantOfIterator({ steps, stepId });
|
||||
|
||||
if (isDescendantOfIterator) {
|
||||
return allStepInfoHistory[iterationIndex];
|
||||
}
|
||||
|
||||
return allStepInfoHistory.at(-1);
|
||||
};
|
||||
Reference in New Issue
Block a user