Skip to content

feat: add wrong routing tab under Insights for reviewing assignment reports#27423

Merged
emrysal merged 55 commits intomainfrom
devin/1769747741-wrong-assignment-dashboard
Feb 12, 2026
Merged

feat: add wrong routing tab under Insights for reviewing assignment reports#27423
emrysal merged 55 commits intomainfrom
devin/1769747741-wrong-assignment-dashboard

Conversation

@joeauyeung
Copy link
Copy Markdown
Contributor

@joeauyeung joeauyeung commented Jan 30, 2026

Stacked on #27405

What does this PR do?

https://www.loom.com/share/04dbd8f411d7408b81c8aff7a6ec7af6

Adds a dedicated "Wrong routing" tab under Insights where team admins can review and manage wrong assignment reports. This builds on top of PR #27405 which added the ability to report wrong assignments.

Key features:

  • Dedicated tab at /insights/wrong-routing in the Insights sidebar navigation
  • Two sub-tabs for filtering reports: "Pending" and "Reviewed" (includes reviewed, resolved, and dismissed statuses)
  • Displays routing form name for each report to help identify which form the booking came from
  • Ability to change report status via dropdown actions (mark as reviewed/resolved/dismissed, or reopen)
  • Tracks who reviewed each report and when (reviewedById, reviewedAt fields)
  • Pagination support for large numbers of reports
  • View routing trace action to see how the booking was routed
  • View form submission action to see the original routing form response in a slide-out sheet

Changes include:

  • Database schema: Added reviewedById and reviewedAt fields to WrongAssignmentReport model
  • Repository: Added methods for listing reports by status and updating status, including routedFromRoutingFormReponse.id for linking to form submissions
  • tRPC: Added getWrongAssignmentReports query and updateWrongAssignmentReportStatus mutation
  • tRPC: Added getFormResponseDisplay endpoint for viewing form responses in a sheet
  • Service: Refactored reportWrongAssignment handler to use WrongAssignmentReportService (business logic extracted from handler)
  • UI: New WrongAssignmentReportsDashboard component with dedicated page and navigation entry
  • UI: New RoutingFormResponseSheet component for viewing form submission details inline
  • UI: RoutingTraceSheet now supports optional onReport and hasExistingReport props for inline report button
  • i18n: Added translation keys for the new tab and dashboard (booking, host, unknown, view_form_submission, you_dont_have_access_to_view_this_form_response, wrong_assignment_reported, etc.)

