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:
+4
-11
@@ -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';
|
||||
|
||||
-239
@@ -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();
|
||||
});
|
||||
});
|
||||
-22
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user