calcom / cal.com

Scheduling infrastructure for absolutely everyone.
https://cal.com
Other
32.07k stars 7.88k forks source link

[CAL-3915] Unable to create managed event type for team members via api v1 #15179

Open Shaik-Sirajuddin opened 5 months ago

Shaik-Sirajuddin commented 5 months ago

Issue Summary

When creating a managed event type via api , the even type data has inconsistency with the one created from web platform. Also its not possible to create a managed event type for team members , while following steps from cal com api reference

Steps to Reproduce

1.Create a managed event type from postman api example cal

  1. Having same post body , append userId field setting it to one of team members id
  2. Admin required error is through ( apikey of team admin is used )

Actual Results

Expected Results

Evidence

Loom Video : View

CAL-3915

dosubot[bot] commented 5 months ago

To create a managed event type for team members via the API v1 without encountering the "Admin required" error, ensure the following:

  1. Permissions: The user making the request must be an admin or owner of the team.
  2. Request Body: Include a valid teamId in the request body.

Here is an example of a request body for creating a team event type:

{
  "title": "Tennis class",
  "slug": "tennis-class-{{$guid}}",
  "length": 60,
  "hidden": false,
  "position": 0,
  "teamId": 3,
  "requiresConfirmation": true,
  "schedulingType": "MANAGED",
  "price": 0,
  "currency": "usd",
  "description": "A test event type",
  "metadata": {}
}

Ensure that the user making this request has the necessary permissions to access the team with the specified teamId.

Additionally, you can refer to the following test case to understand how managed event types are created and managed in the system:

import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";

import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";

import { test } from "./lib/fixtures";
import {
  bookTimeSlot,
  fillStripeTestCheckout,
  localize,
  selectFirstAvailableTimeSlotNextMonth,
} from "./lib/testUtils";

test.afterAll(({ users }) => users.deleteAll());

