Skip to content

Commit

Permalink
Feat #12
Browse files Browse the repository at this point in the history
Add SQLite support
Add $missingDataLabels auto discovery
Add trends in percents option
Fix PostgreSQL support bugs
Update CHANGELOG.md
Update README.md
  • Loading branch information
eliseekn committed Apr 7, 2024
1 parent 94e93d6 commit c1a8129
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 42 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to `laravel-metrics` will be documented in this file

## 2.9.0-beta-1

- Add SQLite support
- Add $missingDataLabels auto discovery
- Add metrics with variations option
- Add trends in percents option
- Fix PostgreSQL support bugs

## 2.8.0

- Add PostgreSQL support
Expand Down
29 changes: 9 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ composer require eliseekn/laravel-metrics
```

## Features
- MySQL and PostgreSQL support
- MySQ, PostgreSQL and SQLite support
- Verbose query builder
- Custom columns and table definition
- Days and months translation with Carbon
Expand Down Expand Up @@ -128,7 +128,7 @@ LaravelMetrics::query(...)
->from(string $date, string $dateIsoFormat)
```

**Note :** Periods are defined for the current day, week, month or year by default. However, you can define a specific value using dedicated methods. For example:
***Note :*** Periods are defined for the current day, week, month or year by default. However, you can define a specific value using dedicated methods. For example:

```php
// generate trends of orders count for the current year
Expand Down Expand Up @@ -168,10 +168,14 @@ LaravelMetrics::query(...)
### Types of data
```php
LaravelMetrics::query(...)
->trends() //or
->metrics()
->trends(bool $inPercent = false) //or
->metrics(?int $withVariationsCount = null)
```

***Note 1 :*** The `trends` method can generate data in percentage format when the `$inPercent` parameter is set to `true`. On the other hand, the `metrics` method can generate variations from the past day, week, month, or year based on the period specified. You can use the `$withVariationsCount` to specify the count for past period.

***Note 2 :*** `$withVariationsCount` should only be used on `day`, `week`, `month`, or `year` period.

### Combining periods and aggregates
Combining different time periods and data aggregates can enhance your overall experience. For example :

Expand All @@ -193,7 +197,6 @@ LaravelMetrics::query(...)
LaravelMetrics::query(...)
->averageFrom(Carbon::now()->subDays(10)->format('Y-m-d'))
->trends();

...
```

Expand Down Expand Up @@ -246,24 +249,10 @@ LaravelMetrics::query(...)
->sumByYear(count: 5)
->fillMissingData()
->trends();

