feat: implement formatContainerStatus helper for human-readable status formatting and add unit tests

This commit is contained in:
Andras Bacsai 2025-11-21 09:12:56 +01:00
parent 840d25a729
commit 01609e7f8b
4 changed files with 247 additions and 72 deletions

View file

@ -3153,3 +3153,46 @@ function generateDockerComposeServiceName(mixed $services, int $pullRequestId =
return $collection;
}
/**
* Transform colon-delimited status format to human-readable parentheses format.
*
* Handles Docker container status formats with optional health check status and exclusion modifiers.
*
* Examples:
* - running:healthy Running (healthy)
* - running:unhealthy:excluded Running (unhealthy, excluded)
* - exited:excluded Exited (excluded)
* - Proxy:running Proxy:running (preserved as-is for headline formatting)
* - running Running
*
* @param string $status The status string to format
* @return string The formatted status string
*/
function formatContainerStatus(string $status): string
{
// Preserve Proxy statuses as-is (they follow different format)
if (str($status)->startsWith('Proxy')) {
return str($status)->headline()->value();
}
// Check for :excluded suffix
$isExcluded = str($status)->endsWith(':excluded');
$parts = explode(':', $status);
if ($isExcluded) {
if (count($parts) === 3) {
// Has health status: running:unhealthy:excluded → Running (unhealthy, excluded)
return str($parts[0])->headline().' ('.$parts[1].', excluded)';
} else {
// No health status: exited:excluded → Exited (excluded)
return str($parts[0])->headline().' (excluded)';
}
} elseif (count($parts) >= 2) {
// Regular colon format: running:healthy → Running (healthy)
return str($parts[0])->headline().' ('.$parts[1].')';
} else {
// Simple status: running → Running
return str($status)->headline()->value();
}
}

View file

@ -1,26 +1,5 @@
@php
// Transform colon format to human-readable format for UI display
// running:healthy → Running (healthy)
// running:unhealthy:excluded → Running (unhealthy, excluded)
// exited:excluded → Exited (excluded)
$isExcluded = str($complexStatus)->endsWith(':excluded');
$parts = explode(':', $complexStatus);
if ($isExcluded) {
if (count($parts) === 3) {
// Has health status: running:unhealthy:excluded → Running (unhealthy, excluded)
$displayStatus = str($parts[0])->headline() . ' (' . $parts[1] . ', excluded)';
} else {
// No health status: exited:excluded → Exited (excluded)
$displayStatus = str($parts[0])->headline() . ' (excluded)';
}
} elseif (count($parts) >= 2 && !str($complexStatus)->startsWith('Proxy')) {
// Regular colon format: running:healthy → Running (healthy)
$displayStatus = str($parts[0])->headline() . ' (' . $parts[1] . ')';
} else {
// No transformation needed (simple status or already in parentheses format)
$displayStatus = str($complexStatus)->headline();
}
$displayStatus = formatContainerStatus($complexStatus);
@endphp
@if (str($displayStatus)->lower()->contains('running'))
<x-status.running :status="$displayStatus" />

View file

@ -90,31 +90,7 @@ class="w-4 h-4 dark:text-warning text-coollabs"
@endcan
</span>
@endif
@php
// Transform colon format to human-readable format
// running:healthy → Running (healthy)
// running:unhealthy:excluded → Running (unhealthy, excluded)
$appStatus = $application->status;
$isExcluded = str($appStatus)->endsWith(':excluded');
$parts = explode(':', $appStatus);
if ($isExcluded) {
if (count($parts) === 3) {
// Has health status: running:unhealthy:excluded → Running (unhealthy, excluded)
$appStatus = str($parts[0])->headline() . ' (' . $parts[1] . ', excluded)';
} else {
// No health status: exited:excluded → Exited (excluded)
$appStatus = str($parts[0])->headline() . ' (excluded)';
}
} elseif (count($parts) >= 2 && !str($appStatus)->startsWith('Proxy')) {
// Regular colon format: running:healthy → Running (healthy)
$appStatus = str($parts[0])->headline() . ' (' . $parts[1] . ')';
} else {
// Simple status or already in parentheses format
$appStatus = str($appStatus)->headline();
}
@endphp
<div class="pt-2 text-xs">{{ $appStatus }}</div>
<div class="pt-2 text-xs">{{ formatContainerStatus($application->status) }}</div>
</div>
<div class="flex items-center px-4">
<a class="mx-4 text-xs font-bold hover:underline"
@ -163,31 +139,7 @@ class="w-4 h-4 dark:text-warning text-coollabs"
@if ($database->description)
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
@endif
@php
// Transform colon format to human-readable format
// running:healthy → Running (healthy)
// running:unhealthy:excluded → Running (unhealthy, excluded)
$dbStatus = $database->status;
$isExcluded = str($dbStatus)->endsWith(':excluded');
$parts = explode(':', $dbStatus);
if ($isExcluded) {
if (count($parts) === 3) {
// Has health status: running:unhealthy:excluded → Running (unhealthy, excluded)
$dbStatus = str($parts[0])->headline() . ' (' . $parts[1] . ', excluded)';
} else {
// No health status: exited:excluded → Exited (excluded)
$dbStatus = str($parts[0])->headline() . ' (excluded)';
}
} elseif (count($parts) >= 2 && !str($dbStatus)->startsWith('Proxy')) {
// Regular colon format: running:healthy → Running (healthy)
$dbStatus = str($parts[0])->headline() . ' (' . $parts[1] . ')';
} else {
// Simple status or already in parentheses format
$dbStatus = str($dbStatus)->headline();
}
@endphp
<div class="text-xs">{{ $dbStatus }}</div>
<div class="text-xs">{{ formatContainerStatus($database->status) }}</div>
</div>
<div class="flex items-center px-4">
@if ($database->isBackupSolutionAvailable() || $database->is_migrated)

