Skip to content

Comments

feat: make orgId optional for user and team scopes in /insights and /insights/routing#23912

Merged
eunjae-lee merged 11 commits intomainfrom
devin/remove-orgid-user-scope-1758116265
Sep 19, 2025
Merged

feat: make orgId optional for user and team scopes in /insights and /insights/routing#23912
eunjae-lee merged 11 commits intomainfrom
devin/remove-orgid-user-scope-1758116265

Conversation

@eunjae-lee
Copy link
Contributor

What does this PR do?

This PR makes the orgId parameter optional for both user and team scopes in the InsightsRoutingBaseService, enabling support for users and teams that don't belong to organizations.

Key Changes:

  • Updated the insightsRoutingServiceOptionsSchema to make orgId optional for user and team scopes
  • Enhanced buildTeamAuthorizationCondition method with validation logic for optional orgId
  • Added comprehensive test coverage for null/undefined orgId scenarios

Background: The original schema required orgId for user scope, but user authorization only checks personal forms (formUserId and formTeamId IS NULL) without using orgId. Similarly, teams can exist independently without belonging to organizations, making the required orgId parameter unnecessarily restrictive.

Visual Demo (For contributors especially)

N/A - This is a backend schema and validation change without UI impact.

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Test the new optional orgId functionality:

  1. User scope with no orgId:

    const service = new InsightsRoutingService({
      prisma,
      options: { scope: "user", userId: 123, orgId: null },
      filters: createDefaultFilters(),
    });
    • Expected: Should return user-specific conditions without org validation
  2. Team scope for standalone teams:

    const service = new InsightsRoutingService({
      prisma,
      options: { scope: "team", userId: 123, orgId: null, teamId: 456 },
      filters: createDefaultFilters(),
    });
    • Expected: Should work for teams with parentId: null
    • Expected: Should return NOTHING_CONDITION for teams that belong to orgs but no orgId provided
  3. Backward compatibility:

    • Existing calls with orgId provided should continue working unchanged
    • Type checking should pass for both old and new usage patterns

Checklist

  • My code follows the style guidelines of this project
  • I have checked if my changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Link to Devin run: https://app.devin.ai/sessions/dabcc6f380174f3b8fecf4017e7d6643
Requested by: @eunjae-lee

Critical Review Areas

🔍 Please pay special attention to:

  1. Team validation logic (lines 855-873): The conditional logic that determines when to return NOTHING_CONDITION vs allowing access is security-critical
  2. Schema backward compatibility: Ensure existing API consumers aren't broken by the optional change
  3. Test execution: The integration tests may need manual verification as the test runner had issues finding the test file during development

…BaseService

- User scope authorization only checks formUserId and formTeamId IS NULL
- Team scope now supports standalone teams without organizations
- Add validation logic to return NOTHING_CONDITION if team belongs to org but no orgId provided
- Add comprehensive test coverage for null/undefined orgId scenarios in both scopes
- Aligns schema with actual usage patterns and supports teams without organizations

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Walkthrough

  • trpc-router: createInsightsBookingService now passes ctx.user.organizationId without fallback to 0, allowing null.
  • InsightsBookingBaseService: Public options and schema allow orgId to be null/undefined for user/team scopes; authorization switches to PermissionCheckService ("insights.read" with OWNER/ADMIN fallback); adds branching for team under org vs standalone; removes direct membership checks; adjusts base conditions.
  • InsightsRoutingBaseService: Mirrors permission-based checks; makes orgId optional/nullish for user/team; teamId optional; updates schema; refines team-scope org/standalone logic; removes MembershipRepository usage.
  • Tests: Expanded integration tests for booking/routing to cover null/undefined orgId scenarios; added fixtures/helpers; export and use new public options types in tests.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed The pull request description is directly related to the changeset and explains the rationale, key code/schema changes, test coverage additions, and how to test the change, aligning with the raw summaries and PR objectives. It gives sufficient context for reviewers to understand purpose and impact without being off-topic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title Check ✅ Passed The title clearly and concisely summarizes the primary change—making orgId optional for user and team scopes in InsightsRoutingBaseService—and directly matches the PR objectives and modified files (routing service schema, authorization logic, and tests). It is a single, specific sentence without noise or unrelated details, so a reviewer scanning history will understand the main intent.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/remove-orgid-user-scope-1758116265

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.

