fix: time and timeUnit partial update workflows api v2 (#26503)

* test: add e2e test for workflow partial update time/timeUnit preservation

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: time and timeUnit partial update workflows api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Morgan
2026-01-08 09:26:55 -03:00
committed by GitHub
co-authored by morgan@cal.com <morgan@cal.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent ac3699dcb4
commit dbc6b15ffc
2 changed files with 84 additions and 16 deletions
@@ -905,6 +905,60 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
.send(partialUpdateDto)
.expect(401);
});
it("should preserve time and timeUnit when not provided in partial update", async () => {
const workflowWithOffset = await request(app.getHttpServer())
.post(basePath)
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
.send({
name: `Workflow With Offset ${randomString()}`,
activation: {
isActiveOnAllEventTypes: true,
activeOnEventTypeIds: [],
},
trigger: {
type: BEFORE_EVENT,
offset: {
value: 2,
unit: DAY,
},
},
steps: [
{
stepNumber: 1,
action: "email_attendee",
recipient: ATTENDEE,
template: REMINDER,
sender: "CalcomE2ETest",
includeCalendarEvent: true,
message: {
subject: "Upcoming: {EVENT_NAME}",
html: "<p>Reminder for your event {EVENT_NAME}.</p>",
},
},
],
})
.expect(201);
const workflowId = workflowWithOffset.body.data.id;
expect(workflowWithOffset.body.data.trigger?.offset?.value).toEqual(2);
expect(workflowWithOffset.body.data.trigger?.offset?.unit).toEqual(DAY);
const partialUpdateDto = {
name: `Updated Workflow Name ${randomString()}`,
};
const updatedWorkflow = await request(app.getHttpServer())
.patch(`${basePath}/${workflowId}`)
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
.send(partialUpdateDto)
.expect(200);
expect(updatedWorkflow.body.data.trigger?.offset?.value).toEqual(2);
expect(updatedWorkflow.body.data.trigger?.offset?.unit).toEqual(DAY);
await workflowsRepositoryFixture.delete(workflowId);
});
});
describe(`DELETE ${basePath}/:workflowId`, () => {
@@ -129,27 +129,41 @@ export class WorkflowsInputService {
teamId: number,
workflowIdToUse: number
) {
const mappedSteps = updateDto?.steps
? await Promise.all(
updateDto.steps.map(async (stepDto: UpdateWorkflowStepDto, index: number) =>
this.mapUpdateWorkflowStepToZodUpdateSchema(stepDto, index, teamId, workflowIdToUse)
)
// 1. Map Steps
let mappedSteps;
if (updateDto?.steps) {
mappedSteps = await Promise.all(
updateDto.steps.map(async (stepDto: UpdateWorkflowStepDto, index: number) =>
this.mapUpdateWorkflowStepToZodUpdateSchema(stepDto, index, teamId, workflowIdToUse)
)
: currentData.steps.map((step) => ({ ...step, senderName: step.sender }));
);
} else {
mappedSteps = currentData.steps.map((step) => ({
...step,
senderName: step.sender,
}));
}
const triggerForZod = updateDto?.trigger?.type
? WORKFLOW_TRIGGER_TO_ENUM[updateDto?.trigger?.type]
: currentData.trigger;
// 2. Map Trigger
let triggerForZod = currentData.trigger;
if (updateDto?.trigger?.type) {
triggerForZod = WORKFLOW_TRIGGER_TO_ENUM[updateDto.trigger.type];
}
const timeUnitForZod = this._isOffsetTrigger(updateDto.trigger)
? updateDto?.trigger?.offset?.unit ?? currentData.timeUnit ?? null
: undefined;
// 3. Map Time and TimeUnit (Keeping currentData if trigger is missing or not an offset)
let timeUnitForZod = (currentData.timeUnit?.toLowerCase() ?? null) as "hour" | "minute" | "day" | null;
let time = currentData.time ?? null;
const time = this._isOffsetTrigger(updateDto.trigger)
? updateDto?.trigger?.offset?.value ?? currentData?.time ?? null
: null;
if (updateDto.trigger && this._isOffsetTrigger(updateDto.trigger)) {
timeUnitForZod = updateDto.trigger.offset?.unit ?? timeUnitForZod ?? null;
time = updateDto.trigger.offset?.value ?? currentData.time ?? null;
}
const timeUnit = timeUnitForZod ? TIME_UNIT_TO_ENUM[timeUnitForZod] : null;
// 4. Final Enum Conversion
let timeUnit = null;
if (timeUnitForZod) {
timeUnit = TIME_UNIT_TO_ENUM[timeUnitForZod];
}
return { mappedSteps, triggerForZod, time, timeUnit };
}