TenantAtlas/apps/platform/app/Filament/Widgets/Operations/OperationsWorkbenchStats.php
ahmido 815262399a feat: productize operations hub decision-first workbench (#389)
## Summary
- productize the operations hub decision-first workbench and related monitoring page surfaces
- add the operations workbench stats widget plus tenantless run viewer and admin scope updates
- extend monitoring, ops UX, and browser coverage for the new workbench behavior
- add Spec 328 artifacts under `specs/328-operations-hub-decision-first-workbench-productization`

## Testing
- not run as part of this handoff

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #389
2026-05-19 00:49:13 +00:00

159 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Widgets\Operations;
use App\Models\OperationRun;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Support\Enums\IconPosition;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use Illuminate\Database\Eloquent\Builder;
class OperationsWorkbenchStats extends StatsOverviewWidget
{
protected static bool $isLazy = false;
protected int|string|array $columnSpan = 'full';
protected int|array|null $columns = [
'@xl' => 4,
'!@lg' => 4,
];
protected ?string $pollingInterval = null;
/**
* @return array<Stat>
*/
protected function getStats(): array
{
$needsAttention = $this->summaryCount(
fn (Builder $query): Builder => $query->dashboardNeedsFollowUp(),
);
$activeOperations = $this->summaryCount(
fn (Builder $query): Builder => $query->active(),
);
$failedOrBlocked = $this->summaryCount(fn (Builder $query): Builder => $query
->where('status', OperationRunStatus::Completed->value)
->whereIn('outcome', [
OperationRunOutcome::Failed->value,
OperationRunOutcome::Blocked->value,
]));
$completedRecently = $this->summaryCount(fn (Builder $query): Builder => $query
->where('status', OperationRunStatus::Completed->value)
->where('completed_at', '>=', now()->subDay()));
return [
$this->workbenchStat(
key: 'needs-attention',
label: 'Needs attention',
value: $needsAttention,
description: 'Failed, blocked, partial, or stale OperationRuns in scope.',
color: $needsAttention > 0 ? 'warning' : 'gray',
descriptionIcon: 'heroicon-m-exclamation-triangle',
),
$this->workbenchStat(
key: 'active-operations',
label: 'Active operations',
value: $activeOperations,
description: 'Queued or running records with trusted progress only.',
color: 'info',
descriptionIcon: 'heroicon-m-bolt',
),
$this->workbenchStat(
key: 'failed-or-blocked',
label: 'Failed or blocked',
value: $failedOrBlocked,
description: 'Terminal execution records that need review before retrying.',
color: 'danger',
descriptionIcon: 'heroicon-m-no-symbol',
),
$this->workbenchStat(
key: 'completed-recently',
label: 'Completed recently',
value: $completedRecently,
description: 'Recent execution results, not environment or governance health.',
color: 'success',
descriptionIcon: 'heroicon-m-check-circle',
),
];
}
private function workbenchStat(
string $key,
string $label,
int $value,
string $description,
string $color,
string $descriptionIcon,
): Stat {
return Stat::make($label, (string) $value)
->description($description)
->descriptionIcon($descriptionIcon, IconPosition::Before)
->color($color)
->extraAttributes([
'data-testid' => 'operations-workbench-stat-'.$key,
'data-stat-key' => $key,
'data-stat-value' => (string) $value,
'data-stat-color' => $color,
'data-stat-label' => $label,
]);
}
private function summaryCount(callable $scope): int
{
$query = $this->scopedOperationRunQuery();
if (! $query instanceof Builder) {
return 0;
}
return (int) $scope($query)->count();
}
private function scopedOperationRunQuery(): ?Builder
{
$workspace = app(WorkspaceContext::class)->currentWorkspace(request());
$user = auth()->user();
if (! $workspace instanceof Workspace || ! $user instanceof User) {
return null;
}
$workspaceId = (int) $workspace->getKey();
$environmentFilter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
$allowedEnvironmentIds = app(ManagedEnvironmentAccessScopeResolver::class)
->allowedManagedEnvironmentIdsForWorkspace($user, $workspaceId);
return OperationRun::query()
->where('workspace_id', $workspaceId)
->when(
$allowedEnvironmentIds !== null,
function (Builder $query) use ($allowedEnvironmentIds): Builder {
return $query->where(function (Builder $query) use ($allowedEnvironmentIds): void {
$query->whereNull('managed_environment_id');
if ($allowedEnvironmentIds !== []) {
$query->orWhereIn(
'managed_environment_id',
array_values(array_unique(array_map('intval', $allowedEnvironmentIds))),
);
}
});
},
)
->when(
$environmentFilter instanceof WorkspaceHubEnvironmentFilter,
fn (Builder $query): Builder => $environmentFilter->applyToQuery($query),
);
}
}