Skip to content

Commit

Permalink
chore: EventManager add cancelEvent method (#14261)
Browse files Browse the repository at this point in the history
* fix timezone display on booking page to reflect event availability timezone

* migrate fetching event owner's schedule to server side

* migrate fetching event owner's schedule to server side

* fix e2e test errors

* Add WEBAPP_URL_FOR_OAUTH to salesforce auth

* In event manager constructor include "_crm" credentials as calendar creds

* Change crm apps to type to end with `_crm`

* Move sendgrid out of CRM

* Add zoho bigin to CRM apps

* When getting apps, use slug

* Add `crm` variants

* Hubspot Oauth use `WEBAPP_URL_FOR_OAUTH`

* Refactor creating credentials

* Fix empty CRM page

* Use credentials with `_crm`

* Abstract getAppCategoryTitle

* Add integration.handler changes

* When searching for credential, look for current credentials in class

* On cancel, delete 3rd party events

* Fix tests

* Type fix

* Type fixes

* Remove apiDeletes

* Type fixes

* Specific typing

---------

Co-authored-by: Shaik-Sirajuddin <sirajuddinshaik30gmail.com>
Co-authored-by: Shaik-Sirajuddin <sirajudddinshaik30@gmail.com>
Co-authored-by: Shaik-Sirajuddin <89742297+Shaik-Sirajuddin@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
  • Loading branch information
5 people committed Apr 23, 2024
1 parent d16428d commit 571e93a
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 172 deletions.
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;

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
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 = [],
videoReferences = [],
allPromises = [];

for (const reference of bookingReferences) {
if (reference.type.includes("_calendar")) {
calendarReferences.push(reference);
allPromises.push(
this.deleteCalendarEventForBookingReference({
reference,
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 }));

// 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
) => {
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
Loading

0 comments on commit 571e93a

Please sign in to comment.