sakanjo/laravel-easy-metrics
Laravel package to quickly build app metrics (value, trend, bar, line, pie, doughnut, polar). Supports ranges, aggregates (count/sum/min/max/avg), and growth rates. Designed to work with Laravel and Filament widgets for dashboards.
Start by installing the package via Composer:
composer require sakanjo/laravel-easy-metrics
First Use Case: Create a simple Value metric to display a single KPI (e.g., total users) in a Filament widget or Blade view:
use SaKanjo\EasyMetrics\Metrics\Value;
use App\Models\User;
// In a Filament widget or controller
$totalUsers = Value::make(User::class)->count();
Where to Look First:
Value, Doughnut, Trend, etc.).Use the appropriate metric class based on your visualization needs:
Value (e.g., ->sum('revenue'), ->count()).Doughnut or Pie (e.g., ->count('status') for user status distribution).Trend, Line, or Bar (e.g., ->countByMonths(), ->averageByWeeks('order_value')).Example: Time-Series Trend
use SaKanjo\EasyMetrics\Metrics\Trend;
use App\Models\Order;
[$labels, $data] = Trend::make(Order::class)
->range(30)
->sumByMonths('total');
Pass a second column to aggregate functions for grouped metrics:
// Group users by gender and count
[$labels, $data] = Doughnut::make(User::class)
->count('gender');
// Group orders by status and calculate average value
[$labels, $data] = Doughnut::make(Order::class)
->average('total', 'status');
Chain range methods to control the time window for metrics:
// Fixed range (30 days)
Trend::make(Order::class)
->range(30)
->countByDays();
// Custom ranges (e.g., 15 days, 30 days, all time)
Trend::make(Order::class)
->ranges([15, 30, Range::ALL])
->countByMonths();
// Predefined ranges (e.g., Month-to-Date)
Trend::make(Order::class)
->range(Range::MTD)
->sumByDays('revenue');
Add comparative metrics to visualize growth:
// Value metric with growth rate (percentage)
[$value, $growth] = Value::make(Order::class)
->withGrowthRate()
->growthRateType(GrowthRateType::Percentage)
->count();
// Trend metric with growth rate
[$labels, $data, $growth] = Trend::make(Order::class)
->withGrowthRate()
->sumByMonths('revenue');
Use metrics directly in Filament widgets for dashboards:
use Filament\Widgets\ChartWidget;
use SaKanjo\EasyMetrics\Metrics\Trend;
class RevenueTrendWidget extends ChartWidget {
protected function getData(): array {
[$labels, $data] = Trend::make(Order::class)
->range($this->filter)
->sumByMonths('total');
return [
'datasets' => [['label' => 'Revenue', 'data' => $data]],
'labels' => $labels,
];
}
protected function getType(): string {
return 'line';
}
}
Specify a custom date column for time-based metrics:
Trend::make(Order::class)
->dateColumn('created_at') // Defaults to 'created_at'
->countByWeeks();
Use SaKanjo\EasyEnum to transform database values into human-readable labels:
// In your enum
enum UserStatus: int {
use EasyEnum;
case Active = 0;
case Inactive = 1;
}
// In your metric
[$labels, $data] = Doughnut::make(User::class)
->count('status'); // Labels auto-converted via getLabel()
Zero-Division in Growth Rates: Growth rates may fail if the baseline value (e.g., previous period) is zero. Handle this in your UI or add a check:
if ($growth->previousValue === 0) {
$growth->percentage = 0;
}
Time Zone Mismatches:
Time-based metrics (e.g., ByMonths, ByDays) assume the database uses UTC. If your app uses a different timezone, explicitly set it:
Trend::make(Order::class)
->dateColumn('created_at')
->setTimezone('America/New_York')
->countByDays();
Performance with Large Datasets:
Time-series metrics (e.g., countByMonths()) can be slow for tables with millions of records. Optimize with:
Custom Ranges and Filament:
When using rangesFromOptions() in Filament, ensure the range values match the metric's expected format (e.g., integers for days, Range::* constants for predefined ranges).
Database Engine Compatibility:
While the package supports multiple database engines, some time-truncation functions (e.g., DATE_TRUNC) may behave differently in PostgreSQL vs. MySQL. Test thoroughly if switching databases.
Query Inspection: Use Laravel's query logging to inspect the generated SQL:
\DB::enableQueryLog();
$result = Value::make(User::class)->count();
\Log::info(\DB::getQueryLog());
Label Customization:
Override default labels by passing a closure to getLabel():
Doughnut::make(User::class)
->count('status')
->getLabel(function ($value) {
return match ($value) {
0 => 'Active Users',
1 => 'Inactive Users',
default => 'Unknown',
};
});
Growth Rate Types:
Ensure you use the correct GrowthRateType for your use case:
GrowthRateType::Value: Absolute change (e.g., "+100 users").GrowthRateType::Percentage: Relative change (e.g., "+20%").Custom Metric Classes:
Extend existing metrics (e.g., Trend) to add domain-specific logic:
class RevenueTrend extends Trend {
public function revenueByMonths() {
return $this->sumByMonths('amount');
}
}
Dynamic Ranges: Create a dynamic range resolver for Filament filters:
public function getFilters(): array {
return collect([15, 30, 60, 90])
->mapWithKeys(fn ($days) => [$days => "Last $days Days"])
->toArray();
}
Caching Layer: Add caching to metrics for performance:
use Illuminate\Support\Facades\Cache;
function getCachedMetric() {
return Cache::remember('users_count', now()->addHours(1), function () {
return Value::make(User::class)->count();
});
}
Testing: Mock metrics in tests using Laravel's query builder:
$mock = Mockery::mock('overload', User::class);
$mock->shouldReceive('count')->andReturn(100);
$result = Value::make(User::class)->count(); // Returns 100
Default Date Column:
The package defaults to created_at. Override it explicitly if your model uses a different column (e.g., updated_at):
Trend::make(Order::class)
->dateColumn('updated_at')
->countByDays();
Range Validation: Ensure range values are valid (e.g., positive integers for days). The package does not validate ranges by default.
Growth Rate Precision: Growth rates are calculated with floating-point arithmetic, which may introduce rounding errors. Format the output to 2 decimal places for consistency:
number_format($growth->percentage,
How can I help you explore Laravel packages today?