Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Metrics Laravel Package

square1-io/nova-metrics

A Laravel Nova package offering reusable metric cards and dashboard widgets for visualizing key application data, with configurable queries, value formats, and trend displays.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require square1-io/nova-metrics

There's no published config or migrations — this is a small concern-style package that only alters how a metric builds its query. Read the README's example metric first, then open src/ (it's a handful of classes) to confirm the exact trait/class names and the method it exposes for building a filter-aware query. Names in the examples below follow the common shape; verify against the source before copy-pasting.

First use case: you have a Value or Trend metric on a resource index page (e.g. "Total Users" on the Users resource). Nova shows the resource filters in the sidebar, but your metric ignores them and always shows the global count. Add the package's trait to the metric and route your aggregate through the filterable query:

use Laravel\Nova\Metrics\Value;
use Square1\NovaMetrics\Concerns\HasResourceFilters; // confirm namespace

class TotalUsers extends Value
{
    use HasResourceFilters;

    public function calculate(NovaRequest $request)
    {
        return $this->count($request, $this->applyResourceFilters($request, User::query()));
    }
}

Drop the metric onto the resource's cards() (not the dashboard) — resource filters are only present in the request when the metric is rendered on a resource index page.

Implementation Patterns

Trait on the metric. The dominant pattern is a trait that reads the decoded filters off the NovaRequest and calls each filter's apply() against a query builder you hand it. Keep the trait on the metric, not the model, so a single model can back metrics with and without filter support.

Chain before the aggregate, not after. Pass the filtered builder into $this->count(), $this->sum(), $this->average(), or the trend helper. Filtering after an aggregate is meaningless, and many filters add where/whereHas clauses that must exist before the select count(*).

One metric, multiple resources. If the same metric class is reused across resources, derive the model and filter list from the request rather than hard-coding User::query():

$model = $request->resource()::newModel();
$query = $this->applyResourceFilters($request, $model->newQuery());

Partition metrics need per-slice queries. For Partition, build the filtered base query once, then clone it per partition before adding the partitioning where. Reusing the same builder instance across partitions leaks constraints between slices.

Trend metrics and ranges. Apply filters to the base query, then let Nova's $request->range handle the time window. Order matters less here, but cloning per range bucket is still required if you split manually.

Dashboard vs. resource index. Metrics are commonly reused on dashboards, where no filters exist. Guard early:

$query = $request->filters
    ? $this->applyResourceFilters($request, User::query())
    : User::query();

Testing. Build a NovaRequest with filters populated (the same encoded payload Nova sends), call calculate(), and assert the resulting value. For filter classes with side effects, use the real filter rather than a mock so apply() is exercised.

Compose with scopes. If you already have query scopes (tenant, soft-deleted, published), apply them outside the trait so filter behavior stays predictable and testable in isolation.

Gotchas and Tips

  • Empty filters array vs. missing key. On a dashboard or a resource with no filters, filters may be absent or []. Both should fall through to the unfiltered query — don't assume a string.
  • FilterDecoder will throw on unknown/unauthorized filters. If a filter class was removed or its authorizedToSee returns false, decoding the request payload can fail. Wrap the decode in a try/catch if the metric is shared across resources with different filter sets.
  • Cache keys. Nova caches metric responses (keyed off the request URI, which includes the encoded filters). If you override cacheFor() or supply a custom cacheKey, include the filter payload in the key or filtered and unfiltered views will collide. Also remember Nova's cache is not invalidated on model writes — if filter results shift often, lower cacheFor.
  • count(*) vs count(distinct). A filter using whereHas or a join can duplicate rows. If your filtered count looks inflated, pass a column to count() or add ->distinct().
  • Filter apply() signatures vary. Older filters may expect a Request and use $request->input(); newer ones read from the filter's $value. If a filter silently no-ops, dump the builder with ->toSql() / ->getBindings() inside the filter to confirm it ran at all.
  • Timezone. Trend and range calculations respect $request->timezone. If your metric builds its own date buckets, normalize with the request timezone rather than now().
  • Aggregate columns. sum/average metrics need the column name passed to the Nova helper, not selected manually — the helper builds the select. Don't ->select() a column yourself before handing the builder over.
  • Extension point. When a package filter doesn't fit, subclass the metric and override the query-building method, or add a ->tap() closure on the returned builder to inject tenant/role constraints that apply to every filtered query.
  • Debugging. ->dd() the decoded filter array first; more filter bugs are "the payload never arrived" than "the apply() was wrong."
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib