Skip to content

Conversation

@anikdhabal
Copy link
Contributor

@anikdhabal anikdhabal commented Sep 24, 2025

What does this PR do?

The changes scope team slug uniqueness to the team’s parent context by constructing a dynamic where clause using parentId in both the API route and TRPC update handler, which now throws a CONFLICT error on collisions. Playwright fixtures are extended to accept an optional teamSlug and add getAllTeamMembership. E2E tests are added to validate slug rules across organizations and non-organization teams, and a test helper import is updated. A data-testid is added to the team profile update button to support tests.

@anikdhabal anikdhabal requested a review from a team September 24, 2025 08:39
@anikdhabal anikdhabal requested a review from a team as a code owner September 24, 2025 08:39
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

Walkthrough

Slug uniqueness check during team updates was moved to TeamRepository.isSlugAvailableForUpdate and is performed only when a new slug is provided; conflicts now return a 409 in the API patch and a TRPC CONFLICT in the update handler. Server-side variable names were simplified ( _teamteam ) and update responses now return the updated team object. Playwright fixtures gained an optional teamSlug parameter and a getAllTeamMembership helper; new e2e tests validate slug rules across org boundaries. The team profile submit Button now has data-testid="update-team-profile".

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title uses a conventional commit prefix and directly references the primary change—allowing teams to share slugs in different contexts by fixing the uniqueness constraint—thus accurately tying to the core update. Although the phrasing “diff cases” is slightly informal, it still conveys the intent of scoped slug validation. This makes the title sufficiently descriptive for a teammate to grasp the main purpose of the PR.
Description Check ✅ Passed The description clearly outlines all the key changes—including parent‐scoped slug uniqueness via parentId, updates to TRPC and API routes, fixture enhancements, end‐to‐end test additions, helper import corrections, and a new data-testid. Each item directly corresponds to the modifications summarized in the raw summary, indicating that the description is on-topic and relevant. Therefore, the description meets the criteria for being related and informative.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch allow-team-with-same-slug

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cc9120e and a274c1f.

📒 Files selected for processing (1)
  • packages/lib/server/repository/team.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/lib/server/repository/team.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@graphite-app graphite-app bot requested a review from a team September 24, 2025 08:39
@vercel
Copy link

vercel bot commented Sep 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Oct 1, 2025 9:38am
cal-eu Ignored Ignored Oct 1, 2025 9:38am

@keithwillcode keithwillcode added the core area: core, team members only label Sep 24, 2025
@dosubot dosubot bot added the teams area: teams, round robin, collective, managed event-types label Sep 24, 2025
@anikdhabal anikdhabal changed the title fix: allow team with same slug in diff cases fix: allow team with same slug for diff cases Sep 24, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/playwright/teams.e2e.ts (1)

7-14: testName is used but not imported — tests will fail at runtime

Re-add testName import from testUtils (assuming it’s still exported).

-import {
+import {
   bookTimeSlot,
   confirmReschedule,
   fillStripeTestCheckout,
   selectFirstAvailableTimeSlotNextMonth,
-  submitAndWaitForResponse,
+  submitAndWaitForResponse,
+  testName,
 } from "./lib/testUtils";
🧹 Nitpick comments (3)
packages/features/ee/teams/pages/team-profile-view.tsx (2)

201-202: Localize toast string

Use t() for the “Copied to clipboard” message to comply with i18n guidelines.

-                showToast("Copied to clipboard", "success");
+                showToast(t("copied_to_clipboard"), "success");

495-495: Prefer named export over default

Improves tree-shaking and refactoring.

-export default ProfileView;
+export { ProfileView };
packages/trpc/server/routers/viewer/teams/update.handler.ts (1)

34-50: Make slug uniqueness check case-insensitive and exclude self

For parity with the REST handler and to avoid case-based collisions, compare case-insensitive and exclude the current team in the query rather than post-filtering.

-  if (input.slug) {
-    const whereClause: Prisma.TeamWhereInput = { slug: input.slug, parentId: null };
+  if (input.slug) {
+    const whereClause: Prisma.TeamWhereInput = {
+      slug: { equals: input.slug, mode: "insensitive" },
+      parentId: null,
+      NOT: { id: input.id },
+    };
     const currentTeam = await prisma.team.findUnique({
       where: { id: input.id },
       select: { parentId: true },
     });
 
     if (currentTeam?.parentId) {
       whereClause.parentId = currentTeam?.parentId;
     }
 
-    const userConflict = await prisma.team.findMany({
-      where: whereClause,
-    });
-    if (userConflict.some((t) => t.id !== input.id)) {
-      throw new TRPCError({ code: "CONFLICT", message: "Slug already in use." });
-    }
+    const conflict = await prisma.team.findFirst({ where: whereClause });
+    if (conflict) throw new TRPCError({ code: "CONFLICT", message: "Slug already in use." });
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b223937 and 517541f.

📒 Files selected for processing (5)
  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts (1 hunks)
  • apps/web/playwright/fixtures/users.ts (5 hunks)
  • apps/web/playwright/teams.e2e.ts (2 hunks)
  • packages/features/ee/teams/pages/team-profile-view.tsx (1 hunks)
  • packages/trpc/server/routers/viewer/teams/update.handler.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts
  • apps/web/playwright/teams.e2e.ts
  • packages/trpc/server/routers/viewer/teams/update.handler.ts
  • apps/web/playwright/fixtures/users.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts
  • packages/features/ee/teams/pages/team-profile-view.tsx
  • apps/web/playwright/teams.e2e.ts
  • packages/trpc/server/routers/viewer/teams/update.handler.ts
  • apps/web/playwright/fixtures/users.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts
  • packages/features/ee/teams/pages/team-profile-view.tsx
  • apps/web/playwright/teams.e2e.ts
  • packages/trpc/server/routers/viewer/teams/update.handler.ts
  • apps/web/playwright/fixtures/users.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/ee/teams/pages/team-profile-view.tsx
🧠 Learnings (1)
📚 Learning: 2025-08-26T20:23:28.396Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/calAIPhone/providers/retellAI/services/AgentService.ts:83-88
Timestamp: 2025-08-26T20:23:28.396Z
Learning: In calcom/cal.com PR #22995, the workflow update handler in packages/trpc/server/routers/viewer/workflows/update.handler.ts includes workflow-level authorization via isAuthorized(userWorkflow, ctx.user.id, "workflow.update") which validates the user can update the workflow before calling updateToolsFromAgentId (per maintainer Udit-takkar).

Applied to files:

  • packages/trpc/server/routers/viewer/teams/update.handler.ts
🧬 Code graph analysis (3)
packages/features/ee/teams/pages/team-profile-view.tsx (1)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
apps/web/playwright/teams.e2e.ts (1)
apps/web/playwright/lib/testUtils.ts (1)
  • submitAndWaitForResponse (488-497)
packages/trpc/server/routers/viewer/teams/update.handler.ts (1)
packages/platform/libraries/index.ts (1)
  • TRPCError (66-66)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Check for E2E label
🔇 Additional comments (6)
packages/features/ee/teams/pages/team-profile-view.tsx (1)

469-475: Test id addition on submit button — LGTM

The data-testid enables stable e2e selection without altering behavior.

apps/web/playwright/teams.e2e.ts (2)

343-346: Confirm waitForResponse URL matching for tRPC requests

Using a plain string may not match if the request has query params (e.g., ?batch=1). Ensure the helper matches via predicate or regex, or confirm your tRPC client uses the exact path.

If needed, update the helper to accept a predicate/RegExp; otherwise these waits can flake or never resolve.

Also applies to: 404-406


305-408: New slug validation e2e coverage — solid scenarios

Covers cross-org allow, intra-org deny, and org vs non-org allow. Good alignment with backend changes.

Please re-run the suite after fixing the import and status code to ensure green.

apps/web/playwright/fixtures/users.ts (2)

163-177: Deterministic team slug support — LGTM

Optional teamSlug param and precedence in slug derivation look good and improve test reliability.

Also applies to: 182-185


720-741: getAllTeamMembership helper — LGTM

Useful addition; filters out org teams and parses metadata.

apps/api/v1/pages/api/teams/[teamId]/_patch.ts (1)

58-66: Ensure DB uniqueness aligns with new scoping (slug within parent context)

If the DB still enforces a global unique constraint on team.slug, writes will fail even if app logic allows duplicates across different parents. Confirm composite unique (lowercased) on (parentId, slug) or equivalent case-insensitive index.

Copy link
Member

@hariombalhara hariombalhara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments

@github-actions github-actions bot marked this pull request as draft September 24, 2025 09:08
@github-actions
Copy link
Contributor

github-actions bot commented Sep 24, 2025

E2E results are ready!

@anikdhabal anikdhabal marked this pull request as ready for review September 24, 2025 12:07
@dosubot dosubot bot added the 🐛 bug Something isn't working label Sep 24, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/lib/server/repository/team.ts (1)

401-420: Tighten slug check: exclude self in WHERE, use case-insensitive compare, and avoid findMany

Current approach does an extra pass in JS and may be case-sensitive. Recommend building the exclusion into the query, using findFirst, and comparing case-insensitively.

  • Please confirm if slug uniqueness should be case-insensitive (previous checks used mode: "insensitive"). If yes, apply the change below.
-  async isSlugAvailableForUpdate({ id, slug }: { id: number; slug: string }) {
-    const whereClause: Prisma.TeamWhereInput = { slug, parentId: null };
-
-    const currentTeam = await this.prismaClient.team.findUnique({
-      where: { id },
-      select: { parentId: true },
-    });
-
-    if (currentTeam?.parentId) {
-      whereClause.parentId = currentTeam.parentId;
-    }
-
-    const conflictingTeams = await this.prismaClient.team.findMany({
-      where: whereClause,
-      select: { id: true },
-    });
-
-    return !conflictingTeams.some((team) => team.id !== id);
-  }
+  async isSlugAvailableForUpdate({ id, slug }: { id: number; slug: string }) {
+    const currentTeam = await this.prismaClient.team.findUnique({
+      where: { id },
+      select: { parentId: true },
+    });
+
+    const conflict = await this.prismaClient.team.findFirst({
+      where: {
+        slug: { equals: slug, mode: "insensitive" },
+        parentId: currentTeam?.parentId ?? null,
+        NOT: { id },
+      },
+      select: { id: true },
+    });
+
+    return !conflict;
+  }

Additionally, please confirm intended scope for root-level teams (parentId null). As written, this enforces global uniqueness across both organizations and standalone teams with parentId null—verify this aligns with “allow same slug for diff cases.”

packages/trpc/server/routers/viewer/teams/update.handler.ts (1)

34-40: Optional: move slug check after fetching prevTeam to short‑circuit unchanged slugs and return NOT_FOUND first

This avoids an extra DB round-trip and ensures 404 beats 409 for invalid ids.

Proposed shape outside current hunk:

  • Fetch prevTeam
  • If not found, throw NOT_FOUND
  • If input.slug and input.slug !== prevTeam.slug, then run repository check
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 517541f and bcda920.

📒 Files selected for processing (4)
  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts (2 hunks)
  • apps/web/playwright/teams.e2e.ts (2 hunks)
  • packages/lib/server/repository/team.ts (1 hunks)
  • packages/trpc/server/routers/viewer/teams/update.handler.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/playwright/teams.e2e.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/server/repository/team.ts
  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts
  • packages/trpc/server/routers/viewer/teams/update.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/server/repository/team.ts
  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts
  • packages/trpc/server/routers/viewer/teams/update.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/server/repository/team.ts
  • apps/api/v1/pages/api/teams/[teamId]/_patch.ts
  • packages/trpc/server/routers/viewer/teams/update.handler.ts
🧠 Learnings (1)
📚 Learning: 2025-08-23T13:45:16.529Z
Learnt from: shaun-ak
PR: calcom/cal.com#23233
File: packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx:100-101
Timestamp: 2025-08-23T13:45:16.529Z
Learning: In Cal.com's team structure, the organization team (root team) is identified by having no parentId (!team.parentId), while sub-teams within an organization have a parentId. Use selectedUser?.teams?.find((team) => !team.parentId) to get the organization team, not by matching org?.id.

Applied to files:

  • packages/trpc/server/routers/viewer/teams/update.handler.ts
🧬 Code graph analysis (2)
apps/api/v1/pages/api/teams/[teamId]/_patch.ts (1)
packages/lib/server/repository/team.ts (1)
  • TeamRepository (170-421)
packages/trpc/server/routers/viewer/teams/update.handler.ts (1)
packages/lib/server/repository/team.ts (1)
  • TeamRepository (170-421)
🪛 GitHub Actions: PR Update
packages/trpc/server/routers/viewer/teams/update.handler.ts

[error] 36-36: TypeScript error TS18004: No value exists in scope for the shorthand property 'id'. Either declare one or provide an initializer.

⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache

@Udit-takkar Udit-takkar dismissed stale reviews from hariombalhara and themself September 29, 2025 14:08

done

Udit-takkar
Udit-takkar previously approved these changes Sep 29, 2025
Copy link
Contributor

@Udit-takkar Udit-takkar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

volnei
volnei previously approved these changes Sep 29, 2025
emrysal
emrysal previously approved these changes Sep 29, 2025
Copy link
Contributor

@emrysal emrysal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

const slugIndex = index ? `-count-${index}` : "";
const slug =
orgRequestedSlug ?? `${isOrg ? "org" : "team"}-${workerInfo.workerIndex}-${Date.now()}${slugIndex}`;
teamSlug ??
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: can we abstract this logic into its own function instead of just using the nullish coalescing operator? this could get messy really fast if in future we might need to update the logic and add some stuff in here

Copy link
Contributor

@Ryukemeister Ryukemeister left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@emrysal emrysal merged commit ff264d6 into main Oct 1, 2025
63 of 65 checks passed
@emrysal emrysal deleted the allow-team-with-same-slug branch October 1, 2025 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 bug Something isn't working core area: core, team members only ready-for-e2e size/L teams area: teams, round robin, collective, managed event-types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants