diff --git a/app/Livewire/Project/Service/BackupExecutions.php b/app/Livewire/Project/Service/BackupExecutions.php
index 34be4b11c9..2fcc321703 100644
--- a/app/Livewire/Project/Service/BackupExecutions.php
+++ b/app/Livewire/Project/Service/BackupExecutions.php
@@ -179,11 +179,11 @@ class BackupExecutions extends Component
private function formatExecutions(Collection $rows): Collection
{
$databaseExecutions = ScheduledDatabaseBackupExecution::query()
- ->with(['scheduledDatabaseBackup.database', 'scheduledDatabaseBackup.s3', 'scheduledDatabaseBackup.s3Storages', 's3Replicas'])
+ ->with(['scheduledDatabaseBackup.database', 's3Replicas.s3'])
->whereIn('id', $rows->where('type', 'database')->pluck('id'))
->get()->keyBy('id');
$volumeExecutions = ScheduledVolumeBackupExecution::query()
- ->with(['scheduledVolumeBackup.backupable.resource', 's3', 's3Replicas'])
+ ->with(['scheduledVolumeBackup.backupable.resource', 's3Replicas.s3'])
->whereIn('id', $rows->where('type', 'storage')->pluck('id'))
->get()->keyBy('id');
@@ -193,21 +193,11 @@ class BackupExecutions extends Component
$isDatabase = $row->type === 'database';
$execution = $isDatabase ? $databaseExecutions->get($row->id) : $volumeExecutions->get($row->id);
$schedule = $isDatabase ? $execution->scheduledDatabaseBackup : $execution->scheduledVolumeBackup;
- $storages = match (true) {
- ! $isDatabase => collect([$execution->s3]),
- ! $schedule->save_s3 => collect(),
- $schedule->s3Storages->isNotEmpty() => $schedule->s3Storages,
- default => collect([$schedule->s3]),
- };
- $storageLabel = $storages
- ->filter(fn ($storage) => $storage?->team_id === $serviceTeamId)
- ->map(fn ($storage) => $storage->name.' (bucket: '.$storage->bucket.')')
- ->implode(', ') ?: 'Unavailable';
- if ($isDatabase && ! $schedule->save_s3) {
- $storageLabel = 'Not configured';
- } elseif (! $isDatabase && ! $execution->s3_storage_id && ! $execution->s3_uploaded && ! $execution->s3_storage_deleted) {
- $storageLabel = 'No destination recorded';
- }
+ $s3Storages = $execution->s3Replicas
+ ->map(fn ($replica) => $replica->s3?->team_id === $serviceTeamId
+ ? $replica->s3->name.' (bucket: '.$replica->s3->bucket.')'
+ : 'Unavailable')
+ ->implode(', ');
return [
'id' => $row->type.':'.$execution->id,
@@ -215,7 +205,7 @@ class BackupExecutions extends Component
'target' => $isDatabase ? ($schedule->database->human_name ?: $schedule->database->name) : $schedule->targetName(),
'type' => $isDatabase ? 'Database' : $schedule->targetType(),
'schedule' => $schedule->frequency,
- 's3_tooltip' => ($isDatabase ? 'Current schedule S3 storage: ' : 'S3 storage: ').$storageLabel,
+ 's3_tooltip' => $s3Storages === '' ? null : 'S3 storage: '.$s3Storages,
'status' => $execution->status,
'started_at' => $execution->created_at,
'size' => $execution->size,
diff --git a/resources/views/livewire/project/service/backup-executions.blade.php b/resources/views/livewire/project/service/backup-executions.blade.php
index f0c7d99ca7..a12f074662 100644
--- a/resources/views/livewire/project/service/backup-executions.blade.php
+++ b/resources/views/livewire/project/service/backup-executions.blade.php
@@ -19,7 +19,13 @@
Size
{{ $selectedExecution['size'] ? formatBytes($selectedExecution['size']) : '-' }}
@if ($selectedExecution['filename'])
- Backup path
{{ $selectedExecution['filename'] }}
+
+
Backup path
+
+ {{ $selectedExecution['filename'] }}
+
+
+
@endif
@if ($selectedExecution['message'])
Output
{{ $selectedExecution['message'] }}
@@ -66,10 +72,11 @@
class="data-table-row service-backup-table-grid cursor-pointer text-left text-[13px] text-neutral-700 dark:text-fg-dim">
{{ $execution['target'] }}
-
-
-
+ @if ($execution['s3_tooltip'])
+ S3
+ @endif
{{ $execution['type'] }}{{ $execution['schedule'] }}
diff --git a/tests/Feature/ResourceTeamAfterTeamSwitchTest.php b/tests/Feature/ResourceTeamAfterTeamSwitchTest.php
index dfa4f32181..e6ecae1ffa 100644
--- a/tests/Feature/ResourceTeamAfterTeamSwitchTest.php
+++ b/tests/Feature/ResourceTeamAfterTeamSwitchTest.php
@@ -189,7 +189,8 @@ test('service backup executions label the S3 storage of the service team after a
'database_id' => $this->serviceDatabase->id,
'database_type' => $this->serviceDatabase->getMorphClass(),
]);
- ScheduledDatabaseBackupExecution::create(['scheduled_database_backup_id' => $schedule->id, 'status' => 'success']);
+ ScheduledDatabaseBackupExecution::create(['scheduled_database_backup_id' => $schedule->id, 'status' => 'success'])
+ ->s3Replicas()->create(['s3_storage_id' => $ownS3->id, 's3_uploaded' => true]);
$component = Livewire::test(BackupExecutions::class, ['service' => $this->service]);
session(['currentTeam' => $this->teamB]);
diff --git a/tests/Feature/ServiceResourceRoutingTest.php b/tests/Feature/ServiceResourceRoutingTest.php
index 3dc6be20d6..93844a8145 100644
--- a/tests/Feature/ServiceResourceRoutingTest.php
+++ b/tests/Feature/ServiceResourceRoutingTest.php
@@ -635,7 +635,7 @@ test('service backup lists identify the configured S3 storage without extra colu
}
});
-test('execution tooltips distinguish current database storage from the recorded storage destination', function () {
+test('execution tooltips show the S3 storage of each run and no icon for local-only runs', function () {
$original = S3Storage::create(['key' => 'key', 'secret' => 'secret', 'region' => 'auto', 'endpoint' => 'https://s3.example.com', 'team_id' => $this->teamA->id, 'name' => 'Cloudflare R2', 'bucket' => 'original']);
$current = S3Storage::create(['key' => 'key', 'secret' => 'secret', 'region' => 'auto', 'endpoint' => 'https://s3.example.com', 'team_id' => $this->teamA->id, 'name' => 'Railway S3', 'bucket' => 'current']);
$databaseSchedule = ScheduledDatabaseBackup::create([
@@ -646,7 +646,9 @@ test('execution tooltips distinguish current database storage from the recorded
'save_s3' => true,
's3_storage_id' => $current->id,
]);
- $databaseSchedule->executions()->create(['status' => 'success', 's3_uploaded' => true]);
+ $databaseSchedule->executions()->create(['status' => 'success', 's3_uploaded' => true])
+ ->s3Replicas()->create(['s3_storage_id' => $original->id, 's3_uploaded' => true]);
+ $databaseSchedule->executions()->create(['status' => 'success']);
$volume = LocalPersistentVolume::create([
'name' => 'service-data',
'mount_path' => '/data',
@@ -659,24 +661,17 @@ test('execution tooltips distinguish current database storage from the recorded
'save_s3' => true,
's3_storage_id' => $current->id,
]);
- $volumeSchedule->executions()->create(['status' => 'success', 's3_uploaded' => true, 's3_storage_id' => $original->id]);
+ $volumeSchedule->executions()->create(['status' => 'success', 's3_uploaded' => true, 's3_storage_id' => $original->id])
+ ->s3Replicas()->create(['s3_storage_id' => $original->id, 's3_uploaded' => true]);
+ $volumeSchedule->executions()->create(['status' => 'success']);
- Livewire::test(BackupExecutions::class, ['service' => $this->ownService])
- ->assertSeeHtml('data-tooltip="Current schedule S3 storage: Railway S3 (bucket: current)"')
- ->assertSeeHtml('data-tooltip="S3 storage: Cloudflare R2 (bucket: original)"');
+ $html = Livewire::test(BackupExecutions::class, ['service' => $this->ownService])->html();
+ expect(substr_count($html, 'data-tooltip="S3 storage: Cloudflare R2 (bucket: original)"'))->toBe(2)
+ ->and(substr_count($html, 'data-tooltip="S3 storage:'))->toBe(2)
+ ->and($html)->not->toContain('Railway S3');
- $current->update(['team_id' => $this->teamB->id]);
- Livewire::test(ServiceVolumeBackupIndex::class, ['service' => $this->ownService])
- ->assertDontSee('Railway S3')
- ->assertSeeHtml('data-tooltip="S3 storage: Unavailable"');
- Livewire::test(BackupExecutions::class, ['service' => $this->ownService])
- ->assertDontSee('Railway S3')
- ->assertSeeHtml('data-tooltip="Current schedule S3 storage: Unavailable"');
-
- $databaseSchedule->update(['save_s3' => false]);
- $original->delete();
- Livewire::test(BackupExecutions::class, ['service' => $this->ownService])
- ->assertSeeHtml('data-tooltip="Current schedule S3 storage: Not configured"')
- ->assertDontSeeHtml('data-tooltip="S3 storage: Cloudflare R2 (bucket: original)"')
- ->assertSeeHtml('data-tooltip="S3 storage: Unavailable"');
+ $original->update(['team_id' => $this->teamB->id]);
+ $html = Livewire::test(BackupExecutions::class, ['service' => $this->ownService])->html();
+ expect(substr_count($html, 'data-tooltip="S3 storage: Unavailable"'))->toBe(2)
+ ->and($html)->not->toContain('Cloudflare R2');
});