feat: add wrong routing tab under Insights for reviewing assignment reports#27423
feat: add wrong routing tab under Insights for reviewing assignment reports#27423
Conversation
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 EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
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>
…orts Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
…vin/1769747741-wrong-assignment-dashboard
…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>
…signment-dashboard
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>
There was a problem hiding this comment.
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.
Devin AI is addressing Cubic AI's review feedbackNew feedback has been sent to the existing Devin session. ✅ Pushed commit |
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>
There was a problem hiding this comment.
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.
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>
emrysal
left a comment
There was a problem hiding this comment.
LGTM, after the repository changes I'm happy to merge this, looks solid.
| <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> |
There was a problem hiding this comment.
🟡 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.
| <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> | |
| )} |
Was this helpful? React with 👍 or 👎 to provide feedback.
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:
/insights/wrong-routingin the Insights sidebar navigationreviewedById,reviewedAtfields)Changes include:
reviewedByIdandreviewedAtfields toWrongAssignmentReportmodelroutedFromRoutingFormReponse.idfor linking to form submissionsgetWrongAssignmentReportsquery andupdateWrongAssignmentReportStatusmutationgetFormResponseDisplayendpoint for viewing form responses in a sheetreportWrongAssignmenthandler to useWrongAssignmentReportService(business logic extracted from handler)WrongAssignmentReportsDashboardcomponent with dedicated page and navigation entryRoutingFormResponseSheetcomponent for viewing form submission details inlineRoutingTraceSheetnow supports optionalonReportandhasExistingReportprops for inline report buttonbooking,host,unknown,view_form_submission,you_dont_have_access_to_view_this_form_response,wrong_assignment_reported, etc.)Updates since last revision
handleStatusChangeinuseCallbackinWrongAssignmentReportsDashboard.tsxto prevent thecolumnsuseMemofrom recalculating every render (addresses review comment)getFormResponseDisplay.handler.ts: changed fromyou_dont_have_access_to_reroute_this_bookingto a newyou_dont_have_access_to_view_this_form_responsei18n key that matches the endpoint's purpose (addresses review comment)WrongAssignmentReportService.ts: replaced hard-coded"Wrong assignment reported successfully"witht("wrong_assignment_reported")BookingActionsDropdown.tsx: kept PR's fragment structure wrapping both dialogs, adopted main's simplifiedbookingprop forWrongAssignmentDialogreportWrongAssignment.handler.ts: kept PR's service-based approach (delegates toWrongAssignmentReportService)reportWrongAssignment.handler.test.ts: adopted main's class-based mock style with PR'sTeamRepositorymock additionWrongAssignmentReportService.tsto align with main's repository method and webhook payload changes:findByUidIncludeUserAndEventTypeTeamAndAttendeesAndAssignmentReason(renamed method)routingReason→firstAssignmentReasonorgIdtogetWebhookscall?? nullinstead of|| ""for nullable guest/host fieldsPrismaAuditActorRepository.ts: Addedselect: { id: true }tofindUniquecalls that are only existence checks, reducing unnecessary data readsRouteBuilder.tsx: Removedtprop fromRouteActionSelectorcomponent, now usesuseLocale()internally instead of prop drillingBookingActionsDropdown.tsx: kept PR's fragment wrapping forWrongAssignmentDialogandRoutingTraceSheetBookingDetailsSheet.tsx: took main's cleaner one-liner forreasonvariablePrismaAuditActorRepository.ts: kept PR'sselect: { id: true }optimizationreportWrongAssignment.handler.ts: kept PR's service-based delegation patternreportWrongAssignment.handler.test.ts: kept PR'sErrorWithCodeimport andTeamRepositorymockRouteBuilder.tsx: kept PR'suseLocale()hook pattern (removedtprop from all 4RouteActionSelectorusages)TRPCErrorassertion inreportWrongAssignment.handler.test.ts- the service-based handler throwsErrorWithCode(notTRPCError) for NOT_FOUND errors, which is then converted by tRPC middlewarefindByTeamIdAndStatusandfindByTeamIdAndStatusesmethods fromWrongAssignmentReportRepository(never called)@@index([teamId, status, createdAt])onWrongAssignmentReportfor the main listing query patternfindByIdwith lightweightfindTeamIdByIdin update-status handler — the old method over-fetched 4 relations just to readteamIdfor permission checkPrismaRoutingFormResponseRepository.findByIdIncludeFormfrominclude(all columns) to top-levelselect: { response, form }— only fields actually used by consumersMandatory Tasks (DO NOT REMOVE)
How should this be tested?
add-wrong-routing-table) is merged firstyarn workspace @calcom/prisma db-migratenpx ts-node scripts/seed-wrong-assignment-reports.tsfor test data)Checklist
Items for Human Review
routingReasontofirstAssignmentReason. Verify no external consumers depend on the old field name.bookingUid,routingReason,guestEmail, etc.) to a singlebookingprop. VerifyWrongAssignmentDialogcomponent accepts this pattern.trpc.viewer.appRoutingForms.getFormResponseDisplay- verify this endpoint returns the expected data structure.isBookingFromRoutingFormblock), not just those withassignmentReason.length > 0. Verify this is intended.you_dont_have_access_to_view_this_form_responseandwrong_assignment_reportedwere only added to English locale - other locales will fall back to English.WrongAssignmentReportService.tsimportsPrismafrom@calcom/prisma/clientfor error handling - per architecture guidelines,packages/featuresshould not import from Prisma client directly.TRPCErrordirectly for FORBIDDEN (access check), but delegates to service which throwsErrorWithCodefor other errors. This is intentional (access check in handler, business logic in service) but worth verifying the error conversion middleware handles this correctly.findByIdfully removed: ThefindByIdmethod was completely removed fromWrongAssignmentReportRepositoryand replaced withfindTeamIdById. Verify no other code paths needed the full report details.RoutingFormResponseRepository.interface.tsnow returns{ response, form }instead of the fullApp_RoutingForms_FormResponse. TypeScript should catch any issues, but verify compilation passes.Link to Devin run: https://app.devin.ai/sessions/d90ea1ab8e2742a681c860f1f05b5e77
Requested by: @emrysal