Dashboard: Analytics Dashboard Page#183
Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR implements a complete analytics dashboard feature. It adds type contracts for analytics metrics and filtering, an API route handler that proxies requests to the backend, utility functions to normalize and merge series data, React hooks for chart and totals data fetching, Recharts-based chart and breakdown components, an all-time totals summary row, a dashboard page that orchestrates filters and displays the data, sidebar navigation integration, and enhancements to the Select component including optional grouping support. ChangesAnalytics Dashboard
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/components/analytics/AnalyticsChartCard.tsx (1)
1-502: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy liftSplit this component to stay under the TS/TSX file size limit.
This file is over the 500 LOC limit and should be split (for example: tooltip, token stat card, and chart formatting helpers into separate modules/hooks).
As per coding guidelines,
**/*.{ts,tsx}: “Do not let any file exceed 500 lines of code (LOC). If a file is approaching or has crossed this limit, split it into smaller modules by extracting sub-components, hooks, utilities, or types into their own files.”🤖 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 `@app/components/analytics/AnalyticsChartCard.tsx` around lines 1 - 502, This component exceeds the 500 LOC limit; split it by extracting subcomponents and helpers: move ChartTooltip into its own component file (exporting TooltipEntry/TooltipRenderProps types), move TokenStat into its own component file, and extract the chart helper functions/consts (SERIES_COLORS, CURRENCY_METRICS, formatMonthLabel, formatValue, formatTokens, formatAxisValue, buildRows) into a chartUtils module that exports them and the ChartRow type; update AnalyticsChartCard to import those new modules and keep logic in-place (retain normalizeAndMergeSeries usage and activeData/totals/tokenSummary computations). Ensure names remain unchanged (ChartTooltip, TokenStat, buildRows, formatValue, etc.) so imports are straightforward and tests/refs keep working.
🧹 Nitpick comments (1)
app/components/analytics/AnalyticsTotalsRow.tsx (1)
13-19: ⚡ Quick winExtract
formatTokensto a shared analytics formatter utility.This formatter is duplicated across analytics components; centralizing it will keep token formatting consistent and reduce drift.
🤖 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 `@app/components/analytics/AnalyticsTotalsRow.tsx` around lines 13 - 19, The formatTokens function is duplicated across analytics components; extract it into a shared utility and replace local copies with an import. Create a new exported helper (e.g., formatTokens) in a shared module like analytics/formatters or utils/analytics, move the existing logic from AnalyticsTotalsRow.tsx into that module, update AnalyticsTotalsRow.tsx to import and use the shared formatTokens, and ensure any other components using the same logic import the same helper to keep formatting consistent.
🤖 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 `@app/`(main)/dashboard/page.tsx:
- Around line 89-92: The flush function currently only clears the parent when
both year and month are empty, leaving a stale filter if one of them is cleared;
update flush (the const flush = (y: string, m: string) => { ... } function) to
call onChange("") whenever either y or m is falsy (i.e., replace the else-if
branch with a default else that calls onChange("")); keep the existing case
where both y and m produce `${y}-${m}-01`, and ensure you use the same onChange
symbol so the parent always receives an empty value when the date is incomplete.
In `@app/api/analytics/monthly/chart/route.ts`:
- Around line 11-17: In the catch block of the route handler replace the current
ad-hoc error stringification with the standardized extractor: call the
extractErrorMessage(errorBody, error instanceof Error ? error.message : 'Unknown
error') utility and use its return value for the error field in the
NextResponse.json payload; update the catch path that currently constructs error
via error instanceof Error ? error.message : String(error) so it invokes
extractErrorMessage and passes that result into NextResponse.json instead.
In `@app/components/analytics/AnalyticsTotalsRow.tsx`:
- Around line 5-10: The prop type AnalyticsTotalsMap is imported from a hook
module but should be moved to shared types; create or export the
AnalyticsTotalsMap type in app/lib/types (or add an appropriate export if it
already exists) and update the import in AnalyticsTotalsRow.tsx to import {
AnalyticsTotalsMap } from "app/lib/types" (replace the current import from
"`@/app/hooks/useAnalyticsTotals`"); ensure any other consumers of
AnalyticsTotalsMap use the shared type export and remove the hook-local type to
avoid boundary coupling.
In `@app/hooks/useAnalyticsChart.ts`:
- Around line 41-62: fetchChart can suffer from race conditions where an older
overlapping fetch resolves after a newer one and overwrites state; fix by adding
a latest-request guard: create a stable ref/variable (e.g., latestRequestIdRef)
incremented each time fetchChart starts, capture the current id in the async
call, and before calling setData/setError/setIsLoading verify the captured id
matches latestRequestIdRef.current (only the latest invocation may update
state); implement this guard inside fetchChart (and any other similar fetch in
this hook) and ensure the ref is initialized with useRef so it survives renders
and is checked in the try/catch/finally blocks.
In `@app/hooks/useAnalyticsTotals.ts`:
- Around line 69-112: fetchTotals can have out-of-order async completions that
overwrite newer state; add a request-sequencing guard using a ref counter (e.g.,
requestSeqRef) that you increment before starting the async work, capture the
current seq in a local variable, and then only call setTotals, setError, and
setIsLoading if the captured seq equals the current ref value; apply the same
pattern to any other async flows mentioned (lines ~114-116) so stale responses
are ignored and only the latest request wins, keeping fetchTotals, setTotals,
setError, setIsLoading and filtersKey as the points of reference when locating
where to add the guard.
---
Outside diff comments:
In `@app/components/analytics/AnalyticsChartCard.tsx`:
- Around line 1-502: This component exceeds the 500 LOC limit; split it by
extracting subcomponents and helpers: move ChartTooltip into its own component
file (exporting TooltipEntry/TooltipRenderProps types), move TokenStat into its
own component file, and extract the chart helper functions/consts
(SERIES_COLORS, CURRENCY_METRICS, formatMonthLabel, formatValue, formatTokens,
formatAxisValue, buildRows) into a chartUtils module that exports them and the
ChartRow type; update AnalyticsChartCard to import those new modules and keep
logic in-place (retain normalizeAndMergeSeries usage and
activeData/totals/tokenSummary computations). Ensure names remain unchanged
(ChartTooltip, TokenStat, buildRows, formatValue, etc.) so imports are
straightforward and tests/refs keep working.
---
Nitpick comments:
In `@app/components/analytics/AnalyticsTotalsRow.tsx`:
- Around line 13-19: The formatTokens function is duplicated across analytics
components; extract it into a shared utility and replace local copies with an
import. Create a new exported helper (e.g., formatTokens) in a shared module
like analytics/formatters or utils/analytics, move the existing logic from
AnalyticsTotalsRow.tsx into that module, update AnalyticsTotalsRow.tsx to import
and use the shared formatTokens, and ensure any other components using the same
logic import the same helper to keep formatting consistent.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: cc4872dc-011f-4efa-9aa0-922a7ca3d365
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
app/(main)/dashboard/page.tsxapp/api/analytics/monthly/chart/route.tsapp/components/Sidebar.tsxapp/components/analytics/AnalyticsChartCard.tsxapp/components/analytics/AnalyticsTotalsRow.tsxapp/components/analytics/index.tsapp/components/icons/index.tsxapp/components/icons/sidebar/ChartBarIcon.tsxapp/hooks/useAnalyticsChart.tsapp/hooks/useAnalyticsTotals.tsapp/lib/navConfig.tsapp/lib/types/analytics.tsapp/lib/utils/analytics/normalizeSeries.tspackage.json
…ntend into feat/dashboard-analytics-page
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/lib/constants.ts (1)
104-112: 💤 Low valueConsider adding parameter validation for edge cases.
The
countparameter is not validated. While negative or zero values will safely produce an empty array, adding a guard could make the behavior more explicit and prevent unintended usage.🛡️ Suggested defensive check
export function getRecentYearOptions( count = 2, ): { value: string; label: string }[] { + if (count <= 0) return []; const now = new Date().getFullYear(); return Array.from({ length: count }, (_, i) => {🤖 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 `@app/lib/constants.ts` around lines 104 - 112, The getRecentYearOptions function lacks validation for the count parameter; add a defensive check at the top of getRecentYearOptions to ensure count is a non-negative integer (e.g., coerce with Number.isFinite and Math.floor, then clamp with Math.max(0, ...)) and handle invalid inputs explicitly by either returning an empty array or throwing a clear TypeError; this makes the behavior explicit for negative, fractional, or non-numeric values while keeping the rest of the implementation unchanged.app/lib/types/analytics.ts (1)
13-19: 💤 Low valueConsider documenting the
data: string[]contract.The
datafield is typed asstring[]rather thannumber[], which suggests the backend returns numeric values as strings (common for precision preservation). Consider adding a JSDoc comment explaining this contract and that consumers should parse these values.📝 Suggested documentation
export interface AnalyticsSeriesPoint { name: string; + /** Numeric data points returned as strings to preserve precision */ data: string[]; total_input_tokens?: number;🤖 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 `@app/lib/types/analytics.ts` around lines 13 - 19, Add a concise JSDoc above the AnalyticsSeriesPoint interface explaining that the data property is string[] because numeric values are serialized as strings for precision/transport, and that consumers should parse each entry (e.g., Number(value) or a BigNumber library) before numeric operations; reference the AnalyticsSeriesPoint type and the data field and also note that total_input_tokens, total_output_tokens, and total_tokens are optional aggregated token counts.
🤖 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 `@app/lib/utils/analytics/formatValue.ts`:
- Around line 20-32: formatCompactMetric currently inserts the negative sign
after the currency symbol (e.g. "$-1.5k"); update the function to compute the
sign separately (e.g. sign = value < 0 ? "-" : "") and use Math.abs(value) (or
the already computed abs) when creating the compact string, then when metric is
in CURRENCY_METRICS prefix the compact string with sign + "$" (or use
parentheses style if you prefer) so negatives render as "-$1.5k" instead of
"$-1.5k"; adjust references inside formatCompactMetric accordingly.
---
Nitpick comments:
In `@app/lib/constants.ts`:
- Around line 104-112: The getRecentYearOptions function lacks validation for
the count parameter; add a defensive check at the top of getRecentYearOptions to
ensure count is a non-negative integer (e.g., coerce with Number.isFinite and
Math.floor, then clamp with Math.max(0, ...)) and handle invalid inputs
explicitly by either returning an empty array or throwing a clear TypeError;
this makes the behavior explicit for negative, fractional, or non-numeric values
while keeping the rest of the implementation unchanged.
In `@app/lib/types/analytics.ts`:
- Around line 13-19: Add a concise JSDoc above the AnalyticsSeriesPoint
interface explaining that the data property is string[] because numeric values
are serialized as strings for precision/transport, and that consumers should
parse each entry (e.g., Number(value) or a BigNumber library) before numeric
operations; reference the AnalyticsSeriesPoint type and the data field and also
note that total_input_tokens, total_output_tokens, and total_tokens are optional
aggregated token counts.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8cd31cf6-50d6-4a77-96b5-52665542f7f8
📒 Files selected for processing (10)
app/(main)/dashboard/page.tsxapp/components/analytics/AnalyticsChartCard.tsxapp/components/analytics/AnalyticsTotalsRow.tsxapp/components/analytics/ChartTooltip.tsxapp/components/analytics/MonthYearPicker.tsxapp/components/analytics/index.tsapp/hooks/useAnalyticsTotals.tsapp/lib/constants.tsapp/lib/types/analytics.tsapp/lib/utils/analytics/formatValue.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- app/components/analytics/index.ts
- app/hooks/useAnalyticsTotals.ts
- app/components/analytics/AnalyticsChartCard.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/components/analytics/AnalyticsChartCard.tsx (1)
143-153:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix AnalyticsChartCard InfoTooltip copy to match LineTrendChart (and drop dead
__range)
app/components/analytics/AnalyticsChartCard.tsxInfoTooltip text says the total is a “thick line” and that there’s a “band … between the lowest and highest group”, butLineTrendChartrenders the total as a thin dashed line (strokeWidth={1}) and only plots the per-series lines (strokeWidth={2.25})—there’s no band/area.buildRowscomputes__range, butChartTooltipfilters out__rangeandLineTrendChartnever uses it; remove the unused__rangecomputation (and associated field if appropriate).🤖 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 `@app/components/analytics/AnalyticsChartCard.tsx` around lines 143 - 153, Update AnalyticsChartCard's InfoTooltip text to match actual rendering in LineTrendChart: change the copy that describes a "thick line" and a "band ... between the lowest and highest group" to instead describe that per-series lines are plotted and the total is shown as a thin dashed line (or similar accurate wording). Also remove the unused __range computation from buildRows and any associated field output since ChartTooltip filters out __range and LineTrendChart never uses it; ensure buildRows and any consuming code no longer emit or expect __range. Reference: InfoTooltip in AnalyticsChartCard, LineTrendChart, buildRows, ChartTooltip, and the __range field.
🧹 Nitpick comments (3)
app/components/icons/common/TooltipIcon.tsx (1)
1-4: ⚡ Quick winMove
IconPropsinto a shared type module.This local prop shape violates the TS/TSX guideline against redefining shared shapes inline. Please import a shared icon prop type from
app/lib/types/instead of declaringIconPropsin this component.As per coding guidelines, "Import shared types from app/lib/types/ and app/lib/models.ts instead of redefining shapes locally in TypeScript/TSX files."
🤖 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 `@app/components/icons/common/TooltipIcon.tsx` around lines 1 - 4, Remove the local IconProps interface from TooltipIcon.tsx and replace it with an import of the shared icon prop type from app/lib/types; specifically, delete the interface declaration named IconProps and add an import like `IconProps` from the shared module, then ensure the TooltipIcon component and any references use that imported IconProps type instead of the locally declared one.app/(main)/analytics/page.tsx (1)
20-20: 💤 Low valueTypo in exported constant name
PROVIDES_OPTIONS.This should almost certainly be
PROVIDERS_OPTIONS. Worth correcting at the source inconstants.tsand here while the surface area is still small.Also applies to: 157-157
🤖 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 `@app/`(main)/analytics/page.tsx at line 20, The exported constant is misspelled as PROVIDES_OPTIONS; rename it to PROVIDERS_OPTIONS at the source (constants.ts) and update all usages (e.g., the import in app/(main)/analytics/page.tsx and any other references) to import and use PROVIDERS_OPTIONS instead of PROVIDES_OPTIONS so the symbol name is consistent across the codebase.app/components/analytics/MonthYearPicker.tsx (1)
84-90: 💤 Low valueConsider closing the popover on
Escape.The dialog only closes via outside
mousedown. Keyboard users cannot dismiss it without clicking away. Adding akeydownlistener forEscapeimproves keyboard accessibility for thisrole="dialog".🤖 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 `@app/components/analytics/MonthYearPicker.tsx` around lines 84 - 90, Add an Escape key handler inside the existing useEffect that watches the popover container: in addition to the existing mousedown handler, register a "keydown" listener that checks for e.key === "Escape" (or e.key === "Esc" for broader support) and calls setOpen(false) when pressed; ensure you attach and remove this listener in the same useEffect cleanup so containerRef, setOpen and the dialog role handling (the popover component using containerRef) properly close for keyboard users.
🤖 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 `@app/components/analytics/MonthYearPicker.tsx`:
- Around line 146-148: The aria-label on the MonthYearPicker dialog currently
uses the optional prop label, which can be undefined and renders "undefined
picker"; update the aria-label logic in the MonthYearPicker component to fall
back to placeholder or a static string (e.g., use (label ?? placeholder ??
"Month and year") + " picker") so the accessible name is never "undefined
picker"; modify the JSX where role="dialog" and aria-label is set to use this
fallback expression and ensure placeholder and label props are referenced (or
defaulted) in the component props/type definitions.
In `@app/lib/navConfig.ts`:
- Around line 59-64: The nav label "Analytics" in the nav item object (name:
"Analytics", route: "/analytics") is inconsistent with the page title
"Dashboard" rendered by the analytics page; make them match by renaming either
the navConfig entry's name to "Dashboard" or updating the page title string to
"Analytics" so the sidebar label and page title are identical (update the name
in the nav item or the title constant/JSX in the analytics page accordingly).
---
Outside diff comments:
In `@app/components/analytics/AnalyticsChartCard.tsx`:
- Around line 143-153: Update AnalyticsChartCard's InfoTooltip text to match
actual rendering in LineTrendChart: change the copy that describes a "thick
line" and a "band ... between the lowest and highest group" to instead describe
that per-series lines are plotted and the total is shown as a thin dashed line
(or similar accurate wording). Also remove the unused __range computation from
buildRows and any associated field output since ChartTooltip filters out __range
and LineTrendChart never uses it; ensure buildRows and any consuming code no
longer emit or expect __range. Reference: InfoTooltip in AnalyticsChartCard,
LineTrendChart, buildRows, ChartTooltip, and the __range field.
---
Nitpick comments:
In `@app/`(main)/analytics/page.tsx:
- Line 20: The exported constant is misspelled as PROVIDES_OPTIONS; rename it to
PROVIDERS_OPTIONS at the source (constants.ts) and update all usages (e.g., the
import in app/(main)/analytics/page.tsx and any other references) to import and
use PROVIDERS_OPTIONS instead of PROVIDES_OPTIONS so the symbol name is
consistent across the codebase.
In `@app/components/analytics/MonthYearPicker.tsx`:
- Around line 84-90: Add an Escape key handler inside the existing useEffect
that watches the popover container: in addition to the existing mousedown
handler, register a "keydown" listener that checks for e.key === "Escape" (or
e.key === "Esc" for broader support) and calls setOpen(false) when pressed;
ensure you attach and remove this listener in the same useEffect cleanup so
containerRef, setOpen and the dialog role handling (the popover component using
containerRef) properly close for keyboard users.
In `@app/components/icons/common/TooltipIcon.tsx`:
- Around line 1-4: Remove the local IconProps interface from TooltipIcon.tsx and
replace it with an import of the shared icon prop type from app/lib/types;
specifically, delete the interface declaration named IconProps and add an import
like `IconProps` from the shared module, then ensure the TooltipIcon component
and any references use that imported IconProps type instead of the locally
declared one.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e0a7b0e3-a83a-4870-a87f-80a37de3de39
📒 Files selected for processing (21)
app/(main)/analytics/page.tsxapp/components/analytics/AnalyticsChartCard.tsxapp/components/analytics/AnalyticsTotalsRow.tsxapp/components/analytics/BreakdownPanel.tsxapp/components/analytics/LineTrendChart.tsxapp/components/analytics/MonthYearPicker.tsxapp/components/icons/common/TooltipIcon.tsxapp/components/icons/index.tsxapp/components/prompt-editor/DiffView.tsxapp/components/ui/InfoTooltip.tsxapp/components/ui/Select.tsxapp/components/ui/index.tsapp/globals.cssapp/hooks/useAnalyticsChart.tsapp/lib/constants.tsapp/lib/navConfig.tsapp/lib/types/analytics.tsapp/lib/types/ui.tsapp/lib/utils/analytics/formatValue.tsapp/lib/utils/analytics/mergeChartData.tsapp/lib/utils/selectOptions.ts
💤 Files with no reviewable changes (1)
- app/components/ui/index.ts
✅ Files skipped from review due to trivial changes (2)
- app/lib/utils/selectOptions.ts
- app/components/prompt-editor/DiffView.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- app/components/icons/index.tsx
- app/lib/utils/analytics/formatValue.ts
- app/lib/constants.ts
- app/components/analytics/AnalyticsTotalsRow.tsx
|
Merging this PR into main branch. |
Issue: #182
Summary:
Adds a new Dashboard page that gives users a single place to track how their API usage and evaluation activity is trending over time. It replaces the need to dig through individual screens to understand cost and volume, everything is now visible in one analytics view, with flexible filters and breakdowns.
What users can do
Track four key metrics
Break the data down by:
Filter the view by:
See totals at a glance — a summary row shows the aggregated totals for the currently applied filters, alongside total input/output tokens where applicable.
Visualise trends over time — a monthly chart plots the selected metric, automatically updating when filters or grouping change
Navigate easily — a new "Dashboard" entry has been added to the sidebar with a chart icon, so the page is reachable from anywhere in the app.