Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9c5c6f4
first whack at the data migration
alexjustesen Sep 23, 2023
6c4b24e
reverting to default timestamps
alexjustesen Sep 23, 2023
5cd54bb
store the results not the output
alexjustesen Sep 23, 2023
d1187a2
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Dec 13, 2023
86cb643
pushed the date forward for the migration
alexjustesen Dec 13, 2023
b17b74a
use model attributes to get attributes from data
alexjustesen Dec 13, 2023
1414c40
removed unused import
alexjustesen Dec 13, 2023
6b44122
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Dec 13, 2023
dcff4a8
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Dec 15, 2023
43452c8
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Feb 4, 2024
23475ae
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Feb 7, 2024
4780d4f
new result status enum
alexjustesen Feb 7, 2024
f656ecf
simplified ddl for results table
alexjustesen Feb 7, 2024
17d8749
updated select statement
alexjustesen Feb 7, 2024
698200b
not using pending status
alexjustesen Feb 7, 2024
c19c52c
use result status and array helper on speedtest cmd
alexjustesen Feb 7, 2024
3945631
refatored result resource for new data model
alexjustesen Feb 7, 2024
09448a6
action to migrate bad data and clean it up
alexjustesen Feb 7, 2024
6636dd3
refactored result model and extended with data attributes
alexjustesen Feb 7, 2024
1feb193
disable the schedule and notify the admin users
alexjustesen Feb 7, 2024
32ff9f5
fixed status widget
alexjustesen Feb 7, 2024
c83f00e
added result url attribute
alexjustesen Feb 7, 2024
9b0bfb8
moved status in table
alexjustesen Feb 7, 2024
c305fa5
some to-dos
alexjustesen Feb 7, 2024
29d7f11
code quality
alexjustesen Feb 7, 2024
94dfe12
removed test code
alexjustesen Feb 7, 2024
01e099e
added result resource migrate button
alexjustesen Feb 7, 2024
cd6a7ff
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Feb 7, 2024
a6af84c
added filter by public ip address
alexjustesen Feb 7, 2024
bf126a9
changed migration date
alexjustesen Feb 7, 2024
3ed360e
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Feb 8, 2024
dc7e518
Merge branch 'main' into fix-incorrectly-formatted-json-data
alexjustesen Feb 10, 2024
b6ee748
Merge branch 'v0.16.0-dev' into fix-incorrectly-formatted-json-data
alexjustesen Feb 19, 2024
52ab4f7
removed default status value
alexjustesen Feb 19, 2024
83dbe29
charts and stats should only pull completed tests
alexjustesen Feb 19, 2024
7c51ef1
added completed filter to dashboard query
alexjustesen Feb 19, 2024
e2113a0
server host attribute
alexjustesen Feb 19, 2024
0356af9
fixed view form
alexjustesen Feb 19, 2024
8a7fa7a
updated api endpoint for homepage dashboard
alexjustesen Feb 19, 2024
79221fc
changed pagination options
alexjustesen Feb 19, 2024
e57f2ff
renamed bad json table
alexjustesen Feb 19, 2024
a085e87
updated InfluxDB attributes
alexjustesen Feb 19, 2024
d6ed9ee
typo
alexjustesen Feb 19, 2024
e24bbcf
data migration setting to track if the migration was completed
alexjustesen Feb 19, 2024
3151b67
missing eol
alexjustesen Feb 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions app/Actions/MigrateBadJsonResults.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

namespace App\Actions;

use App\Enums\ResultStatus;
use App\Models\User;
use App\Settings\DataMigrationSettings;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Lorisleiva\Actions\Concerns\AsAction;