...
```

**Note :** For custom ```labelColumn```definition, you must define a ```missingDataLabel```. For example :

```php
LaravelMetrics::query(...)
->countByMonth(count: 12)
->forYear(now()->year)
->labelColumn('status')
->fillMissingData(missingDataLabels: [
'pending',
'delivered',
'cancelled'
])
->trends();
```
***Note :*** The `fillMissingData` method automatically discovers all labels, ensuring that data is filled for all available labels without the need for explicit label specification.

### Group period (only when using ```between``` method)
You can group period by days, months, weeks or years when using the ```between``` method (***default is day***). For example :
Expand Down
12 changes: 8 additions & 4 deletions src/DatesFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use DateTime;
use Eliseekn\LaravelMetrics\Enums\Period;
use Eliseekn\LaravelMetrics\Exceptions\InvalidDateFormatException;
use Illuminate\Support\Facades\DB;

trait DatesFunctions
{
Expand Down Expand Up @@ -59,7 +60,7 @@ protected function formatPeriod(string $period): string

if ($driver === 'mysql') {
return match ($period) {
Period::DAY->value => "weekday($this->dateColumn)",
Period::TODAY->value, Period::DAY->value => "weekday($this->dateColumn)",
Period::WEEK->value => "week($this->dateColumn)",
Period::MONTH->value => "month($this->dateColumn)",
default => "year($this->dateColumn)",
Expand All @@ -68,15 +69,15 @@ protected function formatPeriod(string $period): string

if ($driver === 'pgsql') {
return match ($period) {
Period::DAY->value => "EXTRACT(DOW FROM $this->dateColumn)",
Period::TODAY->value, Period::DAY->value => "EXTRACT(DOW FROM $this->dateColumn)",
Period::WEEK->value => "EXTRACT(WEEK FROM $this->dateColumn)",
Period::MONTH->value => "EXTRACT(MONTH FROM $this->dateColumn)",
default => "EXTRACT(YEAR FROM $this->dateColumn)",
};
}

return match ($period) {
Period::DAY->value => "strftime('%w', $this->dateColumn)",
Period::TODAY->value, Period::DAY->value => "strftime('%w', $this->dateColumn)",
Period::WEEK->value => "strftime('%W', $this->dateColumn)",
Period::MONTH->value => "strftime('%m', $this->dateColumn)",
default => "strftime('%Y', $this->dateColumn)",
Expand Down Expand Up @@ -193,7 +194,10 @@ protected function getLabelsData(): array
{
$result = [];

foreach ($this->missingDataLabels as $label) {
$labelColumn = explode('.', $this->labelColumn)[1];
$missingDataLabels = DB::table($this->builder->from)->get()->pluck($labelColumn)->toArray();

foreach ($missingDataLabels as $label) {
$result[$label] = $this->missingDataValue;
}

Expand Down
1 change: 1 addition & 0 deletions src/Enums/Period.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

enum Period: string
{
case TODAY = 'today';
case DAY = 'day';
case WEEK = 'week';
case MONTH = 'month';
Expand Down
18 changes: 18 additions & 0 deletions src/Exceptions/InvalidVariationsCountException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Eliseekn\LaravelMetrics\Exceptions;

use Exception;

/**
* This exception occurs when withVariationsCount parameter is equal 0
*/
class InvalidVariationsCountException extends Exception
{
public function __construct()
{
parent::__construct('Invalid withVariationsCount value. withVariationsCount value should be more than 0');
}
}
107 changes: 89 additions & 18 deletions src/LaravelMetrics.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Eliseekn\LaravelMetrics\Enums\Period;
use Eliseekn\LaravelMetrics\Exceptions\InvalidAggregateException;
use Eliseekn\LaravelMetrics\Exceptions\InvalidPeriodException;
use Eliseekn\LaravelMetrics\Exceptions\InvalidVariationsCountException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -50,8 +51,6 @@ class LaravelMetrics

protected bool $fillMissingData = false;

protected ?array $missingDataLabels = null;

protected int $missingDataValue = 0;

protected string $groupBy;
Expand Down Expand Up @@ -442,15 +441,43 @@ public function labelColumn(string $column): self
return $this;
}

public function fillMissingData(int $missingDataValue = 0, array $missingDataLabels = null): self
public function fillMissingData(int $missingDataValue = 0): self
{
$this->fillMissingData = true;
$this->missingDataLabels = $missingDataLabels;
$this->missingDataValue = $missingDataValue;

return $this;
}

protected function withVariations(int $count = 1): int
{
if (! is_string($this->period) || ! in_array($this->period, Period::values())) {
throw new InvalidPeriodException();
}

$laravelMetrics = (new self($this->builder));

$result = match ($this->period) {
Period::DAY->value => $laravelMetrics
->forDay(Carbon::now()->subDays($count)->day)
->metricsData(),

Period::WEEK->value => $laravelMetrics
->forWeek(Carbon::now()->subWeeks($count)->week)
->metricsData(),

Period::MONTH->value => $laravelMetrics
->forMonth(Carbon::now()->subMonths($count)->month)
->metricsData(),

default => $laravelMetrics
->forYear(Carbon::now()->subYears($count)->year)
->metricsData(),
};

return is_null($result) ? 0 : ($result->data ?? 0);
}

protected function metricsData(): mixed
{
if (is_array($this->period)) {
Expand All @@ -466,10 +493,10 @@ protected function metricsData(): mixed
->whereYear($this->dateColumn, $this->year)
->whereMonth($this->dateColumn, $this->month)
->when($this->count === 1, function (Builder|QueryBuilder $query) {
return $query->where(DB::raw("day($this->dateColumn)"), $this->day);
return $query->where(DB::raw($this->formatPeriod(Period::TODAY->value)), $this->day);
})
->when($this->count > 1, function (Builder|QueryBuilder $query) {
return $query->whereBetween(DB::raw("day($this->dateColumn)"), $this->getDayPeriod());
return $query->whereBetween(DB::raw($this->formatPeriod(Period::TODAY->value)), $this->getDayPeriod());
})
->first(),

Expand Down Expand Up @@ -531,10 +558,10 @@ protected function trendsData(): Collection
->whereYear($this->dateColumn, $this->year)
->whereMonth($this->dateColumn, $this->month)
->when($this->count === 1, function (Builder|QueryBuilder $query) {
return $query->where(DB::raw("day($this->dateColumn)"), $this->day);
return $query->where(DB::raw($this->formatPeriod(Period::TODAY->value)), $this->day);
})
->when($this->count > 1, function (Builder|QueryBuilder $query) {
return $query->whereBetween(DB::raw("day($this->dateColumn)"), $this->getDayPeriod());
return $query->whereBetween(DB::raw($this->formatPeriod(Period::TODAY->value)), $this->getDayPeriod());
})
->groupBy('label')
->orderBy('label')
Expand Down Expand Up @@ -605,7 +632,7 @@ protected function asLabel(string $label = null, bool $format = true): string
return $label.' as label';
}

protected function populateMissingDataForPeriod(array $data): array
protected function populateMissingDataForPeriod(array $data, bool $inPercent = false): array
{
$dates = $this->getCustomPeriod();
$data = collect($data);
Expand All @@ -629,7 +656,7 @@ protected function populateMissingDataForPeriod(array $data): array

$result = $this->formatDate($result);

return $this->formatTrends($result);
return $this->formatTrends($result, $inPercent);
}

protected function populateMissingData(array $labels, array $data): array
Expand All @@ -656,17 +683,47 @@ protected function populateMissingData(array $labels, array $data): array
/**
* Generate metrics data
*/
public function metrics(): mixed
public function metrics(int $withVariationsCount = null): int|array
{
$metricsData = $this->metricsData();
$count = is_null($metricsData) ? 0 : ($metricsData->data ?? 0);

return is_null($metricsData) ? 0 : ($metricsData->data ?? 0);
if (is_null($withVariationsCount)) {
return $count;
}

if ($withVariationsCount <= 0) {
throw new InvalidVariationsCountException();
}

$result['count'] = $count;
$result['variation'] = [];

$data = $count - $this->withVariations($withVariationsCount);

if ($data > 0) {
$result['variation'] = [
'type' => 'increment',
'value' => $data,
'period' => $this->period,
'count' => $withVariationsCount,
];
} elseif ($data < 0) {
$result['variation'] = [
'type' => 'decrement',
'value' => abs($data),
'period' => $this->period,
'count' => $withVariationsCount,
];
}

return $result;
}

/**
* Generate trends data for charts
*/
public function trends(): array
public function trends(bool $inPercent = false): array
{
$trendsData = $this
->trendsData()
Expand All @@ -677,22 +734,22 @@ public function trends(): array
if (! $this->fillMissingData) {
$trendsData = $this->formatDate($trendsData);

return $this->formatTrends($trendsData);
return $this->formatTrends($trendsData, $inPercent);
} else {
if (! is_null($this->labelColumn)) {
$trendsData = $this->formatTrends($trendsData);
$trendsData = $this->formatTrends($trendsData, $inPercent);

return $this->populateMissingData($this->getLabelsData(), $trendsData);
}

if (is_array($this->period)) {
return $this->populateMissingDataForPeriod($trendsData);
return $this->populateMissingDataForPeriod($trendsData, $inPercent);
}

if (is_string($this->period)) {
$trendsData = $this->formatDate($trendsData);

return $this->populateMissingData($this->getPeriod(), $this->formatTrends($trendsData));
return $this->populateMissingData($this->getPeriod(), $this->formatTrends($trendsData, $inPercent));
}
}

Expand All @@ -702,8 +759,9 @@ public function trends(): array
];
}

protected function formatTrends(array $data): array
protected function formatTrends(array $data, bool $inPercent = false): array
{
$total = 0;
$result = [
'labels' => [],
'data' => [],
Expand All @@ -712,8 +770,21 @@ protected function formatTrends(array $data): array
foreach ($data as $datum) {
$result['labels'][] = $datum['label'];
$result['data'][] = $datum['data'];
$total += $datum['data'];
}

if (! $inPercent) {
return $result;
}

$percentData = [];

foreach ($result['data'] as $item) {
$percentData[] = round(($item / $total) * 100, 2);
}

$result['data'] = $percentData;

return $result;
}

Expand Down

0 comments on commit c1a8129

Please sign in to comment.