fix(shared-variables): support direct mount params and comment field for server variables

Allow SharedVariables Livewire components (Environment, Project, Server)
to accept UUID parameters directly via mount() instead of relying solely
on route parameters. This enables Livewire component testing without a
live route context.

Also adds comment field support when saving/updating server shared
environment variables, guards PostgreSQL-specific migration statements
from running under SQLite (test environment compatibility), and expands
the feature test suite with server shared variable scenarios including
inline comment preservation and update behaviour.
This commit is contained in:
Andras Bacsai
2026-03-31 14:58:01 +02:00
5 changed files with 121 additions and 17 deletions
@@ -51,11 +51,14 @@ class Show extends Component
}
}
public function mount()
public function mount(?string $project_uuid = null, ?string $environment_uuid = null)
{
$this->parameters = get_route_parameters();
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->firstOrFail();
$this->environment = $this->project->environments()->where('uuid', request()->route('environment_uuid'))->firstOrFail();
$projectUuid = $project_uuid ?? request()->route('project_uuid');
$environmentUuid = $environment_uuid ?? request()->route('environment_uuid');
$this->project = Project::ownedByCurrentTeam()->where('uuid', $projectUuid)->firstOrFail();
$this->environment = $this->project->environments()->where('uuid', $environmentUuid)->firstOrFail();
$this->getDevView();
}
@@ -44,9 +44,9 @@ class Show extends Component
}
}
public function mount()
public function mount(?string $project_uuid = null)
{
$projectUuid = request()->route('project_uuid');
$projectUuid = $project_uuid ?? request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (! $project) {
+10 -4
View File
@@ -37,6 +37,7 @@ class Show extends Component
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'comment' => $data['comment'] ?? null,
'type' => 'server',
'team_id' => currentTeam()->id,
]);
@@ -47,9 +48,9 @@ class Show extends Component
}
}
public function mount()
public function mount(?string $server_uuid = null)
{
$serverUuid = request()->route('server_uuid');
$serverUuid = $server_uuid ?? request()->route('server_uuid');
$teamId = currentTeam()->id;
$server = Server::where('team_id', $teamId)->where('uuid', $serverUuid)->first();
if (! $server) {
@@ -140,7 +141,10 @@ class Show extends Component
private function updateOrCreateVariables($variables)
{
$count = 0;
foreach ($variables as $key => $value) {
foreach ($variables as $key => $data) {
$value = is_array($data) ? ($data['value'] ?? '') : $data;
$comment = is_array($data) ? ($data['comment'] ?? null) : null;
// Skip predefined variables
if (in_array($key, ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])) {
continue;
@@ -149,8 +153,9 @@ class Show extends Component
if ($found) {
if (! $found->is_shown_once && ! $found->is_multiline) {
if ($found->value !== $value) {
if ($found->value !== $value || $found->comment !== $comment) {
$found->value = $value;
$found->comment = $comment;
$found->save();
$count++;
}
@@ -159,6 +164,7 @@ class Show extends Component
$this->server->environment_variables()->create([
'key' => $key,
'value' => $value,
'comment' => $comment,
'is_multiline' => false,
'is_literal' => false,
'type' => 'server',
@@ -13,8 +13,10 @@ return new class extends Migration
public function up(): void
{
DB::transaction(function () {
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment', 'server'))");
if (DB::getDriverName() !== 'sqlite') {
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment', 'server'))");
}
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->foreignId('server_id')->nullable()->constrained()->onDelete('cascade');
// NULL != NULL in PostgreSQL unique indexes, so this only enforces uniqueness
@@ -36,8 +38,10 @@ return new class extends Migration
$table->dropForeign(['server_id']);
$table->dropColumn('server_id');
});
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment'))");
if (DB::getDriverName() !== 'sqlite') {
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment'))");
}
});
}
};
+95 -4
View File
@@ -1,7 +1,11 @@
<?php
use App\Livewire\SharedVariables\Environment\Show;
use App\Livewire\SharedVariables\Team\Index;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server;
use App\Models\SharedEnvironmentVariable;
use App\Models\Team;
use App\Models\User;
@@ -19,13 +23,35 @@ beforeEach(function () {
$this->environment = Environment::factory()->create([
'project_id' => $this->project->id,
]);
InstanceSettings::unguarded(function () {
InstanceSettings::updateOrCreate([
'id' => 0,
], [
'is_registration_enabled' => true,
'is_api_enabled' => true,
'smtp_enabled' => true,
'smtp_host' => 'localhost',
'smtp_port' => 1025,
'smtp_from_address' => 'hi@example.com',
'smtp_from_name' => 'Coolify',
]);
});
$this->actingAs($this->user);
session(['currentTeam' => $this->team]);
});
afterEach(function () {
request()->setRouteResolver(function () {
return null;
});
});
test('environment shared variable dev view saves without openssl_encrypt error', function () {
Livewire::test(\App\Livewire\SharedVariables\Environment\Show::class)
Livewire::test(Show::class, [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
])
->set('variables', "MY_VAR=my_value\nANOTHER_VAR=another_value")
->call('submit')
->assertHasNoErrors();
@@ -38,7 +64,9 @@ test('environment shared variable dev view saves without openssl_encrypt error',
});
test('project shared variable dev view saves without openssl_encrypt error', function () {
Livewire::test(\App\Livewire\SharedVariables\Project\Show::class)
Livewire::test(App\Livewire\SharedVariables\Project\Show::class, [
'project_uuid' => $this->project->uuid,
])
->set('variables', 'PROJ_VAR=proj_value')
->call('submit')
->assertHasNoErrors();
@@ -49,7 +77,7 @@ test('project shared variable dev view saves without openssl_encrypt error', fun
});
test('team shared variable dev view saves without openssl_encrypt error', function () {
Livewire::test(\App\Livewire\SharedVariables\Team\Index::class)
Livewire::test(Index::class)
->set('variables', 'TEAM_VAR=team_value')
->call('submit')
->assertHasNoErrors();
@@ -69,7 +97,10 @@ test('environment shared variable dev view updates existing variable', function
'team_id' => $this->team->id,
]);
Livewire::test(\App\Livewire\SharedVariables\Environment\Show::class)
Livewire::test(Show::class, [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
])
->set('variables', 'EXISTING_VAR=new_value')
->call('submit')
->assertHasNoErrors();
@@ -77,3 +108,63 @@ test('environment shared variable dev view updates existing variable', function
$var = $this->environment->environment_variables()->where('key', 'EXISTING_VAR')->first();
expect($var->value)->toBe('new_value');
});
test('server shared variable dev view saves without openssl_encrypt error', function () {
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
Livewire::test(App\Livewire\SharedVariables\Server\Show::class, [
'server_uuid' => $this->server->uuid,
])
->set('variables', "SERVER_VAR=server_value\nSECOND_SERVER_VAR=second_value")
->call('submit')
->assertHasNoErrors();
$vars = $this->server->environment_variables()->pluck('value', 'key')->toArray();
expect($vars)->toHaveKey('SERVER_VAR')
->and($vars['SERVER_VAR'])->toBe('server_value')
->and($vars)->toHaveKey('SECOND_SERVER_VAR')
->and($vars['SECOND_SERVER_VAR'])->toBe('second_value');
});
test('server shared variable dev view preserves inline comments', function () {
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
Livewire::test(App\Livewire\SharedVariables\Server\Show::class, [
'server_uuid' => $this->server->uuid,
])
->set('variables', 'COMMENTED_SERVER_VAR=value # note from dev view')
->call('submit')
->assertHasNoErrors();
$var = $this->server->environment_variables()->where('key', 'COMMENTED_SERVER_VAR')->first();
expect($var)->not->toBeNull()
->and($var->value)->toBe('value')
->and($var->comment)->toBe('note from dev view');
});
test('server shared variable dev view updates existing variable', function () {
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
SharedEnvironmentVariable::create([
'key' => 'EXISTING_SERVER_VAR',
'value' => 'old_value',
'comment' => 'old comment',
'type' => 'server',
'server_id' => $this->server->id,
'team_id' => $this->team->id,
]);
Livewire::test(App\Livewire\SharedVariables\Server\Show::class, [
'server_uuid' => $this->server->uuid,
])
->set('variables', 'EXISTING_SERVER_VAR=new_value # updated comment')
->call('submit')
->assertHasNoErrors();
$var = $this->server->environment_variables()->where('key', 'EXISTING_SERVER_VAR')->first();
expect($var->value)->toBe('new_value')
->and($var->comment)->toBe('updated comment');
});