// Project Writeup

FSMetrics Dashboard

A standalone HTML dashboard that pulls live ticket data from the Freshservice API and renders KPIs, breakdowns by category, service, and priority, plus daily volume charts — all from a single file opened locally in Edge.

JavaScript REST API Freshservice Internal Tool

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_at
    The first build filtered client-side on created_at, which only showed tickets opened in the window. Freshservice's native reporting filters on updated_at — any ticket touched in that period. This mismatch caused persistent count discrepancies. Switching to updated_at brought dashboard numbers in line with Freshservice's own reports.
  • Still Open = Total minus Resolved
    Originally, "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 weeks
    The "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_fields
    Freshservice 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 backend
    Every 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

Language
Vanilla JavaScript
API
Freshservice REST v2
Platform
Single-file HTML
Browser
Microsoft Edge (local)
Design
DM Sans + JetBrains Mono, GitHub-dark palette
Storage
None (stateless)

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.