fix/google-calendar-removes-attendees-when-seats-5336 (#5366)

* Adding missing code that copied booking attendees to event attendees

* Clean up code

* Fix types

* Fix types

* Doing deep clone for sending seats email
This commit is contained in:
alannnc
2022-11-05 18:58:35 +00:00
committed by GitHub
parent 7517feb62a
commit dff08dc5b9
9 changed files with 57 additions and 30 deletions
@@ -54,7 +54,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<div className="flex flex-col space-y-8">
{/**
* Only display calendar selector if user has connected calendars AND if it's not
* a team event. Since we don't have logic to handle each attende calendar (for now).
* a team event. Since we don't have logic to handle each attendee calendar (for now).
* This will fallback to each user selected destination calendar.
*/}
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
@@ -292,7 +292,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
description={t("offer_seats_description")}
checked={value}
onCheckedChange={(e) => {
// Enabling seats will disable guests and requiring confimation until fully supported
// Enabling seats will disable guests and requiring confirmation until fully supported
if (e) {
formMethods.setValue("disableGuests", true);
formMethods.setValue("requiresConfirmation", false);
@@ -316,9 +316,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
label={t("number_of_seats")}
type="number"
defaultValue={value || 2}
min={1}
addOnSuffix={<>{t("seats")}</>}
onChange={(e) => {
onChange(Number(e.target.value));
onChange(Math.abs(Number(e.target.value)));
}}
/>
<div className="mt-2">
@@ -160,6 +160,10 @@ export default class GoogleCalendarService implements Calendar {
async updateEvent(uid: string, event: CalendarEvent, externalCalendarId: string): Promise<any> {
return new Promise(async (resolve, reject) => {
const myGoogleAuth = await this.auth.getToken();
const eventAttendees = event.attendees.map(({ id, ...rest }) => ({
...rest,
responseStatus: "accepted",
}));
const payload: calendar_v3.Schema$Event = {
summary: event.title,
description: getRichDescription(event),
@@ -179,10 +183,7 @@ export default class GoogleCalendarService implements Calendar {
responseStatus: "accepted",
},
// eslint-disable-next-line
...event.attendees.map(({ id, ...rest }) => ({
...rest,
responseStatus: "accepted",
})),
...eventAttendees,
],
reminders: {
useDefault: true,
+26 -12
View File
@@ -7,8 +7,14 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi
import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
import getApps from "@calcom/app-store/utils";
import prisma from "@calcom/prisma";
import { Attendee } from "@calcom/prisma/client";
import { createdEventSchema } from "@calcom/prisma/zod-utils";
import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar";
import type {
AdditionalInformation,
CalendarEvent,
NewCalendarEventType,
Person,
} from "@calcom/types/Calendar";
import { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
import type { Event } from "@calcom/types/Event";
import type {
@@ -294,6 +300,7 @@ export default class EventManager {
}
public async updateCalendarAttendees(event: CalendarEvent, booking: PartialBooking) {
// @NOTE: This function is only used for updating attendees on a calendar event. Can we remove this?
await this.updateAllCalendarEvents(event, booking);
}
@@ -430,11 +437,14 @@ export default class EventManager {
try {
// Bookings should only have one calendar reference
calendarReference = booking.references.filter((reference) => reference.type.includes("_calendar"))[0];
if (!calendarReference) throw new Error("bookingRef");
if (!calendarReference) {
throw new Error("bookingRef");
}
const { uid: bookingRefUid, externalCalendarId: bookingExternalCalendarId } = calendarReference;
if (!bookingExternalCalendarId) throw new Error("externalCalendarId");
if (!bookingExternalCalendarId) {
throw new Error("externalCalendarId");
}
let result = [];
if (calendarReference.credentialId) {
@@ -458,13 +468,15 @@ export default class EventManager {
.map(async (cred) => {
const calendarReference = booking.references.find((ref) => ref.type === cred.type);
if (!calendarReference)
return {
appName: cred.appName,
type: cred.type,
success: false,
uid: "",
originalEvent: event,
};
if (!calendarReference) {
return {
appName: cred.appName,
type: cred.type,
success: false,
uid: "",
originalEvent: event,
};
}
const { externalCalendarId: bookingExternalCalendarId, meetingId: bookingRefUid } =
calendarReference;
return await updateEvent(cred, event, bookingRefUid ?? null, bookingExternalCalendarId ?? null);
@@ -474,7 +486,9 @@ export default class EventManager {
return Promise.all(result);
} catch (error) {
let message = `Tried to 'updateAllCalendarEvents' but there was no '{thing}' for '${credential?.type}', userId: '${credential?.userId}', bookingId: '${booking?.id}'`;
if (error instanceof Error) message = message.replace("{thing}", error.message);
if (error instanceof Error) {
message = message.replace("{thing}", error.message);
}
console.error(message);
return Promise.resolve([
{
@@ -7,6 +7,7 @@ import {
WebhookTriggerEvents,
} from "@prisma/client";
import async from "async";
import { cloneDeep } from "lodash";
import type { NextApiRequest } from "next";
import { RRule } from "rrule";
import short from "short-uuid";
@@ -482,8 +483,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
if (reqBody.bookingUid) {
if (!eventType.seatsPerTimeSlot)
if (!eventType.seatsPerTimeSlot) {
throw new HttpError({ statusCode: 404, message: "Event type does not have seats" });
}
const booking = await prisma.booking.findUnique({
where: {
@@ -505,7 +507,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
},
},
});
if (!booking) throw new HttpError({ statusCode: 404, message: "Booking not found" });
if (!booking) {
throw new HttpError({ statusCode: 404, message: "Booking not found" });
}
// Need to add translation for attendees to pass type checks. Since these values are never written to the db we can just use the new attendee language
const bookingAttendees = booking.attendees.map((attendee) => {
@@ -514,11 +518,13 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
evt = { ...evt, attendees: [...bookingAttendees, invitee[0]] };
if (eventType.seatsPerTimeSlot <= booking.attendees.length)
if (eventType.seatsPerTimeSlot <= booking.attendees.length) {
throw new HttpError({ statusCode: 409, message: "Booking seats are full" });
}
if (booking.attendees.some((attendee) => attendee.email === invitee[0].email))
if (booking.attendees.find((attendee) => attendee.email === invitee[0].email)) {
throw new HttpError({ statusCode: 409, message: "Already signed up for time slot" });
}
await prisma.booking.update({
where: {
@@ -537,8 +543,13 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
});
const newSeat = booking.attendees.length !== 0;
await sendScheduledSeatsEmails(evt, invitee[0], newSeat, !!eventType.seatsShowAttendees);
/**
* Remember objects are passed into functions as references
* so if you modify it in a inner function it will be modified in the outer function
* deep cloning evt to avoid this
*/
const copyEvent = cloneDeep(evt);
await sendScheduledSeatsEmails(copyEvent, invitee[0], newSeat, !!eventType.seatsShowAttendees);
const credentials = await refreshCredentials(organizerUser.credentials);
const eventManager = new EventManager({ ...organizerUser, credentials });
@@ -5,9 +5,9 @@ import { FixedSizeList as List } from "react-window";
import dayjs from "@calcom/dayjs";
import { CAL_URL } from "@calcom/lib/constants";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Avatar } from "@calcom/ui/components/avatar";
import Select from "@calcom/ui/form/Select";
import TimezoneSelect, { ITimezone } from "@calcom/ui/form/TimezoneSelect";
import { Avatar } from "@calcom/ui/components/avatar";
import DatePicker from "@calcom/ui/v2/core/form/DatePicker";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
@@ -5,9 +5,9 @@ import { FixedSizeList as List } from "react-window";
import dayjs from "@calcom/dayjs";
import { CAL_URL } from "@calcom/lib/constants";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Avatar } from "@calcom/ui/components/avatar";
import Select from "@calcom/ui/form/Select";
import TimezoneSelect, { ITimezone } from "@calcom/ui/form/TimezoneSelect";
import { Avatar } from "@calcom/ui/components/avatar";
import DatePicker from "@calcom/ui/v2/core/form/DatePicker";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
@@ -6,8 +6,8 @@ import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Form } from "@calcom/ui/form/fields";
import { Button } from "@calcom/ui/components/button";
import { Form } from "@calcom/ui/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
export function NewScheduleButton({ name = "new-schedule" }: { name?: string }) {
@@ -5,8 +5,8 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { EmptyScreen, SkeletonText } from "@calcom/ui/v2";
import { Button } from "@calcom/ui/components/button";
import { EmptyScreen, SkeletonText } from "@calcom/ui/v2";
import Meta from "@calcom/ui/v2/core/Meta";
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
+1 -1
View File
@@ -5,8 +5,8 @@ import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { App } from "@calcom/types/App";
import { Icon } from "@calcom/ui/Icon";
import { showToast } from "@calcom/ui/v2";
import { Button } from "@calcom/ui/components/button";
import { showToast } from "@calcom/ui/v2";
interface AppCardProps {
app: App;