fix: Broken team events if a user with the same name exists (#10724)
* fix: Broken team events if a user with the same name exists * Fix tests + fix usernameList optionalitypull/10718/head^2
parent
4d5697f5a5
commit
ceb18167cf
|
@ -274,6 +274,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
// As per Google Calendar Availability, only 4PM(4-4:45PM) GMT slot would be available
|
||||
|
@ -358,6 +359,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
// getSchedule returns timeslots in GMT
|
||||
|
@ -387,6 +389,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus2DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus3DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
expect(scheduleForDayWithOneBooking).toHaveTimeSlots(
|
||||
|
@ -450,6 +453,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
expect(scheduleForEventWith30Length).toHaveTimeSlots(
|
||||
|
@ -483,6 +487,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
// `slotInterval` takes precedence over `length`
|
||||
// 4:30 is utc so it is 10:00 in IST
|
||||
|
@ -545,6 +550,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${minus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${todayDateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
expect(scheduleForEventWithBookingNotice13Hrs).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -563,6 +569,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${minus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${todayDateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
expect(scheduleForEventWithBookingNotice10Hrs).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -625,6 +632,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus2DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus3DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
expect(scheduleForEventOnADayWithNonCalBooking).toHaveTimeSlots(
|
||||
|
@ -697,6 +705,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
expect(scheduleForEventOnADayWithCalBooking).toHaveTimeSlots(
|
||||
|
@ -753,6 +762,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
expect(schedule).toHaveTimeSlots(
|
||||
|
@ -815,6 +825,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
expect(scheduleForEventOnADayWithDateOverride).toHaveTimeSlots(
|
||||
|
@ -899,6 +910,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
});
|
||||
|
||||
expect(thisUserAvailability).toHaveTimeSlots(
|
||||
|
@ -995,6 +1007,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${todayDateString}T18:30:00.000Z`,
|
||||
endTime: `${plus1DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
});
|
||||
|
||||
expect(scheduleForTeamEventOnADayWithNoBooking).toHaveTimeSlots(
|
||||
|
@ -1022,6 +1035,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
});
|
||||
|
||||
// A user with blocked time in another event, still affects Team Event availability
|
||||
|
@ -1128,6 +1142,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
});
|
||||
// A user with blocked time in another event, still affects Team Event availability
|
||||
expect(scheduleForTeamEventOnADayWithOneBookingForEachUserButOnDifferentTimeslots).toHaveTimeSlots(
|
||||
|
@ -1153,6 +1168,7 @@ describe("getSchedule", () => {
|
|||
startTime: `${plus2DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus3DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
});
|
||||
// A user with blocked time in another event, still affects Team Event availability
|
||||
expect(scheduleForTeamEventOnADayWithOneBookingForEachUserOnSameTimeSlot).toHaveTimeSlots(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useSearchParams } from "next/navigation";
|
||||
import { useSearchParams, usePathname } from "next/navigation";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import { useSchedule } from "@calcom/features/schedules";
|
||||
|
@ -59,8 +59,10 @@ export const useScheduleForEvent = ({
|
|||
(state) => [state.username, state.eventSlug, state.month, state.selectedDuration],
|
||||
shallow
|
||||
);
|
||||
const serachParams = useSearchParams();
|
||||
const rescheduleUid = serachParams.get("rescheduleUid");
|
||||
const searchParams = useSearchParams();
|
||||
const rescheduleUid = searchParams.get("rescheduleUid");
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
return useSchedule({
|
||||
username: usernameFromStore ?? username,
|
||||
|
@ -71,5 +73,6 @@ export const useScheduleForEvent = ({
|
|||
rescheduleUid,
|
||||
month: monthFromStore ?? month,
|
||||
duration: durationFromStore ?? duration,
|
||||
isTeamEvent: pathname.indexOf("/team/") !== -1,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ type UseScheduleWithCacheArgs = {
|
|||
prefetchNextMonth?: boolean;
|
||||
duration?: number | null;
|
||||
rescheduleUid?: string | null;
|
||||
isTeamEvent?: boolean;
|
||||
};
|
||||
|
||||
export const useSchedule = ({
|
||||
|
@ -22,6 +23,7 @@ export const useSchedule = ({
|
|||
prefetchNextMonth,
|
||||
duration,
|
||||
rescheduleUid,
|
||||
isTeamEvent,
|
||||
}: UseScheduleWithCacheArgs) => {
|
||||
const monthDayjs = month ? dayjs(month) : dayjs();
|
||||
const nextMonthDayjs = monthDayjs.add(1, "month");
|
||||
|
@ -30,6 +32,7 @@ export const useSchedule = ({
|
|||
// no satisfy typscript.
|
||||
return trpc.viewer.public.slots.getSchedule.useQuery(
|
||||
{
|
||||
isTeamEvent,
|
||||
usernameList: getUsernameList(username ?? ""),
|
||||
// Prioritize slug over id, since slug is the first value we get available.
|
||||
// If we have a slug, we don't need to fetch the id.
|
||||
|
|
|
@ -13,7 +13,7 @@ export const getScheduleSchema = z
|
|||
// invitee timezone
|
||||
timeZone: z.string().optional(),
|
||||
// or list of users (for dynamic events)
|
||||
usernameList: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
usernameList: z.array(z.string()).min(1).optional(),
|
||||
debug: z.boolean().optional(),
|
||||
// to handle event types with multiple duration options
|
||||
duration: z
|
||||
|
@ -21,6 +21,8 @@ export const getScheduleSchema = z
|
|||
.optional()
|
||||
.transform((val) => val && parseInt(val)),
|
||||
rescheduleUid: z.string().optional().nullable(),
|
||||
// whether to do team event or user event
|
||||
isTeamEvent: z.boolean().optional().default(false),
|
||||
})
|
||||
.transform((val) => {
|
||||
// Need this so we can pass a single username in the query string form public API
|
||||
|
|
|
@ -69,42 +69,53 @@ export const checkIfIsAvailable = ({
|
|||
});
|
||||
};
|
||||
|
||||
async function getEventTypeId({
|
||||
slug,
|
||||
eventTypeSlug,
|
||||
isTeamEvent,
|
||||
}: {
|
||||
slug?: string;
|
||||
eventTypeSlug?: string;
|
||||
isTeamEvent: boolean;
|
||||
}) {
|
||||
if (!eventTypeSlug || !slug) return null;
|
||||
|
||||
let teamId;
|
||||
let userId;
|
||||
if (isTeamEvent) {
|
||||
teamId = await getTeamIdFromSlug(slug);
|
||||
} else {
|
||||
userId = await getUserIdFromUsername(slug);
|
||||
}
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
where: {
|
||||
slug: eventTypeSlug,
|
||||
...(teamId ? { teamId } : {}),
|
||||
...(userId ? { userId } : {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
if (!eventType) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
return eventType?.id;
|
||||
}
|
||||
|
||||
export async function getEventType(input: TGetScheduleInputSchema) {
|
||||
const { eventTypeSlug, usernameList } = input;
|
||||
let eventTypeId = input.eventTypeId;
|
||||
const { eventTypeSlug, usernameList, isTeamEvent } = input;
|
||||
const eventTypeId =
|
||||
input.eventTypeId ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(await getEventTypeId({
|
||||
slug: usernameList?.[0],
|
||||
eventTypeSlug: eventTypeSlug,
|
||||
isTeamEvent,
|
||||
}));
|
||||
|
||||
if (eventTypeId === undefined && eventTypeSlug && usernameList && usernameList.length === 1) {
|
||||
// If we only have the slug and usernameList, we need to get the id first
|
||||
const username = usernameList[0];
|
||||
const userId = await getUserIdFromUsername(username);
|
||||
let teamId;
|
||||
|
||||
if (!userId) {
|
||||
teamId = await getTeamIdFromSlug(username);
|
||||
if (!teamId) {
|
||||
throw new TRPCError({
|
||||
message: "User or team not found",
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
where: {
|
||||
slug: eventTypeSlug,
|
||||
...(teamId ? { teamId } : {}),
|
||||
...(userId ? { userId } : {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!eventType) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
|
||||
eventTypeId = eventType.id;
|
||||
if (!eventTypeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
|
@ -165,7 +176,7 @@ export async function getEventType(input: TGetScheduleInputSchema) {
|
|||
},
|
||||
});
|
||||
if (!eventType) {
|
||||
return eventType;
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue