fix(previews): key docker tags by preview id so a delete cannot shift them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-10-24 18:14:51 +02:00
co-authored by Claude Opus 5.5
parent f1ab9956e8
commit 5f460c06c6
4 changed files with 24 additions and 15 deletions
+5 -12
View File
@@ -80,8 +80,8 @@ class Previews extends Component
private function syncDockerTags(): void
{
$this->previewDockerTags = [];
foreach ($this->application->previews as $key => $preview) {
$this->previewDockerTags[$key] = $preview->docker_registry_image_tag;
foreach ($this->application->previews as $preview) {
$this->previewDockerTags[$preview->id] = $preview->docker_registry_image_tag;
}
}
@@ -118,16 +118,8 @@ class Previews extends Component
throw new \Exception('Preview not found');
}
$previewKey = $this->application->previews->search(function ($item) use ($preview_id) {
return $item->id == $preview_id;
});
if ($previewKey === false) {
throw new \Exception('Preview not found');
}
$this->validateOnly("previewDockerTags.{$previewKey}");
$preview->docker_registry_image_tag = $this->previewDockerTags[$previewKey] ?? null;
$this->validateOnly("previewDockerTags.{$preview->id}");
$preview->docker_registry_image_tag = $this->previewDockerTags[$preview->id] ?? null;
$changedFields = $preview->isDirty('docker_registry_image_tag') ? ['docker_registry_image_tag'] : [];
$preview->save();
if ($changedFields !== []) {
@@ -385,6 +377,7 @@ class Previews extends Component
// Refresh the application and its previews relationship to reflect the soft delete
$this->application->load('previews');
unset($this->previewDockerTags[$preview->id]);
$this->dispatch('update_links');
$this->dispatch('success', 'Preview deletion started. It may take a few moments to complete.');
} catch (\Throwable $e) {
@@ -121,7 +121,7 @@
<x-application.settings-section id="preview-deployments-section" title="Preview deployments"
helper="Manage domains, deployments, logs, and lifecycle actions for configured previews." flush>
@forelse (data_get($application, 'previews') as $previewName => $preview)
@forelse (data_get($application, 'previews') as $preview)
@php
$previewStatus = str(data_get($preview, 'status'));
$previewIsStopped = $previewStatus->startsWith('exited');
@@ -298,7 +298,7 @@
class="application-settings-section-body is-flush mt-3 overflow-visible">
<div class="data-table-header grid-cols-1"><span>Docker tag</span></div>
<div class="p-3">
<x-forms.input id="previewDockerTags.{{ $previewName }}" canGate="update"
<x-forms.input id="previewDockerTags.{{ $preview->id }}" canGate="update"
:canResource="$application"
wire:change="save_preview('{{ $preview->id }}')" />
</div>
@@ -2,6 +2,7 @@
use App\Livewire\Project\Application\Previews;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\Environment;
use App\Models\GitlabApp;
use App\Models\InstanceSettings;
@@ -12,6 +13,7 @@ use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;
use Livewire\Livewire;
@@ -159,3 +161,17 @@ it('denies preview setting changes without application update permission', funct
->is_preview_deployments_enabled->toBeFalse()
->is_pr_deployments_public_enabled->toBeFalse();
})->with([['member', false], ['owner', true]]);
it('saves the docker tag of the remaining preview after another preview is deleted', function () {
$this->application->update(['build_pack' => 'dockerimage']);
$previewA = ApplicationPreview::create(['application_id' => $this->application->id, 'pull_request_id' => 2, 'pull_request_html_url' => '', 'docker_registry_image_tag' => 'tag-a']);
$previewB = ApplicationPreview::create(['application_id' => $this->application->id, 'pull_request_id' => 1, 'pull_request_html_url' => '', 'docker_registry_image_tag' => 'tag-b']);
Queue::fake();
Livewire::test(Previews::class, ['application' => $this->application->fresh()])
->call('delete', 2)
->call('save_preview', $previewB->id)
->assertHasNoErrors();
expect($previewB->fresh()->docker_registry_image_tag)->toBe('tag-b');
});
@@ -127,7 +127,7 @@ it('audits adding, retagging and deleting a preview', function () {
->and($created->first()->metadata['pull_request_id'])->toBe(42)
->and($created->first()->resource_uuid)->toBe($this->application->uuid);
$component->set('previewDockerTags.0', 'pr-42')
$component->set('previewDockerTags.'.ApplicationPreview::query()->where('pull_request_id', 42)->value('id'), 'pr-42')
->call('save_preview', ApplicationPreview::query()->where('pull_request_id', 42)->value('id'));
$updated = applicationSettingsAuditEvents('ui.application.preview_updated');