class MigrateBadJsonResults
{
use AsAction;

public int $jobTimeout = 60 * 5;

public int $jobTries = 1;

public function handle(User $user)
{
$dataSettings = new DataMigrationSettings();

$tableName = 'results_bad_json';

if ($dataSettings->bad_json_migrated) {
Notification::make()
->title('❌ Hmmm it seems someone has already migrated the data!')
->body('Check your results table and make sure you\'re not triggering a duplicate data migration.')
->danger()
->sendToDatabase($user);

return;
}

if (! Schema::hasTable('results')) {
Notification::make()
->title('❌ Could not migrate bad json results!')
->body('The "results" table is missing.')
->danger()
->sendToDatabase($user);

return;
}

if (! Schema::hasTable($tableName)) {
Notification::make()
->title('❌ Could not migrate bad json results!')
->body('The "results_bad_json" table is missing.')
->danger()
->sendToDatabase($user);

return;
}

/**
* Copy backup data to the new results table and reformat it.
*/
try {
DB::table($tableName)->chunkById(100, function ($results) {
foreach ($results as $result) {
$record = [
'service' => 'ookla',
'ping' => $result->ping,
'download' => $result->download,
'upload' => $result->upload,
'comments' => $result->comments,
'data' => json_decode($result->data),
'status' => match ($result->successful) {
1 => ResultStatus::Completed,
default => ResultStatus::Failed,
},
'scheduled' => $result->scheduled,
'created_at' => $result->created_at,
'updated_at' => now(),
];

DB::table('results')->insert($record);
}
});
} catch (\Throwable $e) {
Log::error($e);

Notification::make()
->title('There was an issue migrating the data!')
->body('Check the logs for an output of the issue.')
->danger()
->sendToDatabase($user);

return;
}

$dataSettings->bad_json_migrated = true;

$dataSettings->save();

Notification::make()
->title('Data migration completed!')
->body('Your history has been successfully migrated.')
->success()
->sendToDatabase($user);
}
}
17 changes: 17 additions & 0 deletions app/Enums/ResultStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Enums;

use Filament\Support\Contracts\HasLabel;

enum ResultStatus: string implements HasLabel
{
case Completed = 'completed'; // a speedtest that ran successfully.
case Failed = 'failed'; // a speedtest that failed to run successfully.
case Started = 'started'; // a speedtest that has been started by a worker but has not finish running.

public function getLabel(): ?string
{
return $this->name;
}
}
3 changes: 3 additions & 0 deletions app/Exports/ResultsSelectedBulkExport.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public function array(): array
return $this->results;
}

/**
* TODO: fix it
*/
public function headings(): array
{
return [
Expand Down
166 changes: 105 additions & 61 deletions app/Filament/Resources/ResultResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@

namespace App\Filament\Resources;

use App\Actions\MigrateBadJsonResults;
use App\Enums\ResultStatus;
use App\Exports\ResultsSelectedBulkExport;
use App\Filament\Resources\ResultResource\Pages;
use App\Helpers\Number;
use App\Helpers\TimeZoneHelper;
use App\Models\Result;
use App\Settings\DataMigrationSettings;
use App\Settings\GeneralSettings;
use Carbon\Carbon;
use Filament\Forms;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Support\Enums\Alignment;
use Filament\Tables;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\HtmlString;
use Maatwebsite\Excel\Facades\Excel;

class ResultResource extends Resource
Expand Down Expand Up @@ -52,17 +58,6 @@ public static function form(Form $form): Form
$component->state(Carbon::parse($state)->format($settings->time_format ?? 'M j, Y G:i:s'));
})
->columnSpan(2),
Forms\Components\TextInput::make('server_id')
->label('Server ID'),
Forms\Components\TextInput::make('server_name')
->label('Server name')
->columnSpan(2),
Forms\Components\TextInput::make('server_host')
->label('Server host')
->columnSpan([
'default' => 2,
'md' => 3,
]),
Forms\Components\TextInput::make('download')
->label('Download (Mbps)')
->afterStateHydrated(function (TextInput $component, $state) {
Expand All @@ -75,11 +70,26 @@ public static function form(Form $form): Form
}),
Forms\Components\TextInput::make('ping')
->label('Ping (Ms)'),
Forms\Components\TextInput::make('data.download.latency.jitter')
->label('Download Jitter (Ms)'),
Forms\Components\TextInput::make('data.upload.latency.jitter')
->label('Upload Jitter (Ms)'),
Forms\Components\TextInput::make('data.ping.jitter')
->label('Ping Jitter (Ms)'),
])
->columnSpan(2),
Forms\Components\Section::make()
->schema([
Forms\Components\Checkbox::make('successful'),
Forms\Components\Placeholder::make('service')
->content(fn (Result $result): string => $result->service),
Forms\Components\Placeholder::make('server_name')
->content(fn (Result $result): string => $result->server_name),
Forms\Components\Placeholder::make('server_id')
->label('Server ID')
->content(fn (Result $result): string => $result->server_id),
Forms\Components\Placeholder::make('server_host')
->label('Server ID')
->content(fn (Result $result): string => $result->server_id),
Forms\Components\Checkbox::make('scheduled'),
])
->columns(1)
Expand All @@ -88,65 +98,93 @@ public static function form(Form $form): Form
'md' => 1,
]),
]),
Forms\Components\Textarea::make('data')
->rows(10)
->columnSpan(2),
]);
}