View file

@ -0,0 +1,201 @@
<?php
describe('formatContainerStatus helper', function () {
describe('colon-delimited format parsing', function () {
it('transforms running:healthy to Running (healthy)', function () {
$result = formatContainerStatus('running:healthy');
expect($result)->toBe('Running (healthy)');
});
it('transforms running:unhealthy to Running (unhealthy)', function () {
$result = formatContainerStatus('running:unhealthy');
expect($result)->toBe('Running (unhealthy)');
});
it('transforms exited:0 to Exited (0)', function () {
$result = formatContainerStatus('exited:0');
expect($result)->toBe('Exited (0)');
});
it('transforms restarting:starting to Restarting (starting)', function () {
$result = formatContainerStatus('restarting:starting');
expect($result)->toBe('Restarting (starting)');
});
});
describe('excluded suffix handling', function () {
it('transforms running:unhealthy:excluded to Running (unhealthy, excluded)', function () {
$result = formatContainerStatus('running:unhealthy:excluded');
expect($result)->toBe('Running (unhealthy, excluded)');
});
it('transforms running:healthy:excluded to Running (healthy, excluded)', function () {
$result = formatContainerStatus('running:healthy:excluded');
expect($result)->toBe('Running (healthy, excluded)');
});
it('transforms exited:excluded to Exited (excluded)', function () {
$result = formatContainerStatus('exited:excluded');
expect($result)->toBe('Exited (excluded)');
});
it('transforms stopped:excluded to Stopped (excluded)', function () {
$result = formatContainerStatus('stopped:excluded');
expect($result)->toBe('Stopped (excluded)');
});
});
describe('simple status format', function () {
it('transforms running to Running', function () {
$result = formatContainerStatus('running');
expect($result)->toBe('Running');
});
it('transforms exited to Exited', function () {
$result = formatContainerStatus('exited');
expect($result)->toBe('Exited');
});
it('transforms stopped to Stopped', function () {
$result = formatContainerStatus('stopped');
expect($result)->toBe('Stopped');
});
it('transforms restarting to Restarting', function () {
$result = formatContainerStatus('restarting');
expect($result)->toBe('Restarting');
});
it('transforms degraded to Degraded', function () {
$result = formatContainerStatus('degraded');
expect($result)->toBe('Degraded');
});
});
describe('Proxy status preservation', function () {
it('preserves Proxy:running without parsing colons', function () {
$result = formatContainerStatus('Proxy:running');
expect($result)->toBe('Proxy:running');
});
it('preserves Proxy:exited without parsing colons', function () {
$result = formatContainerStatus('Proxy:exited');
expect($result)->toBe('Proxy:exited');
});
it('preserves Proxy:healthy without parsing colons', function () {
$result = formatContainerStatus('Proxy:healthy');
expect($result)->toBe('Proxy:healthy');
});
it('applies headline formatting to Proxy statuses', function () {
$result = formatContainerStatus('proxy:running');
expect($result)->toBe('Proxy (running)');
});
});
describe('headline transformation', function () {
it('applies headline to simple lowercase status', function () {
$result = formatContainerStatus('running');
expect($result)->toBe('Running');
});
it('applies headline to uppercase status', function () {
// headline() adds spaces between capital letters
$result = formatContainerStatus('RUNNING');
expect($result)->toBe('R U N N I N G');
});
it('applies headline to mixed case status', function () {
// headline() adds spaces between capital letters
$result = formatContainerStatus('RuNnInG');
expect($result)->toBe('Ru Nn In G');
});
it('applies headline to first part of colon format', function () {
// headline() adds spaces between capital letters
$result = formatContainerStatus('RUNNING:healthy');
expect($result)->toBe('R U N N I N G (healthy)');
});
});
describe('edge cases', function () {
it('handles empty string gracefully', function () {
$result = formatContainerStatus('');
expect($result)->toBe('');
});
it('handles multiple colons beyond expected format', function () {
// Only first two parts should be used (or three with :excluded)
$result = formatContainerStatus('running:healthy:extra:data');
expect($result)->toBe('Running (healthy)');
});
it('handles status with spaces in health part', function () {
$result = formatContainerStatus('running:health check failed');
expect($result)->toBe('Running (health check failed)');
});
it('handles single colon with empty second part', function () {
$result = formatContainerStatus('running:');
expect($result)->toBe('Running ()');
});
});
describe('real-world scenarios', function () {
it('handles typical running healthy container', function () {
$result = formatContainerStatus('running:healthy');
expect($result)->toBe('Running (healthy)');
});
it('handles degraded container with health issues', function () {
$result = formatContainerStatus('degraded:unhealthy');
expect($result)->toBe('Degraded (unhealthy)');
});
it('handles excluded unhealthy container', function () {
$result = formatContainerStatus('running:unhealthy:excluded');
expect($result)->toBe('Running (unhealthy, excluded)');
});
it('handles proxy container status', function () {
$result = formatContainerStatus('Proxy:running');
expect($result)->toBe('Proxy:running');
});
it('handles stopped container', function () {
$result = formatContainerStatus('stopped');
expect($result)->toBe('Stopped');
});
});
});