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

chore: EventManager add cancelEvent method #14261

Merged
merged 58 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
bad017a
fix timezone display on booking page to reflect event availability ti…
Mar 18, 2024
e4f9679
migrate fetching event owner's schedule to server side
Mar 19, 2024
2dc2272
migrate fetching event owner's schedule to server side
Mar 19, 2024
12f228f
fix e2e test errors
Mar 19, 2024
df6672e
Merge branch 'calcom:main' into main
Shaik-Sirajuddin Mar 19, 2024
5f274fc
Merge branch 'main' of https://github.com/Shaik-Sirajuddin/cal.com
Mar 19, 2024
4cd9236
Merge branch 'main' into main
joeauyeung Mar 19, 2024
dc45e8c
Merge branch 'calcom:main' into main
Shaik-Sirajuddin Mar 19, 2024
046a3a0
Add WEBAPP_URL_FOR_OAUTH to salesforce auth
joeauyeung Mar 19, 2024
2f19c18
In event manager constructor include "_crm" credentials as calendar c…
joeauyeung Mar 19, 2024
58eb841
Change crm apps to type to end with `_crm`
joeauyeung Mar 19, 2024
4ce8a85
Move sendgrid out of CRM
joeauyeung Mar 19, 2024
288915e
Add zoho bigin to CRM apps
joeauyeung Mar 20, 2024
1777a3a
When getting apps, use slug
joeauyeung Mar 20, 2024
a9bb57f
Add `crm` variants
joeauyeung Mar 20, 2024
698d41e
Hubspot Oauth use `WEBAPP_URL_FOR_OAUTH`
joeauyeung Mar 20, 2024
d40835e
Refactor creating credentials
joeauyeung Mar 20, 2024
bbc068f
Fix empty CRM page
joeauyeung Mar 20, 2024
747c71a
Use credentials with `_crm`
joeauyeung Mar 20, 2024
be2f2b1
Merge branch 'main' into add-crm-app-type
joeauyeung Mar 20, 2024
11b6c7f
Merge branch 'add-crm-app-type' of https://github.com/calcom/cal.com …
joeauyeung Mar 20, 2024
7f5fdb4
Abstract getAppCategoryTitle
joeauyeung Mar 20, 2024
9cff361
Merge branch 'main' into add-crm-app-type
joeauyeung Mar 20, 2024
1db94c8
Merge branch 'main' into add-crm-app-type
joeauyeung Mar 20, 2024
9986bb4
Merge branch 'add-crm-app-type' of https://github.com/calcom/cal.com …
joeauyeung Mar 20, 2024
db8f38e
Add integration.handler changes
joeauyeung Mar 20, 2024
f568c3e
Merge branch 'main' into add-crm-app-type
sean-brydon Mar 25, 2024
b07473d
Merge branch 'main' into add-crm-app-type
joeauyeung Mar 26, 2024
f068be4
Merge branch 'main' into add-crm-app-type
joeauyeung Mar 29, 2024
bd65dad
When searching for credential, look for current credentials in class
joeauyeung Mar 29, 2024
8e12836
On cancel, delete 3rd party events
joeauyeung Mar 29, 2024
8f19358
Merge branch 'main' into add-crm-app-type
joeauyeung Mar 29, 2024
4ba899a
Merge branch 'add-crm-app-type' into event-manager-add-cancel-method
joeauyeung Apr 4, 2024
666dcb3
Merge branch 'main' into add-crm-app-type
joeauyeung Apr 4, 2024
db36cbf
Merge branch 'main' into add-crm-app-type
joeauyeung Apr 4, 2024
8b1b206
Merge branch 'main' into add-crm-app-type
joeauyeung Apr 5, 2024
9c2cb92
Fix tests
joeauyeung Apr 5, 2024
de1c55d
Type fix
joeauyeung Apr 5, 2024
d9368a6
Merge branch 'main' into add-crm-app-type
joeauyeung Apr 5, 2024
db7ef81
Merge branch 'add-crm-app-type' into event-manager-add-cancel-method
joeauyeung Apr 5, 2024
1870fcf
Merge branch 'main' into event-manager-add-cancel-method
zomars Apr 10, 2024
9d610ac
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 12, 2024
f0139fc
Type fixes
joeauyeung Apr 12, 2024
541eb8b
Remove apiDeletes
joeauyeung Apr 12, 2024
375c6a4
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 12, 2024
022df0f
Type fixes
joeauyeung Apr 12, 2024
1cd2955
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 12, 2024
95d333f
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 12, 2024
b35869e
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 12, 2024
30cac3f
Specific typing
joeauyeung Apr 12, 2024
a9c310f
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 15, 2024
cf7c15f
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 15, 2024
bbf4648
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 15, 2024
43cfd58
Merge branch 'main' into event-manager-add-cancel-method
sean-brydon Apr 17, 2024
09a1c05
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 18, 2024
9ef46d7
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 19, 2024
b06d24d
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 22, 2024
32b119b
Merge branch 'main' into event-manager-add-cancel-method
joeauyeung Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
160 changes: 102 additions & 58 deletions packages/core/EventManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DestinationCalendar } from "@prisma/client";
import type { DestinationCalendar, BookingReference } from "@prisma/client";
// eslint-disable-next-line no-restricted-imports
import { cloneDeep, merge } from "lodash";
import { v5 as uuidv5 } from "uuid";
Expand Down Expand Up @@ -246,25 +246,36 @@ export default class EventManager {
}

