Migrate update step changes to diff (#14663)
This PR should fix optimistic rendering issues on step updates: - compute a diff for trigger and steps on mutations - build an util that applies that diff (built a more robust version from https://github.com/AsyncBanana/micropatch) - apply diff in cache
This commit is contained in:
+183
-16
@@ -1,24 +1,191 @@
|
||||
import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import {
|
||||
type WorkflowAction,
|
||||
WorkflowActionType,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import {
|
||||
type WorkflowTrigger,
|
||||
WorkflowTriggerType,
|
||||
} from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
|
||||
describe('computeWorkflowVersionStepChanges', () => {
|
||||
it('should compute next step ids', () => {
|
||||
const input = {
|
||||
trigger: { nextStepIds: ['1', '2'] } as WorkflowTrigger,
|
||||
steps: [
|
||||
{ id: '1', nextStepIds: ['3'] },
|
||||
{ id: '2', nextStepIds: ['3'] },
|
||||
] as WorkflowAction[],
|
||||
deletedStepIds: ['5'],
|
||||
const mockTrigger: WorkflowTrigger = {
|
||||
name: 'Test Manual Trigger',
|
||||
type: WorkflowTriggerType.MANUAL,
|
||||
settings: {
|
||||
input: {},
|
||||
outputSchema: {},
|
||||
},
|
||||
nextStepIds: ['step-1'],
|
||||
};
|
||||
|
||||
const mockSteps: WorkflowAction[] = [
|
||||
{
|
||||
id: 'step-1',
|
||||
name: 'Form Step',
|
||||
type: WorkflowActionType.FORM,
|
||||
settings: {
|
||||
input: [],
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
nextStepIds: ['step-2'],
|
||||
},
|
||||
{
|
||||
id: 'step-2',
|
||||
name: 'Email Step',
|
||||
type: WorkflowActionType.SEND_EMAIL,
|
||||
settings: {
|
||||
input: {
|
||||
connectedAccountId: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
},
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
nextStepIds: [],
|
||||
},
|
||||
];
|
||||
|
||||
it('should compute trigger diff when trigger is updated', () => {
|
||||
const existingTrigger = mockTrigger;
|
||||
const updatedTrigger = {
|
||||
...mockTrigger,
|
||||
nextStepIds: ['step-1', 'step-3'],
|
||||
};
|
||||
|
||||
const expectedResult = {
|
||||
triggerNextStepIds: ['1', '2'],
|
||||
stepsNextStepIds: { '1': ['3'], '2': ['3'] },
|
||||
deletedStepIds: ['5'],
|
||||
};
|
||||
const result = computeWorkflowVersionStepChanges({
|
||||
existingTrigger,
|
||||
existingSteps: mockSteps,
|
||||
updatedTrigger,
|
||||
updatedSteps: mockSteps,
|
||||
});
|
||||
|
||||
expect(computeWorkflowVersionStepChanges(input)).toEqual(expectedResult);
|
||||
expect(result.triggerDiff).toMatchObject([
|
||||
{ path: ['trigger', 'nextStepIds', 1], type: 'CREATE', value: 'step-3' },
|
||||
]);
|
||||
expect(result.stepsDiff).toMatchObject([]);
|
||||
expect(result.stepsDiff.length).toBe(0); // No steps changed
|
||||
|
||||
// Verify the trigger diff contains the nextStepIds change
|
||||
const nextStepIdsDiff = result.triggerDiff.find((diff) =>
|
||||
diff.path.includes('nextStepIds'),
|
||||
);
|
||||
|
||||
expect(nextStepIdsDiff).toMatchObject({
|
||||
type: 'CREATE',
|
||||
path: ['trigger', 'nextStepIds', 1],
|
||||
value: 'step-3',
|
||||
});
|
||||
});
|
||||
|
||||
it('should compute steps diff when steps are updated', () => {
|
||||
const existingSteps = mockSteps;
|
||||
const updatedSteps = [
|
||||
...mockSteps,
|
||||
{
|
||||
id: 'step-3',
|
||||
name: 'Code Step',
|
||||
type: WorkflowActionType.CODE,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId: '',
|
||||
serverlessFunctionVersion: '',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
valid: true,
|
||||
nextStepIds: [],
|
||||
} as WorkflowAction,
|
||||
];
|
||||
|
||||
const result = computeWorkflowVersionStepChanges({
|
||||
existingTrigger: mockTrigger,
|
||||
existingSteps,
|
||||
updatedTrigger: mockTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
|
||||
expect(result.stepsDiff).toBeDefined();
|
||||
expect(result.stepsDiff.length).toBeGreaterThan(0);
|
||||
expect(result.triggerDiff).toBeDefined();
|
||||
expect(result.triggerDiff.length).toBe(0); // No trigger changed
|
||||
|
||||
// Verify the steps diff contains the new step
|
||||
const createDiff = result.stepsDiff.find((diff) => diff.type === 'CREATE');
|
||||
|
||||
expect(createDiff).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return empty diffs when no changes are made', () => {
|
||||
const result = computeWorkflowVersionStepChanges({
|
||||
existingTrigger: mockTrigger,
|
||||
existingSteps: mockSteps,
|
||||
updatedTrigger: mockTrigger,
|
||||
updatedSteps: mockSteps,
|
||||
});
|
||||
|
||||
expect(result.triggerDiff).toBeDefined();
|
||||
expect(result.triggerDiff.length).toBe(0);
|
||||
expect(result.stepsDiff).toBeDefined();
|
||||
expect(result.stepsDiff.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle null existing trigger and steps', () => {
|
||||
const updatedTrigger = mockTrigger;
|
||||
const updatedSteps = mockSteps;
|
||||
|
||||
const result = computeWorkflowVersionStepChanges({
|
||||
existingTrigger: null,
|
||||
existingSteps: null,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
|
||||
expect(result.triggerDiff).toBeDefined();
|
||||
expect(result.triggerDiff.length).toBeGreaterThan(0);
|
||||
expect(result.stepsDiff).toBeDefined();
|
||||
expect(result.stepsDiff.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify change diffs are present
|
||||
const triggerChangeDiff = result.triggerDiff.find(
|
||||
(diff) => diff.type === 'CHANGE',
|
||||
);
|
||||
|
||||
expect(triggerChangeDiff).toBeDefined();
|
||||
|
||||
const stepsChangeDiff = result.stepsDiff.find(
|
||||
(diff) => diff.type === 'CHANGE',
|
||||
);
|
||||
|
||||
expect(stepsChangeDiff).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return empty diffs when updated values are undefined', () => {
|
||||
const result = computeWorkflowVersionStepChanges({
|
||||
existingTrigger: mockTrigger,
|
||||
existingSteps: mockSteps,
|
||||
// updatedTrigger and updatedSteps are undefined
|
||||
});
|
||||
|
||||
expect(result.triggerDiff).toBeDefined();
|
||||
expect(result.triggerDiff.length).toBe(0);
|
||||
expect(result.stepsDiff).toBeDefined();
|
||||
expect(result.stepsDiff.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
+22
-18
@@ -1,24 +1,28 @@
|
||||
import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto';
|
||||
import { isDefined } from 'class-validator';
|
||||
import diff, { type Difference } from 'microdiff';
|
||||
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
|
||||
export const computeWorkflowVersionStepChanges = ({
|
||||
trigger,
|
||||
steps,
|
||||
createdStep,
|
||||
deletedStepIds,
|
||||
existingTrigger,
|
||||
existingSteps,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
}: {
|
||||
trigger: WorkflowTrigger | null;
|
||||
steps: WorkflowAction[] | null;
|
||||
createdStep?: WorkflowAction;
|
||||
deletedStepIds?: string[];
|
||||
}): WorkflowVersionStepChangesDTO => {
|
||||
return {
|
||||
triggerNextStepIds: trigger?.nextStepIds,
|
||||
stepsNextStepIds: Object.fromEntries(
|
||||
(steps || []).map((step) => [step.id, step.nextStepIds]),
|
||||
),
|
||||
createdStep,
|
||||
deletedStepIds,
|
||||
};
|
||||
existingTrigger: WorkflowTrigger | null;
|
||||
existingSteps: WorkflowAction[] | null;
|
||||
updatedTrigger?: WorkflowTrigger | null;
|
||||
updatedSteps?: WorkflowAction[] | null;
|
||||
}) => {
|
||||
const triggerDiff: Difference[] =
|
||||
updatedTrigger !== undefined
|
||||
? diff({ trigger: existingTrigger }, { trigger: updatedTrigger })
|
||||
: [];
|
||||
|
||||
const stepsDiff: Difference[] = isDefined(updatedSteps)
|
||||
? diff({ steps: existingSteps }, { steps: updatedSteps })
|
||||
: [];
|
||||
|
||||
return { triggerDiff, stepsDiff };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user