A comprehensive Zendesk custom app that tracks agent actions — ticket reassignments, field changes, macro usage — and visualizes productivity patterns, response consistency, and compliance adherence over time.
Built with React 18, Zendesk Garden design system, Recharts visualizations, and IndexedDB for local caching with background sync.
- Features
- Architecture Overview
- Data Flow
- Caching Strategy
- Component Hierarchy
- API Integration
- Metrics Reference
- Visualizations
- Tech Stack
- Project Structure
- Getting Started
- Build & Deploy
- Configuration
- Export
- License
- 10 Key Metrics — Each with an info icon that explains the metric, its formula, and data source on click
- 10 Interactive Charts — Bar, Line, Area, Pie, Radar, Funnel, and two Heatmap visualizations
- IndexedDB Caching — API responses stored locally for instant loads; configurable TTL (15min to 24hrs)
- Background Sync — Fresh data fetched in the background without freezing the UI
- Last Updated Display — Shows relative time ("Updated 5 minutes ago") with manual refresh
- PDF Export — Downloads the full dashboard with all charts as a multi-page PDF
- CSV Export — Downloads all agent metrics as a spreadsheet-ready CSV file
- Sortable Agent Table — Search, sort by any column, paginated, with color-coded SLA/CSAT badges
- Audit Trail Timeline — Chronological event log with date grouping, type filtering, and lazy loading
- Date Range & Agent Filters — Quick presets (7d/30d/90d/6mo/1yr) and custom date range with agent selector
- Zendesk Garden UI — Fully themed with Zendesk's official design system components
graph TB
subgraph Zendesk["Zendesk Platform"]
ZAF["ZAF SDK<br/>(App Framework)"]
API1["Audit Logs API"]
API2["Ticket Audits API"]
API3["Ticket Metrics API"]
API4["Metric Events API"]
API5["Satisfaction Ratings API"]
API6["Users API"]
API7["Groups API"]
API8["Group Memberships API"]
end
subgraph App["Custom App (iframe)"]
subgraph Services["Services Layer"]
ZC["zafClient.js<br/>ZAF Client Singleton"]
AP["api.js<br/>8 API Fetchers +<br/>Auto-Pagination"]
CA["cache.js<br/>IndexedDB (idb)<br/>8 Object Stores"]
DP["dataProcessor.js<br/>Pure Metric<br/>Computation"]
EX["exportService.js<br/>PDF & CSV<br/>Generation"]
end
subgraph Hooks["React Hooks"]
UAD["useAuditData<br/>Fetch + Cache +<br/>Background Sync"]
UM["useMetrics<br/>Filter + Compute +<br/>Memoize"]
end
subgraph UI["UI Components"]
DASH["Dashboard<br/>Main Layout"]
MC["MetricCard ×10"]
FIL["Filters"]
SS["SyncStatus"]
EC["ExportControls"]
APT["AgentPerformanceTable"]
AT["AuditTimeline"]
subgraph Charts["10 Chart Components"]
C1["ResponseTimeChart"]
C2["TicketVolumeChart"]
C3["CSATTrendChart"]
C4["ActivityHeatmap"]
C5["HourlyHeatmap"]
C6["AgentComparisonRadar"]
C7["ComplianceChart"]
C8["ResolutionFunnel"]
C9["WorkloadDistribution"]
C10["ReassignmentChart"]
end
end
end
subgraph Browser["Browser Storage"]
IDB["IndexedDB<br/>zendesk-audit-dashboard"]
end
ZAF --> ZC
ZC --> AP
AP --> API1 & API2 & API3 & API4 & API5 & API6 & API7 & API8
AP --> CA
CA --> IDB
CA --> UAD
UAD --> UM
UM --> DASH
DASH --> MC & FIL & SS & EC & APT & AT & Charts
DP --> UM
EX --> EC
style Zendesk fill:#1f73b7,color:#fff
style App fill:#f8f9f9,color:#2f3941
style Browser fill:#e9ebed,color:#2f3941
style Services fill:#edf8f4,color:#2f3941
style Hooks fill:#fff6e5,color:#2f3941
style UI fill:#fff0f1,color:#2f3941
style Charts fill:#dceefb,color:#2f3941
sequenceDiagram
participant User
participant Dashboard
participant useAuditData
participant IndexedDB
participant API as Zendesk APIs (×8)
participant useMetrics
participant Charts
User->>Dashboard: Opens nav_bar app
Dashboard->>useAuditData: Mount hook
Note over useAuditData,IndexedDB: Phase 1: Instant Load from Cache
useAuditData->>IndexedDB: getCachedData() for all 8 stores
IndexedDB-->>useAuditData: Cached data (or empty)
useAuditData-->>Dashboard: { data, isLoading: false }
Dashboard->>useMetrics: Pass raw data + filters
useMetrics->>useMetrics: computeAgentMetrics()<br/>computeSummaryMetrics()<br/>computeChartData()
useMetrics-->>Dashboard: { agentMetrics, summaryMetrics, chartData }
Dashboard->>Charts: Render all visualizations
Note over useAuditData,API: Phase 2: Background Sync (non-blocking)
useAuditData->>useAuditData: Check isCacheStale() (TTL)
alt Cache is stale
useAuditData->>useAuditData: setTimeout → isSyncing = true
useAuditData->>API: fetchAll() in parallel (8 requests)
API-->>useAuditData: Fresh data
useAuditData->>IndexedDB: setCachedData() + setLastSyncTime()
useAuditData-->>Dashboard: Updated data + isSyncing: false
Dashboard->>useMetrics: Recompute metrics
Dashboard->>Charts: Re-render with fresh data
end
Note over User,Dashboard: User Actions
User->>Dashboard: Change date range / agent filter
Dashboard->>useMetrics: New filters
useMetrics-->>Dashboard: Filtered metrics
Dashboard->>Charts: Update visualizations
User->>Dashboard: Click "Refresh"
Dashboard->>useAuditData: refresh()
useAuditData->>API: Force re-fetch all
API-->>useAuditData: Fresh data
useAuditData->>IndexedDB: Update cache
useAuditData-->>Dashboard: Updated data
User->>Dashboard: Click "Export PDF"
Dashboard->>Dashboard: html2canvas → jsPDF → download
User->>Dashboard: Click "Export CSV"
Dashboard->>Dashboard: papaparse → Blob → download
flowchart TD
START([App Opens]) --> LOAD_CACHE[Load from IndexedDB<br/>All 8 stores in parallel]
LOAD_CACHE --> HAS_DATA{Has cached data?}
HAS_DATA -->|Yes| SHOW_CACHED[Render UI with cached data<br/>isLoading = false]
HAS_DATA -->|No| SHOW_EMPTY[Render empty state<br/>isLoading = false]
SHOW_CACHED --> CHECK_TTL{Cache stale?<br/>age > TTL}
SHOW_EMPTY --> FORCE_SYNC[Start sync immediately]
CHECK_TTL -->|Fresh| DONE([Wait for next check])
CHECK_TTL -->|Stale| BG_SYNC[Start background sync<br/>isSyncing = true]
BG_SYNC --> FETCH_API[Fetch all 8 APIs<br/>in parallel via<br/>Promise.allSettled]
FORCE_SYNC --> FETCH_API
FETCH_API --> STORE_CACHE[Store results in IndexedDB<br/>Clear old data + bulk put]
STORE_CACHE --> UPDATE_SYNC[Update syncMeta<br/>lastSync timestamp]
UPDATE_SYNC --> UPDATE_UI[Update React state<br/>isSyncing = false]
UPDATE_UI --> DONE
subgraph TTL["Configurable TTL"]
direction LR
T1["15 min"]
T2["30 min"]
T3["1 hour ★ default"]
T4["2 hours"]
T5["6 hours"]
T6["24 hours"]
end
subgraph IDB["IndexedDB Stores"]
direction LR
S1["auditLogs"]
S2["ticketAudits"]
S3["ticketMetrics"]
S4["metricEvents"]
S5["satisfactionRatings"]
S6["agents"]
S7["groups"]
S8["syncMeta"]
end
style START fill:#1f73b7,color:#fff
style DONE fill:#038153,color:#fff
style BG_SYNC fill:#ffb648,color:#2f3941
style FORCE_SYNC fill:#cc3340,color:#fff
graph TD
INDEX["index.js<br/>ZAF Init + React Render"] --> APP["App.jsx<br/>ThemeProvider"]
APP --> DASH["Dashboard.jsx<br/>Main Layout + State"]
DASH --> TOP["Top Bar"]
DASH --> METRICS["Metrics Section"]
DASH --> PERF["Performance Section"]
DASH --> ACTIVITY["Activity Section"]
DASH --> COMPARE["Comparison Section"]
DASH --> WORKLOAD["Workload Section"]
DASH --> TABLE["Agent Details Section"]
DASH --> TIMELINE["Audit Trail Section"]
TOP --> SS["SyncStatus<br/>Last updated + Refresh"]
TOP --> FIL["Filters<br/>Date Range + Agent"]
TOP --> EC["ExportControls<br/>PDF + CSV"]
METRICS --> MC1["MetricCard<br/>Avg Response Time"]
METRICS --> MC2["MetricCard<br/>Avg Resolution Time"]
METRICS --> MC3["MetricCard<br/>Tickets Resolved"]
METRICS --> MC4["MetricCard<br/>Reassignments"]
METRICS --> MC5["MetricCard<br/>Field Changes"]
METRICS --> MC6["MetricCard<br/>Macro Usage"]
METRICS --> MC7["MetricCard<br/>SLA Compliance"]
METRICS --> MC8["MetricCard<br/>CSAT Score"]
METRICS --> MC9["MetricCard<br/>Work Time"]
METRICS --> MC10["MetricCard<br/>Wait Time"]
MC1 & MC2 & MC3 & MC4 & MC5 & MC6 & MC7 & MC8 & MC9 & MC10 --> IM["InfoModal<br/>Metric Explanation"]
PERF --> RTC["ResponseTimeChart<br/>📊 Bar Chart"]
PERF --> TVC["TicketVolumeChart<br/>📈 Area Chart"]
PERF --> CSAT["CSATTrendChart<br/>📉 Line Chart"]
ACTIVITY --> AH["ActivityHeatmap<br/>🟩 Calendar Heatmap"]
ACTIVITY --> HH["HourlyActivityHeatmap<br/>🟦 Hour×Day Grid"]
COMPARE --> ACR["AgentComparisonRadar<br/>🕸️ Radar Chart"]
COMPARE --> CC["ComplianceChart<br/>🍩 Donut Chart"]
COMPARE --> RF["ResolutionFunnel<br/>📐 Funnel Chart"]
WORKLOAD --> WD["WorkloadDistribution<br/>📊 Stacked Area"]
WORKLOAD --> RC["ReassignmentChart<br/>📊 Horizontal Bar"]
TABLE --> APT["AgentPerformanceTable<br/>Sortable + Searchable"]
TIMELINE --> AT["AuditTimeline<br/>Date Groups + Scroll"]
style DASH fill:#1f73b7,color:#fff
style METRICS fill:#edf8f4,color:#2f3941
style PERF fill:#dceefb,color:#2f3941
style ACTIVITY fill:#fff6e5,color:#2f3941
style COMPARE fill:#fff0f1,color:#2f3941
flowchart LR
subgraph Client["ZAF Client"]
REQ["client.request()"]
end
subgraph Pagination["Auto-Pagination"]
CP["Cursor-Based<br/>paginateRequest()"]
IP["Incremental<br/>incrementalFetch()"]
end
subgraph APIs["8 Zendesk APIs"]
A1["GET /api/v2/audit_logs.json<br/>Admin audit trail, macro usage"]
A2["GET /api/v2/ticket_audits.json<br/>Field changes, reassignments"]
A3["GET /api/v2/ticket_metrics.json<br/>Response & resolution times"]
A4["GET /api/v2/incremental/<br/>ticket_metric_events.json<br/>SLA breaches, work time"]
A5["GET /api/v2/satisfaction_ratings.json<br/>CSAT scores per agent"]
A6["GET /api/v2/users.json?role=agent<br/>Agent list & profiles"]
A7["GET /api/v2/groups.json<br/>Team/group structure"]
A8["GET /api/v2/group_memberships.json<br/>Agent-to-group mapping"]
end
REQ --> CP & IP
CP --> A1 & A2 & A3 & A5 & A6 & A7 & A8
IP --> A4
style Client fill:#1f73b7,color:#fff
style Pagination fill:#ffb648,color:#2f3941
| API | Endpoint | Pagination | Key Data |
|---|---|---|---|
| Audit Logs | /api/v2/audit_logs.json |
Cursor | actor_name, action, change_description, source_type |
| Ticket Audits | /api/v2/ticket_audits.json |
Cursor | author_id, events[] (field changes, status transitions) |
| Ticket Metrics | /api/v2/ticket_metrics.json |
Cursor | reply_time_in_minutes, full_resolution_time_in_minutes |
| Metric Events | /api/v2/incremental/ticket_metric_events.json |
Time-based | metric, status (breached/fulfilled), sla_policy |
| Satisfaction Ratings | /api/v2/satisfaction_ratings.json |
Cursor | assignee_id, score (good/bad) |
| Users | /api/v2/users.json?role=agent |
Cursor | name, email, photo |
| Groups | /api/v2/groups.json |
Cursor | id, name |
| Group Memberships | /api/v2/group_memberships.json |
Cursor | user_id, group_id |
| # | Metric | Unit | Formula | Source |
|---|---|---|---|---|
| 1 | Avg First Response Time | hours | Sum of first response times / Total tickets | Ticket Metrics |
| 2 | Avg Resolution Time | hours | Sum of resolution times / Total resolved tickets | Ticket Metrics |
| 3 | Tickets Resolved | count | Count of status changed to solved/closed | Ticket Audits |
| 4 | Reassignment Count | count | Count of assignee_id field change events | Ticket Audits |
| 5 | Field Change Frequency | count | Count of all field change events | Ticket Audits |
| 6 | Macro Usage | count | Count of macro-related audit events | Audit Logs |
| 7 | SLA Compliance Rate | % | (Non-breached / Total SLA events) × 100 | Metric Events |
| 8 | CSAT Score | % | (Good ratings / Total ratings) × 100 | Satisfaction Ratings |
| 9 | Agent Work Time | hours | Sum of agent_work_time metric events | Metric Events |
| 10 | Requester Wait Time | hours | Sum of requester_wait_time / Total tickets | Metric Events |
Click the ⓘ icon next to any metric in the dashboard to see its full description, formula, and data source.
| # | Chart | Type | Library | Description |
|---|---|---|---|---|
| 1 | Response Time | Bar | Recharts | Avg first response & resolution time per agent |
| 2 | Ticket Volume | Area | Recharts | Daily ticket resolution trend over time |
| 3 | CSAT Trend | Line | Recharts | Monthly CSAT score with 80% target reference line |
| 4 | Activity Heatmap | Calendar | react-calendar-heatmap | GitHub-style 365-day activity intensity |
| 5 | Hourly Heatmap | Grid | Custom CSS Grid | 7×24 matrix (Day of Week × Hour) activity pattern |
| 6 | Agent Comparison | Radar | Recharts | Multi-agent comparison across 5 dimensions |
| 7 | SLA Compliance | Donut | Recharts | Met vs Breached with center percentage |
| 8 | Resolution Funnel | Funnel | Recharts | New → Open → Pending → Solved → Closed pipeline |
| 9 | Workload Distribution | Stacked Area | Recharts | Agent workload share over time |
| 10 | Reassignments | Horizontal Bar | Recharts | Reassignment count per agent |
Activity Heatmap (green scale):
□ No activity ■ Low ■ Medium ■ High ■ Very High
Hourly Heatmap (blue scale):
□ No activity ■ Minimal ■ Light ■ Moderate ■ Active ■ Peak
| Category | Technology | Purpose |
|---|---|---|
| Framework | React 18 | UI rendering |
| Bundler | Webpack 5 | Module bundling |
| Transpiler | Babel 7 | JSX + modern JS |
| UI Library | Zendesk Garden | Design system components |
| Styling | styled-components 5 | CSS-in-JS (required by Garden) |
| Charts | Recharts 2 | Bar, Line, Area, Pie, Radar, Funnel |
| Heatmap | react-calendar-heatmap | GitHub-style calendar |
| Caching | idb 8 | IndexedDB Promise wrapper |
| html2canvas + jsPDF | Dashboard screenshot → PDF | |
| CSV | PapaParse | JSON → CSV conversion |
| Dates | date-fns 3 | Date formatting & math |
| Platform | ZAF SDK 2.0 | Zendesk App Framework |
zendesk-agent-performance-audit-custom-app/
├── manifest.json # Zendesk app manifest (v2.0)
├── package.json # Dependencies & scripts
├── webpack.config.js # Webpack 5 config
├── .babelrc # Babel presets
├── .gitignore
├── LICENSE
│
├── assets/
│ ├── iframe.html # App entry point (loads ZAF SDK)
│ ├── logo.svg # App logo (128×128)
│ └── icon_nav_bar.svg # Navigation bar icon
│
├── translations/
│ └── en.json # English strings
│
└── src/
├── index.js # ZAF init → React render
├── App.jsx # ThemeProvider wrapper
│
├── constants/
│ ├── config.js # TTL, colors, DB config, presets
│ └── metricDefinitions.js # 10 metric descriptions & formulas
│
├── services/
│ ├── zafClient.js # ZAF client singleton
│ ├── api.js # 8 API fetchers + auto-pagination
│ ├── cache.js # IndexedDB CRUD (8 stores)
│ ├── dataProcessor.js # Pure metric computation functions
│ └── exportService.js # PDF & CSV generation
│
├── hooks/
│ ├── useAuditData.js # Cache-first + background sync
│ └── useMetrics.js # Filter + compute + memoize
│
└── components/
├── Dashboard.jsx # Main layout & state management
├── MetricCard.jsx # Metric value + info icon
├── InfoModal.jsx # Metric explanation modal
├── Filters.jsx # Date range + agent selector
├── SyncStatus.jsx # Last updated + refresh + TTL
├── ExportControls.jsx # PDF/CSV export buttons
├── AgentPerformanceTable.jsx # Sortable agent stats table
├── AuditTimeline.jsx # Scrollable audit event log
│
└── charts/
├── ChartWrapper.jsx # Shared chart card with info icon
├── ResponseTimeChart.jsx # 📊 Bar: response time per agent
├── TicketVolumeChart.jsx # 📈 Area: tickets over time
├── CSATTrendChart.jsx # 📉 Line: CSAT trend
├── ActivityHeatmap.jsx # 🟩 Calendar: yearly activity
├── HourlyActivityHeatmap.jsx # 🟦 Grid: hour × day matrix
├── AgentComparisonRadar.jsx # 🕸️ Radar: multi-metric compare
├── ComplianceChart.jsx # 🍩 Donut: SLA met vs breached
├── ResolutionFunnel.jsx # 📐 Funnel: ticket pipeline
├── WorkloadDistribution.jsx # 📊 Stacked area: workload
└── ReassignmentChart.jsx # 📊 Horizontal bar: reassigns
- Node.js 18+ and npm 9+
- Zendesk account (Enterprise plan for Audit Logs API; other APIs work on all plans)
- Zendesk CLI (
@zendesk/zcli) for local development and deployment
# Clone the repository
git clone https://github.com/mohdasim/Zendesk-Agent-Performance-Audit-Custom-App.git
cd Zendesk-Agent-Performance-Audit-Custom-App
# Install dependencies
npm install# Start webpack dev server
npm start
# App available at http://localhost:4567
# Or use Zendesk CLI for in-Zendesk testing
npm run build
npm run server
# Then append ?zcli_apps=true to your Zendesk URLnpm run validate# Production build
npm run build
# Output in dist/
# dist/manifest.json
# dist/assets/iframe.html
# dist/assets/bundle.[hash].js
# dist/translations/en.json# Package for upload
cd dist && zip -r ../app.zip . && cd ..
# Upload via Zendesk Admin → Apps → Upload private app
# Or use zcli:
npx @zendesk/zcli apps:create distThe app will appear as an icon in the left navigation bar of Zendesk Support.
The auto-refresh interval is configurable from the dashboard via the dropdown next to the refresh button:
| Setting | Value |
|---|---|
| 15 minutes | 900000 ms |
| 30 minutes | 1800000 ms |
| 1 hour (default) | 3600000 ms |
| 2 hours | 7200000 ms |
| 6 hours | 21600000 ms |
| 24 hours | 86400000 ms |
The selected TTL is persisted in IndexedDB and survives page reloads.
Quick filter presets available in the toolbar:
- Last 7 days
- Last 30 days (default)
- Last 90 days
- Last 6 months
- Last year
Custom start/end dates can also be set manually.
Captures the full dashboard (all charts, metrics, tables) as a landscape PDF:
- Uses
html2canvasto screenshot the DOM at 2× resolution - Generates multi-page PDF via
jsPDFif content exceeds one page - Includes header with report title and generation timestamp
Downloads agent performance metrics as a CSV file with columns:
- Agent Name, Email, Group(s)
- Tickets Resolved, Avg First Response Time, Avg Resolution Time
- Reassignment Count, Field Changes, Macro Usage
- SLA Compliance %, CSAT Score %, CSAT Rating Count
- Agent Work Time, Requester Wait Time