Updates since last revision

  • Wrapped handleStatusChange in useCallback in WrongAssignmentReportsDashboard.tsx to prevent the columns useMemo from recalculating every render (addresses review comment)
  • Fixed error message in getFormResponseDisplay.handler.ts: changed from you_dont_have_access_to_reroute_this_booking to a new you_dont_have_access_to_view_this_form_response i18n key that matches the endpoint's purpose (addresses review comment)
  • Localized success message in WrongAssignmentReportService.ts: replaced hard-coded "Wrong assignment reported successfully" with t("wrong_assignment_reported")
  • Merged main branch (first pass), resolving conflicts in 3 files:
    • BookingActionsDropdown.tsx: kept PR's fragment structure wrapping both dialogs, adopted main's simplified booking prop for WrongAssignmentDialog
    • reportWrongAssignment.handler.ts: kept PR's service-based approach (delegates to WrongAssignmentReportService)
    • reportWrongAssignment.handler.test.ts: adopted main's class-based mock style with PR's TeamRepository mock addition
  • Updated WrongAssignmentReportService.ts to align with main's repository method and webhook payload changes:
    • Uses findByUidIncludeUserAndEventTypeTeamAndAttendeesAndAssignmentReason (renamed method)
    • Webhook payload field renamed: routingReasonfirstAssignmentReason
    • Added orgId to getWebhooks call
    • Uses ?? null instead of || "" for nullable guest/host fields
  • Addressed Cubic AI review feedback:
    • PrismaAuditActorRepository.ts: Added select: { id: true } to findUnique calls that are only existence checks, reducing unnecessary data reads
    • RouteBuilder.tsx: Removed t prop from RouteActionSelector component, now uses useLocale() internally instead of prop drilling
  • Merged main branch (second pass), resolving conflicts in 6 files:
    • BookingActionsDropdown.tsx: kept PR's fragment wrapping for WrongAssignmentDialog and RoutingTraceSheet
    • BookingDetailsSheet.tsx: took main's cleaner one-liner for reason variable
    • PrismaAuditActorRepository.ts: kept PR's select: { id: true } optimization
    • reportWrongAssignment.handler.ts: kept PR's service-based delegation pattern
    • reportWrongAssignment.handler.test.ts: kept PR's ErrorWithCode import and TeamRepository mock
    • RouteBuilder.tsx: kept PR's useLocale() hook pattern (removed t prop from all 4 RouteActionSelector usages)
  • Fixed unit test failure: Removed stale TRPCError assertion in reportWrongAssignment.handler.test.ts - the service-based handler throws ErrorWithCode (not TRPCError) for NOT_FOUND errors, which is then converted by tRPC middleware
  • Query performance optimizations (addresses review feedback on Prisma queries):
    • Removed dead code: findByTeamIdAndStatus and findByTeamIdAndStatuses methods from WrongAssignmentReportRepository (never called)
    • Added composite index @@index([teamId, status, createdAt]) on WrongAssignmentReport for the main listing query pattern
    • Replaced findById with lightweight findTeamIdById in update-status handler — the old method over-fetched 4 relations just to read teamId for permission check
    • Narrowed PrismaRoutingFormResponseRepository.findByIdIncludeForm from include (all columns) to top-level select: { response, form } — only fields actually used by consumers

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. N/A - no documentation changes needed.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Ensure PR feat: Write wrong assignment reports to the database #27405 (base branch add-wrong-routing-table) is merged first
  2. Run database migration: yarn workspace @calcom/prisma db-migrate
  3. Log in as a user who is part of a team with a paid plan (Insights requires paid plan)
  4. Navigate to Insights > Wrong routing in the sidebar
  5. Select a team from the team/org filter dropdown
  6. Create some wrong assignment reports via the booking details page (or run npx ts-node scripts/seed-wrong-assignment-reports.ts for test data)
  7. Verify the dashboard shows reports in the "Pending" tab
  8. Test status changes via the dropdown menu
  9. Verify reports move to "Reviewed" tab after status change
  10. Verify reviewer info is displayed correctly
  11. Test the "View routing trace" action - should open the routing trace sheet
  12. Test the "View form submission" action - should open a sheet showing the form fields and responses

Checklist

  • My code follows the style guidelines of this project
  • I have checked if my changes generate no new warnings

Items for Human Review

  1. Webhook payload breaking change: The webhook payload field was renamed from routingReason to firstAssignmentReason. Verify no external consumers depend on the old field name.
  2. WrongAssignmentDialog prop change: Changed from individual props (bookingUid, routingReason, guestEmail, etc.) to a single booking prop. Verify WrongAssignmentDialog component accepts this pattern.
  3. Authorization: Currently any team member can view and update report status. Should this be restricted to admins only?
  4. No automated tests: This PR does not include automated tests for the new dashboard functionality.
  5. Team context required: The dashboard shows an empty state prompting users to select a team when no team is selected in the insights context.
  6. Navigation placement: The "Wrong routing" tab was added as the last item under Insights - verify this is the desired position.
  7. RoutingFormResponseSheet: The new sheet component relies on trpc.viewer.appRoutingForms.getFormResponseDisplay - verify this endpoint returns the expected data structure.
  8. RoutingTraceSheet behavior change: The sheet is now shown for all routing form bookings (inside isBookingFromRoutingForm block), not just those with assignmentReason.length > 0. Verify this is intended.
  9. New i18n keys: you_dont_have_access_to_view_this_form_response and wrong_assignment_reported were only added to English locale - other locales will fall back to English.
  10. Prisma import in service: WrongAssignmentReportService.ts imports Prisma from @calcom/prisma/client for error handling - per architecture guidelines, packages/features should not import from Prisma client directly.
  11. Mixed error types in handler: The handler throws TRPCError directly for FORBIDDEN (access check), but delegates to service which throws ErrorWithCode for other errors. This is intentional (access check in handler, business logic in service) but worth verifying the error conversion middleware handles this correctly.
  12. findById fully removed: The findById method was completely removed from WrongAssignmentReportRepository and replaced with findTeamIdById. Verify no other code paths needed the full report details.
  13. Interface return type narrowed: RoutingFormResponseRepository.interface.ts now returns { response, form } instead of the full App_RoutingForms_FormResponse. TypeScript should catch any issues, but verify compilation passes.

