mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 02:27:57 +00:00
feat(service): warn when required environment variables are missing
Surface unset required service env vars in the configuration checker popup and sidebar, refresh on env updates, and keep the env table horizontally scrollable with correct managed/hardcoded pagination order.
This commit is contained in:
@@ -21,6 +21,10 @@ class ConfigurationChecker extends Component
|
||||
|
||||
public array $configurationDiff = [];
|
||||
|
||||
public int $missingRequiredEnvironmentVariableCount = 0;
|
||||
|
||||
public array $missingRequiredEnvironmentVariableNames = [];
|
||||
|
||||
public Application|Service|StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource;
|
||||
|
||||
public function getListeners(): array
|
||||
@@ -30,6 +34,7 @@ class ConfigurationChecker extends Component
|
||||
return [
|
||||
"echo-private:team.{$teamId},ApplicationConfigurationChanged" => 'configurationChanged',
|
||||
'configurationChanged' => 'configurationChanged',
|
||||
'envsUpdated' => 'configurationChanged',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -83,6 +88,12 @@ class ConfigurationChecker extends Component
|
||||
{
|
||||
$this->resource->refresh();
|
||||
|
||||
if ($this->resource instanceof Service) {
|
||||
$missingVariables = $this->resource->missingRequiredEnvironmentVariables();
|
||||
$this->missingRequiredEnvironmentVariableCount = $missingVariables->count();
|
||||
$this->missingRequiredEnvironmentVariableNames = $missingVariables->pluck('key')->all();
|
||||
}
|
||||
|
||||
if ($this->resource instanceof Application) {
|
||||
$diff = $this->resource->pendingDeploymentConfigurationDiff();
|
||||
$this->isConfigurationChanged = $diff->isChanged();
|
||||
|
||||
@@ -456,7 +456,7 @@ class All extends Component
|
||||
|
||||
/**
|
||||
* Ordered segments used for pagination: production managed → production hardcoded
|
||||
* → preview managed → preview hardcoded (matching the historical table order).
|
||||
* → preview managed → preview hardcoded.
|
||||
*
|
||||
* @return list<array{kind: string, is_preview: bool, count: int}>
|
||||
*/
|
||||
@@ -469,6 +469,12 @@ class All extends Component
|
||||
$segments = [];
|
||||
|
||||
if ($includeProduction) {
|
||||
$segments[] = [
|
||||
'kind' => 'managed',
|
||||
'is_preview' => false,
|
||||
'count' => $this->countManagedEnvironmentVariables(false),
|
||||
];
|
||||
|
||||
if ($this->includesHardcodedVariables() && $this->showsHardcodedEnvironmentVariables()) {
|
||||
$segments[] = [
|
||||
'kind' => 'hardcoded',
|
||||
@@ -477,14 +483,15 @@ class All extends Component
|
||||
];
|
||||
}
|
||||
|
||||
$segments[] = [
|
||||
'kind' => 'managed',
|
||||
'is_preview' => false,
|
||||
'count' => $this->countManagedEnvironmentVariables(false),
|
||||
];
|
||||
}
|
||||
|
||||
if ($includePreview) {
|
||||
$segments[] = [
|
||||
'kind' => 'managed',
|
||||
'is_preview' => true,
|
||||
'count' => $this->countManagedEnvironmentVariables(true),
|
||||
];
|
||||
|
||||
if ($this->includesHardcodedVariables() && $this->showsHardcodedEnvironmentVariables()) {
|
||||
$segments[] = [
|
||||
'kind' => 'hardcoded',
|
||||
@@ -493,11 +500,6 @@ class All extends Component
|
||||
];
|
||||
}
|
||||
|
||||
$segments[] = [
|
||||
'kind' => 'managed',
|
||||
'is_preview' => true,
|
||||
'count' => $this->countManagedEnvironmentVariables(true),
|
||||
];
|
||||
}
|
||||
|
||||
return $segments;
|
||||
@@ -514,9 +516,13 @@ class All extends Component
|
||||
$query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
$query->orderByRaw("CASE WHEN key LIKE 'SERVICE_FQDN%' OR key LIKE 'SERVICE_URL%' OR key LIKE 'SERVICE_NAME%' THEN 0 ELSE 1 END");
|
||||
$missingRequiredIds = $this->missingRequiredEnvironmentVariableIds($isPreview);
|
||||
if ($missingRequiredIds !== []) {
|
||||
$placeholders = implode(', ', array_fill(0, count($missingRequiredIds), '?'));
|
||||
$query->orderByRaw("CASE WHEN id IN ({$placeholders}) THEN 0 ELSE 1 END", $missingRequiredIds);
|
||||
}
|
||||
|
||||
$query->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END");
|
||||
$query->orderByRaw("CASE WHEN key LIKE 'SERVICE_FQDN%' OR key LIKE 'SERVICE_URL%' OR key LIKE 'SERVICE_NAME%' THEN 0 ELSE 1 END");
|
||||
|
||||
if ($this->searchTerm() !== '') {
|
||||
$escapedSearch = addcslashes(Str::lower($this->searchTerm()), '%_\\');
|
||||
@@ -551,6 +557,22 @@ class All extends Component
|
||||
return $query;
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
private function missingRequiredEnvironmentVariableIds(bool $isPreview): array
|
||||
{
|
||||
return EnvironmentVariable::query()
|
||||
->where('resourceable_type', $this->resource->getMorphClass())
|
||||
->where('resourceable_id', $this->resource->id)
|
||||
->where('is_preview', $isPreview)
|
||||
->where('is_required', true)
|
||||
->get()
|
||||
->filter(fn (EnvironmentVariable $environmentVariable): bool => $environmentVariable->is_really_required)
|
||||
->pluck('id')
|
||||
->map(fn (int|string $id): int => (int) $id)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function countManagedEnvironmentVariables(bool $isPreview): int
|
||||
{
|
||||
if ($isPreview && ! $this->supportsPreviewEnvironmentVariables()) {
|
||||
|
||||
+10
-10
@@ -1640,16 +1640,16 @@ class Service extends BaseModel
|
||||
protected function isDeployable(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$envs = $this->environment_variables()->where('is_required', true)->get();
|
||||
foreach ($envs as $env) {
|
||||
if ($env->is_really_required) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
get: fn (): bool => $this->missingRequiredEnvironmentVariables()->isEmpty()
|
||||
);
|
||||
}
|
||||
|
||||
public function missingRequiredEnvironmentVariables(): Collection
|
||||
{
|
||||
return $this->environment_variables()
|
||||
->where('is_required', true)
|
||||
->get()
|
||||
->filter(fn (EnvironmentVariable $environmentVariable): bool => $environmentVariable->is_really_required)
|
||||
->values();
|
||||
}
|
||||
}
|
||||
|
||||
+12
-36
@@ -1931,6 +1931,16 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 6rem 4rem 4.5rem 4.8rem 4.2rem 3rem;
|
||||
}
|
||||
|
||||
.environment-table-scroll {
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.environment-table-scroll .env-table-grid {
|
||||
min-width: 53rem;
|
||||
}
|
||||
|
||||
.env-table-grid.env-table-grid-no-type {
|
||||
grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 4rem 4.5rem 4.8rem 4.2rem 3rem;
|
||||
}
|
||||
@@ -1940,21 +1950,8 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
grid-template-columns: minmax(0, 1.6fr) 6rem minmax(0, 1fr) 4.5rem 3rem;
|
||||
}
|
||||
|
||||
/* Env vars: collapse flag columns on tablet, card layout on phone */
|
||||
/* Shared env vars collapse secondary columns on tablet and use cards on phone. */
|
||||
@media (max-width: 1100px) {
|
||||
.env-table-grid {
|
||||
grid-template-columns: minmax(0, 1.4fr) 4.8rem 6rem 3rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Hide Literal / Multiline / Buildtime / Runtime (4–7 of 8) */
|
||||
.env-table-grid > :nth-child(4),
|
||||
.env-table-grid > :nth-child(5),
|
||||
.env-table-grid > :nth-child(6),
|
||||
.env-table-grid > :nth-child(7) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.env-table-grid-shared {
|
||||
grid-template-columns: minmax(0, 1.4fr) 6rem minmax(0, 1fr) 3rem;
|
||||
gap: 0.75rem;
|
||||
@@ -1967,10 +1964,6 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.env-table-grid {
|
||||
grid-template-columns: minmax(0, 1fr) 4.8rem 6rem 3rem;
|
||||
}
|
||||
|
||||
.env-table-grid-shared {
|
||||
grid-template-columns: minmax(0, 1fr) 6rem 3rem;
|
||||
}
|
||||
@@ -1981,12 +1974,10 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.data-table-header.env-table-grid,
|
||||
.data-table-header.env-table-grid-shared {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.data-table-row.env-table-grid,
|
||||
.data-table-row.env-table-grid-shared {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
@@ -2000,39 +1991,30 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
}
|
||||
|
||||
/* Name */
|
||||
.data-table-row.env-table-grid > :nth-child(1),
|
||||
.data-table-row.env-table-grid-shared > :nth-child(1) {
|
||||
grid-area: name;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.data-table-row.env-table-grid > :nth-child(1) .env-key-label,
|
||||
.data-table-row.env-table-grid-shared > :nth-child(1) .env-key-label {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Managed and Type desktop columns */
|
||||
.data-table-row.env-table-grid > :nth-child(2),
|
||||
.data-table-row.env-table-grid > :nth-child(3),
|
||||
/* Type column */
|
||||
.data-table-row.env-table-grid-shared > :nth-child(2) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Comment / flags already hidden; keep meta area for optional second line */
|
||||
.data-table-row.env-table-grid > :nth-child(4),
|
||||
.data-table-row.env-table-grid > :nth-child(5),
|
||||
.data-table-row.env-table-grid > :nth-child(6),
|
||||
.data-table-row.env-table-grid > :nth-child(7),
|
||||
.data-table-row.env-table-grid-shared > :nth-child(3),
|
||||
.data-table-row.env-table-grid-shared > :nth-child(4) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.data-table-row.env-table-grid > :nth-child(8),
|
||||
.data-table-row.env-table-grid-shared > :nth-child(5) {
|
||||
grid-area: actions;
|
||||
align-self: center;
|
||||
@@ -2045,12 +2027,6 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.env-type-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.domains-table-grid {
|
||||
grid-template-columns: minmax(0, 1.8fr) 8.5rem minmax(7rem, 0.9fr) 6.5rem;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,11 @@
|
||||
x-transition:leave-start="translate-y-0 opacity-100"
|
||||
x-transition:leave-end="translate-y-3 opacity-0"
|
||||
class="fixed bottom-4 right-4 z-999"
|
||||
:class="compact ? 'w-auto max-w-[calc(100%-2rem)]' : 'w-[calc(100%-2rem)] max-w-sm'">
|
||||
:class="iconOnly
|
||||
? 'w-auto max-w-[calc(100%-2rem)]'
|
||||
: (compact
|
||||
? 'w-[calc(100%-2rem)] sm:w-auto sm:max-w-[calc(100%-2rem)]'
|
||||
: 'w-[calc(100%-2rem)] max-w-sm')">
|
||||
<div class="relative flex items-start gap-2.5 rounded-lg p-3 pr-10"
|
||||
:class="compact ? (iconOnly ? 'cursor-pointer p-2! pr-2!' : 'cursor-pointer') : ''" @click="restore()"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
$configurationItems = collect([
|
||||
['label' => 'General', 'route' => 'project.service.configuration', 'icon' => 'settings'],
|
||||
['label' => 'Domains', 'route' => 'project.service.domains', 'icon' => 'globe'],
|
||||
['label' => 'Environment Variables', 'route' => 'project.service.environment-variables', 'icon' => 'variables'],
|
||||
['label' => 'Environment Variables', 'route' => 'project.service.environment-variables', 'icon' => 'variables', 'hasWarning' => ! $service->isDeployable],
|
||||
['label' => 'Persistent Storage', 'route' => 'project.service.storages', 'icon' => 'storages'],
|
||||
['label' => 'Backups', 'route' => 'project.service.volume-backups.index', 'icon' => 'database'],
|
||||
['label' => 'Runtime', 'route' => 'project.service.logs', 'icon' => 'unordered-list', 'navigate' => false],
|
||||
@@ -56,9 +56,11 @@
|
||||
href="{{ route($menuItem['route'], $serviceRouteParameters) }}">
|
||||
<x-reicon :name="$menuItem['icon']" class="menu-item-icon" />
|
||||
<span class="menu-item-label">{{ $menuItem['label'] }}</span>
|
||||
@if ($menuItem['hasWarning'] ?? false)
|
||||
<span class="ml-auto size-2 shrink-0 rounded-full bg-error" title="Required environment variables missing"></span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
$configurationItems = collect([
|
||||
['label' => 'General', 'route' => 'project.service.configuration', 'icon' => 'settings'],
|
||||
['label' => 'Domains', 'route' => 'project.service.domains', 'icon' => 'globe'],
|
||||
['label' => 'Environment Variables', 'route' => 'project.service.environment-variables', 'icon' => 'variables'],
|
||||
['label' => 'Environment Variables', 'route' => 'project.service.environment-variables', 'icon' => 'variables', 'hasWarning' => ! $service->isDeployable],
|
||||
['label' => 'Persistent Storage', 'route' => 'project.service.storages', 'icon' => 'storages'],
|
||||
['label' => 'Backups', 'route' => 'project.service.volume-backups.index', 'icon' => 'database'],
|
||||
['label' => 'Runtime', 'route' => 'project.service.logs', 'icon' => 'unordered-list', 'navigate' => false],
|
||||
@@ -71,6 +71,9 @@
|
||||
href="{{ route($menuItem['route'], $serviceRouteParameters) }}">
|
||||
<x-reicon :name="$menuItem['icon']" class="menu-item-icon" />
|
||||
<span class="menu-item-label">{{ $menuItem['label'] }}</span>
|
||||
@if ($menuItem['hasWarning'] ?? false)
|
||||
<span class="ml-auto size-2 shrink-0 rounded-full bg-error" title="Required environment variables missing"></span>
|
||||
@endif
|
||||
</a>
|
||||
@if ($menuItem['active'] && $menuItem['route'] === 'project.service.storages' && $storageSections->isNotEmpty())
|
||||
<div class="nav-children hidden flex-col gap-0.5 py-1 xl:flex"
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
<div>
|
||||
@if ($resource instanceof \App\Models\Service && $missingRequiredEnvironmentVariableCount > 0)
|
||||
@php
|
||||
$environmentVariablesUrl = route('project.service.environment-variables', [
|
||||
'project_uuid' => $resource->environment->project->uuid,
|
||||
'environment_uuid' => $resource->environment->uuid,
|
||||
'service_uuid' => $resource->uuid,
|
||||
]);
|
||||
@endphp
|
||||
<x-popup-small :compact-after="5000" compact-storage-key="required-environment-variables:{{ $resource->uuid }}">
|
||||
<x-slot:title>
|
||||
{{ $missingRequiredEnvironmentVariableCount === 1 ? 'Required environment variable missing' : 'Required environment variables missing' }}
|
||||
</x-slot:title>
|
||||
<x-slot:icon>
|
||||
<x-reicon name="alert-triangle" class="size-4" />
|
||||
</x-slot:icon>
|
||||
<x-slot:description>
|
||||
<span>
|
||||
{{ implode(', ', $missingRequiredEnvironmentVariableNames) }} must be set before this service can be deployed.
|
||||
<a href="{{ $environmentVariablesUrl }}" {{ wireNavigate() }}
|
||||
class="ml-0.5 inline-flex items-center gap-0.5 font-semibold text-coollabs transition-colors hover:text-coollabs-100 dark:text-warning dark:hover:text-warning/80">
|
||||
Open environment variables
|
||||
<x-reicon name="arrow-right" class="size-2.5" />
|
||||
</a>
|
||||
</span>
|
||||
</x-slot:description>
|
||||
</x-popup-small>
|
||||
@endif
|
||||
|
||||
@if ($isConfigurationChanged && !is_null($resource->config_hash) && !$resource->isExited())
|
||||
@php
|
||||
$compactStoragePrefix = "configuration-warning:{$resource->uuid}:";
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
description="No variables match your search." />
|
||||
@elseif ($totalRows > 0)
|
||||
<div class="data-table w-full">
|
||||
<div class="relative">
|
||||
<div class="environment-table-scroll relative">
|
||||
<div class="transition-all"
|
||||
wire:loading.class="pointer-events-none opacity-40 blur-[2px]"
|
||||
wire:target="toggleVariableFilter,toggleServiceFilter,clearFilters,setEnvironmentFilter,setTableSort,setEnvironmentVariablePage,previousEnvironmentVariablePage,nextEnvironmentVariablePage">
|
||||
|
||||
@@ -211,6 +211,48 @@ it('searches service environment variables without requiring preview variables',
|
||||
->and($component->instance()->showPreview)->toBeFalse();
|
||||
});
|
||||
|
||||
it('pins required service environment variables only while their values are missing', function () {
|
||||
$service = Service::factory()->create([
|
||||
'environment_id' => $this->environment->id,
|
||||
'docker_compose_raw' => <<<'YAML'
|
||||
services:
|
||||
app:
|
||||
image: nginx
|
||||
environment:
|
||||
HARDCODED_FIRST: configured
|
||||
YAML,
|
||||
]);
|
||||
|
||||
EnvironmentVariable::create([
|
||||
'key' => 'OPTIONAL_FIRST',
|
||||
'value' => 'configured',
|
||||
'order' => 1,
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $service->id,
|
||||
]);
|
||||
|
||||
$required = EnvironmentVariable::create([
|
||||
'key' => 'REQUIRED_SECOND',
|
||||
'value' => '',
|
||||
'order' => 2,
|
||||
'is_required' => true,
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $service->id,
|
||||
]);
|
||||
|
||||
$component = Livewire::test(All::class, ['resource' => $service])
|
||||
->call('loadEnvironmentVariables');
|
||||
|
||||
expect($component->instance()->environmentVariablePageRows->pluck('environmentVariable.key')->all())
|
||||
->toBe(['REQUIRED_SECOND', 'OPTIONAL_FIRST', 'HARDCODED_FIRST']);
|
||||
|
||||
$required->update(['value' => 'configured']);
|
||||
$component->call('$refresh');
|
||||
|
||||
expect($component->instance()->environmentVariablePageRows->pluck('environmentVariable.key')->all())
|
||||
->toBe(['OPTIONAL_FIRST', 'REQUIRED_SECOND', 'HARDCODED_FIRST']);
|
||||
});
|
||||
|
||||
it('does not show the empty production message when search only matches hardcoded variables', function () {
|
||||
$service = Service::factory()->create([
|
||||
'environment_id' => $this->environment->id,
|
||||
|
||||
@@ -80,6 +80,19 @@ test('resource environment variables table has a Managed column and no name-cell
|
||||
expect($show)->toContain('<x-helper :helper="e($comment)" />');
|
||||
});
|
||||
|
||||
test('resource environment variables table remains horizontally scrollable on mobile', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/project/shared/environment-variable/all.blade.php'));
|
||||
$css = file_get_contents(resource_path('css/app.css'));
|
||||
|
||||
expect($view)
|
||||
->toContain('environment-table-scroll')
|
||||
->and($css)
|
||||
->toContain(".environment-table-scroll {\n overflow-x: auto;")
|
||||
->toContain(".environment-table-scroll .env-table-grid {\n min-width: 53rem;")
|
||||
->not->toContain('.data-table-row.env-table-grid > :nth-child')
|
||||
->not->toContain(".env-type-desktop {\n display: none");
|
||||
});
|
||||
|
||||
test('shared environment variables table still omits Managed column', function () {
|
||||
$editor = file_get_contents(resource_path('views/components/shared-variables/editor.blade.php'));
|
||||
$show = file_get_contents(resource_path('views/livewire/project/shared/environment-variable/show.blade.php'));
|
||||
@@ -104,7 +117,20 @@ test('managed environment variables are ordered first', function () {
|
||||
|
||||
expect($component)
|
||||
->toContain("CASE WHEN key LIKE 'SERVICE_FQDN%'")
|
||||
->toMatch("/'kind' => 'hardcoded',[\\s\\S]+?'kind' => 'managed'/");
|
||||
->toMatch("/'kind' => 'managed',[\\s\\S]+?'kind' => 'hardcoded'/");
|
||||
});
|
||||
|
||||
test('missing required environment variables are ordered before generated service variables', function () {
|
||||
$component = file_get_contents(app_path('Livewire/Project/Shared/EnvironmentVariable/All.php'));
|
||||
|
||||
$requiredOrder = strpos($component, '$this->missingRequiredEnvironmentVariableIds($isPreview)', strpos($component, 'private function managedEnvironmentVariablesQuery'));
|
||||
$generatedOrder = strpos($component, "CASE WHEN key LIKE 'SERVICE_FQDN%'", strpos($component, 'private function managedEnvironmentVariablesQuery'));
|
||||
|
||||
expect($requiredOrder)
|
||||
->not->toBeFalse()
|
||||
->toBeLessThan($generatedOrder)
|
||||
->and($component)
|
||||
->toContain('->filter(fn (EnvironmentVariable $environmentVariable): bool => $environmentVariable->is_really_required)');
|
||||
});
|
||||
|
||||
test('environment variable toolbar does not use blade directives inside component attributes', function () {
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Environment;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
@@ -93,7 +94,35 @@ it('supports timed compact popup notifications', function () {
|
||||
->toContain('@click="restore()"')
|
||||
->toContain('@click.stop="minimizeToIcon()"')
|
||||
->toContain('x-show="!iconOnly"')
|
||||
->toContain('x-show="!compact"');
|
||||
->toContain('x-show="!compact"')
|
||||
->toContain("'w-[calc(100%-2rem)] sm:w-auto sm:max-w-[calc(100%-2rem)]'");
|
||||
});
|
||||
|
||||
it('warns when a service has missing required environment variables', function () {
|
||||
$service = Service::factory()->create(['environment_id' => $this->environment->id]);
|
||||
$service->environment_variables()->create([
|
||||
'key' => 'PLUNK_API_KEY',
|
||||
'value' => '',
|
||||
'is_required' => true,
|
||||
]);
|
||||
|
||||
Livewire::test(ConfigurationChecker::class, ['resource' => $service])
|
||||
->assertSet('missingRequiredEnvironmentVariableCount', 1)
|
||||
->assertSee('Required environment variable missing')
|
||||
->assertSee('PLUNK_API_KEY')
|
||||
->assertSee('Open environment variables');
|
||||
});
|
||||
|
||||
it('marks the service environment variables menu when required values are missing', function () {
|
||||
$configuration = file_get_contents(resource_path('views/livewire/project/service/configuration.blade.php'));
|
||||
$sidebar = file_get_contents(resource_path('views/components/service/configuration-sidebar.blade.php'));
|
||||
|
||||
expect($configuration)
|
||||
->toContain("'hasWarning' => ! \$service->isDeployable")
|
||||
->toContain('title="Required environment variables missing"')
|
||||
->and($sidebar)
|
||||
->toContain("'hasWarning' => ! \$service->isDeployable")
|
||||
->toContain('title="Required environment variables missing"');
|
||||
});
|
||||
|
||||
it('refreshes configuration changes when the event is received', function () {
|
||||
|
||||
Reference in New Issue
Block a user