Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Delete/Create WebhookScheduledTriggers for existing bookings #14121

88 changes: 80 additions & 8 deletions packages/features/webhooks/lib/scheduleTrigger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Prisma } from "@prisma/client";
import type { Prisma, Webhook } from "@prisma/client";
import { v4 } from "uuid";

import { getHumanReadableLocationValue } from "@calcom/core/location";
Expand All @@ -10,6 +10,11 @@ import prisma from "@calcom/prisma";
import type { ApiKey } from "@calcom/prisma/client";
import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums";

const SCHEDULING_TRIGGER: WebhookTriggerEvents[] = [
WebhookTriggerEvents.MEETING_ENDED,
WebhookTriggerEvents.MEETING_STARTED,
];

const log = logger.getSubLogger({ prefix: ["[node-scheduler]"] });

export async function addSubscription({
Expand Down Expand Up @@ -321,7 +326,8 @@ export async function scheduleTrigger(
export async function cancelScheduledJobs(
booking: { uid: string; scheduledJobs?: string[] },
appId?: string | null,
isReschedule?: boolean
isReschedule?: boolean,
triggerEvent?: WebhookTriggerEvents
) {
if (!booking.scheduledJobs) return;

Expand All @@ -338,12 +344,24 @@ export async function cancelScheduledJobs(
}
} else {
//if no specific appId given, delete all scheduled jobs of booking
await prisma.webhookScheduledTriggers.deleteMany({
where: {
jobName: scheduledJob,
},
});
scheduledJobs = [];
if (triggerEvent) {
const shouldContain = `"triggerEvent":"${triggerEvent}"`;
await prisma.webhookScheduledTriggers.deleteMany({
where: {
jobName: scheduledJob,
payload: {
contains: shouldContain,
},
},
});
} else {
await prisma.webhookScheduledTriggers.deleteMany({
where: {
jobName: scheduledJob,
},
});
scheduledJobs = [];
}
}

if (!isReschedule) {
Expand All @@ -364,3 +382,57 @@ export async function cancelScheduledJobs(
console.error("Error cancelling scheduled jobs", error);
}
}

export async function updateTriggerForExistingBookings(
webhook: Webhook,
existingEventTriggers: WebhookTriggerEvents[],
updatedEventTriggers: WebhookTriggerEvents[]
) {
const addedEventTriggers = updatedEventTriggers.filter(
(trigger) => !existingEventTriggers.includes(trigger) && SCHEDULING_TRIGGER.includes(trigger)
);
const removedEventTriggers = existingEventTriggers.filter(
(trigger) => !updatedEventTriggers.includes(trigger) && SCHEDULING_TRIGGER.includes(trigger)
);
const currentTime = new Date();
const bookings = await prisma.booking.findMany({
where: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's early return before that if there are no added or removed triggers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

eventTypeId: webhook.eventTypeId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can already query for only ACCEPTED bookings here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah right, slipped my mind 😅

OR: [{ startTime: { gt: currentTime } }, { endTime: { gt: currentTime } }],
},
});

if (bookings.length === 0) return;

if (addedEventTriggers.length > 0) {
const promise = bookings.flatMap((booking) => {
if (booking.status === BookingStatus.ACCEPTED) {
return addedEventTriggers.map((trigger) => {
scheduleTrigger(booking, webhook.subscriberUrl, webhook, trigger);
});
} else {
return [];
}
});

await Promise.all(promise);
}

if (
removedEventTriggers.length > 0 &&
removedEventTriggers.some((trigger) => SCHEDULING_TRIGGER.includes(trigger))
) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removedEventTriggers.some((trigger) => SCHEDULING_TRIGGER.includes(trigger))

Why do we need that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to remove it from here.

const promise = bookings.map((booking) => {
removedEventTriggers.map((trigger) =>
cancelScheduledJobs(
{ uid: booking.uid, scheduledJobs: booking.scheduledJobs },
undefined,
false,
trigger
)
);
});

await Promise.all(promise);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { v4 } from "uuid";

import { updateTriggerForExistingBookings } from "@calcom/features/webhooks/lib/scheduleTrigger";
import { prisma } from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";

Expand Down Expand Up @@ -28,12 +29,15 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
});
}
if (input.eventTypeId || input.teamId) {
return await prisma.webhook.create({
const webhook = await prisma.webhook.create({
data: {
id: v4(),
...input,
},
});

await updateTriggerForExistingBookings(webhook, [], webhook.eventTriggers);
return webhook;
}

return await prisma.webhook.create({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { updateTriggerForExistingBookings } from "@calcom/features/webhooks/lib/scheduleTrigger";
import type { Prisma } from "@prisma/client";

import { prisma } from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";

Expand Down Expand Up @@ -39,6 +39,8 @@ export const deleteHandler = async ({ ctx, input }: DeleteOptions) => {
id: webhookToDelete.id,
},
});

await updateTriggerForExistingBookings(webhookToDelete, webhookToDelete.eventTriggers, []);
}

return {
Expand Down
11 changes: 9 additions & 2 deletions packages/trpc/server/routers/viewer/webhook/edit.handler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { updateTriggerForExistingBookings } from "@calcom/features/webhooks/lib/scheduleTrigger";
import { prisma } from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";

Expand All @@ -24,18 +25,24 @@ export const editHandler = async ({ input, ctx }: EditOptions) => {
if (!webhook) {
return null;
}

if (webhook.platform) {
const { user } = ctx;
if (user?.role !== "ADMIN") {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}

return await prisma.webhook.update({
const updatedWebhook = await prisma.webhook.update({
where: {
id,
},
data,
});

if (!webhook.eventTypeId) return updatedWebhook;

await updateTriggerForExistingBookings(webhook, webhook.eventTriggers, updatedWebhook.eventTriggers);

return updatedWebhook;
};