Link to Devin run: https://app.devin.ai/sessions/d90ea1ab8e2742a681c860f1f05b5e77
Requested by: @emrysal

joeauyeung and others added 9 commits January 29, 2026 15:32
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
… hasWrongAssignmentReport

- Add @unique constraint on bookingUid in WrongAssignmentReport model to prevent duplicate reports at DB level
- Add booking ownership check using BookingAccessService in hasWrongAssignmentReport endpoint
- Refactor hasWrongAssignmentReport into separate handler and schema files

Addresses Cubic AI review feedback on PR #27405

Co-Authored-By: unknown <>
- Add routingFormId field to WrongAssignmentReport model in schema.prisma
- Add relation to App_RoutingForms_Form with SetNull on delete
- Update WrongAssignmentReportRepository.createReport to accept routingFormId
- Update BookingRepository.findByUidIncludeEventTypeAndTeamAndAssignmentReason to include routedFromRoutingFormReponse
- Extract routingFormId from booking in reportWrongAssignment handler
- Fix Select clearing issue: handle null case when user clears team member selection
- Update tests to include routingFormId field

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
- Add reviewedById and reviewedAt fields to WrongAssignmentReport model
- Add repository methods for listing reports by status and updating status
- Create tRPC endpoints for fetching reports and updating status
- Create dashboard UI with pending/reviewed tabs showing routing form name
- Add translation keys for dashboard UI
- Integrate dashboard into routing insights page

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
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' or '@devin'.
  • 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

@github-actions github-actions Bot added the ❗️ migrations contains migration files label Jan 30, 2026
devin-ai-integration Bot and others added 7 commits January 30, 2026 04:49
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
- hasWrongAssignmentReport: throw UNAUTHORIZED error instead of returning false
- reportWrongAssignment: add try-catch for Prisma P2002 unique constraint error
- WrongAssignmentReport: add Team relation to teamId field
- WrongAssignmentReportRepository: use findUnique instead of findFirst
- reportWrongAssignment: use i18n for error messages

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
…access

Per PR checklist, hasWrongAssignmentReport should return { hasReport: false }
when user lacks access to booking, not throw an error. This allows the UI
to gracefully treat 'no access' as 'no report exists'.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
…ions

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat: add wrong assignment reports dashboard under routing tab feat: add wrong routing tab under Insights for reviewing assignment reports Feb 3, 2026
@keithwillcode keithwillcode added the core area: core, team members only label Feb 3, 2026
joeauyeung and others added 9 commits February 3, 2026 10:10
…orts

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
…ions

- Create slide-out sheet to display routing form responses
- Map option IDs to display labels for select/multiselect fields
- Handle both legacy and modern option formats
- Add i18n strings: form_submission, no_responses_found

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Integrate RoutingFormResponseSheet as slide-out panel instead of new tab
- Fix dropdown padding by using StartIcon prop instead of manual Icon
- Allow direct status changes for reviewed reports (no need to reopen first)
- Remove unused Icon import

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
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 some comments

Comment thread apps/web/components/booking/RoutingTraceSheet.tsx Outdated
Comment thread apps/web/components/booking/actions/BookingActionsDropdown.tsx Outdated
Comment thread apps/web/app/(use-page-wrapper)/insights/wrong-routing/page.tsx
Comment thread packages/trpc/server/routers/viewer/bookings/getWrongAssignmentReports.handler.ts Outdated
Comment thread packages/trpc/server/routers/apps/routing-forms/getFormResponseDisplay.handler.ts Outdated
@github-actions github-actions Bot marked this pull request as draft February 11, 2026 11:50
joeauyeung and others added 6 commits February 11, 2026 10:17
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…culation

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Move business logic (booking lookup, duplicate check, report creation,
webhook dispatch, org-level team resolution) into a dedicated service
in packages/features. Handlers become thin controllers that only handle
auth checks and delegate to the service.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove MembershipRole.MEMBER from fallbackRoles in
updateWrongAssignmentReportStatus permission check. Updating report
status is an administrative action that should be limited to team
admins and owners.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@joeauyeung joeauyeung marked this pull request as ready for review February 11, 2026 18:03
Comment thread packages/trpc/server/routers/viewer/bookings/reportWrongAssignment.handler.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/features/bookings/services/WrongAssignmentReportService.ts">

<violation number="1" location="packages/features/bookings/services/WrongAssignmentReportService.ts:91">
P2: Localize the success message instead of returning a hard-coded English string.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/features/bookings/services/WrongAssignmentReportService.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 11, 2026

Devin AI is addressing Cubic AI's review feedback

New feedback has been sent to the existing Devin session.

View Devin Session


✅ Pushed commit a74d5a9

devin-ai-integration Bot and others added 3 commits February 11, 2026 18:13
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Rename the "Reviewed" tab to "Handled" since it groups three distinct
statuses (Reviewed, Resolved, Dismissed). Also add missing translation
keys for "resolved" and "dismissed" status badges.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge main into devin/1769747741-wrong-assignment-dashboard, resolving conflicts in:
- BookingActionsDropdown.tsx: use main's booking prop with PR's fragment structure
- reportWrongAssignment.handler.ts: keep PR's service-based approach
- reportWrongAssignment.handler.test.ts: use main's class-based mocks with PR's additions
- WrongAssignmentReportService.ts: align with main's repo method and field names

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 370 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/modules/bookings/components/BookingDetailsSheet.tsx">

<violation number="1" location="apps/web/modules/bookings/components/BookingDetailsSheet.tsx:218">
P2: Guard the length access before indexing. The current line still reads `.length` on an undefined array, which can crash the sheet for bookings without assignment reasons.</violation>
</file>

<file name="packages/features/booking-audit/lib/repository/PrismaAuditActorRepository.ts">

<violation number="1" location="packages/features/booking-audit/lib/repository/PrismaAuditActorRepository.ts:50">
P2: Limit the `findUnique` projection to the minimal field (e.g., `id`) since this is only an existence check. This reduces unnecessary data reads and exposure.

(Based on your team's feedback about selecting only required fields in Prisma queries.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx">

<violation number="1" location="apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx:362">
P2: `RouteActionSelector` should call `useLocale()` internally instead of receiving `t` as a prop. This is unnecessary prop drilling — the component can access `t` directly via the hook, which also avoids the need for a custom type signature that may diverge from the real `t` type.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/web/modules/bookings/components/BookingDetailsSheet.tsx Outdated
Comment thread apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx Outdated
joeauyeung and others added 7 commits February 11, 2026 13:59
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…le refactor

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
…thCode)

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
…uses methods

Co-Authored-By: alex@cal.com <me@alexvanandel.com>
…dIncludeForm select

Co-Authored-By: alex@cal.com <me@alexvanandel.com>
… of findById

Co-Authored-By: alex@cal.com <me@alexvanandel.com>
Copy link
Copy Markdown
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, after the repository changes I'm happy to merge this, looks solid.

@emrysal emrysal enabled auto-merge (squash) February 12, 2026 13:01
@emrysal emrysal merged commit 7cfb053 into main Feb 12, 2026
90 of 92 checks passed
@emrysal emrysal deleted the devin/1769747741-wrong-assignment-dashboard branch February 12, 2026 13:02
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 13 additional findings in Devin Review.

Open in Devin Review

Comment on lines 47 to +61
<SheetContent>
<SheetHeader>
<SheetTitle>{t("routing_trace")}</SheetTitle>
<SheetHeader showCloseButton={!showReportButton} className="w-full">
{showReportButton ? (
<div className="flex w-full items-center justify-between">
<SheetTitle>{t("routing_trace")}</SheetTitle>
<div className="flex items-center gap-2 pl-2">
<Button
color="secondary"
size="sm"
StartIcon="flag"
disabled={hasExistingReport}
onClick={onReport}>
{t("report")}
</Button>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Missing close button in RoutingTraceSheet header when Report button is shown

When onReport is provided to RoutingTraceSheet, the header close button is hidden via showCloseButton={!showReportButton} but no replacement close button is added.

Root Cause

The codebase convention (visible in BookingDetailsSheet.tsx:235-278) is that when showCloseButton={false} is set on SheetHeader, the developer must provide their own explicit close (X) button. In BookingDetailsSheet, this is done correctly:

<SheetHeader showCloseButton={false} ...>
  ...
  <Button variant="icon" ... StartIcon="x" onClick={handleClose} />
</SheetHeader>

But in RoutingTraceSheet, when showReportButton is true, the close button is removed and the custom header only includes a Report button — no close/X button is provided:

<SheetHeader showCloseButton={!showReportButton} className="w-full">
  <div className="flex w-full items-center justify-between">
    <SheetTitle>{t("routing_trace")}</SheetTitle>
    <div className="flex items-center gap-2 pl-2">
      <Button ... onClick={onReport}>{t("report")}</Button>
      <!-- no close button here -->
    </div>
  </div>
</SheetHeader>

This happens specifically when the sheet is opened from BookingActionsDropdown (apps/web/components/booking/actions/BookingActionsDropdown.tsx:463-469) which always passes onReport. Users can still dismiss the sheet via Escape key or overlay click, but the visible X button is missing.

Impact: UX regression — users opening the routing trace sheet from booking actions will not see a close button in the sheet header.

Suggested change
<SheetContent>
<SheetHeader>
<SheetTitle>{t("routing_trace")}</SheetTitle>
<SheetHeader showCloseButton={!showReportButton} className="w-full">
{showReportButton ? (
<div className="flex w-full items-center justify-between">
<SheetTitle>{t("routing_trace")}</SheetTitle>
<div className="flex items-center gap-2 pl-2">
<Button
color="secondary"
size="sm"
StartIcon="flag"
disabled={hasExistingReport}
onClick={onReport}>
{t("report")}
</Button>
</div>
<SheetHeader showCloseButton={!showReportButton} className="w-full">
{showReportButton ? (
<div className="flex w-full items-center justify-between">
<SheetTitle>{t("routing_trace")}</SheetTitle>
<div className="flex items-center gap-2 pl-2">
<Button
color="secondary"
size="sm"
StartIcon="flag"
disabled={hasExistingReport}
onClick={onReport}>
{t("report")}
</Button>
<Button
variant="icon"
size="sm"
color="secondary"
StartIcon="x"
onClick={() => setIsOpen(false)}
/>
</div>
</div>
) : (
<SheetTitle>{t("routing_trace")}</SheetTitle>
)}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO ❗️ migrations contains migration files ready-for-e2e size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants