TenantAtlas/apps/platform/app/Filament/Pages/EnvironmentDashboard.php
ahmido 9a564d6bf2 feat: environment dashboard operator guidance consolidation (spec 352) (#423)
Implemented the consolidated operator guidance panel for the environment dashboard. Updated EnvironmentDashboardSummaryBuilder to prioritize and select guidance based on the operator guidance contract. Added comprehensive unit, feature, and browser tests to verify the guidance selection logic and UI rendering.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #423
2026-06-04 12:56:02 +00:00

653 lines
25 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Filament\Pages\Governance\GovernanceInbox;
use App\Filament\Widgets\Dashboard\DashboardKpis;
use App\Filament\Widgets\Dashboard\EnvironmentDashboardContextChips;
use App\Filament\Widgets\Dashboard\EnvironmentDashboardOverview;
use App\Filament\Widgets\ManagedEnvironment\ManagedEnvironmentTriageArrivalContinuity;
use App\Models\ManagedEnvironment;
use App\Models\SupportRequest;
use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Auth\CapabilityResolver;
use App\Support\Auth\Capabilities;
use App\Support\EnvironmentDashboard\EnvironmentDashboardSummary;
use App\Support\EnvironmentDashboard\EnvironmentDashboardSummaryBuilder;
use App\Support\ManagedEnvironmentLinks;
use App\Support\ProductTelemetry\ProductTelemetryRecorder;
use App\Support\ProductTelemetry\ProductUsageEventCatalog;
use App\Support\Rbac\UiEnforcement;
use App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder;
use App\Support\SupportRequests\ExternalSupportDeskHandoffService;
use App\Support\SupportRequests\SupportRequestSubmissionService;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Facades\Filament;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\Dashboard;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Support\Enums\Width;
use Filament\Widgets\Widget;
use Filament\Widgets\WidgetConfiguration;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\HtmlString;
class EnvironmentDashboard extends Dashboard
{
protected Width|string|null $maxContentWidth = Width::Full;
/**
* @var list<string>
*/
public array $supportDiagnosticsAuditKeys = [];
private ?EnvironmentDashboardSummary $dashboardSummary = null;
public static function getNavigationLabel(): string
{
return __('localization.dashboard.environment_title');
}
public function getTitle(): string|Htmlable
{
$tenant = Filament::getTenant();
if (! $tenant instanceof ManagedEnvironment) {
return __('localization.dashboard.environment_title');
}
$summary = $this->dashboardSummary();
if (! $summary instanceof EnvironmentDashboardSummary) {
return (string) $tenant->name;
}
return new HtmlString(sprintf(
'<span class="inline-flex flex-wrap items-center gap-3" data-testid="tenant-dashboard-heading"><span>%s</span><span data-testid="tenant-dashboard-posture-pill" class="%s">%s</span></span>',
e((string) $tenant->name),
e($this->posturePillClasses((string) ($summary->operatorGuidance['tone'] ?? $summary->posture['tone'] ?? 'gray'))),
e((string) ($summary->operatorGuidance['status'] ?? $summary->posture['status'] ?? '')),
));
}
public function getSubheading(): string|Htmlable|null
{
return __('localization.dashboard.overview.page_subheading');
}
/**
* @param array<mixed> $parameters
*/
public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null, bool $shouldGuessMissingParameters = false): string
{
$resolvedTenant = $tenant instanceof ManagedEnvironment
? $tenant
: (($parameters['tenant'] ?? $parameters['environment'] ?? null) instanceof ManagedEnvironment
? ($parameters['tenant'] ?? $parameters['environment'])
: null);
if (! $resolvedTenant instanceof ManagedEnvironment) {
return url('/admin');
}
$query = array_diff_key($parameters, array_flip(['tenant', 'environment', 'workspace']));
return ManagedEnvironmentLinks::viewUrl($resolvedTenant, $query);
}
/**
* @return array<class-string<Widget> | WidgetConfiguration>
*/
protected function getHeaderWidgets(): array
{
return [
EnvironmentDashboardContextChips::class,
];
}
public function getHeaderWidgetsColumns(): int|array
{
return 1;
}
/**
* @return array<class-string<Widget> | WidgetConfiguration>
*/
public function getWidgets(): array
{
return [
ManagedEnvironmentTriageArrivalContinuity::class,
DashboardKpis::class,
EnvironmentDashboardOverview::class,
];
}
public function getColumns(): int|array
{
return ['default' => 1, 'xl' => 12];
}
/**
* @return array<Action>
*/
protected function getHeaderActions(): array
{
$moreActions = array_values(array_filter([
$this->secondaryHeaderAction(),
$this->requestSupportAction(),
$this->openSupportDiagnosticsAction(),
]));
if ($moreActions === []) {
return [];
}
return [
ActionGroup::make($moreActions)
->label(__('localization.dashboard.more_actions'))
->icon('heroicon-o-ellipsis-horizontal')
->color('gray'),
];
}
private function secondaryHeaderAction(): ?Action
{
$payload = $this->secondaryHeaderPayload();
if (! is_array($payload)) {
return null;
}
return $this->summaryHeaderAction(
name: 'reviewOutput',
payload: $payload,
color: 'gray',
icon: 'heroicon-o-document-duplicate',
);
}
/**
* @return array<string, mixed>|null
*/
private function primaryFollowUpHeaderPayload(): ?array
{
$summary = $this->dashboardSummary();
if (! $summary instanceof EnvironmentDashboardSummary) {
return null;
}
$payload = $summary->operatorGuidance;
return is_array($payload) && filled($payload['actionLabel'] ?? null)
? $payload
: null;
}
/**
* @return array<string, mixed>|null
*/
private function secondaryHeaderPayload(): ?array
{
$summary = $this->dashboardSummary();
if (! $summary instanceof EnvironmentDashboardSummary) {
return null;
}
$primaryPayload = $this->primaryFollowUpHeaderPayload();
foreach ($summary->operatorGuidance['secondaryActions'] ?? [] as $secondaryAction) {
if (! is_array($secondaryAction) || ! filled($secondaryAction['actionLabel'] ?? null)) {
continue;
}
return $secondaryAction;
}
foreach ($summary->readinessCards as $payload) {
if (! is_array($payload) || ! filled($payload['actionLabel'] ?? null)) {
continue;
}
if (! in_array($payload['key'] ?? null, ['customer_safe_output', 'current_review'], true)) {
continue;
}
if (
is_array($primaryPayload)
&& ($payload['actionLabel'] ?? null) === ($primaryPayload['actionLabel'] ?? null)
&& ($payload['actionUrl'] ?? null) === ($primaryPayload['actionUrl'] ?? null)
) {
continue;
}
return $payload;
}
return null;
}
private function governanceInboxHeaderAction(): ?Action
{
$tenant = Filament::getTenant();
$user = auth()->user();
if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User || ! $user->canAccessTenant($tenant)) {
return null;
}
return Action::make('primaryFollowUp')
->label(__('localization.dashboard.overview.action_open_governance_inbox'))
->icon('heroicon-o-inbox-stack')
->color('primary')
->url(GovernanceInbox::getUrl(panel: 'admin').'?'.http_build_query([
'environment_id' => (int) $tenant->getKey(),
]));
}
/**
* @param array<string, mixed> $payload
*/
private function summaryHeaderAction(string $name, array $payload, string $color, string $icon): ?Action
{
$label = $payload['actionLabel'] ?? null;
$url = $payload['actionUrl'] ?? null;
$helperText = $payload['helperText'] ?? null;
if (! is_string($label) || $label === '') {
return null;
}
if ((! is_string($url) || $url === '') && (! is_string($helperText) || $helperText === '')) {
return null;
}
$action = Action::make($name)
->label($label)
->icon($icon)
->color($color);
if (is_string($url) && $url !== '') {
$action->url($url);
} else {
$action->disabled();
}
if (is_string($helperText) && $helperText !== '') {
$action->tooltip($helperText);
}
return $action;
}
private function dashboardSummary(): ?EnvironmentDashboardSummary
{
if ($this->dashboardSummary instanceof EnvironmentDashboardSummary) {
return $this->dashboardSummary;
}
$tenant = Filament::getTenant();
$user = auth()->user();
if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
return null;
}
$this->dashboardSummary = app(EnvironmentDashboardSummaryBuilder::class)->build($tenant, $user);
return $this->dashboardSummary;
}
private function posturePillClasses(string $tone): string
{
return match ($tone) {
'success' => 'inline-flex items-center rounded-full border border-success-200 bg-success-50 px-3 py-1 text-sm font-medium text-success-700 shadow-sm dark:border-success-800 dark:bg-success-500/10 dark:text-success-300',
'danger' => 'inline-flex items-center rounded-full border border-danger-200 bg-danger-50 px-3 py-1 text-sm font-medium text-danger-700 shadow-sm dark:border-danger-800 dark:bg-danger-500/10 dark:text-danger-300',
'warning' => 'inline-flex items-center rounded-full border border-warning-200 bg-warning-50 px-3 py-1 text-sm font-medium text-warning-700 shadow-sm dark:border-warning-800 dark:bg-warning-500/10 dark:text-warning-300',
default => 'inline-flex items-center rounded-full border border-gray-200 bg-white px-3 py-1 text-sm font-medium text-gray-700 shadow-sm dark:border-white/10 dark:bg-white/5 dark:text-gray-300',
};
}
public function authorizeTenantSupportRequest(): void
{
$this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_REQUESTS_CREATE);
}
private function requestSupportAction(): Action
{
$action = Action::make('requestSupport')
->label(__('localization.dashboard.request_support'))
->icon('heroicon-o-paper-airplane')
->color('gray')
->slideOver()
->stickyModalHeader()
->modalHeading(__('localization.dashboard.support_request_heading'))
->modalDescription(__('localization.dashboard.support_request_description'))
->modalSubmitActionLabel(__('localization.dashboard.submit_request'))
->form([
Placeholder::make('included_context')
->label(__('localization.dashboard.included_context'))
->content(fn (): string => $this->tenantSupportRequestAttachmentSummary())
->columnSpanFull(),
Placeholder::make('latest_external_handoff')
->label(__('localization.dashboard.latest_external_handoff'))
->content(fn (): string => $this->tenantLatestSupportRequestHandoffSummary())
->columnSpanFull(),
Select::make('external_handoff_mode')
->label(__('localization.dashboard.external_handoff_mode'))
->options(fn (): array => $this->supportHandoffModeOptions())
->default(SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY)
->helperText(fn (): string => $this->supportDeskTargetAvailable()
? __('localization.dashboard.external_handoff_mode_helper_available')
: __('localization.dashboard.external_handoff_mode_helper_unavailable'))
->required()
->live()
->native(false),
Placeholder::make('handoff_mutation_scope')
->label(__('localization.dashboard.handoff_mutation_scope'))
->content(fn (Get $get): string => $this->externalHandoffMutationScope($get('external_handoff_mode')))
->columnSpanFull(),
TextInput::make('external_ticket_reference')
->label(__('localization.dashboard.external_ticket_reference'))
->helperText(__('localization.dashboard.external_ticket_reference_helper'))
->required(fn (Get $get): bool => $get('external_handoff_mode') === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET)
->visible(fn (Get $get): bool => $this->supportDeskTargetAvailable()
&& $get('external_handoff_mode') === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET),
TextInput::make('external_ticket_url')
->label(__('localization.dashboard.external_ticket_url'))
->helperText(__('localization.dashboard.external_ticket_url_helper'))
->url()
->visible(fn (Get $get): bool => $this->supportDeskTargetAvailable()
&& $get('external_handoff_mode') === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET)
->columnSpanFull(),
Select::make('severity')
->label(__('localization.dashboard.severity'))
->options(SupportRequest::severityOptions())
->default(SupportRequest::SEVERITY_NORMAL)
->required()
->native(false),
TextInput::make('summary')
->label(__('localization.dashboard.summary'))
->required()
->columnSpanFull(),
Textarea::make('reproduction_notes')
->label(__('localization.dashboard.reproduction_notes'))
->rows(4)
->columnSpanFull(),
TextInput::make('contact_name')
->label(__('localization.dashboard.contact_name'))
->default(fn (): ?string => $this->resolveDashboardActor()->name),
TextInput::make('contact_email')
->label(__('localization.dashboard.contact_email'))
->email()
->default(fn (): ?string => $this->resolveDashboardActor()->email),
])
->action(function (array $data): void {
$actor = $this->resolveDashboardActor();
$tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_REQUESTS_CREATE);
$supportRequest = app(SupportRequestSubmissionService::class)->submitForTenant($tenant, $actor, $data);
Notification::make()
->title(__('localization.dashboard.support_request_submitted'))
->body($this->supportRequestNotificationBody($supportRequest))
->when(
$supportRequest->hasExternalHandoffFailure(),
fn (Notification $notification): Notification => $notification->warning(),
fn (Notification $notification): Notification => $notification->success(),
)
->when(
$supportRequest->external_ticket_url !== null,
fn (Notification $notification): Notification => $notification->actions([
Action::make('openExternalTicket')
->label(__('localization.dashboard.open_external_ticket'))
->url((string) $supportRequest->external_ticket_url, shouldOpenInNewTab: true),
]),
)
->send();
});
return UiEnforcement::forAction($action)
->requireCapability(Capabilities::SUPPORT_REQUESTS_CREATE)
->apply();
}
private function openSupportDiagnosticsAction(): Action
{
$action = Action::make('openSupportDiagnostics')
->label(__('localization.dashboard.open_support_diagnostics'))
->icon('heroicon-o-lifebuoy')
->color('gray')
->modal()
->slideOver()
->stickyModalHeader()
->modalHeading(__('localization.dashboard.support_diagnostics'))
->modalDescription(__('localization.dashboard.support_diagnostics_description'))
->modalSubmitAction(false)
->modalCancelAction(fn (Action $action): Action => $action->label(__('localization.dashboard.close')))
->mountUsing(function (): void {
$this->auditTenantSupportDiagnosticsOpen();
})
->modalContent(fn (): View => view('filament.modals.support-diagnostic-bundle', [
'bundle' => $this->tenantSupportDiagnosticBundle(),
]));
return UiEnforcement::forAction($action)
->requireCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW)
->apply();
}
/**
* @return array<string, mixed>
*/
public function tenantSupportDiagnosticBundle(): array
{
$user = $this->resolveDashboardActor();
$tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW);
return app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant, $user);
}
private function auditTenantSupportDiagnosticsOpen(): void
{
$user = $this->resolveDashboardActor();
$tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW);
$this->recordSupportDiagnosticsOpened(
tenant: $tenant,
bundle: $this->tenantSupportDiagnosticBundle(),
user: $user,
);
}
/**
* @param array<string, mixed> $bundle
*/
private function recordSupportDiagnosticsOpened(ManagedEnvironment $tenant, array $bundle, User $user): void
{
$auditKey = 'tenant:'.$tenant->getKey();
if (in_array($auditKey, $this->supportDiagnosticsAuditKeys, true)) {
return;
}
app(WorkspaceAuditLogger::class)->logSupportDiagnosticsOpened(
tenant: $tenant,
contextType: 'tenant',
bundle: $bundle,
actor: $user,
);
app(ProductTelemetryRecorder::class)->record(
eventName: ProductUsageEventCatalog::SUPPORT_DIAGNOSTICS_OPENED,
workspaceId: (int) $tenant->workspace_id,
tenantId: (int) $tenant->getKey(),
userId: (int) $user->getKey(),
subjectType: 'tenant',
subjectId: (int) $tenant->getKey(),
metadata: [
'source_surface' => 'tenant_dashboard',
],
);
$this->supportDiagnosticsAuditKeys[] = $auditKey;
}
private function resolveDashboardActor(): User
{
$user = auth()->user();
if (! $user instanceof User) {
abort(404);
}
return $user;
}
private function resolveCurrentTenantForCapability(string $capability): ManagedEnvironment
{
$user = $this->resolveDashboardActor();
$tenant = Filament::getTenant();
if (! $tenant instanceof ManagedEnvironment) {
abort(404);
}
$resolver = app(CapabilityResolver::class);
if (! $resolver->isMember($user, $tenant)) {
abort(404);
}
if (! $resolver->can($user, $tenant, $capability)) {
abort(403);
}
return $tenant;
}
private function tenantSupportRequestAttachmentSummary(): string
{
$tenant = Filament::getTenant();
$user = auth()->user();
if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
return 'Only canonical redacted tenant context will be attached.';
}
$resolver = app(CapabilityResolver::class);
if (! $resolver->isMember($user, $tenant)) {
return 'Only canonical redacted tenant context will be attached.';
}
return $resolver->can($user, $tenant, Capabilities::SUPPORT_DIAGNOSTICS_VIEW)
? 'A redacted diagnostic snapshot and the canonical tenant context will be attached.'
: 'Only the canonical redacted tenant context will be attached because you cannot view support diagnostics.';
}
private function tenantLatestSupportRequestHandoffSummary(): string
{
$tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_REQUESTS_CREATE);
$user = $this->resolveDashboardActor();
$summary = app(SupportRequestSubmissionService::class)->latestTenantHandoffSummary($tenant, $user);
return $this->formatLatestHandoffSummary($summary);
}
/**
* @return array<string, string>
*/
private function supportHandoffModeOptions(): array
{
if (! $this->supportDeskTargetAvailable()) {
return [
SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY => __('localization.dashboard.handoff_mode_internal_only'),
];
}
return [
SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY => __('localization.dashboard.handoff_mode_internal_only'),
SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET => __('localization.dashboard.handoff_mode_create_external_ticket'),
SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET => __('localization.dashboard.handoff_mode_link_existing_ticket'),
];
}
private function supportDeskTargetAvailable(): bool
{
return app(ExternalSupportDeskHandoffService::class)->targetIsConfigured();
}
private function externalHandoffMutationScope(mixed $mode): string
{
return match ($mode) {
SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET => __('localization.dashboard.mutation_scope_external_create'),
SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET => __('localization.dashboard.mutation_scope_external_link'),
default => __('localization.dashboard.mutation_scope_internal_only'),
};
}
/**
* @param array<string, mixed>|null $summary
*/
private function formatLatestHandoffSummary(?array $summary): string
{
if ($summary === null) {
return __('localization.dashboard.latest_external_handoff_none');
}
$internalReference = (string) $summary['internal_reference'];
if (($summary['has_failure'] ?? false) === true) {
return __('localization.dashboard.latest_external_handoff_failed', [
'reference' => $internalReference,
'failure' => (string) $summary['external_handoff_failure_summary'],
]);
}
if (($summary['has_external_link'] ?? false) === true) {
return __('localization.dashboard.latest_external_handoff_linked', [
'reference' => $internalReference,
'external' => (string) $summary['external_ticket_reference'],
]);
}
return __('localization.dashboard.latest_external_handoff_internal_only', [
'reference' => $internalReference,
]);
}
private function supportRequestNotificationBody(SupportRequest $supportRequest): string
{
return match ($supportRequest->externalHandoffOutcome()) {
SupportRequest::HANDOFF_OUTCOME_EXTERNAL_TICKET_CREATED => __('localization.dashboard.support_request_submitted_created', [
'reference' => $supportRequest->internal_reference,
'external' => $supportRequest->external_ticket_reference,
]),
SupportRequest::HANDOFF_OUTCOME_EXTERNAL_TICKET_LINKED => __('localization.dashboard.support_request_submitted_linked', [
'reference' => $supportRequest->internal_reference,
'external' => $supportRequest->external_ticket_reference,
]),
SupportRequest::HANDOFF_OUTCOME_EXTERNAL_HANDOFF_FAILED => __('localization.dashboard.support_request_submitted_failed', [
'reference' => $supportRequest->internal_reference,
'failure' => $supportRequest->external_handoff_failure_summary,
]),
default => __('localization.dashboard.support_request_submitted_internal_only', [
'reference' => $supportRequest->internal_reference,
]),
};
}
}