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

refactor: Refactor seats logic #12905

Merged
merged 105 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
251146a
Refactor createBooking
joeauyeung Oct 18, 2023
1e1ceb9
Type fix
joeauyeung Oct 18, 2023
010fd62
Merge branch 'main' into refactor/create-booking
joeauyeung Oct 18, 2023
f02119e
Abstract handleSeats
joeauyeung Oct 18, 2023
ad76c74
Merge branch 'main' into refactor/create-booking
joeauyeung Oct 19, 2023
63cd63d
Create Invitee type
joeauyeung Oct 20, 2023
ba99a3c
Create OrganizerUser type
joeauyeung Oct 20, 2023
d8f9855
Abstract addVideoCallDataToEvt
joeauyeung Oct 20, 2023
34e5b5d
Abstract createLoggerWithEventDetails
joeauyeung Oct 20, 2023
d768b6b
Abstract `handleAppStatus` from handler
joeauyeung Oct 23, 2023
b29acc0
Create ReqAppsStatus type
joeauyeung Oct 23, 2023
d4e7ae0
Merge branch 'main' into refactor/create-booking
joeauyeung Oct 23, 2023
0c54667
Move `deleteMeeting` and `getCalendar`
joeauyeung Oct 23, 2023
21b86b1
Set parameters for `handleSeats`
joeauyeung Oct 23, 2023
593ac64
Merge branch 'main' into refactor/create-booking
joeauyeung Oct 24, 2023
4cf5e30
Merge branch 'main' into refactor/create-booking
joeauyeung Oct 26, 2023
9c93891
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Oct 26, 2023
776519e
Merge branch 'main' into refactor/create-booking
joeauyeung Oct 30, 2023
94c9881
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Oct 30, 2023
bc8b1e7
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 13, 2023
ffd67c5
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Nov 13, 2023
d80f8c0
Typescript refactor
joeauyeung Nov 13, 2023
6a9bb9a
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 15, 2023
76905a7
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Nov 15, 2023
8e21da3
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 17, 2023
d375e78
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Nov 17, 2023
49a7617
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 20, 2023
9175760
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Nov 20, 2023
1d96a3b
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 21, 2023
7a9fca4
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 24, 2023
b9dd07b
Change function params from req
joeauyeung Nov 24, 2023
343e59b
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 24, 2023
f7630f5
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Nov 24, 2023
97e998f
Merge branch 'main' into refactor/create-booking
ThyMinimalDev Nov 27, 2023
6ca8ab2
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 27, 2023
64f465d
Merge branch 'main' into refactor/create-booking
joeauyeung Nov 28, 2023
1b3612d
Merge branch 'refactor/create-booking' into refactor/handle-seats
joeauyeung Nov 28, 2023
0bb475b
Merge branch 'main' into refactor/handle-seats
joeauyeung Dec 13, 2023
e48efca
Type fix
joeauyeung Dec 13, 2023
afa8d6c
Merge branch 'main' into refactor/handle-seats
joeauyeung Dec 13, 2023
64f3a2d
Merge branch 'main' into refactor/handle-seats
joeauyeung Dec 13, 2023
4c4816d
Move handleSeats
joeauyeung Dec 16, 2023
ffaf5f8
Abstract lastAttendeeDeleteBooking
joeauyeung Dec 16, 2023
ebb822d
Create function for rescheduling seated events
joeauyeung Dec 16, 2023
b94e820
Fix imports on reschedule seats function
joeauyeung Dec 16, 2023
bf7e59d
Fix imports
joeauyeung Dec 16, 2023
553441f
Import handleSeats function
joeauyeung Dec 16, 2023
a73dabb
Fix rescheduleUid type
joeauyeung Dec 16, 2023
93ea649
Refactor owner reschedule to new time slot
joeauyeung Dec 16, 2023
c6cf2a2
Refactor combine two booking times together
joeauyeung Dec 16, 2023
435a4ab
Reschedule as an attendee
joeauyeung Dec 16, 2023
878eec0
Refactor createNewSeat
joeauyeung Dec 18, 2023
2669006
Remove old handleSeats
joeauyeung Dec 18, 2023
fb075f0
Remove lastAttendeeDeleteBooking from handleNewBooking
joeauyeung Dec 19, 2023
e8c2f52
Test for new attendee right params are passed
joeauyeung Dec 19, 2023
26656dd
Unit test params for reschedule
joeauyeung Dec 20, 2023
f50e425
Typo fix
joeauyeung Dec 20, 2023
6c945e1
Clean up
joeauyeung Dec 20, 2023
deeafa3
Create new seat test
joeauyeung Jan 3, 2024
d3657c0
Test when attendee already signs up for booking
joeauyeung Jan 3, 2024
2f9efed
Type fix
joeauyeung Jan 3, 2024
f9a0541
Test reschedule move attendee to existing booking
joeauyeung Jan 4, 2024
2e8e599
On reschedule create new booking
joeauyeung Jan 4, 2024
45eb62d
Test on last attendee cancel booking
joeauyeung Jan 4, 2024
6fc8a65
Owner reschedule to new time slot
joeauyeung Jan 4, 2024
294152d
Owner rescheduling, merge two bookings together
joeauyeung Jan 4, 2024
27b04fb
Test: when merging more than available seats, then fail
joeauyeung Jan 4, 2024
d030f61
Test: fail when event is full
joeauyeung Jan 4, 2024
0e4d03f
Remove duplicate E2E tests
joeauyeung Jan 5, 2024
25c89f2
Merge branch 'main' into refactor/handle-seats
joeauyeung Jan 5, 2024
818b367
Merge branch 'main' into refactor/handle-seats
joeauyeung Jan 5, 2024
8d27cff
Clean up
joeauyeung Jan 5, 2024
922a2af
Merge branch 'refactor/handle-seats' into refactor-seats-logic
joeauyeung Jan 5, 2024
758224e
Rename `addVideoCallDataToEvt` to `addVideoCallDataToEvent`
joeauyeung Jan 9, 2024
9bd9494
Refactor `calcAppsStatus`
joeauyeung Jan 9, 2024
649dd1f
Assign `evt` to resutl of `addVideoCallDataToEvent`
joeauyeung Jan 9, 2024
adddd1b
Use prisma.transaction when moving attendees
joeauyeung Jan 9, 2024
3ea89ec
Merge branch 'main' into refactor/handle-seats
joeauyeung Jan 10, 2024
c6e5853
Clean create seat call
joeauyeung Jan 10, 2024
1825944
Use ErrorCode enum
joeauyeung Jan 10, 2024
0bff2e0
Merge branch 'refactor/handle-seats' into refactor-seats-logic
PeerRich Jan 11, 2024
13090ae
Use attendeeRescheduledSeatedBooking function
joeauyeung Jan 11, 2024
4d8d3fa
Await function
joeauyeung Jan 11, 2024
7f972fc
Prevent double triggering of workflows
joeauyeung Jan 11, 2024
9ec2f0e
Use inviteeToAdd in createNewSeat
joeauyeung Jan 11, 2024
6c31a03
Remove unused error code
joeauyeung Jan 11, 2024
6e30d9a
Merge branch 'main' into refactor/handle-seats
exception Jan 12, 2024
8c081fd
Merge branch 'main' into refactor/handle-seats
joeauyeung Jan 12, 2024
d4d5b92
Merge branch 'refactor/handle-seats' into refactor-seats-logic
joeauyeung Jan 12, 2024
9598f42
Merge branch 'main' into refactor-seats-logic
joeauyeung Jan 15, 2024
cf0d5e4
Merge branch 'main' into refactor-seats-logic
joeauyeung Jan 15, 2024
e75c85b
Remove old handleSeats file
joeauyeung Jan 15, 2024
3351d06
Merge branch 'main' into refactor-seats-logic
joeauyeung Jan 15, 2024
25a1ee7
Type fix
joeauyeung Jan 15, 2024
a8404cd
Type fix
joeauyeung Jan 15, 2024
f917220
Type fix
joeauyeung Jan 15, 2024
3e99713
Type fix
joeauyeung Jan 15, 2024
e740806
Type fix
joeauyeung Jan 15, 2024
0ddcde3
Type fix
joeauyeung Jan 15, 2024
ae22585
Type fix
joeauyeung Jan 15, 2024
ae48f30
Type fix
joeauyeung Jan 15, 2024
c233a8c
Fix tests
joeauyeung Jan 15, 2024
d6b7d6c
Merge branch 'main' into refactor-seats-logic
joeauyeung Jan 15, 2024
5d852ac
chore: add error message for no availability (#13230)
Udit-takkar Jan 15, 2024
17ca289
Merge branch 'main' into refactor-seats-logic
joeauyeung Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 3 additions & 294 deletions apps/web/playwright/booking-seats.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { expect } from "@playwright/test";
import { uuid } from "short-uuid";
import { v4 as uuidv4 } from "uuid";

import { randomString } from "@calcom/lib/random";
Expand All @@ -8,7 +7,6 @@ import { BookingStatus } from "@calcom/prisma/enums";

import { test } from "./lib/fixtures";
import {
bookTimeSlot,
createNewSeatedEventType,
selectFirstAvailableTimeSlotNextMonth,
createUserWithSeatedEventAndAttendees,
Expand All @@ -29,75 +27,8 @@ test.describe("Booking with Seats", () => {
await expect(page.locator(`text=Event type updated successfully`)).toBeVisible();
});

test("Multiple Attendees can book a seated event time slot", async ({ users, page }) => {
const slug = "my-2-seated-event";
const user = await users.create({
name: "Seated event user",
eventTypes: [
{
title: "My 2-seated event",
slug,
length: 60,
seatsPerTimeSlot: 2,
seatsShowAttendees: true,
},
],
});
await page.goto(`/${user.username}/${slug}`);

let bookingUrl = "";

await test.step("Attendee #1 can book a seated event time slot", async () => {
await selectFirstAvailableTimeSlotNextMonth(page);
await bookTimeSlot(page);
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
});
await test.step("Attendee #2 can book the same seated event time slot", async () => {
await page.goto(`/${user.username}/${slug}`);
await selectFirstAvailableTimeSlotNextMonth(page);

await page.waitForURL(/bookingUid/);
bookingUrl = page.url();
await bookTimeSlot(page, { email: "[email protected]", name: "Jane Doe" });
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
});
await test.step("Attendee #3 cannot click on the same seated event time slot", async () => {
await page.goto(`/${user.username}/${slug}`);

await page.click('[data-testid="incrementMonth"]');

// TODO: Find out why the first day is always booked on tests
await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).click();
await expect(page.locator('[data-testid="time"][data-disabled="true"]')).toBeVisible();
});
await test.step("Attendee #3 cannot book the same seated event time slot accessing via url", async () => {
await page.goto(bookingUrl);

await bookTimeSlot(page, { email: "[email protected]", name: "Rick" });
await expect(page.locator("[data-testid=success-page]")).toBeHidden();
});

await test.step("User owner should have only 1 booking with 3 attendees", async () => {
// Make sure user owner has only 1 booking with 3 attendees
const bookings = await prisma.booking.findMany({
where: { eventTypeId: user.eventTypes.find((e) => e.slug === slug)?.id },
select: {
id: true,
attendees: {
select: {
id: true,
},
},
},
});

expect(bookings).toHaveLength(1);
expect(bookings[0].attendees).toHaveLength(2);
});
});

test(`Attendees can cancel a seated event time slot`, async ({ page, users, bookings }) => {
const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
test(`Prevent attendees from cancel when having invalid URL params`, async ({ page, users, bookings }) => {
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
{ name: "John First", email: "[email protected]", timeZone: "Europe/Berlin" },
{ name: "Jane Second", email: "[email protected]", timeZone: "Europe/Berlin" },
{ name: "John Third", email: "[email protected]", timeZone: "Europe/Berlin" },
Expand All @@ -120,30 +51,6 @@ test.describe("Booking with Seats", () => {
data: bookingSeats,
});

await test.step("Attendee #1 should be able to cancel their booking", async () => {
await page.goto(`/booking/${booking.uid}?seatReferenceUid=${bookingSeats[0].referenceUid}`);

await page.locator('[data-testid="cancel"]').click();
await page.fill('[data-testid="cancel_reason"]', "Double booked!");
await page.locator('[data-testid="confirm_cancel"]').click();
await page.waitForLoadState("networkidle");

await expect(page).toHaveURL(/\/booking\/.*/);

const cancelledHeadline = page.locator('[data-testid="cancelled-headline"]');
await expect(cancelledHeadline).toBeVisible();

// Old booking should still exist, with one less attendee
const updatedBooking = await prisma.booking.findFirst({
where: { id: bookingSeats[0].bookingId },
include: { attendees: true },
});

const attendeeIds = updatedBooking?.attendees.map(({ id }) => id);
expect(attendeeIds).toHaveLength(2);
expect(attendeeIds).not.toContain(bookingAttendees[0].id);
});

await test.step("Attendee #2 shouldn't be able to cancel booking using only booking/uid", async () => {
await page.goto(`/booking/${booking.uid}`);

Expand All @@ -156,29 +63,6 @@ test.describe("Booking with Seats", () => {
// expect cancel button to don't be in the page
await expect(page.locator("[text=Cancel]")).toHaveCount(0);
});

await test.step("All attendees cancelling should delete the booking for the user", async () => {
// The remaining 2 attendees cancel
for (let i = 1; i < bookingSeats.length; i++) {
await page.goto(`/booking/${booking.uid}?seatReferenceUid=${bookingSeats[i].referenceUid}`);

await page.locator('[data-testid="cancel"]').click();
await page.fill('[data-testid="cancel_reason"]', "Double booked!");
await page.locator('[data-testid="confirm_cancel"]').click();

await expect(page).toHaveURL(/\/booking\/.*/);

const cancelledHeadline = page.locator('[data-testid="cancelled-headline"]');
await expect(cancelledHeadline).toBeVisible();
}

// Should expect old booking to be cancelled
const updatedBooking = await prisma.booking.findFirst({
where: { id: bookingSeats[0].bookingId },
});
expect(updatedBooking).not.toBeNull();
expect(updatedBooking?.status).toBe(BookingStatus.CANCELLED);
});
});

test("Owner shouldn't be able to cancel booking without login in", async ({ page, bookings, users }) => {
Expand Down Expand Up @@ -224,181 +108,6 @@ test.describe("Booking with Seats", () => {
});

test.describe("Reschedule for booking with seats", () => {
test("Should reschedule booking with seats", async ({ page, users, bookings }) => {
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
{ name: "John First", email: `first+seats-${uuid()}@cal.com`, timeZone: "Europe/Berlin" },
{ name: "Jane Second", email: `second+seats-${uuid()}@cal.com`, timeZone: "Europe/Berlin" },
{ name: "John Third", email: `third+seats-${uuid()}@cal.com`, timeZone: "Europe/Berlin" },
]);
const bookingAttendees = await prisma.attendee.findMany({
where: { bookingId: booking.id },
select: {
id: true,
email: true,
},
});

const bookingSeats = [
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
{ bookingId: booking.id, attendeeId: bookingAttendees[2].id, referenceUid: uuidv4() },
];

await prisma.bookingSeat.createMany({
data: bookingSeats,
});

const references = await prisma.bookingSeat.findMany({
where: { bookingId: booking.id },
});

await page.goto(`/reschedule/${references[2].referenceUid}`);

await selectFirstAvailableTimeSlotNextMonth(page);

// expect input to be filled with attendee number 3 data
const thirdAttendeeElement = await page.locator("input[name=name]");
const attendeeName = await thirdAttendeeElement.inputValue();
expect(attendeeName).toBe("John Third");

await page.locator('[data-testid="confirm-reschedule-button"]').click();

// should wait for URL but that path starts with booking/
await page.waitForURL(/\/booking\/.*/);

await expect(page).toHaveURL(/\/booking\/.*/);

// Should expect new booking to be created for John Third
const newBooking = await prisma.booking.findFirst({
where: {
attendees: {
some: { email: bookingAttendees[2].email },
},
},
include: { seatsReferences: true, attendees: true },
});
expect(newBooking?.status).toBe(BookingStatus.PENDING);
expect(newBooking?.attendees.length).toBe(1);
expect(newBooking?.attendees[0].name).toBe("John Third");
expect(newBooking?.seatsReferences.length).toBe(1);

// Should expect old booking to be accepted with two attendees
const oldBooking = await prisma.booking.findFirst({
where: { uid: booking.uid },
include: { seatsReferences: true, attendees: true },
});

expect(oldBooking?.status).toBe(BookingStatus.ACCEPTED);
expect(oldBooking?.attendees.length).toBe(2);
expect(oldBooking?.seatsReferences.length).toBe(2);
});

test("Should reschedule booking with seats and if everyone rescheduled it should be deleted", async ({
page,
users,
bookings,
}) => {
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
{ name: "John First", email: "[email protected]", timeZone: "Europe/Berlin" },
{ name: "Jane Second", email: "[email protected]", timeZone: "Europe/Berlin" },
]);

const bookingAttendees = await prisma.attendee.findMany({
where: { bookingId: booking.id },
select: {
id: true,
},
});

const bookingSeats = [
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
];

await prisma.bookingSeat.createMany({
data: bookingSeats,
});

const references = await prisma.bookingSeat.findMany({
where: { bookingId: booking.id },
});

await page.goto(`/reschedule/${references[0].referenceUid}`);

await selectFirstAvailableTimeSlotNextMonth(page);

await page.locator('[data-testid="confirm-reschedule-button"]').click();

await page.waitForURL(/\/booking\/.*/);

await page.goto(`/reschedule/${references[1].referenceUid}`);

await selectFirstAvailableTimeSlotNextMonth(page);

await page.locator('[data-testid="confirm-reschedule-button"]').click();

// Using waitForUrl here fails the assertion `expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);` probably because waitForUrl is considered complete before waitForNavigation and till that time the booking is not cancelled
await page.waitForURL(/\/booking\/.*/);

// Should expect old booking to be cancelled
const oldBooking = await prisma.booking.findFirst({
where: { uid: booking.uid },
include: {
seatsReferences: true,
attendees: true,
eventType: {
include: { users: true, hosts: true },
},
},
});

expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);
});

test("Should cancel with seats and have no attendees and cancelled", async ({ page, users, bookings }) => {
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
{ name: "John First", email: "[email protected]", timeZone: "Europe/Berlin" },
{ name: "Jane Second", email: "[email protected]", timeZone: "Europe/Berlin" },
]);
await user.apiLogin();

const oldBooking = await prisma.booking.findFirst({
where: { uid: booking.uid },
include: { seatsReferences: true, attendees: true },
});

const bookingAttendees = await prisma.attendee.findMany({
where: { bookingId: booking.id },
select: {
id: true,
},
});

const bookingSeats = [
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
];

await prisma.bookingSeat.createMany({
data: bookingSeats,
});

// Now we cancel the booking as the organizer
await page.goto(`/booking/${booking.uid}?cancel=true`);

await page.locator('[data-testid="confirm_cancel"]').click();

await expect(page).toHaveURL(/\/booking\/.*/);

// Should expect old booking to be cancelled
const updatedBooking = await prisma.booking.findFirst({
where: { uid: booking.uid },
include: { seatsReferences: true, attendees: true },
});

expect(oldBooking?.startTime).not.toBe(updatedBooking?.startTime);
});

test("If rescheduled/cancelled booking with seats it should display the correct number of seats", async ({
page,
users,
Expand Down Expand Up @@ -457,7 +166,7 @@ test.describe("Reschedule for booking with seats", () => {
expect(await page.locator("text=9 / 10 Seats available").count()).toEqual(0);
});

test("Should cancel with seats but event should be still accesible and with one less attendee/seat", async ({
test("Should cancel with seats but event should be still accessible and with one less attendee/seat", async ({
page,
users,
bookings,
Expand Down
5 changes: 5 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2194,6 +2194,11 @@
"uprade_to_create_instant_bookings": "Upgrade to Enterprise and let guests join an instant call that attendees can jump straight into. This is only for team event types",
"dont_want_to_wait": "Don't want to wait?",
"meeting_started": "Meeting Started",
"booking_not_found_error": "Could not find booking",
"booking_seats_full_error": "Booking seats are full",
"missing_payment_credential_error": "Missing payment credentials",
"missing_payment_app_id_error": "Missing payment app id",
"not_enough_available_seats_error": "Booking does not have enough available seats",
"user_redirect_title": "{{username}} is currently away for a brief period of time.",
"user_redirect_description": "In the meantime, {{profile.username}} will be in charge of all the new scheduled meetings on behalf of {{username}}.",
"out_of_office": "Out of office",
Expand Down
Loading
Loading