fix: no show webhook triggers not updating (#21345)
* fix: no show webhook triggers not updating * fix: add missing booking uid * refactor: fetch bookings from webhook * use allSettled --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
co-authored by
Udit Takkar
Anik Dhabal Babu
parent
b62eba8128
commit
9c3ee262b4
@@ -9,7 +9,10 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR
|
||||
import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import {
|
||||
deleteWebhookScheduledTriggers,
|
||||
cancelNoShowTasksForBooking,
|
||||
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
||||
import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import EventManager from "@calcom/lib/EventManager";
|
||||
@@ -517,6 +520,7 @@ async function handler(input: CancelBookingInput) {
|
||||
for (const booking of updatedBookings) {
|
||||
// delete scheduled webhook triggers of cancelled bookings
|
||||
webhookTriggerPromises.push(deleteWebhookScheduledTriggers({ booking }));
|
||||
webhookTriggerPromises.push(cancelNoShowTasksForBooking({ bookingUid: booking.uid }));
|
||||
|
||||
//Workflows - cancel all reminders for cancelled bookings
|
||||
workflowReminderPromises.push(WorkflowRepository.deleteAllWorkflowReminders(booking.workflowReminders));
|
||||
|
||||
@@ -42,6 +42,7 @@ export async function handleConfirmation(args: {
|
||||
booking: {
|
||||
startTime: Date;
|
||||
id: number;
|
||||
uid: string;
|
||||
eventType: {
|
||||
currency: string;
|
||||
description: string | null;
|
||||
@@ -444,6 +445,7 @@ export async function handleConfirmation(args: {
|
||||
startTime: booking.startTime,
|
||||
id: booking.id,
|
||||
location: booking.location,
|
||||
uid: booking.uid,
|
||||
},
|
||||
triggerForUser,
|
||||
organizerUser: { id: booking.userId },
|
||||
|
||||
@@ -44,6 +44,7 @@ import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebh
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import {
|
||||
deleteWebhookScheduledTriggers,
|
||||
cancelNoShowTasksForBooking,
|
||||
scheduleTrigger,
|
||||
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
@@ -2261,15 +2262,22 @@ async function handler(
|
||||
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
|
||||
const subscribersMeetingStarted = await getWebhooks(subscriberOptionsMeetingStarted);
|
||||
|
||||
let deleteWebhookScheduledTriggerPromise: Promise<unknown> = Promise.resolve();
|
||||
const deleteWebhookScheduledTriggerPromises: Promise<unknown>[] = [];
|
||||
const scheduleTriggerPromises = [];
|
||||
|
||||
if (rescheduleUid && originalRescheduledBooking) {
|
||||
//delete all scheduled triggers for meeting ended and meeting started of booking
|
||||
deleteWebhookScheduledTriggerPromise = deleteWebhookScheduledTriggers({
|
||||
booking: originalRescheduledBooking,
|
||||
isDryRun,
|
||||
});
|
||||
deleteWebhookScheduledTriggerPromises.push(
|
||||
deleteWebhookScheduledTriggers({
|
||||
booking: originalRescheduledBooking,
|
||||
isDryRun,
|
||||
})
|
||||
);
|
||||
deleteWebhookScheduledTriggerPromises.push(
|
||||
cancelNoShowTasksForBooking({
|
||||
bookingUid: originalRescheduledBooking.uid,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (booking && booking.status === BookingStatus.ACCEPTED) {
|
||||
@@ -2302,12 +2310,20 @@ async function handler(
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([deleteWebhookScheduledTriggerPromise, ...scheduleTriggerPromises]).catch((error) => {
|
||||
const scheduledTriggerResults = await Promise.allSettled([
|
||||
...deleteWebhookScheduledTriggerPromises,
|
||||
...scheduleTriggerPromises,
|
||||
]);
|
||||
const failures = scheduledTriggerResults.filter((result) => result.status === "rejected");
|
||||
|
||||
if (failures.length > 0) {
|
||||
loggerWithEventDetails.error(
|
||||
"Error while scheduling or canceling webhook triggers",
|
||||
JSON.stringify({ error })
|
||||
safeStringify({
|
||||
errors: failures.map((f) => f.reason),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Send Webhook call if hooked to BOOKING_CREATED & BOOKING_RESCHEDULED
|
||||
await handleWebhookTrigger({
|
||||
@@ -2410,7 +2426,12 @@ async function handler(
|
||||
try {
|
||||
if (isConfirmedByDefault) {
|
||||
await scheduleNoShowTriggers({
|
||||
booking: { startTime: booking.startTime, id: booking.id, location: booking.location },
|
||||
booking: {
|
||||
startTime: booking.startTime,
|
||||
id: booking.id,
|
||||
location: booking.location,
|
||||
uid: booking.uid,
|
||||
},
|
||||
triggerForUser,
|
||||
organizerUser: { id: organizerUser.id },
|
||||
eventTypeId,
|
||||
|
||||
@@ -10,6 +10,7 @@ type ScheduleNoShowTriggersArgs = {
|
||||
startTime: Date;
|
||||
id: number;
|
||||
location: string | null;
|
||||
uid: string;
|
||||
};
|
||||
triggerForUser?: number | true | null;
|
||||
organizerUser: { id: number | null };
|
||||
@@ -62,7 +63,7 @@ const _scheduleNoShowTriggers = async (args: ScheduleNoShowTriggersArgs) => {
|
||||
// Prevents null values from being serialized
|
||||
webhook: { ...webhook, time: webhook.time, timeUnit: webhook.timeUnit },
|
||||
},
|
||||
{ scheduledAt }
|
||||
{ scheduledAt, referenceUid: booking.uid }
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
@@ -93,7 +94,7 @@ const _scheduleNoShowTriggers = async (args: ScheduleNoShowTriggersArgs) => {
|
||||
// Prevents null values from being serialized
|
||||
webhook: { ...webhook, time: webhook.time, timeUnit: webhook.timeUnit },
|
||||
},
|
||||
{ scheduledAt }
|
||||
{ scheduledAt, referenceUid: booking.uid }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Prisma, Webhook, Booking } from "@prisma/client";
|
||||
import type { Booking, Prisma, Webhook } from "@prisma/client";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
import { selectOOOEntries } from "@calcom/app-store/zapier/api/subscriptions/listOOOEntries";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { getHumanReadableLocationValue } from "@calcom/lib/location";
|
||||
import tasker from "@calcom/features/tasker";
|
||||
import { DailyLocationType, getHumanReadableLocationValue } from "@calcom/lib/location";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
@@ -17,6 +19,11 @@ const SCHEDULING_TRIGGER: WebhookTriggerEvents[] = [
|
||||
WebhookTriggerEvents.MEETING_STARTED,
|
||||
];
|
||||
|
||||
const NO_SHOW_TRIGGERS: WebhookTriggerEvents[] = [
|
||||
WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
||||
WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
||||
];
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["[node-scheduler]"] });
|
||||
|
||||
export async function addSubscription({
|
||||
@@ -367,20 +374,9 @@ export const deleteWebhookScheduledTriggers = withReporting(
|
||||
"deleteWebhookScheduledTriggers"
|
||||
);
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
if (addedEventTriggers.length === 0 && removedEventTriggers.length === 0) return;
|
||||
|
||||
async function fetchBookingsFromWebhook(
|
||||
webhook: Pick<Webhook, "id" | "userId" | "teamId" | "eventTypeId">
|
||||
): Promise<Booking[]> {
|
||||
const currentTime = new Date();
|
||||
const where: Prisma.BookingWhereInput = {
|
||||
AND: [{ status: BookingStatus.ACCEPTED }],
|
||||
@@ -470,27 +466,71 @@ export async function updateTriggerForExistingBookings(
|
||||
}
|
||||
}
|
||||
|
||||
return bookings;
|
||||
}
|
||||
|
||||
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 addedNoShowTriggers = updatedEventTriggers.filter(
|
||||
(trigger) => !existingEventTriggers.includes(trigger) && NO_SHOW_TRIGGERS.includes(trigger)
|
||||
);
|
||||
const removedNoShowTriggers = existingEventTriggers.filter(
|
||||
(trigger) => !updatedEventTriggers.includes(trigger) && NO_SHOW_TRIGGERS.includes(trigger)
|
||||
);
|
||||
|
||||
if (
|
||||
addedEventTriggers.length === 0 &&
|
||||
removedEventTriggers.length === 0 &&
|
||||
addedNoShowTriggers.length === 0 &&
|
||||
removedNoShowTriggers.length === 0
|
||||
)
|
||||
return;
|
||||
|
||||
const bookings = await fetchBookingsFromWebhook(webhook);
|
||||
|
||||
if (bookings.length === 0) return;
|
||||
|
||||
if (addedEventTriggers.length > 0) {
|
||||
const promise = bookings.map((booking) => {
|
||||
return addedEventTriggers.map((triggerEvent) => {
|
||||
if (
|
||||
triggerEvent === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW ||
|
||||
triggerEvent === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW
|
||||
)
|
||||
return Promise.resolve();
|
||||
|
||||
scheduleTrigger({ booking, subscriberUrl: webhook.subscriberUrl, subscriber: webhook, triggerEvent });
|
||||
});
|
||||
if (addedEventTriggers.length > 0 || addedNoShowTriggers.length > 0 || removedNoShowTriggers.length > 0) {
|
||||
const allPromises = bookings.flatMap((booking) => {
|
||||
return [
|
||||
...addedEventTriggers.map(async (triggerEvent) => {
|
||||
if (NO_SHOW_TRIGGERS.includes(triggerEvent)) return;
|
||||
await scheduleTrigger({
|
||||
booking,
|
||||
subscriberUrl: webhook.subscriberUrl,
|
||||
subscriber: webhook,
|
||||
triggerEvent,
|
||||
});
|
||||
}),
|
||||
...addedNoShowTriggers.map(async (triggerEvent) => {
|
||||
await scheduleNoShowTaskForBooking(booking, webhook, triggerEvent);
|
||||
}),
|
||||
...removedNoShowTriggers.map((triggerEvent) =>
|
||||
cancelNoShowTasksForBooking({
|
||||
bookingUid: booking.uid,
|
||||
triggerEvent,
|
||||
})
|
||||
),
|
||||
];
|
||||
});
|
||||
|
||||
await Promise.all(promise);
|
||||
await Promise.all(allPromises);
|
||||
}
|
||||
|
||||
const promise = removedEventTriggers.map((triggerEvent) =>
|
||||
deleteWebhookScheduledTriggers({ triggerEvent, webhookId: webhook.id })
|
||||
);
|
||||
|
||||
await Promise.all(promise);
|
||||
}
|
||||
|
||||
@@ -546,3 +586,86 @@ export async function listOOOEntries(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelNoShowTasksForBooking({
|
||||
bookingUid,
|
||||
triggerEvent,
|
||||
webhook,
|
||||
}: {
|
||||
bookingUid?: string;
|
||||
triggerEvent?: WebhookTriggerEvents;
|
||||
webhook?: Pick<Webhook, "id" | "userId" | "teamId" | "eventTypeId">;
|
||||
}) {
|
||||
if (bookingUid) {
|
||||
if (triggerEvent && !NO_SHOW_TRIGGERS.includes(triggerEvent)) return;
|
||||
|
||||
if (triggerEvent === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW) {
|
||||
await tasker.cancelWithReference(bookingUid, "triggerHostNoShowWebhook");
|
||||
} else if (triggerEvent === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW) {
|
||||
await tasker.cancelWithReference(bookingUid, "triggerGuestNoShowWebhook");
|
||||
} else {
|
||||
await prisma.task.deleteMany({
|
||||
where: {
|
||||
referenceUid: bookingUid,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (webhook) {
|
||||
const bookings = await fetchBookingsFromWebhook(webhook);
|
||||
|
||||
if (bookings.length === 0) return;
|
||||
|
||||
const promises = bookings.map(async (booking) => {
|
||||
return await prisma.task.deleteMany({
|
||||
where: {
|
||||
referenceUid: booking.uid,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
}
|
||||
|
||||
export async function scheduleNoShowTaskForBooking(
|
||||
booking: { id: number; uid: string; startTime: Date; location: string | null },
|
||||
webhook: Webhook,
|
||||
triggerEvent: WebhookTriggerEvents
|
||||
) {
|
||||
if (!webhook.time || !webhook.timeUnit || !booking.startTime || !booking.location) return;
|
||||
|
||||
const isCalVideoLocation = booking.location === DailyLocationType || booking.location?.trim() === "";
|
||||
if (!isCalVideoLocation) return;
|
||||
|
||||
if (
|
||||
triggerEvent !== WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW &&
|
||||
triggerEvent !== WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
|
||||
)
|
||||
return;
|
||||
|
||||
const scheduledAt = dayjs(booking.startTime)
|
||||
.add(webhook.time ?? 0, webhook.timeUnit?.toLowerCase() as dayjs.ManipulateType)
|
||||
.toDate();
|
||||
|
||||
const taskType =
|
||||
triggerEvent === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW
|
||||
? "triggerHostNoShowWebhook"
|
||||
: "triggerGuestNoShowWebhook";
|
||||
|
||||
await tasker.create(
|
||||
taskType,
|
||||
{
|
||||
triggerEvent,
|
||||
bookingId: booking.id,
|
||||
webhook: {
|
||||
...webhook,
|
||||
time: webhook.time ?? 0,
|
||||
timeUnit: webhook.timeUnit ?? "HOUR",
|
||||
},
|
||||
},
|
||||
{
|
||||
scheduledAt,
|
||||
referenceUid: booking.uid,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ export const Handler = async ({ ctx, input }: Options) => {
|
||||
startTime: updatedBooking.startTime,
|
||||
id: updatedBooking.id,
|
||||
location: updatedBooking.location,
|
||||
uid: updatedBooking.uid,
|
||||
},
|
||||
triggerForUser: !eventType?.teamId || (eventType?.teamId && eventType?.parentId),
|
||||
organizerUser: { id: user.id },
|
||||
|
||||
@@ -6,7 +6,10 @@ import dayjs from "@calcom/dayjs";
|
||||
import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import {
|
||||
deleteWebhookScheduledTriggers,
|
||||
cancelNoShowTasksForBooking,
|
||||
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
||||
import { CalendarEventBuilder } from "@calcom/lib/builders/CalendarEvent/builder";
|
||||
import { CalendarEventDirector } from "@calcom/lib/builders/CalendarEvent/director";
|
||||
@@ -154,6 +157,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
|
||||
// delete scheduled jobs of previous booking
|
||||
const webhookPromises = [];
|
||||
webhookPromises.push(deleteWebhookScheduledTriggers({ booking: bookingToReschedule }));
|
||||
webhookPromises.push(cancelNoShowTasksForBooking({ bookingUid: bookingToReschedule.uid }));
|
||||
|
||||
await Promise.all(webhookPromises).catch((error) => {
|
||||
log.error("Error while deleting scheduled webhook triggers", JSON.stringify({ error }));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
updateTriggerForExistingBookings,
|
||||
deleteWebhookScheduledTriggers,
|
||||
cancelNoShowTasksForBooking,
|
||||
} from "@calcom/features/webhooks/lib/scheduleTrigger";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
@@ -51,6 +52,14 @@ export const editHandler = async ({ input, ctx }: EditOptions) => {
|
||||
const activeTriggersBefore = webhook.active ? webhook.eventTriggers : [];
|
||||
await updateTriggerForExistingBookings(webhook, activeTriggersBefore, updatedWebhook.eventTriggers);
|
||||
} else if (!data.active && webhook.active) {
|
||||
await cancelNoShowTasksForBooking({
|
||||
webhook: {
|
||||
id: webhook.id,
|
||||
userId: webhook.userId,
|
||||
teamId: webhook.teamId,
|
||||
eventTypeId: webhook.eventTypeId,
|
||||
},
|
||||
});
|
||||
await deleteWebhookScheduledTriggers({ webhookId: webhook.id });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user