diff --git a/resources/css/app.css b/resources/css/app.css index c963d109fe..f45494098d 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -2952,13 +2952,14 @@ input[type="search"]::-webkit-search-results-decoration { width: 100%; } -.data-table-row.service-backup-table-grid { - background: var(--coollabs-base); - border-bottom: 1px solid var(--coollabs-fill); +.data-table-header.service-backup-table-grid { + border-bottom: 0; } -.data-table-row.service-backup-table-grid:last-child { - border-bottom: 0; +/* Rows are reordered with CSS order, so dividers must not depend on DOM position. */ +.data-table-row.service-backup-table-grid { + background: var(--coollabs-base); + border-top: 1px solid var(--coollabs-fill); } .data-table-row.service-backup-table-grid:hover { diff --git a/resources/views/livewire/project/application/backup/index.blade.php b/resources/views/livewire/project/application/backup/index.blade.php index 7e89bbcd07..f842ce95d0 100644 --- a/resources/views/livewire/project/application/backup/index.blade.php +++ b/resources/views/livewire/project/application/backup/index.blade.php @@ -167,7 +167,7 @@ @if ($backups->isNotEmpty()) -
+
Target Type diff --git a/resources/views/livewire/project/service/volume-backup/index.blade.php b/resources/views/livewire/project/service/volume-backup/index.blade.php index 26b835cdc8..61c4657924 100644 --- a/resources/views/livewire/project/service/volume-backup/index.blade.php +++ b/resources/views/livewire/project/service/volume-backup/index.blade.php @@ -240,7 +240,7 @@ @if ($backups->isNotEmpty() || $databaseBackups->isNotEmpty())
-
+
Target Type diff --git a/tests/v4/Browser/BackupSortTest.php b/tests/v4/Browser/BackupSortTest.php new file mode 100644 index 0000000000..096515f56a --- /dev/null +++ b/tests/v4/Browser/BackupSortTest.php @@ -0,0 +1,96 @@ +stack = seedBrowserResourceStack(); +}); + +/** + * Creates volume backups for bravo, alpha, and charlie, in that order from oldest to newest. + */ +function createSortableVolumeBackups(Model $resource): void +{ + foreach (['bravo', 'alpha', 'charlie'] as $index => $name) { + $volume = LocalPersistentVolume::create([ + 'uuid' => (string) new Cuid2, + 'name' => $name.'-data', + 'mount_path' => '/data/'.$name, + 'resource_id' => $resource->id, + 'resource_type' => $resource->getMorphClass(), + ]); + + $backup = ScheduledVolumeBackup::create([ + 'backupable_type' => $volume->getMorphClass(), + 'backupable_id' => $volume->id, + 'team_id' => 0, + 'frequency' => '0 0 * * *', + 'enabled' => true, + ]); + + ScheduledVolumeBackup::whereKey($backup->id)->update(['created_at' => now()->subDays(10 - $index)]); + } +} + +/** + * Returns the backup target names in the order the browser shows them, top to bottom. + */ +function visibleBackupOrderScript(): string +{ + return <<<'JS' + () => [...document.querySelectorAll('.data-table-row')] + .filter((row) => row.offsetParent !== null) + .sort((left, right) => left.getBoundingClientRect().top - right.getBoundingClientRect().top) + .map((row) => row.querySelector('span').textContent.trim()) + .join(',') + JS; +} + +function assertBackupSortOrders(mixed $page, string $screenshot): void +{ + $page->assertSee('alpha-data') + ->assertScript(visibleBackupOrderScript(), 'alpha-data,bravo-data,charlie-data') + ->click('Sort') + ->click('Target Z–A') + ->assertScript(visibleBackupOrderScript(), 'charlie-data,bravo-data,alpha-data') + ->click('Sort') + ->click('Oldest first') + ->assertScript(visibleBackupOrderScript(), 'bravo-data,alpha-data,charlie-data') + ->click('Sort') + ->click('Newest first') + ->assertScript(visibleBackupOrderScript(), 'charlie-data,alpha-data,bravo-data') + ->screenshot(filename: $screenshot); +} + +it('reorders application storage backups when the sort option changes', function () { + $application = createBrowserApplication($this->stack, [ + 'uuid' => 'app-browser-backup-sort', + 'name' => 'Backup Sort App', + ]); + createSortableVolumeBackups($application); + + loginAndSkipBoarding(); + + $page = visit(applicationConfigurationUrl($this->stack['project'], $this->stack['environment'], $application).'/backups'); + + assertBackupSortOrders($page, 'application-backup-sort'); +}); + +it('reorders service storage backups when the sort option changes', function () { + ['service' => $service, 'serviceApplication' => $serviceApplication] = createBrowserService($this->stack, [ + 'uuid' => 'svc-browser-backup-sort', + ]); + createSortableVolumeBackups($serviceApplication); + + loginAndSkipBoarding(); + + $page = visit(serviceConfigurationUrl($this->stack['project'], $this->stack['environment'], $service).'/storage-backups'); + + assertBackupSortOrders($page, 'service-backup-sort'); +});