public static function table(Table $table): Table
{
$dataSettings = new DataMigrationSettings();

$settings = new GeneralSettings();

return $table
->columns([
TextColumn::make('id')
Tables\Columns\TextColumn::make('id')
->label('ID')
->sortable(),
TextColumn::make('server')
->getStateUsing(fn (Result $record): ?string => ! blank($record->server_id) ? $record->server_id.' ('.$record->server_name.')' : null)
Tables\Columns\TextColumn::make('ip_address')
->label('IP address')
->toggleable()
->toggledHiddenByDefault()
->sortable(),
IconColumn::make('successful')
->boolean()
->toggleable(),
IconColumn::make('scheduled')
->boolean()
->toggleable(),
TextColumn::make('download')
->label('Download (Mbps)')
->getStateUsing(fn (Result $record): ?string => ! blank($record->download) ? toBits(convertSize($record->download), 2) : null)
Tables\Columns\TextColumn::make('service')
->toggleable()
->toggledHiddenByDefault()
->sortable(),
Tables\Columns\TextColumn::make('server_id')
->label('Server ID')
->toggleable()
->sortable(),
Tables\Columns\TextColumn::make('server_name')
->toggleable()
->sortable(),
Tables\Columns\TextColumn::make('download')
->getStateUsing(fn (Result $record): ?string => ! blank($record->download) ? Number::fileSizeBits(bits: $record->download, precision: 2, perSecond: true) : null)
->sortable(),
TextColumn::make('upload')
->label('Upload (Mbps)')
->getStateUsing(fn (Result $record): ?string => ! blank($record->upload) ? toBits(convertSize($record->upload), 2) : null)
Tables\Columns\TextColumn::make('upload')
->getStateUsing(fn (Result $record): ?string => ! blank($record->upload) ? Number::fileSizeBits(bits: $record->upload, precision: 2, perSecond: true) : null)
->sortable(),
TextColumn::make('ping')
->label('Ping (Ms)')
Tables\Columns\TextColumn::make('ping')
->toggleable()
->sortable(),
TextColumn::make('download_jitter')
->getStateUsing(fn (Result $record): ?string => json_decode($record->data, true)['download']['latency']['jitter'] ?? null)
Tables\Columns\TextColumn::make('download_jitter')
->toggleable()
->toggledHiddenByDefault()
->sortable(),
TextColumn::make('upload_jitter')
->getStateUsing(fn (Result $record): ?string => json_decode($record->data, true)['upload']['latency']['jitter'] ?? null)
Tables\Columns\TextColumn::make('upload_jitter')
->toggleable()
->toggledHiddenByDefault()
->sortable(),
TextColumn::make('ping_jitter')
->getStateUsing(fn (Result $record): ?string => json_decode($record->data, true)['ping']['jitter'] ?? null)
Tables\Columns\TextColumn::make('ping_jitter')
->toggleable()
->toggledHiddenByDefault()
->sortable(),
TextColumn::make('created_at')
->label('Created')
Tables\Columns\TextColumn::make('status')
->toggleable()
->sortable(),
Tables\Columns\IconColumn::make('scheduled')
->boolean()
->toggleable()
->toggledHiddenByDefault()
->alignment(Alignment::Center),
Tables\Columns\TextColumn::make('created_at')
->dateTime($settings->time_format ?? 'M j, Y G:i:s')
->timezone(TimeZoneHelper::displayTimeZone($settings))
->sortable(),
->sortable()
->alignment(Alignment::End),
Tables\Columns\TextColumn::make('updated_at')
->dateTime($settings->time_format ?? 'M j, Y G:i:s')
->timezone(TimeZoneHelper::displayTimeZone($settings))
->toggleable()
->toggledHiddenByDefault()
->sortable()
->alignment(Alignment::End),
])
->filters([
Tables\Filters\SelectFilter::make('ip_address')
->label('IP address')
->multiple()
->options(function (): array {
return Result::query()
->select('data->interface->externalIp AS public_ip_address')
->distinct()
->get()
->mapWithKeys(function (Result $item, int $key) {
return [$item['public_ip_address'] => $item['public_ip_address']];
})
->toArray();
})
->attribute('data->interface->externalIp'),
Tables\Filters\TernaryFilter::make('scheduled')
->placeholder('-')
->trueLabel('Only scheduled speedtests')
Expand All @@ -156,23 +194,17 @@ public static function table(Table $table): Table
false: fn (Builder $query) => $query->where('scheduled', false),
blank: fn (Builder $query) => $query,
),
Tables\Filters\TernaryFilter::make('successful')
->placeholder('-')
->trueLabel('Only successful speedtests')
->falseLabel('Only failed speedtests')
->queries(
true: fn (Builder $query) => $query->where('successful', true),
false: fn (Builder $query) => $query->where('successful', false),
blank: fn (Builder $query) => $query,
),
Tables\Filters\SelectFilter::make('status')
->multiple()
->options(ResultStatus::class),
])
->actions([
Tables\Actions\ActionGroup::make([
Action::make('view result')
->label('View on Speedtest.net')
->icon('heroicon-o-link')
->url(fn (Result $record): ?string => $record?->url)
->hidden(fn (Result $record): bool => ! $record->is_successful)
->url(fn (Result $record): ?string => $record->result_url)
->hidden(fn (Result $record): bool => $record->status !== ResultStatus::Completed)
->openUrlInNewTab(),
Tables\Actions\ViewAction::make(),
Tables\Actions\Action::make('updateComments')
Expand Down Expand Up @@ -206,14 +238,26 @@ public static function table(Table $table): Table
}),
Tables\Actions\DeleteBulkAction::make(),
])
->defaultSort('created_at', 'desc');
}
->headerActions([
Tables\Actions\Action::make('migrate')
->action(function (): void {
Notification::make()
->title('Starting data migration...')
->body('This can take a little bit depending how much data you have.')
->warning()
->sendToDatabase(Auth::user());

public static function getRelations(): array
{
return [
//
];
MigrateBadJsonResults::dispatch(Auth::user());
})
->hidden($dataSettings->bad_json_migrated)
->requiresConfirmation()
->modalHeading('Migrate History')
->modalDescription(new HtmlString('<p>v0.16.0 archived the old <code>"results"</code> table, to migrate your history click the button below.</p><p>For more information read the <a href="#" target="_blank" rel="nofollow" class="underline">docs</a>.</p>'))
->modalSubmitActionLabel('Yes, migrate it'),
])
->defaultSort('created_at', 'desc')
->paginated([5, 15, 25, 50, 100])
->defaultPaginationPageOption(15);
}

public static function getPages(): array
Expand Down
Loading