feat(dashboard): label analytics and page-level filters on usage page#461
feat(dashboard): label analytics and page-level filters on usage page#461SantiagoDePolonia wants to merge 3 commits into
Conversation
- Usage by Label breakdown (chart/table toggle) with one deterministic
color per label, shared between chart bars and label chips
- GET /admin/usage/labels aggregation implemented in all three storage
backends (SQLite json_each, PostgreSQL jsonb_array_elements_text,
MongoDB $unwind); label filter on the request log
- Request log renders clickable label chips; clicking one filters the
whole page, clicking again clears it
- Filters (model, provider, label, user path) moved out of the request
log into a page-level bar that drives every widget; search and the
hide-cached toggle stay log-scoped
- Model/provider/label filters centralized in UsageQueryParams and the
shared per-backend condition builders, deduplicating the log readers
and all three pricing recalculators; every usage aggregate endpoint
now accepts them
- Total Requests and Estimated Cost stat cards for the selected period
and filters, with provider/cache and input/output tooltips
- Standard inline help on the label section ("One request can have
multiple labels...")
- Demo seeder labels roughly two thirds of generated traffic
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis PR adds label-based usage analytics and filtering across the admin usage stack, introduces ChangesLabel-based usage filtering and tagging settings
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Confidence Score: 5/5Safe to merge with low risk. Backend filters are parameterized across SQLite, PostgreSQL, and MongoDB. The new route is registered and documented. The frontend consistently composes the page-level filter query for affected widgets. No blocking correctness or security issues were identified. No files require special attention.
What T-Rex did
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Operator as Dashboard operator
participant UI as Usage page UI
participant Admin as Admin API
participant Reader as UsageReader backend
participant DB as Storage backend
Operator->>UI: Select period/model/provider/label/user_path
UI->>Admin: GET /admin/usage/summary + filters
UI->>Admin: GET /admin/usage/models + filters
UI->>Admin: GET /admin/usage/user-paths + filters
UI->>Admin: GET /admin/usage/labels + filters
UI->>Admin: GET /admin/cache/overview + filters
UI->>Admin: GET /admin/usage/log + filters + log options
Admin->>Reader: UsageQueryParams / UsageLogParams
Reader->>DB: Apply date, model, provider, label, user_path, cache mode
DB-->>Reader: Filtered aggregates and rows
Reader-->>Admin: Summary, breakdowns, cache overview, log entries
Admin-->>UI: JSON responses
UI-->>Operator: Stat cards, charts/tables, label chips, filtered log
%%{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 Operator as Dashboard operator
participant UI as Usage page UI
participant Admin as Admin API
participant Reader as UsageReader backend
participant DB as Storage backend
Operator->>UI: Select period/model/provider/label/user_path
UI->>Admin: GET /admin/usage/summary + filters
UI->>Admin: GET /admin/usage/models + filters
UI->>Admin: GET /admin/usage/user-paths + filters
UI->>Admin: GET /admin/usage/labels + filters
UI->>Admin: GET /admin/cache/overview + filters
UI->>Admin: GET /admin/usage/log + filters + log options
Admin->>Reader: UsageQueryParams / UsageLogParams
Reader->>DB: Apply date, model, provider, label, user_path, cache mode
DB-->>Reader: Filtered aggregates and rows
Reader-->>Admin: Summary, breakdowns, cache overview, log entries
Admin-->>UI: JSON responses
UI-->>Operator: Stat cards, charts/tables, label chips, filtered log
Comments Outside Diff (1)
Reviews (1): Last reviewed commit: "feat(dashboard): label analytics and pag..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
internal/usage/recalculate_pricing.go (1)
57-62: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winLabel filter isn't trimmed like Model/Provider.
normalizedRecalculatePricingParamstrimsModelandProviderbut leavesLabeluntouched, even thoughLabelnow flows through the same embeddedUsageQueryParamsand is used as an exact-match filter (jsonb_exists/labelsmatch) in the postgres/mongo recalculation queries. The sibling test file explicitly padsModel/Providerwith spaces to verify trimming (recalculate_pricing_mongodb_test.gouses" gpt-4o "," primary-openai "), showing the codebase already guards against this class of bug for exact-match fields —Labelis missing the same protection, so a caller passing a label with incidental whitespace would silently match zero rows.🩹 Proposed fix
func normalizedRecalculatePricingParams(params RecalculatePricingParams) RecalculatePricingParams { params.Model = strings.TrimSpace(params.Model) params.Provider = strings.TrimSpace(params.Provider) + params.Label = strings.TrimSpace(params.Label) params.CacheMode = CacheModeAll return params }🤖 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 `@internal/usage/recalculate_pricing.go` around lines 57 - 62, normalizedRecalculatePricingParams currently trims Model and Provider but leaves Label unchanged, so add the same whitespace normalization for Label in RecalculatePricingParams. Update the normalization path in normalizedRecalculatePricingParams so Label is trimmed before the recalculation queries run, keeping it consistent with the exact-match filters used by the postgres/mongo logic and the existing trimming behavior for Model and Provider.internal/usage/recalculate_pricing_sqlite_test.go (1)
20-188: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winAdd Label-filter coverage to recalculation tests The recalculation paths already use the shared usage-condition builders, so the remaining gap is test coverage: none of the sqlite/postgres/mongo recalculation tests exercise
UsageQueryParams.Label, leaving that filter without a regression guard.🤖 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 `@internal/usage/recalculate_pricing_sqlite_test.go` around lines 20 - 188, Add test coverage for the Label filter in the SQLite recalculation tests, since `UsageQueryParams.Label` is not exercised here. Extend one of the existing `TestSQLiteStoreRecalculatePricing...` cases to write usage rows with a label and call `Store.RecalculatePricing` with `UsageQueryParams.Label` set, then assert only the matching row is recalculated. Use the existing `WriteBatch`, `RecalculatePricing`, and `UsageQueryParams` symbols so the new assertion verifies the label-based condition builder path.Source: Coding guidelines
internal/admin/dashboard/static/js/modules/usage.js (1)
619-650: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFacet dropdowns self-filter
usageFilterModelOptions(),usageFilterProviderOptions(), andusageFilterLabelOptions()are built from page-filtered fetches, so each select can collapse to the active value and lose the other choices. Source facet options from data that omits that facet’s own filter while keeping the other page filters.🤖 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 `@internal/admin/dashboard/static/js/modules/usage.js` around lines 619 - 650, The facet option builders in usage.js are self-filtering because usageFilterModelOptions(), usageFilterProviderOptions(), and usageFilterLabelOptions() all derive from already page-filtered data, which can collapse each dropdown to only the active value. Update these option getters to source from data filtered by the other page filters only, so each facet preserves its full choice set while still honoring the remaining active filters. Keep the existing symbols usageFilterModelOptions, usageFilterProviderOptions, usageFilterLabelOptions, and the related usageFilterModel/usageFilterProvider/usageFilterLabel state when adjusting the data source.
🤖 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 `@internal/usage/recalculate_pricing.go`:
- Around line 15-19: The doc comment on RecalculatePricingParams is missing
Label as one of the selector fields used by the embedded UsageQueryParams.
Update the comment to include Label alongside date range, model, provider, and
user path, so the documented recalculation scope matches the filters actually
applied in pgUsageConditions and mongoUsageMatchFilters when params.Label is
set.
---
Outside diff comments:
In `@internal/admin/dashboard/static/js/modules/usage.js`:
- Around line 619-650: The facet option builders in usage.js are self-filtering
because usageFilterModelOptions(), usageFilterProviderOptions(), and
usageFilterLabelOptions() all derive from already page-filtered data, which can
collapse each dropdown to only the active value. Update these option getters to
source from data filtered by the other page filters only, so each facet
preserves its full choice set while still honoring the remaining active filters.
Keep the existing symbols usageFilterModelOptions, usageFilterProviderOptions,
usageFilterLabelOptions, and the related
usageFilterModel/usageFilterProvider/usageFilterLabel state when adjusting the
data source.
In `@internal/usage/recalculate_pricing_sqlite_test.go`:
- Around line 20-188: Add test coverage for the Label filter in the SQLite
recalculation tests, since `UsageQueryParams.Label` is not exercised here.
Extend one of the existing `TestSQLiteStoreRecalculatePricing...` cases to write
usage rows with a label and call `Store.RecalculatePricing` with
`UsageQueryParams.Label` set, then assert only the matching row is recalculated.
Use the existing `WriteBatch`, `RecalculatePricing`, and `UsageQueryParams`
symbols so the new assertion verifies the label-based condition builder path.
In `@internal/usage/recalculate_pricing.go`:
- Around line 57-62: normalizedRecalculatePricingParams currently trims Model
and Provider but leaves Label unchanged, so add the same whitespace
normalization for Label in RecalculatePricingParams. Update the normalization
path in normalizedRecalculatePricingParams so Label is trimmed before the
recalculation queries run, keeping it consistent with the exact-match filters
used by the postgres/mongo logic and the existing trimming behavior for Model
and Provider.
🪄 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: 504ad074-98ab-47c3-a3fc-176abfe9d051
📒 Files selected for processing (32)
CLAUDE.mdcmd/gomodel/docs/docs.godocs/openapi.jsoninternal/admin/dashboard/static/css/dashboard.cssinternal/admin/dashboard/static/js/dashboard.jsinternal/admin/dashboard/static/js/modules/charts.jsinternal/admin/dashboard/static/js/modules/charts.test.cjsinternal/admin/dashboard/static/js/modules/dashboard-display.test.cjsinternal/admin/dashboard/static/js/modules/dashboard-layout.test.cjsinternal/admin/dashboard/static/js/modules/live-logs.jsinternal/admin/dashboard/static/js/modules/live-logs.test.cjsinternal/admin/dashboard/static/js/modules/usage.jsinternal/admin/dashboard/static/js/modules/usage.test.cjsinternal/admin/dashboard/templates/page-usage.htmlinternal/admin/handler.gointernal/admin/handler_test.gointernal/admin/handler_usage.gointernal/admin/routes.gointernal/admin/routes_test.gointernal/usage/labels_sqlite_test.gointernal/usage/reader.gointernal/usage/reader_mongodb.gointernal/usage/reader_mongodb_test.gointernal/usage/reader_postgresql.gointernal/usage/reader_sqlite.gointernal/usage/recalculate_pricing.gointernal/usage/recalculate_pricing_mongodb.gointernal/usage/recalculate_pricing_mongodb_test.gointernal/usage/recalculate_pricing_postgresql.gointernal/usage/recalculate_pricing_sqlite.gointernal/usage/recalculate_pricing_sqlite_test.gotools/seed-demo-data.sh
💤 Files with no reviewable changes (1)
- internal/usage/recalculate_pricing_sqlite.go
The Total Requests card derived its cached-hit count from the cache overview endpoint, which is unavailable when cache analytics is disabled — so with historical cached rows in storage the card silently undercounted versus the log's default all-mode view (and overcounted when "Hide cached requests" was on). The stat-card summary now fetches both cache modes: the requests card follows the log's visible scope (all rows by default, provider rows when hide-cached is on) with the provider/cache split derived as the difference of the two summaries, independent of the cache-analytics flag. Estimated Cost stays on uncached mode, since cached rows carry the avoided cost that must not inflate real spend. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Facet dropdown options now follow the faceted-search rule: each facet's choices honor every active filter except its own, fetched from dedicated per-facet aggregate queries (shared when the by-model queries coincide). Previously the options derived from the fully filtered chart data, so selecting a model collapsed the model dropdown to that single value and switching required clearing first. Review nits: RecalculatePricingParams doc comment lists label as a selector; the label filter is trimmed like model/provider; a sqlite recalculation test covers label-scoped recalculation (with a padded label exercising the trim). Also inlined the single-use *ByUserPathConditions wrapper aliases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
Builds the dashboard UI for the labelling system shipped in #454, centered on the usage page, and restructures the page's filtering along the way.
Label analytics
?help on the section: one request can carry several labels, so label rows overlap and don't sum to the period totals.GET /admin/usage/labelsendpoint, implemented in all three storage backends: SQLitejson_each, PostgreSQLjsonb_array_elements_text, MongoDB$unwind.Page-level filters
model/provider/labelfilters centralized inUsageQueryParamsand each backend's shared condition builder, so summaries, breakdowns, cache overview, and the log filter identically. This deduplicated the hand-rolled filter blocks in the log readers and all three pricing recalculators (RecalculatePricingParamsis now a plain embed). All usage aggregate endpoints accept the new query params (OpenAPI regenerated).Period totals
Misc
tools/seed-demo-data.shnow labels roughly two thirds of generated traffic and tolerantly adds thelabelscolumn to pre-labelling databases.User-visible impact
Operators using tagging headers can now see where labelled traffic goes and what it costs, filter the entire usage page by any label/model/provider/user-path combination, and read period totals at a glance. Deployments without tagging see an unchanged page (plus the new stat cards).
Testing
golangci-lintclean.🤖 Generated with Claude Code
Summary by CodeRabbit