Skip to content

refactor(ui): convert projects page chart to shadcn/recharts - #32722

Merged
ryan-crabbe-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_shadcn_charts_projects
Jul 12, 2026
Merged

refactor(ui): convert projects page chart to shadcn/recharts#32722
ryan-crabbe-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_shadcn_charts_projects

Conversation

@ryan-crabbe-berri

@ryan-crabbe-berri ryan-crabbe-berri commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

QA on the project details page (screenshots to follow in a comment):

  1. run the proxy (python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml) and the dashboard dev server (npm run dev in ui/litellm-dashboard)
  2. open http://localhost:3000/projects/ (the route works directly even if the Projects nav item is behind the enableProjectsUI flag) and click the ID of a project that already has spend; the "Spend by Model" card holds the converted chart
  3. if no project has spend, seed one: create a project on that page, generate a key for it with curl -s http://localhost:4000/key/generate -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -d '{"project_id": "<project id>"}', then send a few completions through that key across two models from your config so multiple bars render, e.g. for m in gpt-5.2 claude-sonnet-5; do curl -s http://localhost:4000/v1/chat/completions -H "Authorization: Bearer <key>" -H "Content-Type: application/json" -d '{"model": "'$m'", "messages": [{"role": "user", "content": "hi"}]}'; done, and reload the project page once the spend tracker flushes
  4. compare the "Spend by Model" card against staging: horizontal cyan bars sorted highest spend first, model names on the left axis, dollar amounts with four decimals on the bottom axis and in the hover tooltip, no legend, chart height growing 40px per model with a 120px floor
  5. a project with no recorded spend still shows the "No model spend recorded yet" empty state

Before (staging, tremor BarChart) and after (this branch, recharts through the shared wrapper) should look near-identical; "cyan" maps to the same cyan-500 the tremor palette used
Screenshot 2026-07-10 at 5 58 44 PM
Screenshot 2026-07-10 at 5 58 37 PM

Type

🧹 Refactoring

Changes

Converts the last tremor chart on the projects page, the "Spend by Model" horizontal BarChart in ProjectDetailsPage, to the shared shadcn/recharts BarChart wrapper from #32668. The change is an import swap; every prop stays as-is (vertical layout, single cyan "spend" series, $ four-decimal valueFormatter on the value axis and tooltip, 140px category axis, legend off, and the dynamic height of 40px per model with a 120px floor carried through the wrapper's style prop). The surrounding antd Card, Row/Col grid, and the antd Empty fallback for projects without spend stay untouched; the card was never tremor, so unlike the pilot there is no Card conversion here

One known deviation from tremor: the wrapper renders every category label on the vertical axis (interval={0}) instead of thinning them, which is the desired behavior here since the chart already grows 40px per model row

ProjectDetailsPage.test.tsx gains a "Spend by Model chart" block asserting against the real recharts SVG (via the scoped jsdom ResizeObserver mock): one cyan-500 bar series with a rectangle per model and no legend, category ticks sorted by spend descending, $x.xxxx value axis ticks, the 40px-per-model/120px-floor container height, and the empty state when model_spend is empty

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Replaces the last remaining @tremor/react BarChart import in ProjectDetailsPage.tsx with the shared @/components/shared/charts wrapper introduced in #32668. The component call site is unchanged — all props (vertical layout, cyan color, dollar valueFormatter, 140px category axis width, legend off, dynamic height) are carried over as-is.

  • ProjectDetailsPage.tsx: one-line import swap; no logic, prop signature, or render tree changes.
  • ProjectDetailsPage.test.tsx: new "Spend by Model chart" describe block adds five tests covering bar count/fill, legend absence, y-axis label ordering, x-axis tick formatting, height scaling (40px/model, 120px floor), and the empty-state path.

Confidence Score: 5/5

A purely mechanical import swap with no logic changes; the shared wrapper accepts all props that were passed to the tremor component, and the new test suite covers the key rendering invariants.

The production change is a single-line import swap; the call site, prop values, and surrounding layout are all untouched. The wrapper's BarChart fully supports every prop used here (layout, colors, valueFormatter, yAxisWidth, showLegend, style). The added tests exercise bar count/color, axis labels, height scaling, and the empty state against the real recharts DOM output.

No files require special attention.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectDetailsPage.tsx Single-line import swap from @tremor/react to @/components/shared/charts; all existing props (layout, colors, valueFormatter, yAxisWidth, showLegend, style height) are fully supported by the shared wrapper.
ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectDetailsPage.test.tsx Adds a 'Spend by Model chart' describe block with five tests covering bar count/color, legend absence, y-axis ordering, x-axis dollar formatting, dynamic height calculation, and empty-state rendering; uses recharts 3 DOM classes that are built dynamically at runtime.

Reviews (2): Last reviewed commit: "refactor(ui): convert projects page char..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_shadcn_charts_projects (8e3b7c2) with litellm_internal_staging (27cf064)

Open in CodSpeed

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai re review. Both P1s are incorrect: recharts 3 builds those classes dynamically in CartesianAxis.js via "recharts-cartesian-axis-tick-labels recharts-".concat(axisType, "-tick-labels"), so .recharts-yAxis-tick-labels and .recharts-xAxis-tick-labels do exist at runtime. A literal grep misses them, and the flagged assertions pass non-vacuously (21/21 in the file; an empty NodeList would fail expect([]).toEqual([...]) loudly)

@ryan-crabbe-berri
ryan-crabbe-berri merged commit d37ba79 into litellm_internal_staging Jul 12, 2026
126 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_shadcn_charts_projects branch July 12, 2026 00:22
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.

2 participants