Merge pull request #6867 from coollabsio/fix-service-refresh-issues
fix: prevent duplicate services on image change and enable real-time UI refresh
This commit is contained in:
commit
1cdd6fb876
6 changed files with 123 additions and 21 deletions
|
|
@ -33,6 +33,8 @@ public function getListeners()
|
|||
|
||||
return [
|
||||
"echo-private:team.{$teamId},ServiceChecked" => 'serviceChecked',
|
||||
'refreshServices' => 'refreshServices',
|
||||
'refresh' => 'refreshServices',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ public function submit()
|
|||
}
|
||||
$this->application->service->parse();
|
||||
$this->dispatch('refresh');
|
||||
$this->dispatch('refreshServices');
|
||||
$this->dispatch('configurationChanged');
|
||||
} catch (\Throwable $e) {
|
||||
$originalFqdn = $this->application->getOriginal('fqdn');
|
||||
|
|
|
|||
|
|
@ -1181,23 +1181,26 @@ function serviceParser(Service $resource): Collection
|
|||
$image = data_get_str($service, 'image');
|
||||
$isDatabase = isDatabaseImage($image, $service);
|
||||
if ($isDatabase) {
|
||||
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
|
||||
$applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first();
|
||||
if ($applicationFound) {
|
||||
$savedService = $applicationFound;
|
||||
} else {
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$savedService = ServiceApplication::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
// Update image if it changed
|
||||
if ($savedService->image !== $image) {
|
||||
$savedService->image = $image;
|
||||
$savedService->save();
|
||||
}
|
||||
}
|
||||
foreach ($services as $serviceName => $service) {
|
||||
$predefinedPort = null;
|
||||
|
|
@ -1514,20 +1517,18 @@ function serviceParser(Service $resource): Collection
|
|||
}
|
||||
|
||||
if ($isDatabase) {
|
||||
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
|
||||
$applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first();
|
||||
if ($applicationFound) {
|
||||
$savedService = $applicationFound;
|
||||
} else {
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$savedService = ServiceApplication::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1317,6 +1317,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
'name' => $serviceName,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
if (is_null($savedService)) {
|
||||
$savedService = ServiceDatabase::create([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($isNew) {
|
||||
|
|
@ -1330,21 +1337,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
'name' => $serviceName,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
}
|
||||
}
|
||||
if (is_null($savedService)) {
|
||||
if ($isDatabase) {
|
||||
$savedService = ServiceDatabase::create([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
} else {
|
||||
$savedService = ServiceApplication::create([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
if (is_null($savedService)) {
|
||||
$savedService = ServiceApplication::create([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
44
tests/Unit/ServiceConfigurationRefreshTest.php
Normal file
44
tests/Unit/ServiceConfigurationRefreshTest.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Unit tests to verify that Configuration component properly listens to
|
||||
* refresh events dispatched when compose file or domain changes.
|
||||
*
|
||||
* These tests verify the fix for the issue where changes to compose or domain
|
||||
* were not visible until manual page refresh.
|
||||
*/
|
||||
it('ensures Configuration component listens to refreshServices event', function () {
|
||||
$configurationFile = file_get_contents(__DIR__.'/../../app/Livewire/Project/Service/Configuration.php');
|
||||
|
||||
// Check that the Configuration component has refreshServices listener
|
||||
expect($configurationFile)
|
||||
->toContain("'refreshServices' => 'refreshServices'")
|
||||
->toContain("'refresh' => 'refreshServices'");
|
||||
});
|
||||
|
||||
it('ensures Configuration component has refreshServices method', function () {
|
||||
$configurationFile = file_get_contents(__DIR__.'/../../app/Livewire/Project/Service/Configuration.php');
|
||||
|
||||
// Check that the refreshServices method exists
|
||||
expect($configurationFile)
|
||||
->toContain('public function refreshServices()')
|
||||
->toContain('$this->service->refresh()')
|
||||
->toContain('$this->applications = $this->service->applications->sort()')
|
||||
->toContain('$this->databases = $this->service->databases->sort()');
|
||||
});
|
||||
|
||||
it('ensures StackForm dispatches refreshServices event on submit', function () {
|
||||
$stackFormFile = file_get_contents(__DIR__.'/../../app/Livewire/Project/Service/StackForm.php');
|
||||
|
||||
// Check that StackForm dispatches refreshServices event
|
||||
expect($stackFormFile)
|
||||
->toContain("->dispatch('refreshServices')");
|
||||
});
|
||||
|
||||
it('ensures EditDomain dispatches refreshServices event on submit', function () {
|
||||
$editDomainFile = file_get_contents(__DIR__.'/../../app/Livewire/Project/Service/EditDomain.php');
|
||||
|
||||
// Check that EditDomain dispatches refreshServices event
|
||||
expect($editDomainFile)
|
||||
->toContain("->dispatch('refreshServices')");
|
||||
});
|
||||
55
tests/Unit/ServiceParserImageUpdateTest.php
Normal file
55
tests/Unit/ServiceParserImageUpdateTest.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Unit tests to verify that service parser correctly handles image updates
|
||||
* without creating duplicate ServiceApplication or ServiceDatabase records.
|
||||
*
|
||||
* These tests verify the fix for the issue where changing an image in a
|
||||
* docker-compose file would create a new service instead of updating the existing one.
|
||||
*/
|
||||
it('ensures service parser does not include image in firstOrCreate query', function () {
|
||||
// Read the serviceParser function from parsers.php
|
||||
$parsersFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/parsers.php');
|
||||
|
||||
// Check that firstOrCreate is called with only name and service_id
|
||||
// and NOT with image parameter in the ServiceApplication presave loop
|
||||
expect($parsersFile)
|
||||
->toContain("firstOrCreate([\n 'name' => \$serviceName,\n 'service_id' => \$resource->id,\n ]);")
|
||||
->not->toContain("firstOrCreate([\n 'name' => \$serviceName,\n 'image' => \$image,\n 'service_id' => \$resource->id,\n ]);");
|
||||
});
|
||||
|
||||
it('ensures service parser updates image after finding or creating service', function () {
|
||||
// Read the serviceParser function from parsers.php
|
||||
$parsersFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/parsers.php');
|
||||
|
||||
// Check that image update logic exists after firstOrCreate
|
||||
expect($parsersFile)
|
||||
->toContain('// Update image if it changed')
|
||||
->toContain('if ($savedService->image !== $image) {')
|
||||
->toContain('$savedService->image = $image;')
|
||||
->toContain('$savedService->save();');
|
||||
});
|
||||
|
||||
it('ensures parseDockerComposeFile does not create duplicates on null savedService', function () {
|
||||
// Read the parseDockerComposeFile function from shared.php
|
||||
$sharedFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/shared.php');
|
||||
|
||||
// Check that the duplicate creation logic after is_null check has been fixed
|
||||
// The old code would create a duplicate if savedService was null
|
||||
// The new code checks for null within the else block and creates only if needed
|
||||
expect($sharedFile)
|
||||
->toContain('if (is_null($savedService)) {')
|
||||
->toContain('$savedService = ServiceDatabase::create([');
|
||||
});
|
||||
|
||||
it('verifies image update logic is present in parseDockerComposeFile', function () {
|
||||
// Read the parseDockerComposeFile function from shared.php
|
||||
$sharedFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/shared.php');
|
||||
|
||||
// Verify the image update logic exists
|
||||
expect($sharedFile)
|
||||
->toContain('// Check if image changed')
|
||||
->toContain('if ($savedService->image !== $image) {')
|
||||
->toContain('$savedService->image = $image;')
|
||||
->toContain('$savedService->save();');
|
||||
});
|
||||
Loading…
Reference in a new issue