Skip to content

Commit

Permalink
refactor: Moved statistics queries to model
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen committed Jul 29, 2024
1 parent c4f4a63 commit 7c15d42
Show file tree
Hide file tree
Showing 3 changed files with 341 additions and 107 deletions.
134 changes: 27 additions & 107 deletions app/Livewire/StatisticsPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
use App\Models\BackupDestination;
use App\Models\BackupTask;
use App\Models\BackupTaskData;
use App\Models\BackupTaskLog;
use App\Models\RemoteServer;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Number;
use Illuminate\View\View;
Expand All @@ -25,31 +21,31 @@
*/
class StatisticsPage extends Component
{
/** @var array<string> Dates for the backup tasks chart */
/** @var array<int, string> Dates for the backup tasks chart */
public array $backupDates = [];

/** @var array<int> Counts of file backups for each date */
/** @var array<int, int> Counts of file backups for each date */
public array $fileBackupCounts = [];

/** @var array<int> Counts of database backups for each date */
/** @var array<int, int> Counts of database backups for each date */
public array $databaseBackupCounts = [];

/** @var array<string> Labels for the backup success rate chart */
/** @var array<int, string> Labels for the backup success rate chart */
public array $backupSuccessRateLabels = [];

/** @var array<float> Data for the backup success rate chart */
/** @var array<int, float> Data for the backup success rate chart */
public array $backupSuccessRateData = [];

/** @var array<string> Labels for the average backup size chart */
/** @var array<int, string> Labels for the average backup size chart */
public array $averageBackupSizeLabels = [];

/** @var array<float> Data for the average backup size chart */
/** @var array<int, float> Data for the average backup size chart */
public array $averageBackupSizeData = [];

/** @var array<string> Labels for the completion time chart */
/** @var array<int, string> Labels for the completion time chart */
public array $completionTimeLabels = [];

/** @var array<float> Data for the completion time chart */
/** @var array<int, float> Data for the completion time chart */
public array $completionTimeData = [];

/** @var string Total data backed up in the past seven days */
Expand Down Expand Up @@ -98,49 +94,26 @@ public function render(): View
*/
private function loadBackupTasksData(): void
{
$startDate = now()->subDays(89);
$endDate = now();

$backupTasks = BackupTask::selectRaw('DATE(created_at) as date, type, COUNT(*) as count')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date', 'type')
->orderBy('date')
->get();

$dates = Collection::make($startDate->daysUntil($endDate)->toArray())
->map(fn (CarbonInterface $date, $key): string => $date->format('Y-m-d'));

$fileBackups = $databaseBackups = array_fill_keys($dates->toArray(), 0);

foreach ($backupTasks as $backupTask) {
$date = $backupTask['date'];
$count = (int) $backupTask['count'];
if ($backupTask['type'] === 'Files') {
$fileBackups[$date] = $count;
} else {
$databaseBackups[$date] = $count;
}
}

$this->backupDates = $dates->values()->toArray();
$this->fileBackupCounts = array_values($fileBackups);
$this->databaseBackupCounts = array_values($databaseBackups);
$data = BackupTask::getBackupTasksData();
$this->backupDates = $data['backupDates'];
$this->fileBackupCounts = $data['fileBackupCounts'];
$this->databaseBackupCounts = $data['databaseBackupCounts'];
}

/**
* Load general statistics
*/
private function loadStatistics(): void
{
$this->dataBackedUpInThePastSevenDays = $this->formatFileSize(
$this->dataBackedUpInThePastSevenDays = Number::fileSize(
(int) BackupTaskData::where('created_at', '>=', now()->subDays(7))->sum('size')
);

$this->dataBackedUpInThePastMonth = $this->formatFileSize(
$this->dataBackedUpInThePastMonth = Number::fileSize(
(int) BackupTaskData::where('created_at', '>=', now()->subMonth())->sum('size')
);

$this->dataBackedUpInTotal = $this->formatFileSize(
$this->dataBackedUpInTotal = Number::fileSize(
(int) BackupTaskData::sum('size')
);

Expand All @@ -155,81 +128,28 @@ private function loadStatistics(): void
*/
private function loadBackupSuccessRateData(): void
{
$startDate = now()->startOfMonth()->subMonths(5);
$endDate = now()->endOfMonth();

$backupLogs = BackupTaskLog::selectRaw("DATE_TRUNC('month', created_at)::date as month")
->selectRaw('COUNT(*) as total')
->selectRaw('SUM(CASE WHEN successful_at IS NOT NULL THEN 1 ELSE 0 END) as successful')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('month')
->orderBy('month')
->get();

$this->backupSuccessRateLabels = $backupLogs->pluck('month')->map(fn ($date): string => Carbon::parse($date)->format('Y-m'))->toArray();
$this->backupSuccessRateData = $backupLogs->map(function ($log): float|int {
$total = (int) ($log['total'] ?? 0);
$successful = (int) ($log['successful'] ?? 0);

return $total > 0 ? round(($successful / $total) * 100, 2) : 0;
})->toArray();
$data = BackupTask::getBackupSuccessRateData();
$this->backupSuccessRateLabels = array_map('strval', $data['labels']);
$this->backupSuccessRateData = array_map('floatval', $data['data']);
}

/**
* Load average backup size data
*/
private function loadAverageBackupSizeData(): void
{
$backupSizes = BackupTask::join('backup_task_data', 'backup_tasks.id', '=', 'backup_task_data.backup_task_id')
->join('backup_task_logs', 'backup_tasks.id', '=', 'backup_task_logs.backup_task_id')
->select('backup_tasks.type')
->selectRaw('AVG(backup_task_data.size) as average_size')
->whereNotNull('backup_task_logs.successful_at')
->groupBy('backup_tasks.type')
->get();

$this->averageBackupSizeLabels = $backupSizes->pluck('type')->toArray();
$this->averageBackupSizeData = $backupSizes->pluck('average_size')
->map(fn ($size): string => $this->formatFileSize((int) $size))
->toArray();
}

private function loadCompletionTimeData(): void
{
$startDate = now()->subMonths(3);
$endDate = now();

$completionTimes = BackupTaskData::join('backup_task_logs', 'backup_task_data.backup_task_id', '=', 'backup_task_logs.backup_task_id')
->selectRaw('DATE(backup_task_logs.created_at) as date')
->selectRaw("
AVG(
CASE
WHEN backup_task_data.duration ~ '^\\d+$' THEN backup_task_data.duration::integer
WHEN backup_task_data.duration ~ '^(\\d+):(\\d+):(\\d+)$' THEN
(SUBSTRING(backup_task_data.duration FROM '^(\\d+)'))::integer * 3600 +
(SUBSTRING(backup_task_data.duration FROM '^\\d+:(\\d+)'))::integer * 60 +
(SUBSTRING(backup_task_data.duration FROM ':(\\d+)$'))::integer
ELSE 0
END
) as avg_duration
")
->whereBetween('backup_task_logs.created_at', [$startDate, $endDate])
->whereNotNull('backup_task_logs.successful_at')
->groupBy('date')
->orderBy('date')
->get();

$this->completionTimeLabels = $completionTimes->pluck('date')->toArray();
$this->completionTimeData = $completionTimes->pluck('avg_duration')
->map(fn ($duration): float => round($duration / 60, 2))
->toArray();
$data = BackupTask::getAverageBackupSizeData();
$this->averageBackupSizeLabels = array_map('strval', $data['labels']);
$this->averageBackupSizeData = array_map('floatval', $data['data']);
}

/**
* Format file size using the Number facade
* Load completion time data
*/
private function formatFileSize(int $bytes): string
private function loadCompletionTimeData(): void
{
return Number::fileSize($bytes);
$data = BackupTask::getCompletionTimeData();
$this->completionTimeLabels = array_map('strval', $data['labels']);
$this->completionTimeData = array_map('floatval', $data['data']);
}
}
141 changes: 141 additions & 0 deletions app/Models/BackupTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Jobs\RunFileBackupTaskJob;
use App\Mail\BackupTasks\OutputMail;
use App\Traits\HasTags;
use Carbon\CarbonInterface;
use Cron\CronExpression;
use Database\Factories\BackupTaskFactory;
use Illuminate\Database\Eloquent\Builder;
Expand All @@ -27,6 +28,7 @@
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use InvalidArgumentException;
use Number;
use RuntimeException;

class BackupTask extends Model
Expand Down Expand Up @@ -110,6 +112,145 @@ public static function backupTasksCountByType(int $userId): array
->toArray();
}

/**
* Get backup tasks data for the past 90 days
*
* @return array<string, mixed>
*/
public static function getBackupTasksData(): array
{
$startDate = now()->subDays(89);
$endDate = now();

$backupTasks = self::selectRaw('DATE(created_at) as date, type, COUNT(*) as count')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date', 'type')
->orderBy('date')
->get();

$dates = Collection::make($startDate->daysUntil($endDate)->toArray())
->map(fn (CarbonInterface $date): string => $date->format('Y-m-d'));

$fileBackups = $databaseBackups = array_fill_keys($dates->toArray(), 0);

foreach ($backupTasks as $backupTask) {
$date = $backupTask['date'];
$count = (int) $backupTask['count'];
if ($backupTask['type'] === 'Files') {
$fileBackups[$date] = $count;
} else {
$databaseBackups[$date] = $count;
}
}

return [
'backupDates' => $dates->values()->toArray(),
'fileBackupCounts' => array_values($fileBackups),
'databaseBackupCounts' => array_values($databaseBackups),
];
}

/**
* Get backup success rate data for the past 6 months
*
* @return array<string, array<int, string|float>>
*/
public static function getBackupSuccessRateData(): array
{
$startDate = now()->startOfMonth()->subMonths(5);
$endDate = now()->endOfMonth();

$backupLogs = BackupTaskLog::selectRaw("DATE_TRUNC('month', created_at)::date as month")
->selectRaw('COUNT(*) as total')
->selectRaw('SUM(CASE WHEN successful_at IS NOT NULL THEN 1 ELSE 0 END) as successful')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('month')
->orderBy('month')
->get();

$labels = $backupLogs->pluck('month')->map(fn ($date): string => Carbon::parse($date)->format('Y-m'))->toArray();
$data = $backupLogs->map(function ($log): float {
$total = (int) ($log['total'] ?? 0);
$successful = (int) ($log['successful'] ?? 0);

return $total > 0 ? round(($successful / $total) * 100, 2) : 0;
})->toArray();

return [
'labels' => $labels,
'data' => $data,
];
}

/**
* Get average backup size data
*
* @return array<string, array<int, string>>
*/
public static function getAverageBackupSizeData(): array
{
$backupSizes = self::join('backup_task_data', 'backup_tasks.id', '=', 'backup_task_data.backup_task_id')
->join('backup_task_logs', 'backup_tasks.id', '=', 'backup_task_logs.backup_task_id')
->select('backup_tasks.type')
->selectRaw('AVG(backup_task_data.size) as average_size')
->whereNotNull('backup_task_logs.successful_at')
->groupBy('backup_tasks.type')
->get();

return [
'labels' => $backupSizes->pluck('type')->toArray(),
'data' => $backupSizes->pluck('average_size')
->map(fn ($size): string => self::formatFileSize((int) $size))
->toArray(),
];
}

/**
* Get completion time data for the past 3 months
*
* @return array<string, array<int, string|float>>
*/
public static function getCompletionTimeData(): array
{
$startDate = now()->subMonths(3);
$endDate = now();

$completionTimes = BackupTaskData::join('backup_task_logs', 'backup_task_data.backup_task_id', '=', 'backup_task_logs.backup_task_id')
->selectRaw('DATE(backup_task_logs.created_at) as date')
->selectRaw("
AVG(
CASE
WHEN backup_task_data.duration ~ '^\\d+$' THEN backup_task_data.duration::integer
WHEN backup_task_data.duration ~ '^(\\d+):(\\d+):(\\d+)$' THEN
(SUBSTRING(backup_task_data.duration FROM '^(\\d+)'))::integer * 3600 +
(SUBSTRING(backup_task_data.duration FROM '^\\d+:(\\d+)'))::integer * 60 +
(SUBSTRING(backup_task_data.duration FROM ':(\\d+)$'))::integer
ELSE 0
END
) as avg_duration
")
->whereBetween('backup_task_logs.created_at', [$startDate, $endDate])
->whereNotNull('backup_task_logs.successful_at')
->groupBy('date')
->orderBy('date')
->get();

return [
'labels' => $completionTimes->pluck('date')->toArray(),
'data' => $completionTimes->pluck('avg_duration')
->map(fn ($duration): float => round($duration / 60, 2))
->toArray(),
];
}

/**
* Format file size using the Number facade
*/
private static function formatFileSize(int $bytes): string
{
return Number::fileSize($bytes);
}

/**
* @param Builder<BackupTask> $builder
* @return Builder<BackupTask>
Expand Down
Loading

0 comments on commit 7c15d42

Please sign in to comment.