- Make orgId optional for user and team scopes in InsightsBookingBaseService
- Update InsightsBookingServicePublicOptions type to allow orgId: number | null
- Add validation logic for team scope to handle missing orgId
- Add comprehensive test coverage for null/undefined orgId scenarios
- Fix type casting issues in test file
- Maintains backward compatibility while supporting teams without organizations

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@vercel
Copy link

vercel bot commented Sep 18, 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 Sep 19, 2025 1:45pm
cal-eu Ignored Ignored Sep 19, 2025 1:45pm

@github-actions
Copy link
Contributor

github-actions bot commented Sep 18, 2025

E2E results are ready!

- Skip isOwnerOrAdmin check for team scope when orgId is null (standalone teams)
- Maintain security for org scope and team scope with orgId
- Fixes integration test failures for null orgId test cases

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
devin-ai-integration bot and others added 2 commits September 18, 2025 14:43
- Properly handle both null and undefined orgId values in authorization logic
- Fix integration test failures where null orgId was incorrectly triggering isOwnerOrAdmin check
- Ensure team scope with null orgId skips ownership validation for standalone teams

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
- Remove orgId condition from isOwnerOrAdmin check for team scope
- Ensure both standalone teams and org-based teams require ownership validation
- Maintain orgId validation logic in buildTeamAuthorizationCondition methods

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
- Change from .optional() to .nullish() to allow both null and undefined
- Fixes schema validation when tests pass orgId: null
- Resolves authorization logic returning NOTHING_CONDITION for valid cases

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
…rvices

- Replace isOwnerOrAdmin method in InsightsBookingBaseService with checkPermission from PermissionCheckService
- Replace isOwnerOrAdmin method in InsightsRoutingBaseService with checkPermission from PermissionCheckService
- Use permission 'insights.read' with fallback roles MembershipRole.OWNER and MembershipRole.ADMIN
- Maintain same method signature and behavior while leveraging PBAC system

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@eunjae-lee eunjae-lee marked this pull request as ready for review September 19, 2025 08:39
@graphite-app graphite-app bot requested a review from a team September 19, 2025 08:39
@eunjae-lee eunjae-lee changed the title feat: make orgId optional for user and team scopes in InsightsRoutingBaseService feat: make orgId optional for user and team scopes in /insights and /insights/routing Sep 19, 2025
@dosubot dosubot bot added insights area: insights, analytics ✨ feature New feature or request labels Sep 19, 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: 4

🧹 Nitpick comments (7)
packages/features/insights/server/trpc-router.ts (2)

685-696: Prisma: replace include with select.

Per repo guideline, avoid include; use select to fetch only needed fields.

-  include: {
-    team: {
-      select: {
-        id: true,
-        name: true,
-        logoUrl: true,
-        slug: true,
-        metadata: true,
-      },
-    },
-  },
+  select: {
+    team: {
+      select: {
+        id: true,
+        name: true,
+        logoUrl: true,
+        slug: true,
+        metadata: true,
+      },
+    },
+  },

746-751: Prisma: replace include with select.

Same guideline; fetch only the needed user fields.

-include: {
-  user: {
-    select: userSelect,
-  },
-},
+select: {
+  user: {
+    select: userSelect,
+  },
+},
packages/lib/server/service/InsightsRoutingBaseService.ts (3)

818-823: Nullish check for targetId (avoid truthy bug).

If a sentinel 0 ever sneaks in, this branch is skipped. Use != null.

