fix: Workflow action 'email to host' only sends to one host of team e… (#14767)

* fix: Workflow action 'email to host' only sends to one host of team event

* add in missing files

* fix host logic

* uncomment working test

* code refactor

* use connected calendar email

* add changes to util file

* fix: extend eventType info in sendCancelledReminders

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
Amit Sharma
2024-08-22 08:04:31 +00:00
committed by GitHub
co-authored by Udit Takkar
parent 30c31d3585
commit 66cb2bcc13
10 changed files with 184 additions and 12 deletions
@@ -107,6 +107,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine
bookingFields: true,
seatsShowAttendees: true,
metadata: true,
schedulingType: true,
hosts: {
select: {
user: true,
@@ -357,7 +358,13 @@ async function handler(req: CustomRequest) {
smsReminderNumber: bookingToDelete.smsReminderNumber,
evt: {
...evt,
...{ eventType: { slug: bookingToDelete.eventType?.slug } },
...{
eventType: {
slug: bookingToDelete.eventType?.slug,
schedulingType: bookingToDelete.eventType?.schedulingType,
hosts: bookingToDelete.eventType?.hosts,
},
},
},
hideBranding: !!bookingToDelete.eventType?.owner?.hideBranding,
});
@@ -20,6 +20,7 @@ import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import type { PrismaClient } from "@calcom/prisma";
import type { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util";
@@ -135,6 +136,15 @@ export async function handleConfirmation(args: {
eventType: {
bookingFields: Prisma.JsonValue | null;
slug: string;
schedulingType: SchedulingType | null;
hosts: {
user: {
email: string;
destinationCalendar?: {
primaryEmail: string | null;
} | null;
};
}[];
owner: {
hideBranding?: boolean | null;
} | null;
@@ -175,6 +185,21 @@ export async function handleConfirmation(args: {
select: {
slug: true,
bookingFields: true,
schedulingType: true,
hosts: {
select: {
user: {
select: {
email: true,
destinationCalendar: {
select: {
primaryEmail: true,
},
},
},
},
},
},
owner: {
select: {
hideBranding: true,
@@ -220,11 +245,26 @@ export async function handleConfirmation(args: {
select: {
slug: true,
bookingFields: true,
schedulingType: true,
owner: {
select: {
hideBranding: true,
},
},
hosts: {
select: {
user: {
select: {
email: true,
destinationCalendar: {
select: {
primaryEmail: true,
},
},
},
},
},
},
},
},
uid: true,
@@ -249,7 +289,11 @@ export async function handleConfirmation(args: {
const evtOfBooking = {
...evt,
metadata: { videoCallUrl: meetingUrl },
eventType: { slug: eventTypeSlug },
eventType: {
slug: eventTypeSlug,
schedulingType: updatedBookings[index].eventType?.schedulingType,
hosts: updatedBookings[index].eventType?.hosts,
},
};
evtOfBooking.startTime = updatedBookings[index].startTime.toISOString();
evtOfBooking.endTime = updatedBookings[index].endTime.toISOString();
@@ -1741,7 +1741,11 @@ async function handler(
loggerWithEventDetails.error("Error while creating booking references", JSON.stringify({ error }));
}
const evtWithMetadata = { ...evt, metadata, eventType: { slug: eventType.slug } };
const evtWithMetadata = {
...evt,
metadata,
eventType: { slug: eventType.slug, schedulingType: eventType.schedulingType, hosts: eventType.hosts },
};
if (!eventType.metadata?.disableStandardEmails?.all?.attendee) {
await scheduleMandatoryReminder(
@@ -392,8 +392,7 @@ describe("handleNewBooking", () => {
});
expectWorkflowToBeTriggered({
// emailsToReceive: [organizer.email].concat(otherTeamMembers.map(member => member.email)),
emailsToReceive: [organizer.email],
emailsToReceive: [organizer.email].concat(otherTeamMembers.map((member) => member.email)),
emails,
});
},
@@ -104,7 +104,17 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => {
await scheduleWorkflowReminders({
workflows,
smsReminderNumber: smsReminderNumber || null,
calendarEvent: { ...evt, ...{ metadata, eventType: { slug: eventType.slug } } },
calendarEvent: {
...evt,
...{
metadata,
eventType: {
slug: eventType.slug,
schedulingType: eventType.schedulingType,
hosts: eventType.hosts,
},
},
},
isNotConfirmed: evt.requiresConfirmation || false,
isRescheduleEvent: !!rescheduleUid,
emailAttendeeSendToOverride: bookerEmail,
@@ -9,7 +9,7 @@ import logger from "@calcom/lib/logger";
import { defaultHandler } from "@calcom/lib/server";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
import { SchedulingType, WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import type { PartialWorkflowReminder } from "../lib/getWorkflowReminders";
@@ -119,6 +119,19 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
switch (reminder.workflowStep.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = reminder.booking?.userPrimaryEmail ?? reminder.booking.user?.email;
const hosts = reminder?.booking?.eventType?.hosts
?.filter((host) =>
reminder.booking?.attendees.some((attendee) => attendee.email === host.user.email)
)
.map(({ user }) => user.destinationCalendar?.primaryEmail ?? user.email);
const schedulingType = reminder.booking.eventType?.schedulingType;
if (
hosts &&
(schedulingType === SchedulingType.COLLECTIVE || schedulingType === SchedulingType.ROUND_ROBIN)
) {
sendTo = sendTo ? [sendTo, ...hosts] : hosts;
}
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = reminder.booking.attendees[0].email;
@@ -27,7 +27,14 @@ type PartialBooking =
| "attendees"
| "userPrimaryEmail"
| "smsReminderNumber"
> & { eventType: (Partial<EventType> & { team: { parentId?: number } }) | null } & {
> & {
eventType:
| (Partial<EventType> & {
team: { parentId?: number };
hosts: { user: { email: string; destinationCalendar?: { primaryEmail: string } } }[] | undefined;
})
| null;
} & {
user: Partial<User> | null;
})
| null;
@@ -159,6 +166,20 @@ export const select: Prisma.WorkflowReminderSelect = {
bookingFields: true,
title: true,
slug: true,
hosts: {
select: {
user: {
select: {
email: true,
destinationCalendar: {
select: {
primaryEmail: true,
},
},
},
},
},
},
recurringEvent: true,
team: {
select: {
@@ -6,7 +6,7 @@ import {
import type { Workflow, WorkflowStep } from "@calcom/features/ee/workflows/lib/types";
import { checkSMSRateLimit } from "@calcom/lib/checkRateLimitAndThrowError";
import { SENDER_NAME } from "@calcom/lib/constants";
import { WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums";
import { SchedulingType, WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { scheduleEmailReminder } from "./emailReminderManager";
@@ -16,7 +16,11 @@ import { scheduleWhatsappReminder } from "./whatsappReminderManager";
export type ExtendedCalendarEvent = CalendarEvent & {
metadata?: { videoCallUrl: string | undefined };
eventType: { slug?: string };
eventType: {
slug?: string;
schedulingType?: SchedulingType | null;
hosts?: { user: { email: string; destinationCalendar?: { primaryEmail: string | null } | null } }[];
};
};
type ProcessWorkflowStepParams = {
@@ -85,6 +89,13 @@ const processWorkflowStep = async (
break;
case WorkflowActions.EMAIL_HOST:
sendTo = [evt.organizer?.email || ""];
const schedulingType = evt.eventType.schedulingType;
const isTeamEvent =
schedulingType === SchedulingType.ROUND_ROBIN || schedulingType === SchedulingType.COLLECTIVE;
if (isTeamEvent && evt.team?.members) {
sendTo = sendTo.concat(evt.team.members.map((member) => member.email));
}
break;
case WorkflowActions.EMAIL_ATTENDEE:
const attendees = !!emailAttendeeSendToOverride
@@ -4,7 +4,7 @@ import { scheduleWhatsappReminder } from "@calcom/features/ee/workflows/lib/remi
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/client";
import { MembershipRole, WorkflowActions } from "@calcom/prisma/enums";
import { MembershipRole, SchedulingType, WorkflowActions } from "@calcom/prisma/enums";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@@ -200,7 +200,26 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
},
include: {
attendees: true,
eventType: true,
eventType: {
select: {
schedulingType: true,
slug: true,
hosts: {
select: {
user: {
select: {
email: true,
destinationCalendar: {
select: {
primaryEmail: true,
},
},
},
},
},
},
},
},
user: true,
},
});
@@ -232,6 +251,8 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
language: { locale: booking?.user?.locale || defaultLocale },
eventType: {
slug: booking.eventType?.slug,
schedulingType: booking.eventType?.schedulingType,
hosts: booking.eventType?.hosts,
},
};
for (const step of eventTypeWorkflow.steps) {
@@ -245,6 +266,19 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = [bookingInfo.organizer?.email];
const schedulingType = bookingInfo.eventType?.schedulingType;
const hosts = bookingInfo.eventType.hosts
?.filter((host) =>
bookingInfo.attendees.some((attendee) => host.user.email === attendee.email)
)
.map(({ user }) => user.destinationCalendar?.primaryEmail ?? user.email);
if (
(schedulingType === SchedulingType.ROUND_ROBIN ||
schedulingType === SchedulingType.COLLECTIVE) &&
hosts
) {
sendTo = sendTo.concat(hosts);
}
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
@@ -31,6 +31,7 @@ import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import type { Prisma, WorkflowStep } from "@calcom/prisma/client";
import type { TimeUnit } from "@calcom/prisma/enums";
import { SchedulingType } from "@calcom/prisma/enums";
import {
BookingStatus,
MembershipRole,
@@ -64,6 +65,21 @@ export const bookingSelect = {
select: {
slug: true,
id: true,
schedulingType: true,
hosts: {
select: {
user: {
select: {
email: true,
destinationCalendar: {
select: {
primaryEmail: true,
},
},
},
},
},
},
},
},
user: {
@@ -732,6 +748,8 @@ export async function scheduleBookingReminders(
language: { locale: booking?.user?.locale || defaultLocale },
eventType: {
slug: booking.eventType?.slug,
schedulingType: booking.eventType?.schedulingType,
hosts: booking.eventType?.hosts,
},
};
if (
@@ -744,6 +762,16 @@ export async function scheduleBookingReminders(
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = [bookingInfo.organizer?.email];
const schedulingType = bookingInfo.eventType.schedulingType;
const hosts = bookingInfo.eventType.hosts
?.filter((host) => bookingInfo.attendees.some((attendee) => attendee.email === host.user.email))
.map(({ user }) => user.destinationCalendar?.primaryEmail ?? user.email);
if (
hosts &&
(schedulingType === SchedulingType.ROUND_ROBIN || schedulingType === SchedulingType.COLLECTIVE)
) {
sendTo = sendTo.concat(hosts);
}
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
@@ -751,6 +779,7 @@ export async function scheduleBookingReminders(
case WorkflowActions.EMAIL_ADDRESS:
await verifyEmailSender(step.sendTo || "", userId, teamId);
sendTo = [step.sendTo || ""];
break;
}
await scheduleEmailReminder({
evt: bookingInfo,