, * dominant_dimension_keys: list, * non_ok_dimension_count: int, * next_link: array{label: string, url: string} * }> */ public function summaries(SystemConsoleWindow|string|null $window = null, ?CarbonImmutable $now = null): Collection { $resolvedWindow = $this->resolveWindow($window); $now ??= CarbonImmutable::now(); $startAt = $resolvedWindow->startAt($now); $workspaces = Workspace::query() ->whereNull('archived_at') ->orderBy('name') ->orderBy('id') ->get(['id', 'name']); if ($workspaces->isEmpty()) { return collect(); } $workspaceIds = $workspaces ->pluck('id') ->map(static fn (mixed $workspaceId): int => (int) $workspaceId) ->all(); $activeTenants = Tenant::query() ->whereIn('workspace_id', $workspaceIds) ->whereNull('deleted_at') ->where('status', '!=', Tenant::STATUS_ARCHIVED) ->orderBy('name') ->orderBy('id') ->get(['id', 'workspace_id', 'external_id', 'name', 'status']); $tenantsByWorkspace = $activeTenants->groupBy(static fn (Tenant $tenant): int => (int) $tenant->workspace_id); $latestOnboardingSessions = TenantOnboardingSession::query() ->whereIn('workspace_id', $workspaceIds) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ->orderByDesc('updated_at') ->orderByDesc('id') ->get(['id', 'workspace_id', 'tenant_id', 'lifecycle_state', 'updated_at', 'created_at']) ->groupBy(static fn (TenantOnboardingSession $session): int => (int) $session->workspace_id) ->map(static fn (Collection $sessions): ?TenantOnboardingSession => $sessions->first()); $providerConnectionsByWorkspace = ProviderConnection::query() ->whereIn('workspace_id', $workspaceIds) ->where('is_default', true) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ->orderByDesc('is_enabled') ->orderBy('id') ->get([ 'id', 'workspace_id', 'tenant_id', 'is_enabled', 'consent_status', 'verification_status', ]) ->groupBy(static fn (ProviderConnection $connection): int => (int) $connection->workspace_id); $recentRunCounts = $this->groupedCounts( OperationRun::query() ->whereIn('workspace_id', $workspaceIds) ->where('created_at', '>=', $startAt) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $recentFailedRunCounts = $this->groupedCounts( OperationRun::query() ->whereIn('workspace_id', $workspaceIds) ->where('created_at', '>=', $startAt) ->where('status', OperationRunStatus::Completed->value) ->where('outcome', OperationRunOutcome::Failed->value) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $recentStuckRunCounts = $this->groupedCounts( $this->stuckRunClassifier->apply( OperationRun::query() ->whereIn('workspace_id', $workspaceIds) ->where('created_at', '>=', $startAt) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }), $now, ) ); $activeHighSeverityFindingCounts = $this->groupedCounts( Finding::query() ->whereIn('workspace_id', $workspaceIds) ->whereIn('severity', Finding::highSeverityValues()) ->whereIn('status', Finding::openStatusesForQuery()) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $anyGovernanceFindingCounts = $this->groupedCounts( Finding::query() ->whereIn('workspace_id', $workspaceIds) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $overdueHighSeverityFindingCounts = $this->groupedCounts( Finding::query() ->whereIn('workspace_id', $workspaceIds) ->whereIn('severity', Finding::highSeverityValues()) ->whereIn('status', Finding::openStatusesForQuery()) ->whereNotNull('due_at') ->where('due_at', '<', $now) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $warningExceptionCounts = $this->groupedCounts( FindingException::query() ->whereIn('workspace_id', $workspaceIds) ->where(function (Builder $query): void { $query ->whereIn('status', [ FindingException::STATUS_PENDING, FindingException::STATUS_EXPIRING, ]) ->orWhere('current_validity_state', FindingException::VALIDITY_EXPIRING); }) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $criticalExceptionCounts = $this->groupedCounts( FindingException::query() ->whereIn('workspace_id', $workspaceIds) ->where(function (Builder $query): void { $query ->whereIn('status', [ FindingException::STATUS_EXPIRED, FindingException::STATUS_REVOKED, ]) ->orWhereIn('current_validity_state', [ FindingException::VALIDITY_EXPIRED, FindingException::VALIDITY_REVOKED, FindingException::VALIDITY_REJECTED, FindingException::VALIDITY_MISSING_SUPPORT, ]); }) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $reviewPackRequestCounts = $this->groupedCounts( ProductUsageEvent::query() ->whereIn('workspace_id', $workspaceIds) ->where('event_name', ProductUsageEventCatalog::REVIEW_PACK_REQUESTED) ->where('occurred_at', '>=', $startAt) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $recentReviewPacks = ReviewPack::query() ->whereIn('workspace_id', $workspaceIds) ->where('created_at', '>=', $startAt) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ->orderByDesc('created_at') ->orderByDesc('id') ->get(['id', 'workspace_id', 'tenant_id', 'status', 'expires_at', 'created_at']) ->groupBy(static fn (ReviewPack $reviewPack): int => (int) $reviewPack->workspace_id) ->map(static fn (Collection $reviewPacks): ?ReviewPack => $reviewPacks->first()); $recentUsageEventCounts = $this->groupedCounts( ProductUsageEvent::query() ->whereIn('workspace_id', $workspaceIds) ->where('occurred_at', '>=', $startAt) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); $historicalUsageEventCounts = $this->groupedCounts( ProductUsageEvent::query() ->whereIn('workspace_id', $workspaceIds) ->where(function (Builder $query): void { $this->constrainToActiveTenantTruth($query); }) ); return $workspaces ->map(function (Workspace $workspace) use ( $tenantsByWorkspace, $latestOnboardingSessions, $providerConnectionsByWorkspace, $recentRunCounts, $recentFailedRunCounts, $recentStuckRunCounts, $activeHighSeverityFindingCounts, $anyGovernanceFindingCounts, $overdueHighSeverityFindingCounts, $warningExceptionCounts, $criticalExceptionCounts, $reviewPackRequestCounts, $recentReviewPacks, $recentUsageEventCounts, $historicalUsageEventCounts, $resolvedWindow, $now, ): array { $workspaceId = (int) $workspace->getKey(); /** @var Collection $workspaceTenants */ $workspaceTenants = $tenantsByWorkspace->get($workspaceId, collect()); /** @var TenantOnboardingSession|null $latestOnboardingSession */ $latestOnboardingSession = $latestOnboardingSessions->get($workspaceId); /** @var Collection $providerConnections */ $providerConnections = $providerConnectionsByWorkspace->get($workspaceId, collect()); /** @var ReviewPack|null $latestReviewPack */ $latestReviewPack = $recentReviewPacks->get($workspaceId); $dimensions = $this->buildDimensions( tenants: $workspaceTenants, latestOnboardingSession: $latestOnboardingSession, providerConnections: $providerConnections, recentRunCount: $this->countForWorkspace($recentRunCounts, $workspaceId), recentFailedRunCount: $this->countForWorkspace($recentFailedRunCounts, $workspaceId), recentStuckRunCount: $this->countForWorkspace($recentStuckRunCounts, $workspaceId), activeHighSeverityFindingCount: $this->countForWorkspace($activeHighSeverityFindingCounts, $workspaceId), anyGovernanceFindingCount: $this->countForWorkspace($anyGovernanceFindingCounts, $workspaceId), overdueHighSeverityFindingCount: $this->countForWorkspace($overdueHighSeverityFindingCounts, $workspaceId), warningExceptionCount: $this->countForWorkspace($warningExceptionCounts, $workspaceId), criticalExceptionCount: $this->countForWorkspace($criticalExceptionCounts, $workspaceId), reviewPackRequestCount: $this->countForWorkspace($reviewPackRequestCounts, $workspaceId), latestReviewPack: $latestReviewPack, recentUsageEventCount: $this->countForWorkspace($recentUsageEventCounts, $workspaceId), historicalUsageEventCount: $this->countForWorkspace($historicalUsageEventCounts, $workspaceId), now: $now, ); $overallLevel = $this->dimensionCatalog->resolveOverallLevel( array_map(static fn (array $dimension): string => $dimension['level'], $dimensions), ); $dominantDimensionKeys = $this->dominantDimensionKeys($dimensions); return [ 'workspace_id' => $workspaceId, 'workspace_name' => (string) $workspace->name, 'overall_level' => $overallLevel, 'dimensions' => $dimensions, 'dominant_dimension_keys' => $dominantDimensionKeys, 'non_ok_dimension_count' => count(array_filter( $dimensions, static fn (array $dimension): bool => $dimension['level'] !== 'ok', )), 'next_link' => $this->nextLink( workspace: $workspace, tenants: $workspaceTenants, dominantDimensionKeys: $dominantDimensionKeys, window: $resolvedWindow, ), ]; }) ->values(); } /** * @return array{ * workspace_id: int, * workspace_name: string, * overall_level: string, * dimensions: array, * dominant_dimension_keys: list, * non_ok_dimension_count: int, * next_link: array{label: string, url: string} * }|null */ public function summaryForWorkspace(Workspace|int $workspace, SystemConsoleWindow|string|null $window = null, ?CarbonImmutable $now = null): ?array { $workspaceId = $workspace instanceof Workspace ? (int) $workspace->getKey() : (int) $workspace; /** @var array{ * workspace_id: int, * workspace_name: string, * overall_level: string, * dimensions: array, * dominant_dimension_keys: list, * non_ok_dimension_count: int, * next_link: array{label: string, url: string} * }|null $summary */ $summary = $this->summaries($window, $now)->firstWhere('workspace_id', $workspaceId); return $summary; } /** * @return array{ok: int, warn: int, critical: int, unknown: int} */ public function healthCounts(SystemConsoleWindow|string|null $window = null, ?CarbonImmutable $now = null): array { $counts = [ 'ok' => 0, 'warn' => 0, 'critical' => 0, 'unknown' => 0, ]; foreach ($this->summaries($window, $now) as $summary) { $counts[$summary['overall_level']]++; } return $counts; } /** * @return Collection, * dominant_dimension_keys: list, * non_ok_dimension_count: int, * next_link: array{label: string, url: string} * }> */ public function attentionNeeded(SystemConsoleWindow|string|null $window = null, int $limit = 10, ?CarbonImmutable $now = null): Collection { return $this->summaries($window, $now) ->filter(static fn (array $summary): bool => $summary['overall_level'] !== 'ok') ->sort(function (array $left, array $right): int { $severityComparison = $this->levelRank($right['overall_level']) <=> $this->levelRank($left['overall_level']); if ($severityComparison !== 0) { return $severityComparison; } $nonOkComparison = $right['non_ok_dimension_count'] <=> $left['non_ok_dimension_count']; if ($nonOkComparison !== 0) { return $nonOkComparison; } $nameComparison = strcasecmp($left['workspace_name'], $right['workspace_name']); if ($nameComparison !== 0) { return $nameComparison; } return $left['workspace_id'] <=> $right['workspace_id']; }) ->values() ->take(max(1, $limit)); } private function resolveWindow(SystemConsoleWindow|string|null $window): SystemConsoleWindow { if ($window instanceof SystemConsoleWindow) { return $window; } return SystemConsoleWindow::fromNullable(is_string($window) ? $window : null); } /** * @param Collection $tenants * @param Collection $providerConnections * @return array */ private function buildDimensions( Collection $tenants, ?TenantOnboardingSession $latestOnboardingSession, Collection $providerConnections, int $recentRunCount, int $recentFailedRunCount, int $recentStuckRunCount, int $activeHighSeverityFindingCount, int $anyGovernanceFindingCount, int $overdueHighSeverityFindingCount, int $warningExceptionCount, int $criticalExceptionCount, int $reviewPackRequestCount, ?ReviewPack $latestReviewPack, int $recentUsageEventCount, int $historicalUsageEventCount, CarbonImmutable $now, ): array { $levels = [ CustomerHealthDimensionCatalog::ONBOARDING_READINESS => $this->onboardingReadinessLevel($tenants, $latestOnboardingSession), CustomerHealthDimensionCatalog::PROVIDER_CONNECTION_HEALTH => $this->providerConnectionHealthLevel($providerConnections), CustomerHealthDimensionCatalog::OPERATIONAL_STABILITY => $this->operationalStabilityLevel($recentRunCount, $recentFailedRunCount, $recentStuckRunCount), CustomerHealthDimensionCatalog::GOVERNANCE_PRESSURE => $this->governancePressureLevel( activeHighSeverityFindingCount: $activeHighSeverityFindingCount, anyGovernanceFindingCount: $anyGovernanceFindingCount, overdueHighSeverityFindingCount: $overdueHighSeverityFindingCount, warningExceptionCount: $warningExceptionCount, criticalExceptionCount: $criticalExceptionCount, ), CustomerHealthDimensionCatalog::REVIEW_PACK_READINESS => $this->reviewPackReadinessLevel($reviewPackRequestCount, $latestReviewPack, $now), CustomerHealthDimensionCatalog::ENGAGEMENT_FRESHNESS => $this->engagementFreshnessLevel($recentUsageEventCount, $historicalUsageEventCount), ]; $dimensions = []; foreach ($this->dimensionCatalog->visibleDimensions() as $dimensionKey => $label) { $dimensions[$dimensionKey] = [ 'label' => $label, 'level' => $levels[$dimensionKey], 'windowed' => $this->dimensionCatalog->isWindowed($dimensionKey), ]; } return $dimensions; } /** * @param Collection $tenants */ private function onboardingReadinessLevel(Collection $tenants, ?TenantOnboardingSession $latestOnboardingSession): string { if ($latestOnboardingSession instanceof TenantOnboardingSession) { return match ($latestOnboardingSession->lifecycleState()) { OnboardingLifecycleState::ReadyForActivation, OnboardingLifecycleState::Completed => 'ok', OnboardingLifecycleState::Cancelled => 'critical', OnboardingLifecycleState::Draft, OnboardingLifecycleState::Verifying, OnboardingLifecycleState::ActionRequired, OnboardingLifecycleState::Bootstrapping => 'warn', }; } if ($tenants->isEmpty()) { return 'unknown'; } if ($tenants->contains(static fn (Tenant $tenant): bool => (string) $tenant->status === Tenant::STATUS_ACTIVE)) { return 'ok'; } return 'warn'; } /** * @param Collection $providerConnections */ private function providerConnectionHealthLevel(Collection $providerConnections): string { if ($providerConnections->isEmpty()) { return 'unknown'; } if ($providerConnections->contains(function (ProviderConnection $connection): bool { if (! $connection->is_enabled) { return false; } $consentStatus = $this->normalizeBackedEnumValue($connection->consent_status); $verificationStatus = $this->normalizeBackedEnumValue($connection->verification_status); return in_array($consentStatus, [ ProviderConsentStatus::Required->value, ProviderConsentStatus::Failed->value, ProviderConsentStatus::Revoked->value, ], true) || in_array($verificationStatus, [ ProviderVerificationStatus::Blocked->value, ProviderVerificationStatus::Error->value, ], true); })) { return 'critical'; } if ($providerConnections->contains(function (ProviderConnection $connection): bool { if (! $connection->is_enabled) { return true; } $consentStatus = $this->normalizeBackedEnumValue($connection->consent_status); $verificationStatus = $this->normalizeBackedEnumValue($connection->verification_status); return in_array($consentStatus, [ ProviderConsentStatus::Unknown->value, ], true) || in_array($verificationStatus, [ ProviderVerificationStatus::Unknown->value, ProviderVerificationStatus::Pending->value, ProviderVerificationStatus::Degraded->value, ], true); })) { return 'warn'; } if ($providerConnections->contains(function (ProviderConnection $connection): bool { return $connection->is_enabled && $this->normalizeBackedEnumValue($connection->consent_status) === ProviderConsentStatus::Granted->value && $this->normalizeBackedEnumValue($connection->verification_status) === ProviderVerificationStatus::Healthy->value; })) { return 'ok'; } return 'unknown'; } private function operationalStabilityLevel(int $recentRunCount, int $recentFailedRunCount, int $recentStuckRunCount): string { if ($recentFailedRunCount > 0 || $recentStuckRunCount > 0) { return 'critical'; } if ($recentRunCount > 0) { return 'ok'; } return 'unknown'; } private function governancePressureLevel( int $activeHighSeverityFindingCount, int $anyGovernanceFindingCount, int $overdueHighSeverityFindingCount, int $warningExceptionCount, int $criticalExceptionCount, ): string { if ($overdueHighSeverityFindingCount > 0 || $criticalExceptionCount > 0) { return 'critical'; } if ($activeHighSeverityFindingCount > 0 || $warningExceptionCount > 0) { return 'warn'; } if ($anyGovernanceFindingCount === 0 && $warningExceptionCount === 0 && $criticalExceptionCount === 0) { return 'unknown'; } return 'ok'; } private function reviewPackReadinessLevel(int $reviewPackRequestCount, ?ReviewPack $latestReviewPack, CarbonImmutable $now): string { if ($reviewPackRequestCount === 0 && ! $latestReviewPack instanceof ReviewPack) { return 'unknown'; } if (! $latestReviewPack instanceof ReviewPack) { return 'warn'; } if ( (string) $latestReviewPack->status === ReviewPack::STATUS_READY && (! $latestReviewPack->expires_at instanceof CarbonImmutable || $latestReviewPack->expires_at->gt($now)) ) { return 'ok'; } if ( in_array((string) $latestReviewPack->status, [ ReviewPack::STATUS_FAILED, ReviewPack::STATUS_EXPIRED, ], true) || ($latestReviewPack->expires_at !== null && $latestReviewPack->expires_at->lte($now)) ) { return 'critical'; } return 'warn'; } private function engagementFreshnessLevel(int $recentUsageEventCount, int $historicalUsageEventCount): string { if ($recentUsageEventCount > 0) { return 'ok'; } if ($historicalUsageEventCount > 0) { return 'warn'; } return 'unknown'; } /** * @param array $dimensions * @return list */ private function dominantDimensionKeys(array $dimensions): array { $catalogOrder = array_flip($this->dimensionCatalog->names()); return collect($dimensions) ->reject(static fn (array $dimension): bool => $dimension['level'] === 'ok') ->keys() ->sort(function (string $left, string $right) use ($dimensions, $catalogOrder): int { $severityComparison = $this->levelRank($dimensions[$right]['level']) <=> $this->levelRank($dimensions[$left]['level']); if ($severityComparison !== 0) { return $severityComparison; } return ($catalogOrder[$left] ?? PHP_INT_MAX) <=> ($catalogOrder[$right] ?? PHP_INT_MAX); }) ->values() ->all(); } /** * @param Collection $tenants * @param list $dominantDimensionKeys * @return array{label: string, url: string} */ private function nextLink(Workspace $workspace, Collection $tenants, array $dominantDimensionKeys, SystemConsoleWindow $window): array { $dominantDimension = $dominantDimensionKeys[0] ?? null; if ($dominantDimension === CustomerHealthDimensionCatalog::OPERATIONAL_STABILITY && $this->canOpenRunsLink()) { return [ 'label' => 'Open runs', 'url' => SystemOperationRunLinks::index(), ]; } /** @var Tenant|null $tenant */ $tenant = $tenants->first(); if ($tenant instanceof Tenant) { return [ 'label' => 'Review health details', 'url' => $this->withWindowQuery(SystemDirectoryLinks::tenantDetail($tenant), $window), ]; } return [ 'label' => 'Review health details', 'url' => $this->withWindowQuery(SystemDirectoryLinks::workspaceDetail($workspace), $window), ]; } private function withWindowQuery(string $url, SystemConsoleWindow $window): string { $separator = str_contains($url, '?') ? '&' : '?'; return $url.$separator.http_build_query(['window' => $window->value]); } private function canOpenRunsLink(): bool { $user = auth('platform')->user(); if (! $user instanceof PlatformUser) { return false; } return $user->hasCapability(PlatformCapabilities::OPERATIONS_VIEW) || ($user->hasCapability(PlatformCapabilities::OPS_VIEW) && $user->hasCapability(PlatformCapabilities::RUNBOOKS_VIEW)); } /** * @param Builder|Builder|Builder|Builder|Builder $query * @return array */ private function groupedCounts(Builder $query): array { return $query ->selectRaw('workspace_id, COUNT(*) as aggregate') ->groupBy('workspace_id') ->pluck('aggregate', 'workspace_id') ->mapWithKeys(static fn (mixed $count, mixed $workspaceId): array => [ (int) $workspaceId => (int) $count, ]) ->all(); } /** * @param array $counts */ private function countForWorkspace(array $counts, int $workspaceId): int { return (int) ($counts[$workspaceId] ?? 0); } private function levelRank(string $level): int { return match ($level) { 'critical' => 4, 'warn' => 3, 'unknown' => 2, default => 1, }; } private function normalizeBackedEnumValue(mixed $value): string { if (is_object($value) && property_exists($value, 'value')) { return (string) $value->value; } return (string) $value; } private function constrainToActiveTenantTruth(Builder $query): void { $query ->whereNull('tenant_id') ->orWhereHas('tenant', function (Builder $tenantQuery): void { $tenantQuery ->whereNull('deleted_at') ->where('status', '!=', Tenant::STATUS_ARCHIVED); }); } }