Skip to content

feat(dashboard): request status code and provider latency charts#501

Merged
SantiagoDePolonia merged 3 commits into
mainfrom
feat/status-server
Jul 7, 2026
Merged

feat(dashboard): request status code and provider latency charts#501
SantiagoDePolonia merged 3 commits into
mainfrom
feat/status-server

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Closes #459

What

Two new charts on the dashboard's Overview page, placed right before Providers Overview so error rates, provider latency, and provider availability read as one health block (they follow the shared date-range picker):

  • Requests by Status — a stacked bar chart of request counts grouped into 2xx / 4xx / 5xx (plus an "Other" series that appears only when 1xx/3xx/unset statuses exist), with header KPIs for the overall success rate and per-class counts. This is the chart requested in Add requests status code chart (2xx/4xx/5xx) #459.
  • Provider Latency (bonus from the issue discussion) — the audit log already records per-request duration, so a line chart shows the average duration of successful requests per provider over the same buckets. Local response-cache hits and failed requests are excluded so averages reflect real provider round trips; buckets with no data render as gaps, not zeros.

Both are powered by a new admin endpoint:

  • GET /admin/audit/stats?days=|start_date=&end_date= — time-bucketed status-class counts, a success-rate summary, and per-provider latency series. Ranges up to 3 days use hourly buckets; longer ranges use daily buckets aligned to the dashboard timezone. Buckets are zero-filled up to the current time.

How

  • Storage backends (SQLite, PostgreSQL, MongoDB) each run a single query grouping audit entries by UTC hour and provider; shared Go code (foldRequestStats) folds hours into hourly or timezone-correct daily buckets, so daily bucketing stays right across DST without per-backend timezone SQL.
  • The charts use the dashboard's existing status tokens (matching the status badges in the audit log) and hand out distinct categorical palette colors to providers in first-seen order, so a provider keeps its color while the page is open. Bucket labels format in the dashboard's effective timezone — the same one the server buckets by.
  • Charts appear only when audit data exists; with LOGGING_ENABLED=false the endpoint returns an empty result and the page looks unchanged.
  • The demo seed (tools/seed-demo-data.sh) now emits 429s alongside 500s and gives each provider a distinct latency profile so the charts are legible out of the box.

User-visible impact

  • New charts on the Overview dashboard page; no behavior change elsewhere.
  • New documented admin endpoint GET /admin/audit/stats (swagger/openapi regenerated, docs page updated).
  • Requires audit logging (LOGGING_ENABLED=true) to show data.

Tests

  • foldRequestStats unit tests (hour/day intervals, DST-safe local-day folding, zero-fill truncation at "now", latency gap semantics, tolerance-based float comparison) and a SQLite reader integration test.
  • Admin handler tests: interval selection by range span, nil-reader fast path, validation-before-nil-reader ordering, invalid date validation, result passthrough.
  • New JS module tests (11 cases: payload normalization, chart configs, render/destroy lifecycle, fetch success/failure paths, effective-timezone labels).
  • Verified visually against seeded demo data (hourly and daily buckets, dark theme, provider latency separation).

🤖 Generated with Claude Code

Add a "Requests by Status" stacked bar chart (2xx/4xx/5xx, with an
overall success-rate summary) and a "Provider Latency" line chart
(average duration of successful uncached requests per provider) to the
dashboard's Audit Logs page, backed by a new GET /admin/audit/stats
endpoint.

Storage backends group audit entries by UTC hour and provider in one
query; shared Go code folds the hours into hourly buckets (ranges up to
3 days) or timezone-correct daily buckets (longer ranges), zero-filled
up to the current time. Latency series align with the status buckets
and keep empty buckets as gaps rather than zeros, and exclude local
response-cache hits and failed requests so averages reflect real
provider round trips.

The demo seed now emits 429s alongside 500s and gives each provider a
distinct latency profile so the new charts are legible out of the box.

Closes #459

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@SantiagoDePolonia, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 84da7377-a5c4-40b2-a631-a84a1367fbaf

📥 Commits

Reviewing files that changed from the base of the PR and between 6e84e2a and 62a19e9.

📒 Files selected for processing (9)
  • docs/advanced/admin-endpoints.mdx
  • internal/admin/dashboard/static/css/dashboard.css
  • internal/admin/dashboard/static/js/dashboard.js
  • internal/admin/dashboard/static/js/modules/audit-stats.js
  • internal/admin/dashboard/static/js/modules/audit-stats.test.cjs
  • internal/admin/dashboard/static/js/modules/usage.js
  • internal/admin/dashboard/templates/page-overview.html
  • internal/admin/handler_audit_stats_test.go
  • internal/auditlog/stats_test.go
📝 Walkthrough

Walkthrough

This PR adds an admin audit request-statistics feature: a new GET /admin/audit/stats endpoint backed by foldRequestStats aggregation logic implemented across MongoDB, PostgreSQL, and SQLite readers, a dashboard UI with stacked status and latency charts, corresponding OpenAPI/docs updates (including previously-undocumented rate-limits endpoints), and demo data seed script adjustments.

Changes

Audit Stats Feature

Layer / File(s) Summary
Stats data model and folding logic
internal/auditlog/stats.go, internal/auditlog/stats_test.go, internal/auditlog/reader.go
Defines RequestStats, RequestStatsParams, bucket/summary/provider-latency types, EmptyRequestStats, foldRequestStats bucketing/aggregation algorithm, and the Reader.GetRequestStats interface method, with tests for hour/day bucketing, zero-fill, and provider ordering.
Storage backend implementations
internal/auditlog/stats_mongodb.go, internal/auditlog/stats_postgresql.go, internal/auditlog/stats_sqlite.go
Implements GetRequestStats for each backend, querying audit logs grouped by hour/provider and computing status/latency aggregates before folding.
Admin handler and route wiring
internal/admin/handler_audit.go, internal/admin/routes.go, internal/admin/routes_test.go, internal/admin/handler_test.go, internal/admin/handler_audit_stats_test.go
Adds Handler.AuditStats, registers GET /audit/stats, updates mock reader and route tests.
Dashboard charts UI
internal/admin/dashboard/static/js/dashboard.js, .../modules/audit-stats.js, .../modules/audit-stats.test.cjs, .../modules/usage.js, internal/admin/dashboard/templates/*, internal/admin/dashboard/static/css/dashboard.css
Adds fetch/normalize/render logic and Chart.js-based status/latency charts wired into the dashboard's audit-logs page.

OpenAPI and Documentation Updates

Layer / File(s) Summary
Audit stats and rate-limits API docs
cmd/gomodel/docs/docs.go, docs/openapi.json, docs/advanced/admin-endpoints.mdx
Adds /admin/audit/stats path/schemas and documents /admin/rate-limits* endpoints and schemas, plus RATE_LIMITS_ENABLED config field.

Demo Data Seed Script

Layer / File(s) Summary
Seed script latency and error-type updates
tools/seed-demo-data.sh
Adjusts latency distribution and error taxonomy (adds rate_limit_exceeded/429) for seeded audit_logs data.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler as Handler.AuditStats
  participant Reader as auditReader.GetRequestStats
  participant Store as Storage backend
  Client->>Handler: GET /admin/audit/stats
  Handler->>Handler: validate date range, compute interval
  Handler->>Reader: GetRequestStats(params)
  Reader->>Store: query audit_logs
  Store-->>Reader: rows
  Reader-->>Handler: RequestStats
  Handler-->>Client: JSON RequestStats
Loading
sequenceDiagram
  participant DashboardJS as dashboard.js
  participant AuditStatsModule as audit-stats.js
  participant API as /admin/audit/stats
  participant ChartJS as Chart.js
  DashboardJS->>AuditStatsModule: fetchAuditStats()
  AuditStatsModule->>API: GET stats query
  API-->>AuditStatsModule: RequestStats JSON
  AuditStatsModule->>AuditStatsModule: normalizeAuditStats(payload)
  AuditStatsModule->>ChartJS: renderAuditStatsCharts()
Loading

Possibly related PRs

  • ENTERPILOT/GoModel#215: Modifies the audit logging data model that this PR's request-stats aggregation depends on.
  • ENTERPILOT/GoModel#482: Overlaps directly with RATE_LIMITS_ENABLED config and /admin/rate-limits/reset-one admin route documentation added in this PR.

Poem

A rabbit peeked at logs today,
2xx, 4xx, 5xx on display 🐰
Buckets filled by hour and day,
Latency lines dance and sway,
Charts now bloom where numbers lay!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The provider-latency chart, audit stats endpoint, and admin rate-limit changes are outside issue #459's request status chart scope. Split the status chart work from unrelated audit-latency and rate-limit changes, or link separate issues for those additions.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds the requested 2xx/4xx/5xx status chart and success-rate KPI, matching issue #459's dashboard visualization goal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding request status and provider latency charts to the dashboard.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/status-server

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mintlify

mintlify Bot commented Jul 6, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
gomodel 🟢 Ready View Preview Jul 6, 2026, 11:07 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 53.35821% with 125 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/auditlog/stats_mongodb.go 0.00% 74 Missing ⚠️
internal/auditlog/stats_postgresql.go 0.00% 29 Missing ⚠️
internal/auditlog/stats.go 90.74% 8 Missing and 2 partials ⚠️
internal/auditlog/stats_sqlite.go 75.00% 4 Missing and 4 partials ⚠️
internal/admin/handler_audit.go 83.33% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

This PR appears safe to merge with low risk.

The endpoint validates date ranges, handles disabled audit logging, and uses shared folding logic for bucket alignment. The changed paths include tests for handler behavior, folding semantics, SQLite aggregation, and dashboard chart behavior. No verified functional or security issues were found.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Reviewed the Audit Logs dashboard after the run and confirmed the date range control is present along with the Requests by Status and Provider Latency charts.
  • Validated the API call to /admin/audit/stats?days=30 returned 200 and included populated buckets and provider latency data.
  • Inspected the runtime proof from the Go harness and Playwright run logs showing the test commands and browser actions executed during the UI flow.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Browser as Audit Logs dashboard
participant Admin as Admin handler
participant Reader as Audit reader
participant Store as Audit log store

Browser->>Admin: GET /admin/audit/stats?days/start_date/end_date
Admin->>Admin: parse date range and choose hour/day interval
alt audit reader disabled
    Admin-->>Browser: empty RequestStats
else audit reader enabled
    Admin->>Reader: GetRequestStats(params)
    Reader->>Store: group audit logs by UTC hour + provider
    Store-->>Reader: status counts + duration aggregates
    Reader->>Reader: fold hours into dashboard buckets
    Reader-->>Admin: RequestStats
    Admin-->>Browser: buckets, summary, provider_latency
end
Browser->>Browser: render status bar and latency line charts
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Browser as Audit Logs dashboard
participant Admin as Admin handler
participant Reader as Audit reader
participant Store as Audit log store

Browser->>Admin: GET /admin/audit/stats?days/start_date/end_date
Admin->>Admin: parse date range and choose hour/day interval
alt audit reader disabled
    Admin-->>Browser: empty RequestStats
else audit reader enabled
    Admin->>Reader: GetRequestStats(params)
    Reader->>Store: group audit logs by UTC hour + provider
    Store-->>Reader: status counts + duration aggregates
    Reader->>Reader: fold hours into dashboard buckets
    Reader-->>Admin: RequestStats
    Admin-->>Browser: buckets, summary, provider_latency
end
Browser->>Browser: render status bar and latency line charts
Loading

Reviews (1): Last reviewed commit: "feat(dashboard): request status code and..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
docs/openapi.json (1)

8511-8530: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Same nullable-items gap as docs.go.

OpenAPI 3 supports per-item "nullable": true, but avg_duration_ms items are declared as plain "type": "number" here too, missing the null entries the endpoint actually returns for buckets with no successful provider requests. Same root-cause fix as noted on cmd/gomodel/docs/docs.go (regenerate from an updated swaggo annotation on ProviderLatencySeries.AvgDurationMs).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/openapi.json` around lines 8511 - 8530, The schema for
auditlog.ProviderLatencySeries still declares avg_duration_ms array items as
non-nullable numbers, so update the source OpenAPI generation by fixing the
swaggo annotation on ProviderLatencySeries.AvgDurationMs and regenerating
docs.go/openapi.json. Make sure the generated schema for avg_duration_ms
includes nullable item support so the runtime null values for empty provider
buckets are reflected consistently in the OpenAPI output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/gomodel/docs/docs.go`:
- Around line 6130-6149: The schema for auditlog.ProviderLatencySeries still
models avg_duration_ms as a plain array of numbers, but
ProviderLatencySeries.AvgDurationMs can contain nil entries. Update the nullable
item annotation in internal/auditlog/stats.go for the AvgDurationMs field so the
generated docs/schema reflect nullable array items instead of number[], and
verify the generated auditlog.ProviderLatencySeries definition picks up the
change.

In `@internal/admin/dashboard/static/js/modules/audit-stats.js`:
- Around line 85-104: The audit bucket label and daily tooltip title are using
browser-local date getters, which can drift from the dashboard’s effective
timezone. Update _auditStatsBucketLabel and the daily branch of
_auditStatsTooltipTitle to use the same timezone-aware formatting path as
formatTimestamp(bucket.start), instead of deriving day/hour from new
Date(bucket.start). Keep the midnight marker and short-date behavior, but base
both on the dashboard timezone so labels stay consistent.

In `@internal/admin/handler_audit_stats_test.go`:
- Around line 39-52: Add a test that combines a nil audit reader with an invalid
start_date to verify the documented validation order in AuditStats. Extend the
existing coverage around TestAuditStats_InvalidDate and TestAuditStats_NilReader
by adding a new case that constructs NewHandler without WithAuditReader, calls
h.AuditStats with an invalid query, and asserts a 400 response before any
disabled-reader fast path can short-circuit. Keep the assertions focused on the
bad date validation path so the ordering guarantee in AuditStats stays locked
in.

In `@internal/auditlog/stats_test.go`:
- Around line 75-78: The average-duration assertion in stats_test.go uses an
exact float comparison between a constant-folded expected value and a
runtime-computed value, which can diverge by a last bit. Update the test around
the AvgDurationMs check to use a tolerance-based comparison instead of `!=`, and
keep the existing `stats.Summary.AvgDurationMs` and `wantAvg` references so the
assertion remains clear and stable.

---

Duplicate comments:
In `@docs/openapi.json`:
- Around line 8511-8530: The schema for auditlog.ProviderLatencySeries still
declares avg_duration_ms array items as non-nullable numbers, so update the
source OpenAPI generation by fixing the swaggo annotation on
ProviderLatencySeries.AvgDurationMs and regenerating docs.go/openapi.json. Make
sure the generated schema for avg_duration_ms includes nullable item support so
the runtime null values for empty provider buckets are reflected consistently in
the OpenAPI output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 802a69e4-17fa-4854-a979-8a2ef7e40309

📥 Commits

Reviewing files that changed from the base of the PR and between 14d4bde and 6e84e2a.

📒 Files selected for processing (22)
  • cmd/gomodel/docs/docs.go
  • docs/advanced/admin-endpoints.mdx
  • docs/openapi.json
  • internal/admin/dashboard/static/css/dashboard.css
  • internal/admin/dashboard/static/js/dashboard.js
  • internal/admin/dashboard/static/js/modules/audit-stats.js
  • internal/admin/dashboard/static/js/modules/audit-stats.test.cjs
  • internal/admin/dashboard/static/js/modules/usage.js
  • internal/admin/dashboard/templates/layout.html
  • internal/admin/dashboard/templates/page-audit-logs.html
  • internal/admin/handler_audit.go
  • internal/admin/handler_audit_stats_test.go
  • internal/admin/handler_test.go
  • internal/admin/routes.go
  • internal/admin/routes_test.go
  • internal/auditlog/reader.go
  • internal/auditlog/stats.go
  • internal/auditlog/stats_mongodb.go
  • internal/auditlog/stats_postgresql.go
  • internal/auditlog/stats_sqlite.go
  • internal/auditlog/stats_test.go
  • tools/seed-demo-data.sh

Comment thread cmd/gomodel/docs/docs.go
Comment thread internal/admin/dashboard/static/js/modules/audit-stats.js Outdated
Comment thread internal/admin/handler_audit_stats_test.go
Comment thread internal/auditlog/stats_test.go Outdated
…eview

Move the "Requests by Status" and "Provider Latency" charts from the
Audit Logs page to the Overview page, right before Providers Overview,
so error rates, provider latency, and provider availability read as one
health block. The audit page keeps its log and filters for drill-down;
both pages share the same date picker.

Review feedback:
- Bucket labels and daily tooltips now format in the dashboard's
  effective timezone (the same X-GoModel-Timezone the server buckets
  by) instead of the browser locale.
- Lock in validation-before-nil-reader ordering with a dedicated test.
- Compare the folded average duration with a tolerance instead of
  exact float equality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Activity card only spaces itself upward, so the Requests by Status
card sat flush against it. Give the audit-stats sections their own top
margin (collapsing between the two charts), scoped so the usage page's
20px filter gap stays untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SantiagoDePolonia SantiagoDePolonia merged commit a94d01e into main Jul 7, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add requests status code chart (2xx/4xx/5xx)

2 participants