insenseanalytics/nova-bar-metrics
A Laravel Nova package that adds a bar chart metric card for visualizing trends and comparisons on your dashboard. Display metric values as clean, responsive bars with customizable labels, colors, and formatting for at-a-glance reporting.
Architecture fit. Nova Bar Metrics is a presentation-layer addition to an existing Laravel Nova admin panel, not an architectural commitment. It ships a PHP Metric subclass, a compiled Vue 2 card component, and a service provider that registers the card and its assets. It introduces no new service, datastore, queue, or external API call — it reads whatever calculate(Request $request) returns from your own database and renders it as bars. That is a genuinely small blast radius, and it is the strongest argument in its favour. It fits exactly one shape of requirement: a horizontal categorical frequency distribution ("tickets by status," "orders by value bucket," "users by plan tier"). It does not answer "how has this moved over time" (native Trend), it has no date-range filter, no click-through, no export, and no cross-period comparison. A TPM should treat those as separate roadmap items rather than implied features of this adoption.
Integration feasibility. The gating event is Composer resolution, not code. The package's constraints target the Nova 1.x era — Laravel 5.6/5.7, PHP 7.1/7.2, Vue 2, Laravel Mix. On anything newer, composer require will refuse to resolve, and widening the constraints gets you past the resolver but not past the runtime. Nova is also a private Composer repository, so every CI runner, container build, and new developer machine needs nova.laravel.com credentials before the spike can even start. Feasibility is therefore binary and cheap to test: a half-day throwaway-branch spike answers it definitively.
Technical risk. The dominant risk is abandonment, and it is not mitigable by contract. Last release December 2018, 40 stars, zero dependents, a single publisher, no CI, no commits in roughly seven years. There will be no security advisory and no compatibility patch — Nova's commercial support does not extend to third-party tools. The compensating control is that the code is small enough to read end-to-end in one sitting and MIT-licensed, so it can be legally forked and owned. The secondary risk is the front-end wall: Nova 4+ moved to Inertia + Vue 3 with a revised asset pipeline, so compiled Mix-era assets will not load and Nova::script()/Nova::style() registration semantics have shifted. That is a rewrite of the card, not a patch — do not let anyone budget it as "a small fix."
Key questions. Which Nova major and PHP version are on the target app? Is the requirement genuinely a horizontal category comparison, or is it already expressible as a native Partition card or a stacked Trend with ->showValues()? Who owns the Vue component after handoff? Does the demo need filtering, drill-down, or export — and if so, is this the wrong tool? What is the row count behind the GROUP BY? And critically: is anyone making a budget or staffing decision off this card? If yes, an unmaintained dependency is the wrong substrate regardless of how clean the code is.
Stack fit. The only "install and go" scenario is a legacy admin panel pinned to Nova 1.x/2.x with a matching Laravel 5.6/5.7 and PHP 7.1/7.2 baseline. On Nova 3+, PHP 8.x, or Laravel 9+, treat the repository as a reference implementation, not a dependency: preserve the shape (one Metric subclass returning a label→value array, a small Vue card rendering Chart.js bars against Nova's metric endpoint) and reimplement against the actual Nova version. That is roughly a day of engineering and yields same pixels with a support path you control.
Migration path. Fork before you require. The rule of thumb is "fork and own," never "composer require and forget." Never patch vendor/. If the fleet runs mixed Nova majors, pin per application — a single constraint will not satisfy all of them. Expect to publish assets and rebuild (php artisan vendor:publish, then npm run dev) depending on what the service provider registers. Record MIT attribution in the file header so it survives the next refactor and the eventual migration to first-party code.
Compatibility. Widen constraints and you clear resolution but inherit PHP 8.2 dynamic-property deprecations and missing-return-type notices from the source. The Vue 2 → Vue 3 / Inertia break is the hard wall and should be scoped as a front-end rewrite. Budget a decision point: if fork-and-verify exceeds about a day, build the card in-house against your actual Nova version — same output, no orphaned SBOM entry, and an owner you can page.
Sequencing. Ship in this order and stop at any failure. First, the spike: verify rendering on the target Nova version in a throwaway branch before the package appears on a roadmap or in a commitment. Second, prove the pipeline with a bounded categorical — "tickets by status" is ideal because the query is cheap and a wrong chart is immediately obvious. Do not start with a histogram; binning is where these implementations get subtle and you want the rendering layer validated first. Third, add numeric histograms with binning done in SQL via a CASE expression (never pull every row into PHP for a card that runs on every page load), then force bucket order in PHP and backfill zero-count buckets, because GROUP BY silently omits empty ranges. Fourth, harden: ->cacheFor() plus indexes, ->canSee() for authorization, and an explicit color map keyed to enum values so "Open" is the same green everywhere. Fifth, decide whether the fork becomes first-party code — it probably should.
Maintenance. You become the maintainer on day one. There is no upstream to lean on, so the fork inherits zero regression safety net and you must supply one: a test asserting calculate()'s exact bucket output against a seeded fixture, a test asserting zero-count buckets are present, and ideally a snapshot of the rendered card. Those tests are what tell you the Nova major bump broke something before a stakeholder does. Add the dependency to the SBOM only with an explicit note that it is unmaintained; better, plan the migration to in-house code as part of the same quarter.
Support. No security response channel, no compatibility patches, one publisher, no community backstop at 40 stars and zero dependents. Restrict the audience to internal staff so a broken or stale card is an annoyance rather than a customer-facing incident. Keep the gate coarse — ->canSee(fn ($request) => $request->user()->can('viewAnalytics')) so the card disappears for support staff rather than 403-ing, since Nova dashboards get shared across roles fast.
Scaling. A COUNT(*) ... GROUP BY over a large table fires on every dashboard page load and will surface in the slow query log. Cache the metric, and pair it with an index on the grouped column — a composite (status, created_at) covers most filtered counts. For time-windowed views, bake the window into the query and encode it in $name ("Tickets by Status (30d)"); registering two cards, 7-day and 30-day, is clearer than one card with ambiguous scope. Anything more interactive belongs on a purpose-built Nova page, not a metric card.
Failure modes. Known and enumerable: Composer resolution failure on modern Nova/PHP; compiled assets refusing to load after a Nova upgrade; a blank white card when calculate() returns [] or all-zero values (guard with a 'No data' => 0 entry or hide via ->canSee()); NULL categories silently vanishing from GROUP BY unless coalesced into an "Unknown" bucket; nondeterministic ordering that silently changes between deploys as data shifts — the single most common "why does this look wrong" report; and stale values behind an over-long cache. The debugging path is fixed: check the DevTools network tab for the metric JSON payload first, and only dd($result) at the end of calculate() if the payload itself is wrong.
Ramp-up. Low for anyone fluent in Nova and Vue 2 — the whole package reads in one sitting, and the deliverable is a metric class plus a card. Realistic effort: half a day to spike and verify, one day to fork and own with tests, one day to rebuild in-house. The real ramp-up cost is not learning the code, it is accepting the ownership: a TPM should present this as "we are adopting a code sample we will maintain," and if that framing is unacceptable to the team, the honest answer is to write the card yourself and skip the dependency entirely.
How can I help you explore Laravel packages today?