-
Notifications
You must be signed in to change notification settings - Fork 0
Comprehensive workflow reminder management for booking lifecycle events #7
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
base: workflow-queue-base
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,13 @@ | ||||||||||||||||||||||||||||||
| import type { App, Credential, EventTypeCustomInput, Prisma } from "@prisma/client"; | ||||||||||||||||||||||||||||||
| import { BookingStatus, SchedulingType, WebhookTriggerEvents } from "@prisma/client"; | ||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||
| App, | ||||||||||||||||||||||||||||||
| BookingStatus, | ||||||||||||||||||||||||||||||
| Credential, | ||||||||||||||||||||||||||||||
| EventTypeCustomInput, | ||||||||||||||||||||||||||||||
| Prisma, | ||||||||||||||||||||||||||||||
| SchedulingType, | ||||||||||||||||||||||||||||||
| WebhookTriggerEvents, | ||||||||||||||||||||||||||||||
| WorkflowMethods, | ||||||||||||||||||||||||||||||
| } from "@prisma/client"; | ||||||||||||||||||||||||||||||
| import async from "async"; | ||||||||||||||||||||||||||||||
| import { isValidPhoneNumber } from "libphonenumber-js"; | ||||||||||||||||||||||||||||||
| import { cloneDeep } from "lodash"; | ||||||||||||||||||||||||||||||
|
|
@@ -28,7 +36,9 @@ import { | |||||||||||||||||||||||||||||
| sendScheduledEmails, | ||||||||||||||||||||||||||||||
| sendScheduledSeatsEmails, | ||||||||||||||||||||||||||||||
| } from "@calcom/emails"; | ||||||||||||||||||||||||||||||
| import { deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; | ||||||||||||||||||||||||||||||
| import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; | ||||||||||||||||||||||||||||||
| import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; | ||||||||||||||||||||||||||||||
| import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; | ||||||||||||||||||||||||||||||
| import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib"; | ||||||||||||||||||||||||||||||
| import { getVideoCallUrl } from "@calcom/lib/CalEventParser"; | ||||||||||||||||||||||||||||||
|
|
@@ -759,6 +769,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { | |||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| payment: true, | ||||||||||||||||||||||||||||||
| workflowReminders: true, | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
@@ -950,6 +961,19 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { | |||||||||||||||||||||||||||||
| let videoCallUrl; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (originalRescheduledBooking?.uid) { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| // cancel workflow reminders from previous rescheduled booking | ||||||||||||||||||||||||||||||
| originalRescheduledBooking.workflowReminders.forEach((reminder) => { | ||||||||||||||||||||||||||||||
| if (reminder.method === WorkflowMethods.EMAIL) { | ||||||||||||||||||||||||||||||
| deleteScheduledEmailReminder(reminder.id, reminder.referenceId, true); | ||||||||||||||||||||||||||||||
| } else if (reminder.method === WorkflowMethods.SMS) { | ||||||||||||||||||||||||||||||
| deleteScheduledSMSReminder(reminder.id, reminder.referenceId); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
Comment on lines
+966
to
+972
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Async reminder deletion functions are called without Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||
| log.error("Error while canceling scheduled workflow reminders", error); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Use EventManager to conditionally use all needed integrations. | ||||||||||||||||||||||||||||||
| const updateManager = await eventManager.reschedule( | ||||||||||||||||||||||||||||||
| evt, | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import dayjs from "@calcom/dayjs"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { defaultHandler } from "@calcom/lib/server"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import prisma from "@calcom/prisma"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Prisma, WorkflowReminder } from "@calcom/prisma/client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import customTemplate, { VariablesType } from "../lib/reminders/templates/customTemplate"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -39,6 +40,42 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //cancel reminders for cancelled/rescheduled bookings that are scheduled within the next hour | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const remindersToCancel = await prisma.workflowReminder.findMany({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancelled: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Missing Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scheduledDate: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lte: dayjs().add(1, "hour").toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const workflowRemindersToDelete: Prisma.Prisma__WorkflowReminderClient<WorkflowReminder, never>[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const reminder of remindersToCancel) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.request({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: "/v3/user/scheduled_sends", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| batch_id: reminder.referenceId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "cancel", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const workflowReminderToDelete = prisma.workflowReminder.delete({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: reminder.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workflowRemindersToDelete.push(workflowReminderToDelete); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await Promise.all(workflowRemindersToDelete); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Error cancelling scheduled Emails: ${error}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+77
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: The Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //find all unscheduled Email reminders | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const unscheduledReminders = await prisma.workflowReminder.findMany({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -194,20 +194,41 @@ export const scheduleEmailReminder = async ( | |||||||||||||||
| } | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| export const deleteScheduledEmailReminder = async (referenceId: string) => { | ||||||||||||||||
| export const deleteScheduledEmailReminder = async ( | ||||||||||||||||
| reminderId: number, | ||||||||||||||||
| referenceId: string | null, | ||||||||||||||||
| immediateDelete?: boolean | ||||||||||||||||
| ) => { | ||||||||||||||||
| try { | ||||||||||||||||
| await client.request({ | ||||||||||||||||
| url: "/v3/user/scheduled_sends", | ||||||||||||||||
| method: "POST", | ||||||||||||||||
| body: { | ||||||||||||||||
| batch_id: referenceId, | ||||||||||||||||
| status: "cancel", | ||||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
| if (!referenceId) { | ||||||||||||||||
| await prisma.workflowReminder.delete({ | ||||||||||||||||
| where: { | ||||||||||||||||
| id: reminderId, | ||||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| return; | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: The Prompt for AI agents
Suggested change
|
||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| await client.request({ | ||||||||||||||||
| url: `/v3/user/scheduled_sends/${referenceId}`, | ||||||||||||||||
| method: "DELETE", | ||||||||||||||||
| if (immediateDelete) { | ||||||||||||||||
| await client.request({ | ||||||||||||||||
| url: "/v3/user/scheduled_sends", | ||||||||||||||||
| method: "POST", | ||||||||||||||||
| body: { | ||||||||||||||||
| batch_id: referenceId, | ||||||||||||||||
| status: "cancel", | ||||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| await prisma.workflowReminder.update({ | ||||||||||||||||
| where: { | ||||||||||||||||
| id: reminderId, | ||||||||||||||||
| }, | ||||||||||||||||
| data: { | ||||||||||||||||
| cancelled: true, | ||||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
| } catch (error) { | ||||||||||||||||
| console.log(`Error canceling reminder with error ${error}`); | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -174,9 +174,16 @@ export const scheduleSMSReminder = async ( | |||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export const deleteScheduledSMSReminder = async (referenceId: string) => { | ||||||||||||||||||||||||||||||||||||||||||
| export const deleteScheduledSMSReminder = async (reminderId: number, referenceId: string | null) => { | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| await twilio.cancelSMS(referenceId); | ||||||||||||||||||||||||||||||||||||||||||
| if (referenceId) { | ||||||||||||||||||||||||||||||||||||||||||
| await twilio.cancelSMS(referenceId); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| await prisma.workflowReminder.delete({ | ||||||||||||||||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||||||||||||||||
| id: reminderId, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+179
to
+186
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Bug: If Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
| console.log(`Error canceling reminder with error ${error}`); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -- AlterTable | ||
| ALTER TABLE "WorkflowReminder" ADD COLUMN "cancelled" BOOLEAN; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -641,6 +641,7 @@ model WorkflowReminder { | |
| scheduled Boolean | ||
| workflowStepId Int | ||
| workflowStep WorkflowStep @relation(fields: [workflowStepId], references: [id], onDelete: Cascade) | ||
| cancelled Boolean? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Consider using Prompt for AI agents |
||
| } | ||
|
|
||
| enum WorkflowTemplates { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: The
deleteScheduledEmailReminderanddeleteScheduledSMSRemindercalls are fire-and-forget here — their returned promises are neither awaited nor collected. These async functions perform DB writes internally (marking reminders ascancelled: trueor deleting them). The old code explicitly awaited the DB cleanup viaprismaPromises. Collect these promises and include them in thePromise.allcall to ensure reminder cancellation completes before the response is sent.Prompt for AI agents