— The Problem
Freshservice's built-in reporting is slow, inflexible, and gated behind roles that not every technician has. As the sole IT presence at TaylorMade Golf's Liberty Ball Plant, I needed a way to pull real-time ticket metrics for any technician, any date range, without waiting on canned reports or requesting dashboard access from corporate.
The tool had to run from a single HTML file — no server, no build step, no cloud subscription. Just open it in Edge, enter your API key, pick a tech and a date window, and get numbers that match what Freshservice's own reporting shows.
— Architecture
The dashboard is a single self-contained HTML file with all CSS and JavaScript inline. On load, it authenticates against Freshservice's REST API using a base64-encoded API key, then paginated-fetches every ticket touched in the selected window.
User opens IT_Metrics_Dashboard.html in Edge
│
├── Selects technician (from hardcoded roster of 13)
├── Selects period: This Week │ 7d │ 14d │ 30d │ 90d │ Custom Range
│
└── Clicks "Load Metrics"
│
├── GET /api/v2/tickets?updated_since={start}&per_page=100
│ └── paginate until exhausted
│
├── Client-side filter on updated_at ∈ [start, end]
│ └── Bucket by responder_id for per-tech view
│
└── Render:
├── KPI tiles: Total │ Resolved/Closed │ Still Open
├── Daily Ticket Volume (bar chart, full width)
├── By Category │ By Sub-Category
├── By Service │ By Priority
└── Ticket Types │ Resolution Time
The API's list endpoint is used instead of the filter endpoint because Freshservice's filter endpoint does not return type_fields, which is where sub-category and custom service data live. The list endpoint's updated_since parameter handles the initial server-side date window, then a client-side pass narrows to the exact range.
— Key Decisions
-
updated_at filtering, not created_atThe first build filtered client-side on
created_at, which only showed tickets opened in the window. Freshservice's native reporting filters onupdated_at— any ticket touched in that period. This mismatch caused persistent count discrepancies. Switching toupdated_atbrought dashboard numbers in line with Freshservice's own reports. -
Still Open = Total minus ResolvedOriginally, "Still Open" enumerated statuses 2 (Open) and 3 (Pending). This missed statuses like Waiting on Customer (6), Waiting on Third Party (7), and Waiting on Internal (8), so Resolved + Open never equaled Total. Inverting the calculation —
total - resolved— is more robust than chasing every possible open status code. -
Sunday-start work weeksThe "This Week" period starts on Sunday, matching TaylorMade's internal reporting cadence. If you load on Wednesday, you see Sunday through today. This made the dashboard a drop-in replacement for the weekly check-in reports the team was already used to.
-
Sub-category via custom_fieldsFreshservice stores the service sub-category in
custom_fields.please_select_the_service, not in the standard category hierarchy. Discovering this required tracing why the sub-category chart was empty despite tickets clearly having sub-categories in the web UI. -
Single-file, no backendEvery dashboard in the suite is a standalone HTML file with zero server dependencies. They run from the local filesystem, store API keys in the browser prompt (never persisted), and need no npm, no Python, no Docker. This makes them trivially portable — drop the file on any tech's desktop and it works.
— Lessons Learned
API endpoints are not interchangeable
Freshservice's filter endpoint and list endpoint return different field sets. The filter endpoint omits type_fields entirely, which means warranty data, custom service fields, and asset state are invisible through it. This cost real debugging time before the root cause was clear — the same ticket ID returned different payloads depending on which endpoint fetched it.
Environment totals ≠ team totals
When the companion Team Overview dashboard launched, the summary KPI tiles used the full API response (every ticket in the environment), while per-tech rows filtered by responder_id against a 13-person roster. The numbers looked wrong because they were measuring different things. The fix was to present both scopes explicitly — an "Environment" row and a "Team" row — rather than trying to reconcile them into a single number.
Match the source of truth first
The single most valuable debugging step was pulling the same date range in Freshservice's native analytics and comparing ticket-by-ticket. Every discrepancy traced back to a filtering assumption, not a rendering bug. Once the data pipeline matched, the charts took care of themselves.
— Stack
— Status & Outcomes
The FSMetrics dashboard is in daily use at the Liberty Ball Plant and has become the primary reporting surface for 1:1s and team check-ins. It replaced a manual workflow of exporting CSVs from Freshservice and pasting into spreadsheets.
The same architectural pattern — single-file HTML, Freshservice API, client-side bucketing — expanded into a family of companion tools: a Team Overview dashboard with dual-scope Environment/Team summaries, a CSAT dashboard, and an SLA Breach Tracker. Each shares the same API authentication flow and dark-theme design language but serves a distinct reporting angle.