Skip to content

Commit

Permalink
fix: add default schedule while creating org member (#16855)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
vijayraghav-io and anikdhabal authored Nov 18, 2024
1 parent 9aa8f8a commit 7b94822
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 51 deletions.
51 changes: 50 additions & 1 deletion apps/web/playwright/lib/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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();
}
51 changes: 2 additions & 49 deletions apps/web/playwright/organization/booking.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";

import { test } from "../lib/fixtures";
import {
bookTeamEvent,
bookTimeSlot,
doOnOrgDomain,
expectPageToBeNotFound,
selectFirstAvailableTimeSlotNextMonth,
submitAndWaitForResponse,
testName,
Expand Down Expand Up @@ -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);
Expand Down
48 changes: 47 additions & 1 deletion apps/web/playwright/organization/organization-invitation.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Expand Down Expand Up @@ -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 };
}
);
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const AddMembersWithSwitch = ({
placeholder = "",
containerClassName = "",
isRRWeightsEnabled,
...rest
}: {
value: Host[];
onChange: (hosts: Host[]) => void;
Expand All @@ -123,6 +124,7 @@ const AddMembersWithSwitch = ({
placeholder?: string;
containerClassName?: string;
isRRWeightsEnabled?: boolean;
"data-testid"?: string;
}) => {
const { t } = useLocale();
const { setValue } = useFormContext<FormValues>();
Expand All @@ -144,6 +146,7 @@ const AddMembersWithSwitch = ({
)}
{!assignAllTeamMembers || !automaticAddAllEnabled ? (
<CheckedHostField
data-testid={rest["data-testid"]}
value={value}
onChange={onChange}
isFixed={isFixed}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const FixedHosts = ({
</>
) : (
<SettingsToggle
data-testid="fixed-hosts-switch"
toggleSwitchAtTheEnd={true}
title={t("fixed_hosts")}
description={FixedHostHelper}
Expand All @@ -175,6 +176,7 @@ const FixedHosts = ({
childrenClassName="lg:ml-0">
<div className="border-subtle flex flex-col gap-6 rounded-bl-md rounded-br-md border border-t-0 px-6">
<AddMembersWithSwitch
data-testid="fixed-hosts-select"
teamMembers={teamMembers}
value={value}
onChange={onChange}
Expand Down
20 changes: 20 additions & 0 deletions packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { TFunction } from "next-i18next";

import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
import { sendTeamInviteEmail } from "@calcom/emails";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { ENABLE_PROFILE_SWITCHER, WEBAPP_URL } from "@calcom/lib/constants";
import { createAProfileForAnExistingUser } from "@calcom/lib/createAProfileForAnExistingUser";
import logger from "@calcom/lib/logger";
Expand Down Expand Up @@ -272,6 +273,7 @@ export async function createNewUsersConnectToOrgIfExists({
timeFormat,
weekStart,
timeZone,
language,
}: {
invitations: Invitation[];
isOrg: boolean;
Expand All @@ -283,6 +285,7 @@ export async function createNewUsersConnectToOrgIfExists({
timeFormat?: number;
weekStart?: string;
timeZone?: string;
language: string;
}) {
// fail if we have invalid emails
invitations.forEach((invitation) => checkInputEmailIsValid(invitation.usernameOrEmail));
Expand All @@ -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,
Expand Down Expand Up @@ -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,
})),
},
},
},
},
},
});

Expand Down Expand Up @@ -908,6 +927,7 @@ export async function handleNewUsersInvites({
orgConnectInfoByUsernameOrEmail,
autoAcceptEmailDomain: autoAcceptEmailDomain,
parentId: team.parentId,
language,
});

const sendVerifyEmailsPromises = invitationsForNewUsers.map((invitation) => {
Expand Down

0 comments on commit 7b94822

Please sign in to comment.