diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 2a1a47b4d..a19837e16 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -39,7 +39,12 @@ class All extends Component 'environmentVariableDeleted' => 'refreshEnvs', ]; - public function updatedSearch() + public function updatedSearch(): void + { + $this->clearEnvironmentVariableCaches(); + } + + private function clearEnvironmentVariableCaches(): void { unset($this->environmentVariables); unset($this->environmentVariablesPreview); @@ -95,7 +100,9 @@ private function getEnvironmentVariables(bool $isPreview, bool $withSearch = tru $query->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END"); if ($withSearch && $this->searchTerm() !== '') { - $query->whereRaw('LOWER(key) LIKE ?', ['%'.Str::lower($this->searchTerm()).'%']); + $escapedSearch = addcslashes(Str::lower($this->searchTerm()), '%_\\'); + + $query->whereRaw("LOWER(key) LIKE ? ESCAPE '\\'", ['%'.$escapedSearch.'%']); } if ($this->is_env_sorting_enabled) { @@ -322,12 +329,7 @@ private function handleSingleSubmit($data) $environment->order = $maxOrder + 1; $environment->save(); - // Clear computed property cache to force refresh - unset($this->environmentVariables); - unset($this->environmentVariablesPreview); - unset($this->hardcodedEnvironmentVariables); - unset($this->hardcodedEnvironmentVariablesPreview); - unset($this->hasEnvironmentVariables); + $this->clearEnvironmentVariableCaches(); $this->dispatch('success', 'Environment variable added.'); } @@ -456,12 +458,7 @@ private function updateOrCreateVariables($isPreview, $variables) public function refreshEnvs() { $this->resource->refresh(); - // Clear computed property cache to force refresh - unset($this->environmentVariables); - unset($this->environmentVariablesPreview); - unset($this->hardcodedEnvironmentVariables); - unset($this->hardcodedEnvironmentVariablesPreview); - unset($this->hasEnvironmentVariables); + $this->clearEnvironmentVariableCaches(); $this->getDevView(); } } diff --git a/resources/views/livewire/project/shared/environment-variable/all.blade.php b/resources/views/livewire/project/shared/environment-variable/all.blade.php index f49765286..991327265 100644 --- a/resources/views/livewire/project/shared/environment-variable/all.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/all.blade.php @@ -70,23 +70,27 @@ class="absolute inset-0 w-4 h-4 text-coollabs dark:text-warning animate-spin" fi @if ($this->isSearchActive && ! $this->hasEnvironmentVariables)
No environment variables found.
@else -
-

Production Environment Variables

-
Environment (secrets) variables for Production.
-
- @forelse ($this->environmentVariables as $env) - - @empty -
No environment variables found.
- @endforelse - @if (($resource->type() === 'service' || $resource?->build_pack === 'dockercompose') && $this->hardcodedEnvironmentVariables->isNotEmpty()) - @foreach ($this->hardcodedEnvironmentVariables as $index => $env) - + @if ($this->environmentVariables->isNotEmpty() || $this->hardcodedEnvironmentVariables->isNotEmpty()) +
+

Production Environment Variables

+
Environment (secrets) variables for Production.
+
+ @foreach ($this->environmentVariables as $env) + @endforeach + @if (($resource->type() === 'service' || $resource?->build_pack === 'dockercompose') && $this->hardcodedEnvironmentVariables->isNotEmpty()) + @foreach ($this->hardcodedEnvironmentVariables as $index => $env) + + @endforeach + @endif @endif - @if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview) + @if ( + $resource->type() === 'application' && + $showPreview && + ($this->environmentVariablesPreview->isNotEmpty() || $this->hardcodedEnvironmentVariablesPreview->isNotEmpty()) + )

Preview Deployments Environment Variables

Environment (secrets) variables for Preview Deployments.
diff --git a/tests/Feature/EnvironmentVariableMultilineToggleViewTest.php b/tests/Feature/EnvironmentVariableMultilineToggleViewTest.php index 632f6f943..de97e6095 100644 --- a/tests/Feature/EnvironmentVariableMultilineToggleViewTest.php +++ b/tests/Feature/EnvironmentVariableMultilineToggleViewTest.php @@ -45,3 +45,12 @@ ->toContain('
No environment variables found.
') ->toContain('@else'); }); + +it('only renders the production section when production variables are visible', function () { + $view = file_get_contents(resource_path('views/livewire/project/shared/environment-variable/all.blade.php')); + + expect($view) + ->toContain('@if ($this->environmentVariables->isNotEmpty() || $this->hardcodedEnvironmentVariables->isNotEmpty())') + ->not->toContain('@forelse ($this->environmentVariables as $env)') + ->not->toContain('@empty'); +}); diff --git a/tests/Feature/EnvironmentVariableSearchTest.php b/tests/Feature/EnvironmentVariableSearchTest.php index 5f49776c9..613144d53 100644 --- a/tests/Feature/EnvironmentVariableSearchTest.php +++ b/tests/Feature/EnvironmentVariableSearchTest.php @@ -56,6 +56,44 @@ ->toBe(['API_KEY']); }); +it('treats production environment variable search wildcards literally', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + ]); + + EnvironmentVariable::create([ + 'key' => 'API_KEY', + 'value' => 'secret', + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + ]); + + EnvironmentVariable::create([ + 'key' => 'APIXKEY', + 'value' => 'other-secret', + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + ]); + + EnvironmentVariable::create([ + 'key' => 'PERCENT%KEY', + 'value' => 'percent-secret', + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + ]); + + $component = Livewire::test(All::class, ['resource' => $application]) + ->set('search', 'api_key'); + + expect($component->instance()->environmentVariables->pluck('key')->all()) + ->toBe(['API_KEY']); + + $component->set('search', '%KEY'); + + expect($component->instance()->environmentVariables->pluck('key')->all()) + ->toBe(['PERCENT%KEY']); +}); + it('filters preview environment variables by key case-insensitively', function () { $application = Application::factory()->create([ 'environment_id' => $this->environment->id, @@ -104,6 +142,26 @@ ->toBe(['API_TOKEN']); }); +it('does not show the empty production message when search only matches hardcoded variables', function () { + $service = Service::factory()->create([ + 'environment_id' => $this->environment->id, + 'docker_compose_raw' => <<<'YAML' +services: + app: + image: nginx + environment: + API_TOKEN: hardcoded-secret + DATABASE_URL: postgres://example +YAML, + ]); + + Livewire::test(All::class, ['resource' => $service]) + ->set('search', 'api') + ->assertSee('Production Environment Variables') + ->assertSee('API_TOKEN') + ->assertDontSee('No environment variables found.'); +}); + it('keeps developer view unfiltered after searching', function () { $application = Application::factory()->create([ 'environment_id' => $this->environment->id, @@ -161,3 +219,33 @@ ->toContain('API_KEY') ->toContain('DATABASE_URL'); }); + +it('hides the preview section when search filters out all preview variables', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + ]); + + EnvironmentVariable::create([ + 'key' => 'API_KEY', + 'value' => 'secret', + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + ]); + + $application->environment_variables_preview()->where('key', 'API_KEY')->delete(); + + EnvironmentVariable::create([ + 'key' => 'PREVIEW_TOKEN', + 'value' => 'preview-secret', + 'is_preview' => true, + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + ]); + + Livewire::test(All::class, ['resource' => $application]) + ->set('search', 'api') + ->assertSee('Production Environment Variables') + ->assertSee('API_KEY') + ->assertDontSee('Preview Deployments Environment Variables') + ->assertDontSee('PREVIEW_TOKEN'); +});