test.describe("Managed Event Types", () => {
  test("Can create managed event type", async ({ page, users }) => {
    // Creating the owner user of the team
    const adminUser = await users.create();
    // Creating the member user of the team
    const memberUser = await users.create();
    // First we work with owner user, logging in
    await adminUser.apiLogin();

    // Let's create a team
    await page.goto("/settings/teams/new");

    await test.step("Managed event option exists for team admin", async () => {
      // Filling team creation form wizard
      await page.locator('input[name="name"]').fill(`${adminUser.username}'s Team`);
      await page.click("[type=submit]");
      if (IS_TEAM_BILLING_ENABLED) await fillStripeTestCheckout(page);
      await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
      await page.getByTestId("new-member-button").click();
      await page.locator('[placeholder="email\\@example\\.com"]').fill(`${memberUser.username}@example.com`);
      await page.getByTestId("invite-new-member-button").click();
      await page.getByTestId("pending-member-list").locator("li:nth-child(2)").waitFor();
      await page.locator("[data-testid=publish-button]").click();
      await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
      await page.goto("/event-types");
      await page.getByTestId("new-event-type").click();
      await page.getByTestId("option-team-1").click();
      await expect(page.locator('button[value="MANAGED"]')).toBeVisible();
      await page.click('button[value="MANAGED"]');
      await page.fill("[name=title]", "managed");
      await page.click("[type=submit]");
      await page.waitForURL("event-types/**");
    });

    await test.step("Managed event type has unlocked fields for admin", async () => {
      await page.getByTestId("update-eventtype").waitFor();
      await expect(page.locator('input[name="title"]')).toBeEditable();
      await expect(page.locator('input[name="slug"]')).toBeEditable();
      await expect(page.locator('input[name="length"]')).toBeEditable();
      await adminUser.apiLogin();
    });

    await test.step("Managed event type exists for added member", async () => {
      await memberUser.apiLogin();
      await page.goto("/teams");
      await page.locator('button[data-testid^="accept-invitation"]').click();
      await page.getByText("Member").waitFor();
      await page.goto("/auth/logout");
      await adminUser.apiLogin();
      await page.goto("/event-types");
      await page.getByTestId("event-types").locator('a[title="managed"]').click();
      await page.getByTestId("vertical-tab-assignment").click();
      await page.getByTestId("assignment-dropdown").click();
      await page.getByTestId(`select-option-${memberUser.id}`).click();
      await page.locator('[type="submit"]').click();
      await page.getByTestId("toast-success").waitFor();
    });

    await test.step("Managed event type can use Organizer's default app as location", async () => {
      await page.getByTestId("vertical-tab-event_setup_tab_title").click();
      await page.locator("#location-select").click();
      const optionText = (await localize("en"))("organizer_default_conferencing_app");
      await page.locator(`text=${optionText}`).click();
      await page.locator("[data-testid=update-eventtype]").click();
      await page.getByTestId("toast-success").waitFor();
      await page.waitForLoadState("networkidle");
      await page.getByTestId("vertical-tab-assignment").click();
      await gotoBookingPage(page);
      await selectFirstAvailableTimeSlotNextMonth(page);
      await bookTimeSlot(page);
      await expect(page.getByTestId("success-page")).toBeVisible();
    });

    await test.step("Managed event type has locked fields for added member", async () => {
      await adminUser.logout();
      await memberUser.apiLogin();
      await page.goto("/event-types");
      await page.getByTestId("event-types").locator('a[title="managed"]').click();
      await page.waitForURL("event-types/**");
      await expect(page.locator('input[name="title"]')).not.toBeEditable();
      await expect(page.locator('input[name="slug"]')).not.toBeEditable();
      await expect(page.locator('input[name="length"]')).not toBeEditable();
      await page.goto("/auth/logout");
    });

    await test.step("Managed event type provides discrete field lock/unlock state for admin", async () => {
      await adminUser.apiLogin();
      await page.goto("/event-types");
      await page.getByTestId("event-types").locator('a[title="managed"]').click();
      await page.waitForURL("event-types/**");
      const titleLockIndicator = page.getByTestId("locked-indicator-title");
      await expect(titleLockIndicator).toBeVisible();
      await expect(titleLockIndicator.locator("[data-state='checked']")).toHaveCount(1);
      titleLockIndicator.click();
      await expect(titleLockIndicator.locator("[data-state='checked']")).toHaveCount(0);
      await expect(titleLockIndicator.locator("[data-state='unchecked']")).toHaveCount(1);
      await page.locator('[type="submit"]').click();
      await page.waitForLoadState("networkidle");
      await page.goto("/auth/logout");
    });

    await test.step("Managed event type shows discretionally unlocked field to member", async () => {
      await memberUser.apiLogin();
      await page.goto("/event-types");
      await page.getByTestId("event-types").locator('a[title="managed"]').click();
      await page.waitForURL("event-types/**");
      await expect(page.locator('input[name="title"]')).toBeEditable();
      await page.waitForLoadState("networkidle");
      await page.goto("/auth/logout");
    });

    await test.step("Managed event type should only update the unlocked fields modified by Admin", async () => {
      await memberUser.apiLogin();
      await page.goto("/event-types");
      await page.getByTestId("event-types").locator('a[title="managed"]').click();
      await page.waitForURL("event-types/**");
      await expect(page.locator('input[name="title"]')).toBeEditable();
      await page.locator('input[name="title"]').fill(`Managed Event Title`);
      await page.locator('[type="submit"]').click();
      await page.getByTestId("toast-success").waitFor();
      await page.waitForLoadState("networkidle");
      await page.goto("/auth/logout");
      await adminUser.apiLogin();
      await page.goto("/event-types");
      await page.getByTestId("event-types").locator('a[title="managed"]').click();
      await page.waitForURL("event-types/**");
      await page.locator('input[name="length"]').fill(`45`);
      await page.locator('[type="submit"]').click();
      await page.getByTestId("toast-success").waitFor();
      await page.waitForLoadState("networkidle");
      await page.goto("/auth/logout");
      await memberUser.apiLogin();
      await page.goto("/event-types");
      await page.getByTestId("event-types").locator('a[title="Managed Event Title"]').click();
      await page.waitForURL("event-types/**");
      expect(await page.locator("[data-testid=duration]").getAttribute("value")).toBe("45");
      expect(await page.locator(`input[name="title"]`).getAttribute("value")).toBe(`Managed Event Title`);
      await page.locator('input[name="title"]').fill(`managed`);
      await page.locator('[type="submit"]').click();
      await page.getByTestId("toast-success").waitFor();
    });
  });
});

async function gotoBookingPage(page: Page) {
  const previewLink = await page.getByTestId("preview-button").getAttribute("href");
  await page.goto(previewLink ?? "");
}

This test case demonstrates the process of creating a managed event type, assigning it to team members, and verifying the permissions and field lock/unlock states.

#### Details **Sources** - [Creating a MANAGED event type is not possible for Non Admins in the API on cal.com](https://github.com/calcom/cal.com/issues/12351) - [apps/api/v1/pages/api/event-types/_post.ts](https://github.com/calcom/cal.com/blob/main/apps/api/v1/pages/api/event-types/_post.ts) - [apps/api/v1/pages/api/event-types/_utils/checkTeamEventEditPermission.ts](https://github.com/calcom/cal.com/blob/main/apps/api/v1/pages/api/event-types/_utils/checkTeamEventEditPermission.ts) - [apps/web/playwright/managed-event-types.e2e.ts](https://github.com/calcom/cal.com/blob/main/apps/web/playwright/managed-event-types.e2e.ts)
**About Dosu** This response is meant to be useful and save you time. It is **not meant to be a precise solution**, but rather a starting point for your own research. Help me be more useful! Please leave a 👍 if this is helpful and 👎 if it is irrelevant.

To continue the conversation, mention @dosu.