fix: duplicate workflow reminders caused by scanWorkflowBody (#21767)

* don't scan if there is newer task

* create seperate tasks per workflowStepId

* revert changed constant

* fix comment

* add back missing import

* code clean up

* fix PR feedback

* change function to hasNewerScanTaskForStepId

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
Carina Wollendorfer
2025-06-16 14:25:41 +00:00
committed by GitHub
co-authored by CarinaWolli Anik Dhabal Babu
parent 09a49dd0fd
commit b69bd966a6
3 changed files with 63 additions and 12 deletions
+21
View File
@@ -3,6 +3,7 @@ import { type Prisma } from "@prisma/client";
import db from "@calcom/prisma";
import { type TaskTypes } from "./tasker";
import { scanWorkflowBodySchema } from "./tasks/scanWorkflowBody";
const whereSucceeded: Prisma.TaskWhereInput = {
succeededAt: { not: null },
@@ -187,4 +188,24 @@ export class Task {
// },
// });
}
static async hasNewerScanTaskForStepId(workflowStepId: number, createdAt: string) {
const tasks = await db.$queryRaw<{ payload: string }[]>`
SELECT "payload"
FROM "Task"
WHERE "type" = 'scanWorkflowBody'
AND "succeededAt" IS NULL
AND (payload::jsonb ->> 'workflowStepId')::int = ${workflowStepId}
`;
return tasks.some((task) => {
try {
const parsed = scanWorkflowBodySchema.parse(JSON.parse(task.payload));
if (!parsed.createdAt) return false;
return new Date(parsed.createdAt) > new Date(createdAt);
} catch {
return false;
}
});
}
}
@@ -2,6 +2,7 @@ import z from "zod";
import { getTemplateBodyForAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
import compareReminderBodyToTemplate from "@calcom/features/ee/workflows/lib/compareReminderBodyToTemplate";
import { Task } from "@calcom/features/tasker/repository";
import { lockUser, LockReason } from "@calcom/lib/autoLock";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server/i18n";
@@ -11,18 +12,39 @@ import { scheduleWorkflowNotifications } from "@calcom/trpc/server/routers/viewe
export const scanWorkflowBodySchema = z.object({
userId: z.number(),
workflowStepIds: z.array(z.number()),
// deprecated: use workflowStepId instead
workflowStepIds: z.array(z.number()).optional(),
workflowStepId: z.number().optional(),
createdAt: z.string().optional(),
});
const log = logger.getSubLogger({ prefix: ["[tasker] scanWorkflowBody"] });
export async function scanWorkflowBody(payload: string) {
const { workflowStepIds, userId } = scanWorkflowBodySchema.parse(JSON.parse(payload));
const { workflowStepIds, userId, createdAt, workflowStepId } = scanWorkflowBodySchema.parse(
JSON.parse(payload)
);
const stepIdsToScan: number[] = workflowStepIds ? workflowStepIds : [];
if (workflowStepId && !workflowStepIds) {
if (createdAt) {
const hasNewerTask = await Task.hasNewerScanTaskForStepId(workflowStepId, createdAt);
if (hasNewerTask) return;
stepIdsToScan.push(workflowStepId);
} else {
stepIdsToScan.push(workflowStepId);
}
}
if (stepIdsToScan.length === 0) return;
const workflowSteps = await prisma.workflowStep.findMany({
where: {
id: {
in: workflowStepIds,
in: stepIdsToScan,
},
},
include: {
@@ -116,7 +138,7 @@ export async function scanWorkflowBody(payload: string) {
await prisma.workflowStep.updateMany({
where: {
id: {
in: workflowStepIds,
in: stepIdsToScan,
},
},
data: {
@@ -130,7 +152,7 @@ export async function scanWorkflowBody(payload: string) {
steps: {
some: {
id: {
in: workflowStepIds,
in: stepIdsToScan,
},
},
},
@@ -142,7 +164,7 @@ export async function scanWorkflowBody(payload: string) {
});
if (!workflow) {
log.warn(`Workflow with steps ${workflowStepIds} not found`);
log.warn(`Workflow with steps ${stepIdsToScan} not found`);
return;
}
@@ -392,7 +392,11 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
});
if (SCANNING_WORKFLOW_STEPS && didBodyChange) {
await tasker.create("scanWorkflowBody", { workflowStepIds: [oldStep.id], userId: ctx.user.id });
await tasker.create("scanWorkflowBody", {
workflowStepId: oldStep.id,
userId: ctx.user.id,
createdAt: new Date().toISOString(),
});
} else {
// schedule notifications for edited steps
await scheduleWorkflowNotifications({
@@ -467,11 +471,15 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
);
if (SCANNING_WORKFLOW_STEPS) {
// workflows are scanned then scheduled in the task
await tasker.create("scanWorkflowBody", {
workflowStepIds: createdSteps.map((step) => step.id),
userId: ctx.user.id,
});
await Promise.all(
createdSteps.map((step) =>
tasker.create("scanWorkflowBody", {
workflowStepId: step.id,
userId: ctx.user.id,
createdAt: new Date().toISOString(),
})
)
);
} else {
// schedule notification for new step
await scheduleWorkflowNotifications({