private async deleteCalendarEventForBookingReference({
bookingCalendarReference,
reference,
event,
isBookingInRecurringSeries,
}: {
bookingCalendarReference: PartialReference;
reference: PartialReference;
event: CalendarEvent;
isBookingInRecurringSeries?: boolean;
}) {
log.debug(
"deleteCalendarEventForBookingReference",
safeStringify({ bookingCalendarReference, event: getPiiFreeCalendarEvent(event) })
safeStringify({ bookingCalendarReference: reference, event: getPiiFreeCalendarEvent(event) })
);

const {
uid: bookingRefUid,
// uid: bookingRefUid,
externalCalendarId: bookingExternalCalendarId,
credentialId,
type: credentialType,
} = bookingCalendarReference;
} = reference;

const calendarCredential = await this.getCredentialAndWarnIfNotFound(credentialId, credentialType);
const bookingRefUid =
isBookingInRecurringSeries && reference?.thirdPartyRecurringEventId
? reference.thirdPartyRecurringEventId
: reference.uid;

Comment on lines +269 to +272
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Check if we're deleting a booking in a recurring series. This change was made in handleCancelBooking but missing in the EventManager

const calendarCredential = await this.getCredentialAndWarnIfNotFound(
credentialId,
this.calendarCredentials,
credentialType
);
if (calendarCredential) {
await deleteEvent({
credential: calendarCredential,
Expand All @@ -275,50 +286,56 @@ export default class EventManager {
}
}

private async deleteVideoEventForBookingReference({
bookingVideoReference,
}: {
bookingVideoReference: PartialReference;
}) {
log.debug("deleteVideoEventForBookingReference", safeStringify({ bookingVideoReference }));
const { uid: bookingRefUid, credentialId } = bookingVideoReference;
private async deleteVideoEventForBookingReference({ reference }: { reference: PartialReference }) {
log.debug("deleteVideoEventForBookingReference", safeStringify({ bookingVideoReference: reference }));
const { uid: bookingRefUid, credentialId } = reference;

const videoCredential = await this.getCredentialAndWarnIfNotFound(
credentialId,
bookingVideoReference.type
this.videoCredentials,
reference.type
);

if (videoCredential) {
await deleteMeeting(videoCredential, bookingRefUid);
}
}

private async getCredentialAndWarnIfNotFound(credentialId: number | null | undefined, type: string) {
const credential =
typeof credentialId === "number" && credentialId > 0
? await prisma.credential.findUnique({
where: {
id: credentialId,
},
select: credentialForCalendarServiceSelect,
private async getCredentialAndWarnIfNotFound(
credentialId: number | null | undefined,
credentials: CredentialPayload[],
type: string
) {
const credential = credentials.find((cred) => cred.id === credentialId);
if (credential) {
return credential;
} else {
const credential =
typeof credentialId === "number" && credentialId > 0
? await prisma.credential.findUnique({
where: {
id: credentialId,
},
select: credentialForCalendarServiceSelect,
})
: // Fallback for zero or nullish credentialId which could be the case of Global App e.g. dailyVideo
Comment on lines +309 to +320
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before this function would query the database for the credential. When we create a new instance of the EventManager class we need to pass the credentials anyways. Let's search through the appropriate credential arrays first before querying the database.

this.videoCredentials.find((cred) => cred.type === type) ||
this.calendarCredentials.find((cred) => cred.type === type) ||
null;

if (!credential) {
log.error(
"getCredentialAndWarnIfNotFound: Could not find credential",
safeStringify({
credentialId,
type,
videoCredentials: this.videoCredentials,
})
: // Fallback for zero or nullish credentialId which could be the case of Global App e.g. dailyVideo
this.videoCredentials.find((cred) => cred.type === type) ||
this.calendarCredentials.find((cred) => cred.type === type) ||
null;

if (!credential) {
log.error(
"getCredentialAndWarnIfNotFound: Could not find credential",
safeStringify({
credentialId,
type,
videoCredentials: this.videoCredentials,
})
);
}
);
}

return credential;
return credential;
}
}

/**
Expand Down Expand Up @@ -385,15 +402,15 @@ export default class EventManager {
log.debug("RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking");
// As the reschedule requires confirmation, we can't update the events and meetings to new time yet. So, just delete them and let it be handled when organizer confirms the booking.
await this.deleteEventsAndMeetings({
booking,
event: { ...event, destinationCalendar: previousHostDestinationCalendar },
bookingReferences: booking.references,
});
} else {
if (changedOrganizer) {
log.debug("RescheduleOrganizerChanged: Deleting Event and Meeting for previous booking");
await this.deleteEventsAndMeetings({
booking,
event: { ...event, destinationCalendar: previousHostDestinationCalendar },
bookingReferences: booking.references,
});

log.debug("RescheduleOrganizerChanged: Creating Event and Meeting for for new booking");
Expand Down Expand Up @@ -451,30 +468,57 @@ export default class EventManager {
};
}

public async cancelEvent(
event: CalendarEvent,
bookingReferences: Pick<
BookingReference,
"uid" | "type" | "externalCalendarId" | "credentialId" | "thirdPartyRecurringEventId"
>[],
isBookingInRecurringSeries?: boolean
) {
await this.deleteEventsAndMeetings({
event,
bookingReferences,
isBookingInRecurringSeries,
});
}

private async deleteEventsAndMeetings({
event,
booking,
bookingReferences,
isBookingInRecurringSeries,
}: {
event: CalendarEvent;
booking: PartialBooking;
bookingReferences: PartialReference[];
isBookingInRecurringSeries?: boolean;
}) {
const calendarReferences = booking.references.filter((reference) => reference.type.includes("_calendar"));
const videoReferences = booking.references.filter((reference) => reference.type.includes("_video"));
log.debug("deleteEventsAndMeetings", safeStringify({ calendarReferences, videoReferences }));
const calendarPromises = calendarReferences.map(async (bookingCalendarReference) => {
return this.deleteCalendarEventForBookingReference({
bookingCalendarReference,
event,
});
});
const calendarReferences = [],
Comment on lines -461 to -469
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're looping through the booking references two times.

videoReferences = [],
allPromises = [];

for (const reference of bookingReferences) {
if (reference.type.includes("_calendar")) {
calendarReferences.push(reference);
allPromises.push(
this.deleteCalendarEventForBookingReference({
reference,
Comment on lines +499 to +503
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's loop through the references once and call the appropriate method.

event,
isBookingInRecurringSeries,
})
);
}

const videoPromises = videoReferences.map(async (bookingVideoReference) => {
return this.deleteVideoEventForBookingReference({
bookingVideoReference,
});
});
if (reference.type.includes("_video")) {
videoReferences.push(reference);
allPromises.push(
this.deleteVideoEventForBookingReference({
reference,
})
);
}
}

const allPromises = [...calendarPromises, ...videoPromises];
log.debug("deleteEventsAndMeetings", safeStringify({ calendarReferences, videoReferences }));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I still kept this for logging though.

// Using allSettled to ensure that if one of the promises rejects, the others will still be executed.
// Because we are just cleaning up the events and meetings, we don't want to throw an error if one of them fails.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Prisma } from "@prisma/client";

import type { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking";
import { UserRepository } from "@calcom/lib/server/repository/user";
import type { userSelect } from "@calcom/prisma";
import prisma from "@calcom/prisma";
Expand All @@ -14,13 +13,13 @@ type User = Prisma.UserGetPayload<typeof userSelect>;
*
*/
export const getAllCredentials = async (
user: User & { credentials: CredentialPayload[] },
eventType: Awaited<ReturnType<typeof getEventTypesFromDB>>
user: { id: number; username: string | null; credentials: CredentialPayload[] },
eventType: { team: { id: number | null } | null; parentId: number | null } | null
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@zomars made some changes here. I think the types casted too broad of an umbrella. I narrowed it down to what's really needed. Thoughts?

) => {
const allCredentials = user.credentials;

// If it's a team event type query for team credentials
if (eventType.team?.id) {
if (eventType?.team?.id) {
const teamCredentialsQuery = await prisma.credential.findMany({
where: {
teamId: eventType.team.id,
Expand All @@ -31,7 +30,7 @@ export const getAllCredentials = async (
}

// If it's a managed event type, query for the parent team's credentials
if (eventType.parentId) {
if (eventType?.parentId) {
const teamCredentialsQuery = await prisma.team.findFirst({
where: {
eventTypes: {
Expand Down