Fix run input tab for filters (#13961)

Previous step id was still calculated the old way for step following
triggers: if first step in the array, means it follows the trigger.
It was not working for steps inserted between trigger and first step.
Removing that code for good.

Before
<img width="500" height="655" alt="Capture d’écran 2025-08-18 à 16 34
50"
src="https://github.com/user-attachments/assets/31365bfe-8c7d-45b5-8141-ac3d4e31127f"
/>

After
<img width="500" height="655" alt="Capture d’écran 2025-08-18 à 16 34
18"
src="https://github.com/user-attachments/assets/b9b42c58-0416-48a6-bc65-2966366a888c"
/>
This commit is contained in:
Thomas Trompette
2025-08-18 17:00:52 +02:00
committed by GitHub
parent 5e905d3004
commit abe6b7828b
3 changed files with 4 additions and 272 deletions
@@ -3,7 +3,6 @@ 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 { getWorkflowPreviousStepId } from '@/workflow/workflow-steps/utils/getWorkflowPreviousStepId';
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
@@ -19,8 +18,8 @@ import {
JsonTreeContextProvider,
type ShouldExpandNodeInitiallyProps,
} from 'twenty-ui/json-visualizer';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
import { type JsonValue } from 'type-fest';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
const { t, i18n } = useLingui();
@@ -45,20 +44,12 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
return null;
}
const previousStepId = getWorkflowPreviousStepId({
stepId,
steps: workflowRun.state.flow.steps,
});
if (previousStepId === undefined) {
return null;
}
const stepDefinition = getStepDefinitionOrThrow({
stepId,
trigger: workflowRun.state.flow.trigger,
steps: workflowRun.state.flow.steps,
});
if (stepDefinition?.type !== 'action') {
throw new Error('The input tab must be rendered with an action step.');
}
@@ -86,6 +77,8 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
throw new Error('The input tab must be rendered with a non-empty context.');
}
const previousStepId = stepContext[stepContext.length - 1].id;
const getNodeHighlighting: GetJsonNodeHighlighting = (keyPath: string) => {
if (variablesUsedInStep.has(keyPath)) {
return 'blue';
@@ -1,239 +0,0 @@
import { type WorkflowStep } from '@/workflow/types/Workflow';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { getWorkflowPreviousStepId } from '../getWorkflowPreviousStepId';
describe('getWorkflowPreviousStepId', () => {
const mockSteps: WorkflowStep[] = [
{
id: 'step-1',
name: 'First Step',
type: 'CREATE_RECORD',
valid: true,
nextStepIds: ['step-2'],
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
objectName: 'Company',
objectRecord: {},
},
outputSchema: {},
},
} as WorkflowStep,
{
id: 'step-2',
name: 'Second Step',
type: 'UPDATE_RECORD',
valid: true,
nextStepIds: ['step-3'],
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
objectName: 'Company',
objectRecord: {},
objectRecordId: 'test-id',
fieldsToUpdate: ['name'],
},
outputSchema: {},
},
} as WorkflowStep,
{
id: 'step-3',
name: 'Third Step',
type: 'SEND_EMAIL',
valid: true,
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
connectedAccountId: 'account-id',
email: 'test@example.com',
},
outputSchema: {},
},
} as WorkflowStep,
];
it('should return undefined for TRIGGER_STEP_ID', () => {
const result = getWorkflowPreviousStepId({
stepId: TRIGGER_STEP_ID,
steps: mockSteps,
});
expect(result).toBeUndefined();
});
it('should return TRIGGER_STEP_ID for first step', () => {
const result = getWorkflowPreviousStepId({
stepId: 'step-1',
steps: mockSteps,
});
expect(result).toBe(TRIGGER_STEP_ID);
});
it('should return previous step ID for middle step', () => {
const result = getWorkflowPreviousStepId({
stepId: 'step-2',
steps: mockSteps,
});
expect(result).toBe('step-1');
});
it('should return previous step ID for last step', () => {
const result = getWorkflowPreviousStepId({
stepId: 'step-3',
steps: mockSteps,
});
expect(result).toBe('step-2');
});
it('should return undefined for non-existent step', () => {
const result = getWorkflowPreviousStepId({
stepId: 'non-existent-step',
steps: mockSteps,
});
expect(result).toBeUndefined();
});
it('should work with single step', () => {
const singleStep = [mockSteps[0]];
const result = getWorkflowPreviousStepId({
stepId: 'step-1',
steps: singleStep,
});
expect(result).toBe(TRIGGER_STEP_ID);
});
it('should handle branching workflow with multiple next steps', () => {
const branchingSteps: WorkflowStep[] = [
{
id: 'step-1',
name: 'First Step',
type: 'CREATE_RECORD',
valid: true,
nextStepIds: ['step-2a', 'step-2b'],
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
objectName: 'Company',
objectRecord: {},
},
outputSchema: {},
},
} as WorkflowStep,
{
id: 'step-2a',
name: 'Second Step A',
type: 'UPDATE_RECORD',
valid: true,
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
objectName: 'Company',
objectRecord: {},
objectRecordId: 'test-id',
fieldsToUpdate: ['name'],
},
outputSchema: {},
},
} as WorkflowStep,
{
id: 'step-2b',
name: 'Second Step B',
type: 'SEND_EMAIL',
valid: true,
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
connectedAccountId: 'account-id',
email: 'test@example.com',
},
outputSchema: {},
},
} as WorkflowStep,
];
const resultA = getWorkflowPreviousStepId({
stepId: 'step-2a',
steps: branchingSteps,
});
const resultB = getWorkflowPreviousStepId({
stepId: 'step-2b',
steps: branchingSteps,
});
expect(resultA).toBe('step-1');
expect(resultB).toBe('step-1');
});
it('should handle workflow where step is not connected to previous steps', () => {
const disconnectedSteps: WorkflowStep[] = [
{
id: 'step-1',
name: 'First Step',
type: 'CREATE_RECORD',
valid: true,
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
objectName: 'Company',
objectRecord: {},
},
outputSchema: {},
},
} as WorkflowStep,
{
id: 'step-2',
name: 'Disconnected Step',
type: 'UPDATE_RECORD',
valid: true,
settings: {
errorHandlingOptions: {
continueOnFailure: { value: false },
retryOnFailure: { value: false },
},
input: {
objectName: 'Company',
objectRecord: {},
objectRecordId: 'test-id',
fieldsToUpdate: ['name'],
},
outputSchema: {},
},
} as WorkflowStep,
];
const result = getWorkflowPreviousStepId({
stepId: 'step-2',
steps: disconnectedSteps,
});
expect(result).toBeUndefined();
});
});
@@ -1,22 +0,0 @@
import { type WorkflowStep } from '@/workflow/types/Workflow';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
export const getWorkflowPreviousStepId = ({
stepId,
steps,
}: {
stepId: string;
steps: Array<WorkflowStep>;
}) => {
if (stepId === TRIGGER_STEP_ID) {
return undefined;
}
if (stepId === steps[0].id) {
return TRIGGER_STEP_ID;
}
const previousStep = steps.find((step) => step.nextStepIds?.includes(stepId));
return previousStep?.id;
};