-
Notifications
You must be signed in to change notification settings - Fork 0
Comprehensive workflow reminder management for booking lifecycle events #4
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); | ||
| } | ||
| }); | ||
| } catch (error) { | ||
| log.error("Error while canceling scheduled workflow reminders", error); | ||
| } | ||
|
Comment on lines
+964
to
+975
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. Async delete operations inside The 🔧 Suggested fix using Promise.all 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);
- }
- });
+ await Promise.all(
+ originalRescheduledBooking.workflowReminders.map(async (reminder) => {
+ if (reminder.method === WorkflowMethods.EMAIL) {
+ await deleteScheduledEmailReminder(reminder.id, reminder.referenceId, true);
+ } else if (reminder.method === WorkflowMethods.SMS) {
+ await deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
+ }
+ })
+ );
} catch (error) {
log.error("Error while canceling scheduled workflow reminders", error);
}🤖 Prompt for AI Agents |
||
|
|
||
| // 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, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scheduledDate: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lte: dayjs().add(1, "hour").toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+51
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. Missing filter for EMAIL method and referenceId in cancellation query. This query fetches all cancelled reminders regardless of method, but this handler is specifically for email reminders. Additionally, reminders without a 🔧 Suggested fix const remindersToCancel = await prisma.workflowReminder.findMany({
where: {
cancelled: true,
+ method: WorkflowMethods.EMAIL,
scheduledDate: {
lte: dayjs().add(1, "hour").toISOString(),
},
+ NOT: {
+ referenceId: null,
+ },
},
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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. Single API failure halts processing of remaining reminders. If the SendGrid cancel request fails for one reminder, the loop breaks and remaining cancelled reminders are not processed. Consider processing each reminder independently and logging individual failures. 🔧 Suggested fix using Promise.allSettled for resilience- 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,
- 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}`);
- }
+ const cancelResults = await Promise.allSettled(
+ remindersToCancel.map(async (reminder) => {
+ try {
+ await client.request({
+ url: "/v3/user/scheduled_sends",
+ method: "POST",
+ body: {
+ batch_id: reminder.referenceId,
+ status: "cancel",
+ },
+ });
+ await prisma.workflowReminder.delete({
+ where: {
+ id: reminder.id,
+ },
+ });
+ } catch (error) {
+ console.log(`Error cancelling scheduled Email reminder ${reminder.id}: ${error}`);
+ }
+ })
+ );📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //find all unscheduled Email reminders | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const unscheduledReminders = await prisma.workflowReminder.findMany({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -387,81 +387,75 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { | |
| }} | ||
| /> | ||
| </div> | ||
| {(isPhoneNumberNeeded || isSenderIdNeeded) && ( | ||
| {isPhoneNumberNeeded && ( | ||
| <div className="mt-2 rounded-md bg-gray-50 p-4 pt-0"> | ||
| {isPhoneNumberNeeded && ( | ||
| <Label className="pt-4">{t("custom_phone_number")}</Label> | ||
| <div className="block sm:flex"> | ||
| <PhoneInput<FormValues> | ||
| control={form.control} | ||
| name={`steps.${step.stepNumber - 1}.sendTo`} | ||
| placeholder={t("phone_number")} | ||
| id={`steps.${step.stepNumber - 1}.sendTo`} | ||
| className="min-w-fit sm:rounded-tl-md sm:rounded-bl-md sm:border-r-transparent" | ||
| required | ||
| onChange={() => { | ||
| const isAlreadyVerified = !!verifiedNumbers | ||
| ?.concat([]) | ||
| .find((number) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`)); | ||
| setNumberVerified(isAlreadyVerified); | ||
| }} | ||
| /> | ||
| <Button | ||
| color="secondary" | ||
| disabled={numberVerified || false} | ||
| className={classNames( | ||
| "-ml-[3px] h-[40px] min-w-fit sm:block sm:rounded-tl-none sm:rounded-bl-none ", | ||
| numberVerified ? "hidden" : "mt-3 sm:mt-0" | ||
| )} | ||
| onClick={() => | ||
| sendVerificationCodeMutation.mutate({ | ||
| phoneNumber: form.getValues(`steps.${step.stepNumber - 1}.sendTo`) || "", | ||
| }) | ||
| }> | ||
| {t("send_code")} | ||
| </Button> | ||
| </div> | ||
|
|
||
| {form.formState.errors.steps && | ||
| form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && ( | ||
| <p className="mt-1 text-xs text-red-500"> | ||
| {form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""} | ||
| </p> | ||
| )} | ||
| {numberVerified ? ( | ||
| <div className="mt-1"> | ||
| <Badge variant="green">{t("number_verified")}</Badge> | ||
| </div> | ||
| ) : ( | ||
| <> | ||
| <Label className="pt-4">{t("custom_phone_number")}</Label> | ||
| <div className="block sm:flex"> | ||
| <PhoneInput<FormValues> | ||
| control={form.control} | ||
| name={`steps.${step.stepNumber - 1}.sendTo`} | ||
| placeholder={t("phone_number")} | ||
| id={`steps.${step.stepNumber - 1}.sendTo`} | ||
| className="min-w-fit sm:rounded-tl-md sm:rounded-bl-md sm:border-r-transparent" | ||
| required | ||
| onChange={() => { | ||
| const isAlreadyVerified = !!verifiedNumbers | ||
| ?.concat([]) | ||
| .find( | ||
| (number) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`) | ||
| ); | ||
| setNumberVerified(isAlreadyVerified); | ||
| <div className="mt-3 flex"> | ||
| <TextField | ||
| className=" border-r-transparent" | ||
| placeholder="Verification code" | ||
| value={verificationCode} | ||
| onChange={(e) => { | ||
| setVerificationCode(e.target.value); | ||
| }} | ||
| required | ||
| /> | ||
| <Button | ||
| color="secondary" | ||
| disabled={numberVerified || false} | ||
| className={classNames( | ||
| "-ml-[3px] h-[40px] min-w-fit sm:block sm:rounded-tl-none sm:rounded-bl-none ", | ||
| numberVerified ? "hidden" : "mt-3 sm:mt-0" | ||
| )} | ||
| onClick={() => | ||
| sendVerificationCodeMutation.mutate({ | ||
| className="-ml-[3px] rounded-tl-none rounded-bl-none " | ||
| disabled={verifyPhoneNumberMutation.isLoading} | ||
| onClick={() => { | ||
| verifyPhoneNumberMutation.mutate({ | ||
| phoneNumber: form.getValues(`steps.${step.stepNumber - 1}.sendTo`) || "", | ||
| }) | ||
| }> | ||
| {t("send_code")} | ||
| code: verificationCode, | ||
| }); | ||
| }}> | ||
| Verify | ||
| </Button> | ||
| </div> | ||
|
|
||
| {form.formState.errors.steps && | ||
| form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && ( | ||
| <p className="mt-1 text-xs text-red-500"> | ||
| {form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""} | ||
| </p> | ||
| )} | ||
| {numberVerified ? ( | ||
| <div className="mt-1"> | ||
| <Badge variant="green">{t("number_verified")}</Badge> | ||
| </div> | ||
| ) : ( | ||
| <> | ||
| <div className="mt-3 flex"> | ||
| <TextField | ||
| className=" border-r-transparent" | ||
| placeholder="Verification code" | ||
| value={verificationCode} | ||
| onChange={(e) => { | ||
| setVerificationCode(e.target.value); | ||
| }} | ||
| required | ||
| /> | ||
| <Button | ||
| color="secondary" | ||
| className="-ml-[3px] rounded-tl-none rounded-bl-none " | ||
| disabled={verifyPhoneNumberMutation.isLoading} | ||
| onClick={() => { | ||
| verifyPhoneNumberMutation.mutate({ | ||
| phoneNumber: form.getValues(`steps.${step.stepNumber - 1}.sendTo`) || "", | ||
| code: verificationCode, | ||
| }); | ||
| }}> | ||
| Verify | ||
| </Button> | ||
| </div> | ||
| </> | ||
| )} | ||
| </> | ||
| )} | ||
|
Comment on lines
435
to
460
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. Fix internationalization and improve UX for verification input. The verification code input flow works correctly, but has the following issues:
Proposed fixes <TextField
- className=" border-r-transparent"
+ className="border-r-transparent"
- placeholder="Verification code"
+ placeholder={t("verification_code")}
value={verificationCode}
onChange={(e) => {
setVerificationCode(e.target.value);
}}
required
/>
<Button
color="secondary"
className="-ml-[3px] rounded-tl-none rounded-bl-none "
- disabled={verifyPhoneNumberMutation.isLoading}
+ disabled={verifyPhoneNumberMutation.isLoading || !verificationCode.trim()}
onClick={() => {
verifyPhoneNumberMutation.mutate({
phoneNumber: form.getValues(`steps.${step.stepNumber - 1}.sendTo`) || "",
code: verificationCode,
});
}}>
- Verify
+ {t("verify")}
</Button>Note: Ensure that the translation keys
🤖 Prompt for AI Agents |
||
| </div> | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
Comment on lines
+213
to
+223
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.
When 🔧 Suggested fix to also delete from DB if (immediateDelete) {
await client.request({
url: "/v3/user/scheduled_sends",
method: "POST",
body: {
batch_id: referenceId,
status: "cancel",
},
});
+ await prisma.workflowReminder.delete({
+ where: {
+ id: reminderId,
+ },
+ });
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 |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -- AlterTable | ||
| ALTER TABLE "WorkflowReminder" ADD COLUMN "cancelled" BOOLEAN; |
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.
Missing await on reminder cancellation calls.
The calls to
deleteScheduledEmailReminderanddeleteScheduledSMSReminder(lines 488, 490) are not awaited, which means errors could be silently ignored and the deletion may not complete before proceeding. This could result in reminders not being cancelled if the process terminates early.🔧 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents