User Insight Dashboard
Aggregates every call a phone number has ever made across an organization's campaigns and agents into one unified view, so admins can see the full history of a single caller instead of digging through campaigns one by one.
Table of Contents
- Architecture Overview
- How It Works
- File Reference
- Data Model
- API Endpoints
- Feature Gating
- How to Test
- Troubleshooting
Architecture Overview
Clicking a row drills into a per-phone call history (/users/:phone/calls), and a "Generate Report" button aggregates that history into a summary (/users/:phone/report).
How It Works
- Table view Admin opens
/admin/user-insights. The page fetchesGET /users, which groups everyContactbyoriginalPhone(the caller's number) within the admin's org and returns one row per unique number with total calls, associated campaigns/agents, and first/last call dates. - Search Typing 3+ characters into the search box re-fetches the list with a case-insensitive substring filter on the phone number.
- Detail view Clicking a row fetches
GET /users/:phone/calls, returning every call for that number (newest first), each with the recording, transcription, and analysis for that specific call. - Report view Clicking "Generate Report" fetches
GET /users/:phone/report, which aggregates all of that number's calls into one summary: totals, inbound/outbound split, per-campaign and per-agent breakdowns, and a combined list of all analysis results and transcriptions. - Every query is scoped to the requesting admin's
orgUIDand excludesisTestCall: truecontacts, so test data and other tenants' data never leak in.
File Reference
| File | Role | Key Functions |
|---|---|---|
backend/client_backend/src/types/userInsight.ts | Shared TypeScript interfaces for summary/call/report shapes | UserInsightSummary, UserInsightCall, UserInsightReport |
backend/client_backend/src/repositories/userInsight.repository.ts | MongoDB aggregation pipelines, org + test-call scoping | aggregateUniqueUsers(), findUserCalls(), aggregateUserReport() |
backend/client_backend/src/controllers/userInsight.controller.ts | Request validation, pagination parsing, response shaping | listUniqueUsers(), getUserCalls(), getUserReport() |
backend/client_backend/src/routes/userInsight.routes.ts | Route + middleware chain definitions | mounts 3 GET routes |
app/src/types/userInsight.ts | Frontend types mirroring backend response envelopes | PaginatedUsersResponse, PaginatedCallsResponse, UserReportResponse |
app/src/services/userInsight.service.ts | Axios client for the 3 endpoints | listUniqueUsers(), getUserCalls(), getUserReport() |
app/src/components/features/UserInsightTable.tsx | Paginated table of unique phone numbers | |
app/src/components/features/UserInsightDetail.tsx | Call history cards (recording, transcription, analysis) | |
app/src/components/features/UserInsightReport.tsx | Aggregated report view | |
app/src/pages/admin/UserInsightsPage.tsx | Orchestrates table/detail/report views + feature gate |
Data Model
No new collections were added. The feature reads from three existing collections:
originalPhone is the caller's number this is the field everything is grouped by, since one phone number can call into multiple campaigns/agents over time.
API Endpoints
All three endpoints share this middleware chain:
verifyJwt → requireRole("admin") → requireOrg → requireFeatureAccess(...FEATURE_MAP.tokenAnalytics)
FEATURE_MAP.tokenAnalyticsmaps to theadmin_analyticsfeature flag the same gate used by Token Consumption Analytics. No new feature flag was created.
List Unique Users
- Method:
GET - Path:
/api/v1/user-insights/users - Query:
page(default 1),limit(default 20, max 100),search(optional, partial phone match) - Response
200:json{ "success": true, "data": { "users": [ { "phone": "+15551234567", "totalCalls": 5, "campaigns": [{ "campaignUID": "camp_1", "campaignName": "Spring Promo" }], "agents": [{ "agentId": "agent_1", "agentName": "Agent Smith" }], "firstCallDate": "2024-01-01T00:00:00.000Z", "lastCallDate": "2024-03-01T00:00:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 } } }
Get User Call History
- Method:
GET - Path:
/api/v1/user-insights/users/:phone/calls - Query:
page(default 1),limit(default 20, max 50) - Response
200:{ success, data: { phone, calls: UserInsightCall[], pagination } }sorted bycallStartedAtdescending.
Get User Insight Report
- Method:
GET - Path:
/api/v1/user-insights/users/:phone/report - Response
200:{ success, data: UserInsightReport }withtotalCalls,callDirectionDistribution,campaignBreakdown,agentBreakdown,analysisResults,transcriptionSegments. - Response
404: no contacts found for that phone in this org.
Shared Error Responses
| Status | Reason |
|---|---|
400 | Invalid page/limit (non-numeric or < 1) |
403 | No org context, non-admin role, or admin_analytics not enabled |
404 | Report requested for a phone with no contacts |
500 | Unexpected server/DB error |
Feature Gating
Reuses the existing admin_analytics flag see Analytics.md → Feature Gating System for the full deny-by-default logic. On the frontend, both the sidebar link and the /admin/user-insights route are wrapped with:
const { hasFeature } = useFeature();
if (!hasFeature(FEATURES.ADMIN_ANALYTICS)) return null;How to Test (End-to-End)
Prerequisites: an org with admin_analytics in its plan features, an admin-role user, and at least one Contact document with isTestCall: false.
- Log in as an admin whose org has
admin_analyticsenabled. - Navigate to Admin → User Insights in the sidebar (hidden if the feature isn't enabled).
- Confirm the table loads unique phone numbers with correct call counts, campaign/agent names, and dates.
- Type 3+ digits of a known phone number into search confirm the list filters down.
- Click a row confirm the detail view shows that number's calls sorted newest-first, with recording/transcription/analysis sections (or placeholders when missing).
- Click Generate Report confirm totals, inbound/outbound counts, and campaign/agent breakdowns add up correctly.
- In MongoDB, verify: no contact with
isTestCall: trueappears anywhere, and no data from a differentorgUIDappears in any view.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Sidebar link / page missing | admin_analytics not in org's plan features | Enable the feature for the org (same toggle used for Token Analytics) |
403 on all endpoints | User role isn't admin, or no orgUID on the request | Confirm JWT role and that requireOrg resolved an org |
404 on report | No non-test contacts exist for that phone in this org | Expected behavior not a bug |
| Row shows raw ID instead of a name | Campaign/Agent was deleted after the call was made | Expected fallback per requirement 4.6 campaignName/agentName is null |
| Slow list/report load | Aggregation exceeded maxTimeMS: 5000 | Check indexes on Contact.orgUID + Contact.originalPhone; narrow the date range or search filter |