-    if (targetId && scope !== "user") {
+    if (targetId != null && scope !== "user") {
       const isOwnerOrAdmin = await this.isOwnerOrAdmin(this.options.userId, targetId);
       if (!isOwnerOrAdmin) {
         return NOTHING_CONDITION;
       }
     }

856-876: Team auth: use nullish check for orgId.

Truthiness on orgId can misclassify valid numeric values (edge). Prefer != null.

-    if (options.orgId) {
+    if (options.orgId != null) {
       // team under org
       const childTeamOfOrg = await teamRepo.findByIdAndParentId({
         id: options.teamId,
         parentId: options.orgId,
         select: { id: true },
       });
       if (!childTeamOfOrg) {
         // teamId and its orgId does not match
         return NOTHING_CONDITION;
       }
     } else {
       // standalone team

291-297: bookingStatus: avoid UPPER to keep enum semantics.

Type declares BookingStatus | null; casting to UPPER(text) returns e.g. 'ACCEPTED', which may diverge from enum values used elsewhere.

-        UPPER(rfrd."bookingStatus"::text) as "bookingStatus",
+        rfrd."bookingStatus" as "bookingStatus",
packages/lib/server/service/InsightsBookingBaseService.ts (2)

382-390: Nullish check for targetId (avoid truthy bug).

Mirror fix from routing service.

-    if (targetId && scope !== "user") {
+    if (targetId != null && scope !== "user") {
       const isOwnerOrAdmin = await this.isOwnerOrAdmin(this.options.userId, targetId);
       if (!isOwnerOrAdmin) {
         return NOTHING_CONDITION;
       }
     }

440-460: Team auth: use nullish check for orgId.

Same edge-case as routing service.

-    if (options.orgId) {
+    if (options.orgId != null) {
       // team under org
       const childTeamOfOrg = await teamRepo.findByIdAndParentId({
         id: options.teamId,
         parentId: options.orgId,
         select: { id: true },
       });
       if (!childTeamOfOrg) {
         // teamId and its orgId does not match
         return NOTHING_CONDITION;
       }
     } else {
       // standalone team
📜 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 ef8b0fa and bfc7508.

📒 Files selected for processing (5)
  • packages/features/insights/server/trpc-router.ts (1 hunks)
  • packages/lib/server/service/InsightsBookingBaseService.ts (5 hunks)
  • packages/lib/server/service/InsightsRoutingBaseService.ts (3 hunks)
  • packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts (3 hunks)
  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*Service.ts

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

Service files must include Service suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts)

Files:

  • packages/lib/server/service/InsightsBookingBaseService.ts
  • packages/lib/server/service/InsightsRoutingBaseService.ts
**/*.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/service/InsightsBookingBaseService.ts
  • packages/features/insights/server/trpc-router.ts
  • packages/lib/server/service/InsightsRoutingBaseService.ts
  • packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts
  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.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/service/InsightsBookingBaseService.ts
  • packages/features/insights/server/trpc-router.ts
  • packages/lib/server/service/InsightsRoutingBaseService.ts
  • packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts
  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.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/service/InsightsBookingBaseService.ts
  • packages/features/insights/server/trpc-router.ts
  • packages/lib/server/service/InsightsRoutingBaseService.ts
  • packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts
  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: eunjae-lee
PR: calcom/cal.com#22702
File: packages/lib/server/service/insightsBooking.ts:120-124
Timestamp: 2025-07-24T08:39:06.185Z
Learning: In the InsightsBookingService (packages/lib/server/service/insightsBooking.ts), the constructor stores null for invalid options or filters but this is handled safely through null checks in buildFilterConditions() and buildAuthorizationConditions() methods. The service uses defensive programming to return safe fallback conditions (null or NOTHING_CONDITION) rather than throwing errors on invalid inputs.
Learnt from: SinghaAnirban005
PR: calcom/cal.com#23343
File: packages/features/insights/server/trpc-router.ts:1080-1101
Timestamp: 2025-08-26T08:08:23.395Z
Learning: In packages/features/insights/server/trpc-router.ts, when filtering personal event types (userId provided, no teamId, not isAll), the query correctly uses user.id (authenticated user) instead of the input userId parameter for security reasons. This prevents users from accessing other users' personal event types by passing arbitrary user IDs.
📚 Learning: 2025-07-24T08:39:06.185Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22702
File: packages/lib/server/service/insightsBooking.ts:120-124
Timestamp: 2025-07-24T08:39:06.185Z
Learning: In the InsightsBookingService (packages/lib/server/service/insightsBooking.ts), the constructor stores null for invalid options or filters but this is handled safely through null checks in buildFilterConditions() and buildAuthorizationConditions() methods. The service uses defensive programming to return safe fallback conditions (null or NOTHING_CONDITION) rather than throwing errors on invalid inputs.

Applied to files:

  • packages/lib/server/service/InsightsBookingBaseService.ts
  • packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts
  • packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
📚 Learning: 2025-08-26T08:08:23.395Z
Learnt from: SinghaAnirban005
PR: calcom/cal.com#23343
File: packages/features/insights/server/trpc-router.ts:1080-1101
Timestamp: 2025-08-26T08:08:23.395Z
Learning: In packages/features/insights/server/trpc-router.ts, when filtering personal event types (userId provided, no teamId, not isAll), the query correctly uses user.id (authenticated user) instead of the input userId parameter for security reasons. This prevents users from accessing other users' personal event types by passing arbitrary user IDs.

Applied to files:

  • packages/features/insights/server/trpc-router.ts
  • packages/lib/server/service/InsightsRoutingBaseService.ts
📚 Learning: 2025-08-14T10:48:52.586Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/ai/_router.ts:46-84
Timestamp: 2025-08-14T10:48:52.586Z
Learning: In calcom/cal.com PR #22995, packages/trpc/server/routers/viewer/ai/_router.ts, the voiceId input parameter in the create endpoint is intentionally not forwarded to aiService.createAgent() as voice customization is not required at the moment (per maintainer Udit-takkar). Future reviews should not flag this as missing functionality unless requirements change.

Applied to files:

  • packages/features/insights/server/trpc-router.ts
📚 Learning: 2025-07-15T13:02:17.403Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/lib/server/service/insightsRouting.ts:367-368
Timestamp: 2025-07-15T13:02:17.403Z
Learning: In the InsightsRoutingService (packages/lib/server/service/insightsRouting.ts), multi-select filter data is already validated by zod before reaching the buildFormFieldSqlCondition method, so null/undefined values are not present in filterValue.data arrays and don't need to be filtered out.

Applied to files:

  • packages/lib/server/service/InsightsRoutingBaseService.ts
📚 Learning: 2025-07-15T12:59:34.389Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

Applied to files:

  • packages/lib/server/service/InsightsRoutingBaseService.ts
📚 Learning: 2025-09-03T09:52:51.182Z
Learnt from: hariombalhara
PR: calcom/cal.com#23541
File: packages/features/bookings/lib/di/modules/RegularBookingServiceModule.ts:22-28
Timestamp: 2025-09-03T09:52:51.182Z
Learning: The IBookingServiceDependencies interface in packages/features/bookings/lib/handleNewBooking.ts contains 6 properties: cacheService, checkBookingAndDurationLimitsService, prismaClient, bookingRepository, featuresRepository, and checkBookingLimitsService. This interface is used by RegularBookingService and matches the depsMap structure in RegularBookingServiceModule.

Applied to files:

  • packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts
🧬 Code graph analysis (4)
packages/lib/server/service/InsightsBookingBaseService.ts (2)
packages/features/pbac/services/permission-check.service.ts (1)
  • PermissionCheckService (19-290)
packages/platform/libraries/index.ts (1)
  • MembershipRole (34-34)
packages/lib/server/service/InsightsRoutingBaseService.ts (1)
packages/features/pbac/services/permission-check.service.ts (1)
  • PermissionCheckService (19-290)
packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts (2)
packages/lib/server/service/InsightsBookingBaseService.ts (1)
  • InsightsBookingServicePublicOptions (125-130)
packages/lib/server/service/InsightsBookingDIService.ts (1)
  • InsightsBookingService (13-29)
packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts (2)
packages/lib/server/service/InsightsRoutingBaseService.ts (1)
  • InsightsRoutingServicePublicOptions (41-46)
packages/lib/server/service/InsightsRoutingDIService.ts (1)
  • InsightsRoutingService (13-29)
🔇 Additional comments (9)
packages/lib/server/service/InsightsRoutingBaseService.ts (2)

22-39: Schema/type updates look correct.

User/team scopes now allow nullish orgId; org scope still requires it. Public options align.


882-889: PBAC-based check is appropriate.

Using PermissionCheckService with OWNER/ADMIN fallback matches rollout strategy.

Please confirm the permission string "insights.read" matches the registered permission in PBAC seeds.

packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts (2)

352-371: Null orgId user-scope test looks good.

Covers the intended scenario.


444-464: Nothing condition test is correct.

Team belongs to org but orgId null should yield NOTHING_CONDITION.

packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts (2)

483-521: Null orgId + standalone team path is covered well.

Asserts the intended team-only condition.


446-475: Unrelated team under org case is correct.

Ensures NOT child-of-org yields NOTHING_CONDITION.

packages/lib/server/service/InsightsBookingBaseService.ts (2)

106-123: Schema: allow nullish orgId for user/team scopes.

Matches router changes; good for standalone teams/users.


1274-1281: PBAC-based owner/admin check looks good.

Leverages “insights.read” with OWNER/ADMIN fallback.

Confirm PBAC feature flag coverage for teams used in tests to avoid false negatives.

packages/features/insights/server/trpc-router.ts (1)

327-329: Approve — orgId should be left nullish; audit remaining '?? 0' fallbacks

orgId change in packages/features/insights/server/trpc-router.ts is correct.

  • Audit remaining "?? 0" occurrences from the rg output and either keep intentional numeric defaults or switch orgId/teamId uses to nullish. Notable hits to check: packages/features/instant-meeting/handleInstantMeeting.ts:37, packages/features/webhooks/lib/getWebhooks.ts:39, packages/features/insights/server/trpc-router.ts:846.

Comment on lines +373 to +392
it("should build user scope conditions with undefined orgId", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});

const service = new InsightsBookingService({
prisma,
options: {
scope: "user",
userId: testData.user.id,
orgId: null,
},
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(Prisma.sql`("userId" = ${testData.user.id}) AND ("teamId" IS NULL)`);

await testData.cleanup();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix test: “undefined orgId” case currently passes null.

Set orgId to undefined to actually exercise that path.

-          orgId: null,
+          orgId: undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("should build user scope conditions with undefined orgId", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const service = new InsightsBookingService({
prisma,
options: {
scope: "user",
userId: testData.user.id,
orgId: null,
},
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(Prisma.sql`("userId" = ${testData.user.id}) AND ("teamId" IS NULL)`);
await testData.cleanup();
});
it("should build user scope conditions with undefined orgId", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const service = new InsightsBookingService({
prisma,
options: {
scope: "user",
userId: testData.user.id,
orgId: undefined,
},
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(Prisma.sql`("userId" = ${testData.user.id}) AND ("teamId" IS NULL)`);
await testData.cleanup();
});
🤖 Prompt for AI Agents
In
packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts
around lines 373 to 392, the test intends to exercise the "undefined orgId" path
but sets orgId: null; change the test to pass orgId: undefined in the service
options so the code path for undefined is executed (update the object literal to
orgId: undefined and leave the rest unchanged).

Comment on lines +466 to +514
it("should build team scope conditions with undefined orgId for standalone team", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});

const standaloneTeam = await prisma.team.create({
data: {
name: "Standalone Team 2",
slug: `standalone-team-2-${Date.now()}-${Math.random().toString(36).substring(7)}`,
isOrganization: false,
parentId: null,
},
});

await prisma.membership.create({
data: {
userId: testData.user.id,
teamId: standaloneTeam.id,
role: MembershipRole.OWNER,
accepted: true,
},
});

const service = new InsightsBookingService({
prisma,
options: {
scope: "team",
userId: testData.user.id,
orgId: null,
teamId: standaloneTeam.id,
},
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(
Prisma.sql`(("teamId" = ${standaloneTeam.id}) AND ("isTeamBooking" = true)) OR (("userId" = ANY(${[
testData.user.id,
]})) AND ("isTeamBooking" = false))`
);

await prisma.membership.deleteMany({
where: { teamId: standaloneTeam.id },
});
await prisma.team.delete({
where: { id: standaloneTeam.id },
});
await testData.cleanup();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix test: “undefined orgId” case currently passes null (team scope).

Set orgId to undefined.

-          orgId: null,
+          orgId: undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("should build team scope conditions with undefined orgId for standalone team", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const standaloneTeam = await prisma.team.create({
data: {
name: "Standalone Team 2",
slug: `standalone-team-2-${Date.now()}-${Math.random().toString(36).substring(7)}`,
isOrganization: false,
parentId: null,
},
});
await prisma.membership.create({
data: {
userId: testData.user.id,
teamId: standaloneTeam.id,
role: MembershipRole.OWNER,
accepted: true,
},
});
const service = new InsightsBookingService({
prisma,
options: {
scope: "team",
userId: testData.user.id,
orgId: null,
teamId: standaloneTeam.id,
},
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(
Prisma.sql`(("teamId" = ${standaloneTeam.id}) AND ("isTeamBooking" = true)) OR (("userId" = ANY(${[
testData.user.id,
]})) AND ("isTeamBooking" = false))`
);
await prisma.membership.deleteMany({
where: { teamId: standaloneTeam.id },
});
await prisma.team.delete({
where: { id: standaloneTeam.id },
});
await testData.cleanup();
});
it("should build team scope conditions with undefined orgId for standalone team", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const standaloneTeam = await prisma.team.create({
data: {
name: "Standalone Team 2",
slug: `standalone-team-2-${Date.now()}-${Math.random().toString(36).substring(7)}`,
isOrganization: false,
parentId: null,
},
});
await prisma.membership.create({
data: {
userId: testData.user.id,
teamId: standaloneTeam.id,
role: MembershipRole.OWNER,
accepted: true,
},
});
const service = new InsightsBookingService({
prisma,
options: {
scope: "team",
userId: testData.user.id,
orgId: undefined,
teamId: standaloneTeam.id,
},
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(
Prisma.sql`(("teamId" = ${standaloneTeam.id}) AND ("isTeamBooking" = true)) OR (("userId" = ANY(${[
testData.user.id,
]})) AND ("isTeamBooking" = false))`
);
await prisma.membership.deleteMany({
where: { teamId: standaloneTeam.id },
});
await prisma.team.delete({
where: { id: standaloneTeam.id },
});
await testData.cleanup();
});
🤖 Prompt for AI Agents
In
packages/lib/server/service/__tests__/InsightsBookingService.integration-test.ts
around lines 466 to 514, the test intended to exercise the "undefined orgId"
case incorrectly passes orgId: null when constructing the InsightsBookingService
options; update the test to pass orgId: undefined instead (i.e., replace orgId:
null with orgId: undefined) so the service receives an undefined orgId as
intended and run the test to confirm it still types and behaves correctly.

Comment on lines +361 to +384
it("should build user scope conditions with undefined orgId", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});

const service = new InsightsRoutingService({
prisma,
options: {
scope: "user",
userId: testData.user.id,
orgId: null,
teamId: undefined,
},
filters: createDefaultFilters(),
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(
Prisma.sql`rfrd."formUserId" = ${testData.user.id} AND rfrd."formTeamId" IS NULL`
);

await testData.cleanup();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix test: “undefined orgId” case currently passes null (user scope).

Set orgId to undefined.

-          orgId: null,
+          orgId: undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("should build user scope conditions with undefined orgId", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const service = new InsightsRoutingService({
prisma,
options: {
scope: "user",
userId: testData.user.id,
orgId: null,
teamId: undefined,
},
filters: createDefaultFilters(),
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(
Prisma.sql`rfrd."formUserId" = ${testData.user.id} AND rfrd."formTeamId" IS NULL`
);
await testData.cleanup();
});
it("should build user scope conditions with undefined orgId", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const service = new InsightsRoutingService({
prisma,
options: {
scope: "user",
userId: testData.user.id,
orgId: undefined,
teamId: undefined,
},
filters: createDefaultFilters(),
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(
Prisma.sql`rfrd."formUserId" = ${testData.user.id} AND rfrd."formTeamId" IS NULL`
);
await testData.cleanup();
});
🤖 Prompt for AI Agents
In
packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
around lines 361 to 384, the test titled "should build user scope conditions
with undefined orgId" currently passes orgId: null but should pass orgId:
undefined; update the test input to set orgId to undefined so it accurately
exercises the "undefined orgId" code path and re-run the assertion unchanged.

Comment on lines +553 to +598
it("should build team scope conditions with undefined orgId for standalone team", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});

const standaloneTeam = await prisma.team.create({
data: {
name: "Standalone Team 2",
slug: `standalone-team-2-${randomUUID()}`,
isOrganization: false,
parentId: null,
},
});

await prisma.membership.create({
data: {
userId: testData.user.id,
teamId: standaloneTeam.id,
role: MembershipRole.OWNER,
accepted: true,
},
});

const service = new InsightsRoutingService({
prisma,
options: {
scope: "team",
userId: testData.user.id,
orgId: null,
teamId: standaloneTeam.id,
},
filters: createDefaultFilters(),
});

const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(Prisma.sql`rfrd."formTeamId" = ${standaloneTeam.id}`);

await prisma.membership.deleteMany({
where: { teamId: standaloneTeam.id },
});
await prisma.team.delete({
where: { id: standaloneTeam.id },
});
await testData.cleanup();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix test: “undefined orgId” case currently passes null (team scope).

Set orgId to undefined.

-          orgId: null,
+          orgId: undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("should build team scope conditions with undefined orgId for standalone team", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const standaloneTeam = await prisma.team.create({
data: {
name: "Standalone Team 2",
slug: `standalone-team-2-${randomUUID()}`,
isOrganization: false,
parentId: null,
},
});
await prisma.membership.create({
data: {
userId: testData.user.id,
teamId: standaloneTeam.id,
role: MembershipRole.OWNER,
accepted: true,
},
});
const service = new InsightsRoutingService({
prisma,
options: {
scope: "team",
userId: testData.user.id,
orgId: null,
teamId: standaloneTeam.id,
},
filters: createDefaultFilters(),
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(Prisma.sql`rfrd."formTeamId" = ${standaloneTeam.id}`);
await prisma.membership.deleteMany({
where: { teamId: standaloneTeam.id },
});
await prisma.team.delete({
where: { id: standaloneTeam.id },
});
await testData.cleanup();
});
it("should build team scope conditions with undefined orgId for standalone team", async () => {
const testData = await createTestData({
teamRole: MembershipRole.OWNER,
orgRole: MembershipRole.OWNER,
});
const standaloneTeam = await prisma.team.create({
data: {
name: "Standalone Team 2",
slug: `standalone-team-2-${randomUUID()}`,
isOrganization: false,
parentId: null,
},
});
await prisma.membership.create({
data: {
userId: testData.user.id,
teamId: standaloneTeam.id,
role: MembershipRole.OWNER,
accepted: true,
},
});
const service = new InsightsRoutingService({
prisma,
options: {
scope: "team",
userId: testData.user.id,
orgId: undefined,
teamId: standaloneTeam.id,
},
filters: createDefaultFilters(),
});
const conditions = await service.getAuthorizationConditions();
expect(conditions).toEqual(Prisma.sql`rfrd."formTeamId" = ${standaloneTeam.id}`);
await prisma.membership.deleteMany({
where: { teamId: standaloneTeam.id },
});
await prisma.team.delete({
where: { id: standaloneTeam.id },
});
await testData.cleanup();
});
🤖 Prompt for AI Agents
In
packages/lib/server/service/__tests__/InsightsRoutingService.integration-test.ts
around lines 553 to 598, the test intends to cover the "undefined orgId" case
but currently passes orgId: null in the service options; change the options to
pass orgId: undefined (e.g., options: { scope: "team", userId: testData.user.id,
orgId: undefined, teamId: standaloneTeam.id }) so the test actually exercises
the undefined path, keeping the rest of setup/teardown the same.

Copy link
Member

@sean-brydon sean-brydon left a comment

Choose a reason for hiding this comment

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

LGTM - i do find the null OR undefined a bit of a weird choice but it seems to be working well

Thanks for the added test cases

@sean-brydon sean-brydon enabled auto-merge (squash) September 19, 2025 09:06
@eunjae-lee
Copy link
Contributor Author

LGTM - i do find the null OR undefined a bit of a weird choice but it seems to be working well

Thanks for the added test cases

yeah initially I had only undefined, but ctx.user.organizationId from the trpc side is nullable, so I had to add null support too.

@eunjae-lee eunjae-lee merged commit a1922ef into main Sep 19, 2025
37 checks passed
@eunjae-lee eunjae-lee deleted the devin/remove-orgid-user-scope-1758116265 branch September 19, 2025 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

consumer core area: core, team members only ✨ feature New feature or request insights area: insights, analytics ready-for-e2e size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants