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

Fix - add team members to emails #7207

Merged
merged 26 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
98cd23b
On booking add team members & translation
joeauyeung Feb 18, 2023
d128c3e
Add team members to round robin create
joeauyeung Feb 18, 2023
d05fe6f
Only update calendars on reschedule if there is a calendar reference
joeauyeung Feb 18, 2023
cbdda75
Send email on reschedules
joeauyeung Feb 18, 2023
3dcb5dd
Send team email on cancelled event
joeauyeung Feb 19, 2023
5e9fda7
Add team members to calendar event description
joeauyeung Feb 19, 2023
ff93467
Clean up
joeauyeung Feb 19, 2023
24becee
Convert other emails to organizer & teams
joeauyeung Feb 19, 2023
b4c7145
Type check fixes
joeauyeung Feb 19, 2023
94a6cae
More type fixes
joeauyeung Feb 19, 2023
8f3f17b
Change organizer scheduled input to an object
joeauyeung Feb 21, 2023
5a9f20b
early return updateCalendarEvent
joeauyeung Feb 21, 2023
5288fec
Introduce team member type
joeauyeung Feb 21, 2023
9035b05
Merge branch 'main' into teams-include-team-members-in-email
joeauyeung Feb 21, 2023
97d7842
Fix type errors
joeauyeung Feb 21, 2023
b4fb91f
Merge branch 'main' into teams-include-team-members-in-email
zomars Feb 22, 2023
25a3d97
Put team members before attendees
joeauyeung Feb 21, 2023
c4c629c
Remove lodash cloneDeep
joeauyeung Feb 21, 2023
7936b3a
Merge branch 'main' into teams-include-team-members-in-email
joeauyeung Feb 22, 2023
e6dfa35
Update packages/core/EventManager.ts
joeauyeung Feb 22, 2023
9f121ff
Remove booking select object
joeauyeung Feb 22, 2023
fe94312
Revert "Remove booking select object"
joeauyeung Feb 22, 2023
8ee8d7a
Refactor email manager (#7270)
joeauyeung Feb 23, 2023
e17f69e
Type change
joeauyeung Feb 24, 2023
3ecbebd
Remove conditional check for updateAllCalendarEvents
joeauyeung Feb 26, 2023
9f463dc
Merge branch 'main' into teams-include-team-members-in-email
zomars Feb 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
On booking add team members & translation
  • Loading branch information
joeauyeung committed Feb 18, 2023
commit 98cd23b1edc90431da269616d5edada6698bc4cf
3 changes: 2 additions & 1 deletion apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1599,5 +1599,6 @@
"booking_with_payment_cancelled_already_paid": "A refund for this booking payment it's on the way.",
"booking_with_payment_cancelled_refunded": "This booking payment has been refunded.",
"booking_confirmation_failed": "Booking confirmation failed",
"get_started_zapier_templates": "Get started with Zapier templates"
"get_started_zapier_templates": "Get started with Zapier templates",
"team_member": "Team member"
}
30 changes: 25 additions & 5 deletions packages/emails/email-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TFunction } from "next-i18next";
import { cloneDeep } from "lodash";
import type { TFunction } from "next-i18next";

import type { CalendarEvent, Person } from "@calcom/types/Calendar";

Expand All @@ -12,8 +13,10 @@ import AttendeeScheduledEmail from "./templates/attendee-scheduled-email";
import AttendeeWasRequestedToRescheduleEmail from "./templates/attendee-was-requested-to-reschedule-email";
import BrokenIntegrationEmail from "./templates/broken-integration-email";
import DisabledAppEmail from "./templates/disabled-app-email";
import FeedbackEmail, { Feedback } from "./templates/feedback-email";
import ForgotPasswordEmail, { PasswordReset } from "./templates/forgot-password-email";
import type { Feedback } from "./templates/feedback-email";
import FeedbackEmail from "./templates/feedback-email";
import type { PasswordReset } from "./templates/forgot-password-email";
import ForgotPasswordEmail from "./templates/forgot-password-email";
import OrganizerCancelledEmail from "./templates/organizer-cancelled-email";
import OrganizerLocationChangeEmail from "./templates/organizer-location-change-email";
import OrganizerPaymentRefundFailedEmail from "./templates/organizer-payment-refund-failed-email";
Expand All @@ -22,10 +25,12 @@ import OrganizerRequestReminderEmail from "./templates/organizer-request-reminde
import OrganizerRequestedToRescheduleEmail from "./templates/organizer-requested-to-reschedule-email";
import OrganizerRescheduledEmail from "./templates/organizer-rescheduled-email";
import OrganizerScheduledEmail from "./templates/organizer-scheduled-email";
import TeamInviteEmail, { TeamInvite } from "./templates/team-invite-email";
import type { TeamInvite } from "./templates/team-invite-email";
import TeamInviteEmail from "./templates/team-invite-email";

export const sendScheduledEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise<unknown>[] = [];
const clonedEvent = cloneDeep(calEvent);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we need to alter the attendee emails to hide attendees if needed. Let's clone the event to pass to the organizer emails

Copy link
Member

Choose a reason for hiding this comment

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

Why are we cloning it? Asking because deepClone might be a bit slow plus require lodash.

Copy link
Contributor Author

@joeauyeung joeauyeung Feb 21, 2023

Choose a reason for hiding this comment

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

With the attendee emails, we sometimes have to filter calEvent.attendees when it is a seats booking and the organizer wants to hide other attendees from each other.


emailsToSend.push(
...calEvent.attendees.map((attendee) => {
Expand All @@ -43,14 +48,29 @@ export const sendScheduledEmails = async (calEvent: CalendarEvent) => {
emailsToSend.push(
new Promise((resolve, reject) => {
try {
const scheduledEmail = new OrganizerScheduledEmail(calEvent);
const scheduledEmail = new OrganizerScheduledEmail(clonedEvent);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerScheduledEmail.sendEmail failed", e));
}
})
);

if (clonedEvent.team) {
for (const teamMember of clonedEvent.team.members) {
emailsToSend.push(
new Promise((resolve, reject) => {
try {
const scheduledEmail = new OrganizerScheduledEmail(clonedEvent, undefined, teamMember);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

By passing a team member, we can extract their locale and translation for the email

resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerScheduledEmail.sendEmail failed", e));
}
})
);
}
hariombalhara marked this conversation as resolved.
Show resolved Hide resolved
}

await Promise.all(emailsToSend);
};

Expand Down
10 changes: 9 additions & 1 deletion packages/emails/src/components/WhoInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TFunction } from "next-i18next";
import type { TFunction } from "next-i18next";

import type { CalendarEvent } from "@calcom/types/Calendar";

Expand Down Expand Up @@ -35,6 +35,14 @@ export function WhoInfo(props: { calEvent: CalendarEvent; t: TFunction }) {
email={attendee.email}
/>
))}
{props.calEvent.team?.members.map((member) => (
joeauyeung marked this conversation as resolved.
Show resolved Hide resolved
<PersonInfo
key={member.id || member.name}
name={member.name}
role={t("team_member")}
email={member.email}
/>
))}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there are team members, loop through and display in the email

</>
}
withSpacer
Expand Down
3 changes: 2 additions & 1 deletion packages/emails/src/templates/OrganizerScheduledEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const OrganizerScheduledEmail = (
calEvent: CalendarEvent;
attendee: Person;
newSeat?: boolean;
teamMember?: Person;
} & Partial<React.ComponentProps<typeof BaseScheduledEmail>>
) => {
let subject;
Expand All @@ -26,7 +27,7 @@ export const OrganizerScheduledEmail = (
title = "new_event_scheduled";
}

const t = props.calEvent.organizer.language.translate;
const t = props.teamMember?.language.translate || props.calEvent.organizer.language.translate;
return (
<BaseScheduledEmail
timeZone={props.calEvent.organizer.timeZone}
Expand Down
28 changes: 16 additions & 12 deletions packages/emails/templates/organizer-scheduled-email.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createEvent, DateArray, Person } from "ics";
import { TFunction } from "next-i18next";
import type { DateArray, Person } from "ics";
import { createEvent } from "ics";
import type { TFunction } from "next-i18next";
import { RRule } from "rrule";

import dayjs from "@calcom/dayjs";
Expand All @@ -14,13 +15,15 @@ export default class OrganizerScheduledEmail extends BaseEmail {
calEvent: CalendarEvent;
t: TFunction;
newSeat?: boolean;
teamMember?: Person;

constructor(calEvent: CalendarEvent, newSeat?: boolean) {
constructor(calEvent: CalendarEvent, newSeat?: boolean, teamMember?: Person) {
joeauyeung marked this conversation as resolved.
Show resolved Hide resolved
super();
this.name = "SEND_BOOKING_CONFIRMATION";
this.calEvent = calEvent;
this.t = this.calEvent.organizer.language.translate;
this.newSeat = newSeat;
this.teamMember = teamMember;
}

protected getiCalEventAsString(): string | undefined {
Expand Down Expand Up @@ -56,15 +59,15 @@ export default class OrganizerScheduledEmail extends BaseEmail {
}

protected getNodeMailerPayload(): Record<string, unknown> {
const toAddresses = [this.calEvent.organizer.email];
if (this.calEvent.team) {
this.calEvent.team.members.forEach((member) => {
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
if (memberAttendee) {
toAddresses.push(memberAttendee.email);
}
});
}
const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email];
// if (this.calEvent.team) {
// this.calEvent.team.members.forEach((member) => {
// const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
// if (memberAttendee) {
// toAddresses.push(memberAttendee.email);
// }
// });
// }

return {
icalEvent: {
Expand All @@ -79,6 +82,7 @@ export default class OrganizerScheduledEmail extends BaseEmail {
html: renderEmail("OrganizerScheduledEmail", {
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
teamMember: this.teamMember,
newSeat: this.newSeat,
}),
text: this.getTextBody(),
Expand Down
30 changes: 14 additions & 16 deletions packages/features/bookings/lib/handleNewBooking.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
App,
BookingStatus,
Credential,
EventTypeCustomInput,
Prisma,
SchedulingType,
WebhookTriggerEvents,
} from "@prisma/client";
import type { App, Credential, EventTypeCustomInput, Prisma } from "@prisma/client";
import { BookingStatus, SchedulingType, WebhookTriggerEvents } from "@prisma/client";
import async from "async";
import { isValidPhoneNumber } from "libphonenumber-js";
import { cloneDeep } from "lodash";
Expand All @@ -16,15 +9,18 @@ import { v5 as uuidv5 } from "uuid";
import z from "zod";

import { metadata as GoogleMeetMetadata } from "@calcom/app-store/googlevideo/_metadata";
import { getLocationValueForDB, LocationObject } from "@calcom/app-store/locations";
import type { LocationObject } from "@calcom/app-store/locations";
import { getLocationValueForDB } from "@calcom/app-store/locations";
import { MeetLocationType } from "@calcom/app-store/locations";
import { handleEthSignature } from "@calcom/app-store/rainbow/utils/ethereum";
import { EventTypeAppsList, getAppFromSlug, getEventTypeAppData } from "@calcom/app-store/utils";
import type { EventTypeAppsList } from "@calcom/app-store/utils";
import { getAppFromSlug, getEventTypeAppData } from "@calcom/app-store/utils";
import { cancelScheduledJobs, scheduleTrigger } from "@calcom/app-store/zapier/lib/nodeScheduler";
import EventManager from "@calcom/core/EventManager";
import { getEventName } from "@calcom/core/event";
import { getUserAvailability } from "@calcom/core/getUserAvailability";
import dayjs, { ConfigType } from "@calcom/dayjs";
import type { ConfigType } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
import {
sendAttendeeRequestEmail,
sendOrganizerRequestEmail,
Expand Down Expand Up @@ -55,9 +51,10 @@ import {
import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime";
import type { AdditionalInformation, AppsStatus, CalendarEvent } from "@calcom/types/Calendar";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
import { WorkingHours } from "@calcom/types/schedule";
import type { WorkingHours } from "@calcom/types/schedule";

import sendPayload, { EventTypeInfo } from "../../webhooks/lib/sendPayload";
import type { EventTypeInfo } from "../../webhooks/lib/sendPayload";
import sendPayload from "../../webhooks/lib/sendPayload";

const translator = short();
const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
Expand Down Expand Up @@ -538,7 +535,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {

const teamMembers = await Promise.all(teamMemberPromises);

const attendeesList = [...invitee, ...guests, ...teamMembers];
Copy link
Member

Choose a reason for hiding this comment

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

This is causing collective events to not add all team members to the created calendar event.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In #7756 team members are added to the attendeesList on line 1442 before creating the booking in the DB & sending it to the event manager to create the calendar event.

I tested this locally and the db record, email, and calendar event description have the team members included.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that data is not the same one being sent to the EventManager. It is passed directly to the DB only:

image

const attendeesList = [...invitee, ...guests];

const eventNameObject = {
attendeeName: reqBody.name || "Nameless",
Expand Down Expand Up @@ -720,7 +717,8 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {

if (eventType.schedulingType === SchedulingType.COLLECTIVE) {
evt.team = {
members: users.map((user) => user.name || user.username || "Nameless"),
// members: users.map((user) => user.name || user.username || "Nameless"),
members: teamMembers,
name: eventType.team?.name || "Nameless",
}; // used for invitee emails
}
Expand Down
2 changes: 1 addition & 1 deletion packages/types/Calendar.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface CalendarEvent {
description?: string | null;
team?: {
name: string;
members: string[];
members: Person[];
};
location?: string | null;
conferenceData?: ConferenceData;
Expand Down