Adds eventTypeId as a parameter (#1217)

This commit is contained in:
Alex van Andel 2021-12-03 17:18:31 +01:00 committed by GitHub
parent 8c1b69cc0f
commit 22aa083883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 59 deletions

View File

@ -19,6 +19,7 @@ import { createPaymentLink } from "@ee/lib/stripe/client";
import { asStringOrNull } from "@lib/asStringOrNull"; import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock"; import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import useTheme from "@lib/hooks/useTheme"; import useTheme from "@lib/hooks/useTheme";
import { LocationType } from "@lib/location"; import { LocationType } from "@lib/location";
@ -137,19 +138,12 @@ const BookingPage = (props: BookingPageProps) => {
}; };
}; };
// can be shortened using .filter(), except TypeScript doesn't know what to make of the types.
const guests = router.query.guest
? Array.isArray(router.query.guest)
? router.query.guest
: [router.query.guest]
: [];
const bookingForm = useForm<BookingFormValues>({ const bookingForm = useForm<BookingFormValues>({
defaultValues: { defaultValues: {
name: (router.query.name as string) || "", name: (router.query.name as string) || "",
email: (router.query.email as string) || "", email: (router.query.email as string) || "",
notes: (router.query.notes as string) || "", notes: (router.query.notes as string) || "",
guests, guests: ensureArray(router.query.guest),
customInputs: props.eventType.customInputs.reduce( customInputs: props.eventType.customInputs.reduce(
(customInputs, input) => ({ (customInputs, input) => ({
...customInputs, ...customInputs,
@ -213,7 +207,7 @@ const BookingPage = (props: BookingPageProps) => {
timeZone: timeZone(), timeZone: timeZone(),
language: i18n.language, language: i18n.language,
rescheduleUid, rescheduleUid,
user: router.query.user as string, user: router.query.user,
location: getLocationValue(booking), location: getLocationValue(booking),
metadata, metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({ customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({

View File

@ -14,6 +14,7 @@ const config: Config.InitialOptions = {
"^@components(.*)$": "<rootDir>/components$1", "^@components(.*)$": "<rootDir>/components$1",
"^@lib(.*)$": "<rootDir>/lib$1", "^@lib(.*)$": "<rootDir>/lib$1",
"^@server(.*)$": "<rootDir>/server$1", "^@server(.*)$": "<rootDir>/server$1",
"^@ee(.*)$": "<rootDir>/ee$1",
}, },
}; };

9
lib/ensureArray.ts Normal file
View File

@ -0,0 +1,9 @@
export function ensureArray<T>(val: unknown): T[] {
if (Array.isArray(val)) {
return val;
}
if (typeof val === "undefined") {
return [];
}
return [val] as T[];
}

View File

@ -16,8 +16,7 @@ export type BookingCreateBody = {
rescheduleUid?: string; rescheduleUid?: string;
start: string; start: string;
timeZone: string; timeZone: string;
users?: string[]; user?: string | string[];
user?: string;
language: string; language: string;
customInputs: { label: string; value: string }[]; customInputs: { label: string; value: string }[];
metadata: { metadata: {

View File

@ -17,6 +17,7 @@ import {
sendRescheduledEmails, sendRescheduledEmails,
sendOrganizerRequestEmail, sendOrganizerRequestEmail,
} from "@lib/emails/email-manager"; } from "@lib/emails/email-manager";
import { ensureArray } from "@lib/ensureArray";
import { getErrorFromUnknown } from "@lib/errors"; import { getErrorFromUnknown } from "@lib/errors";
import { getEventName } from "@lib/event"; import { getEventName } from "@lib/event";
import EventManager, { EventResult, PartialReference } from "@lib/events/EventManager"; import EventManager, { EventResult, PartialReference } from "@lib/events/EventManager";
@ -123,6 +124,59 @@ function isOutOfBounds(
} }
} }
const userSelect = Prisma.validator<Prisma.UserArgs>()({
select: {
id: true,
email: true,
name: true,
username: true,
timeZone: true,
credentials: true,
bufferTime: true,
},
});
const getUserNameWithBookingCounts = async (eventTypeId: number, selectedUserNames: string[]) => {
const users = await prisma.user.findMany({
where: {
username: { in: selectedUserNames },
bookings: {
some: {
startTime: {
gt: new Date(),
},
eventTypeId,
},
},
},
select: {
id: true,
username: true,
},
});
const userNamesWithBookingCounts = await Promise.all(
users.map(async (user) => ({
username: user.username,
bookingCount: await prisma.booking.count({
where: {
user: {
id: user.id,
},
startTime: {
gt: new Date(),
},
eventTypeId,
},
}),
}))
);
return userNamesWithBookingCounts;
};
type User = Prisma.UserGetPayload<typeof userSelect>;
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const reqBody = req.body as BookingCreateBody; const reqBody = req.body as BookingCreateBody;
const eventTypeId = reqBody.eventTypeId; const eventTypeId = reqBody.eventTypeId;
@ -144,28 +198,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json(error); return res.status(400).json(error);
} }
const userSelect = Prisma.validator<Prisma.UserSelect>()({
id: true,
email: true,
name: true,
username: true,
timeZone: true,
credentials: true,
bufferTime: true,
});
const userData = Prisma.validator<Prisma.UserArgs>()({
select: userSelect,
});
const eventType = await prisma.eventType.findUnique({ const eventType = await prisma.eventType.findUnique({
where: { where: {
id: eventTypeId, id: eventTypeId,
}, },
select: { select: {
users: { users: userSelect,
select: userSelect,
},
team: { team: {
select: { select: {
id: true, id: true,
@ -198,43 +236,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
where: { where: {
id: eventType.userId, id: eventType.userId,
}, },
select: userSelect, ...userSelect,
}); });
if (!eventTypeUser) return res.status(404).json({ message: "eventTypeUser.notFound" }); if (!eventTypeUser) return res.status(404).json({ message: "eventTypeUser.notFound" });
users.push(eventTypeUser); users.push(eventTypeUser);
} }
if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
const selectedUsers = reqBody.users || []; const bookingCounts = await getUserNameWithBookingCounts(
const selectedUsersDataWithBookingsCount = await prisma.user.findMany({ eventTypeId,
where: { ensureArray(reqBody.user) || users.map((user) => user.username)
username: { in: selectedUsers }, );
bookings: {
every: {
startTime: {
gt: new Date(),
},
},
},
},
select: {
username: true,
_count: {
select: { bookings: true },
},
},
});
const bookingCounts = selectedUsersDataWithBookingsCount.map((userData) => ({ users = getLuckyUsers(users, bookingCounts);
username: userData.username,
bookingCount: userData._count?.bookings || 0,
}));
if (!bookingCounts.length) users.slice(0, 1);
const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1));
const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username);
users = luckyUser ? [luckyUser] : users;
} }
const invitee = [{ email: reqBody.email, name: reqBody.name, timeZone: reqBody.timeZone }]; const invitee = [{ email: reqBody.email, name: reqBody.name, timeZone: reqBody.timeZone }];
@ -354,7 +368,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
let results: EventResult[] = []; let results: EventResult[] = [];
let referencesToCreate: PartialReference[] = []; let referencesToCreate: PartialReference[] = [];
type User = Prisma.UserGetPayload<typeof userData>;
let user: User | null = null; let user: User | null = null;
for (const currentUser of users) { for (const currentUser of users) {
@ -555,3 +568,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// booking successful // booking successful
return res.status(201).json(booking); return res.status(201).json(booking);
} }
export function getLuckyUsers(
users: User[],
bookingCounts: Prisma.PromiseReturnType<typeof getUserNameWithBookingCounts>
) {
if (!bookingCounts.length) users.slice(0, 1);
const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1));
const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username);
return luckyUser ? [luckyUser] : users;
}

View File

@ -0,0 +1,30 @@
import { getLuckyUsers } from "../../pages/api/book/event";
it("can find lucky users", async () => {
const users = [
{
id: 1,
username: "test",
name: "Test User",
credentials: [],
timeZone: "GMT",
bufferTime: 0,
email: "test@example.com",
},
{
id: 2,
username: "test2",
name: "Test 2 User",
credentials: [],
timeZone: "GMT",
bufferTime: 0,
email: "test2@example.com",
},
];
expect(
getLuckyUsers(users, [
{ username: "test", bookingCount: 2 },
{ username: "test2", bookingCount: 0 },
])
).toStrictEqual([users[1]]);
});