From 7b94822c8d03b036b4ea855c08c4b69b903232ed Mon Sep 17 00:00:00 2001 From: Vijay Date: Mon, 18 Nov 2024 20:03:24 +0530 Subject: [PATCH] fix: add default schedule while creating org member (#16855) * add default schedule while creating org user * naming as per review comments * added e2e test * move bookTeamEvent and expectPageNotFound to testUtils * Update EventTeamAssignmentTab.tsx * Update AddMembersWithSwitch.tsx * Update utils.ts --------- Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> --- apps/web/playwright/lib/testUtils.ts | 51 ++++++++++++++++++- .../playwright/organization/booking.e2e.ts | 51 +------------------ .../organization-invitation.e2e.ts | 48 ++++++++++++++++- .../components/AddMembersWithSwitch.tsx | 3 ++ .../assignment/EventTeamAssignmentTab.tsx | 2 + .../viewer/teams/inviteMember/utils.ts | 20 ++++++++ 6 files changed, 124 insertions(+), 51 deletions(-) diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index 39d49ee8b21963..15fd6be921fe48 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -9,7 +9,7 @@ import type { Messages } from "mailhog"; import { totp } from "otplib"; import type { Prisma } from "@calcom/prisma/client"; -import { BookingStatus } from "@calcom/prisma/enums"; +import { BookingStatus, SchedulingType } from "@calcom/prisma/enums"; import type { IntervalLimit } from "@calcom/types/Calendar"; import type { createEmailsFixture } from "../fixtures/emails"; @@ -464,3 +464,52 @@ export async function confirmReschedule(page: Page, url = "/api/book/event") { action: () => page.locator('[data-testid="confirm-reschedule-button"]').click(), }); } + +export async function bookTeamEvent({ + page, + team, + event, + teamMatesObj, + opts, +}: { + page: Page; + team: { + slug: string | null; + name: string | null; + }; + event: { slug: string; title: string; schedulingType: SchedulingType | null }; + teamMatesObj?: { name: string }[]; + opts?: { attendeePhoneNumber?: string }; +}) { + // Note that even though the default way to access a team booking in an organization is to not use /team in the URL, but it isn't testable with playwright as the rewrite is taken care of by Next.js config which can't handle on the fly org slug's handling + // So, we are using /team in the URL to access the team booking + // There are separate tests to verify that the next.config.js rewrites are working + // Also there are additional checkly tests that verify absolute e2e flow. They are in __checks__/organization.spec.ts + await page.goto(`/team/${team.slug}/${event.slug}`); + + await selectFirstAvailableTimeSlotNextMonth(page); + await bookTimeSlot(page, opts); + await expect(page.getByTestId("success-page")).toBeVisible(); + + // The title of the booking + if (event.schedulingType === SchedulingType.ROUND_ROBIN && teamMatesObj) { + const bookingTitle = await page.getByTestId("booking-title").textContent(); + + const isMatch = teamMatesObj?.some((teamMate) => { + const expectedTitle = `${event.title} between ${teamMate.name} and ${testName}`; + return expectedTitle.trim() === bookingTitle?.trim(); + }); + + expect(isMatch).toBe(true); + } else { + const BookingTitle = `${event.title} between ${team.name} and ${testName}`; + await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle); + } + // The booker should be in the attendee list + await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName); +} + +export async function expectPageToBeNotFound({ page, url }: { page: Page; url: string }) { + await page.goto(`${url}`); + await expect(page.getByTestId(`404-page`)).toBeVisible(); +} diff --git a/apps/web/playwright/organization/booking.e2e.ts b/apps/web/playwright/organization/booking.e2e.ts index 1b2903a6eafd8e..c0a8eca4f40684 100644 --- a/apps/web/playwright/organization/booking.e2e.ts +++ b/apps/web/playwright/organization/booking.e2e.ts @@ -9,8 +9,10 @@ import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; import { test } from "../lib/fixtures"; import { + bookTeamEvent, bookTimeSlot, doOnOrgDomain, + expectPageToBeNotFound, selectFirstAvailableTimeSlotNextMonth, submitAndWaitForResponse, testName, @@ -604,55 +606,6 @@ async function bookUserEvent({ await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName); } -async function bookTeamEvent({ - page, - team, - event, - teamMatesObj, - opts, -}: { - page: Page; - team: { - slug: string | null; - name: string | null; - }; - event: { slug: string; title: string; schedulingType: SchedulingType | null }; - teamMatesObj?: { name: string }[]; - opts?: { attendeePhoneNumber?: string }; -}) { - // Note that even though the default way to access a team booking in an organization is to not use /team in the URL, but it isn't testable with playwright as the rewrite is taken care of by Next.js config which can't handle on the fly org slug's handling - // So, we are using /team in the URL to access the team booking - // There are separate tests to verify that the next.config.js rewrites are working - // Also there are additional checkly tests that verify absolute e2e flow. They are in __checks__/organization.spec.ts - await page.goto(`/team/${team.slug}/${event.slug}`); - - await selectFirstAvailableTimeSlotNextMonth(page); - await bookTimeSlot(page, opts); - await expect(page.getByTestId("success-page")).toBeVisible(); - - // The title of the booking - if (event.schedulingType === SchedulingType.ROUND_ROBIN && teamMatesObj) { - const bookingTitle = await page.getByTestId("booking-title").textContent(); - - const isMatch = teamMatesObj?.some((teamMate) => { - const expectedTitle = `${event.title} between ${teamMate.name} and ${testName}`; - return expectedTitle.trim() === bookingTitle?.trim(); - }); - - expect(isMatch).toBe(true); - } else { - const BookingTitle = `${event.title} between ${team.name} and ${testName}`; - await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle); - } - // The booker should be in the attendee list - await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName); -} - -async function expectPageToBeNotFound({ page, url }: { page: Page; url: string }) { - await page.goto(`${url}`); - await expect(page.getByTestId(`404-page`)).toBeVisible(); -} - const markPhoneNumberAsRequiredAndEmailAsOptional = async (page: Page, eventId: number) => { // Make phone as required await markPhoneNumberAsRequiredField(page, eventId); diff --git a/apps/web/playwright/organization/organization-invitation.e2e.ts b/apps/web/playwright/organization/organization-invitation.e2e.ts index 21333d12a2720d..881e4f426abb5e 100644 --- a/apps/web/playwright/organization/organization-invitation.e2e.ts +++ b/apps/web/playwright/organization/organization-invitation.e2e.ts @@ -3,11 +3,12 @@ import { expect } from "@playwright/test"; import prisma from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/client"; +import { SchedulingType } from "@calcom/prisma/enums"; import { moveUserToOrg } from "@lib/orgMigration"; import { test } from "../lib/fixtures"; -import { getInviteLink } from "../lib/testUtils"; +import { bookTeamEvent, doOnOrgDomain, expectPageToBeNotFound, getInviteLink } from "../lib/testUtils"; import { expectInvitationEmailToBeReceived } from "./expects"; test.describe.configure({ mode: "parallel" }); @@ -417,6 +418,51 @@ test.describe("Organization", () => { }); }); }); + + test("can book an event with auto accepted invitee (not completed on-boarding) added as fixed host.", async ({ + page, + users, + }) => { + const orgOwner = await users.create(undefined, { + hasTeam: true, + isOrg: true, + hasSubteam: true, + isOrgVerified: true, + isDnsSetup: true, + orgRequestedSlug: "example", + schedulingType: SchedulingType.ROUND_ROBIN, + }); + const { team: org } = await orgOwner.getOrgMembership(); + const { team } = await orgOwner.getFirstTeamMembership(); + + await orgOwner.apiLogin(); + await page.goto(`/settings/teams/${team.id}/members`); + const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" }); + await inviteAnEmail(page, invitedUserEmail, true); + + //add invitee as fixed host to team event + const teamEvent = await orgOwner.getFirstTeamEvent(team.id); + await page.goto(`/event-types/${teamEvent.id}?tabName=team`); + await page.locator('[data-testid="fixed-hosts-switch"]').click(); + await page.locator('[data-testid="fixed-hosts-select"]').click(); + await page.locator(`text="${invitedUserEmail}"`).click(); + await page.locator('[data-testid="update-eventtype"]').click(); + await page.waitForResponse("/api/trpc/eventTypes/update?batch=1"); + + await expectPageToBeNotFound({ page, url: `/team/${team.slug}/${teamEvent.slug}` }); + await doOnOrgDomain( + { + orgSlug: org.slug, + page, + }, + async ({ page, goToUrlWithErrorHandling }) => { + const result = await goToUrlWithErrorHandling(`/team/${team.slug}/${teamEvent.slug}`); + await bookTeamEvent({ page, team, event: teamEvent }); + await expect(page.getByText(invitedUserEmail, { exact: true })).toBeVisible(); + return { url: result.url }; + } + ); + }); }); }); diff --git a/packages/features/eventtypes/components/AddMembersWithSwitch.tsx b/packages/features/eventtypes/components/AddMembersWithSwitch.tsx index 4a136f766861bd..1fcae7f35a0657 100644 --- a/packages/features/eventtypes/components/AddMembersWithSwitch.tsx +++ b/packages/features/eventtypes/components/AddMembersWithSwitch.tsx @@ -111,6 +111,7 @@ const AddMembersWithSwitch = ({ placeholder = "", containerClassName = "", isRRWeightsEnabled, + ...rest }: { value: Host[]; onChange: (hosts: Host[]) => void; @@ -123,6 +124,7 @@ const AddMembersWithSwitch = ({ placeholder?: string; containerClassName?: string; isRRWeightsEnabled?: boolean; + "data-testid"?: string; }) => { const { t } = useLocale(); const { setValue } = useFormContext(); @@ -144,6 +146,7 @@ const AddMembersWithSwitch = ({ )} {!assignAllTeamMembers || !automaticAddAllEnabled ? ( ) : (
checkInputEmailIsValid(invitation.usernameOrEmail)); @@ -307,6 +310,8 @@ export async function createNewUsersConnectToOrgIfExists({ const isBecomingAnOrgMember = parentId || isOrg; + const defaultAvailability = getAvailabilityFromSchedule(DEFAULT_SCHEDULE); + const t = await getTranslation(language ?? "en", "common"); const createdUser = await tx.user.create({ data: { username: isBecomingAnOrgMember ? orgMemberUsername : regularTeamMemberUsername, @@ -340,6 +345,20 @@ export async function createNewUsersConnectToOrgIfExists({ accepted: autoAccept, // If the user is invited to a child team, they are automatically accepted }, }, + schedules: { + create: { + name: t("default_schedule_name"), + availability: { + createMany: { + data: defaultAvailability.map((schedule) => ({ + days: schedule.days, + startTime: schedule.startTime, + endTime: schedule.endTime, + })), + }, + }, + }, + }, }, }); @@ -908,6 +927,7 @@ export async function handleNewUsersInvites({ orgConnectInfoByUsernameOrEmail, autoAcceptEmailDomain: autoAcceptEmailDomain, parentId: team.parentId, + language, }); const sendVerifyEmailsPromises = invitationsForNewUsers.map((invitation) => {