using System.Globalization; using StellaOps.JobEngine.WebService.Endpoints; namespace StellaOps.JobEngine.WebService.Services; /// /// Builds deterministic release dashboard snapshots from in-memory seed data. /// public static class ReleaseDashboardSnapshotBuilder { private static readonly PipelineDefinition[] PipelineDefinitions = { new("dev", "development", "Development", 1), new("staging", "staging", "Staging", 2), new("uat", "uat", "UAT", 3), new("production", "production", "Production", 4), }; private static readonly HashSet AllowedReleaseStatuses = new(StringComparer.OrdinalIgnoreCase) { "draft", "ready", "promoting", "deployed", "failed", "deprecated", "rolled_back", }; public static ReleaseDashboardSnapshot Build( IReadOnlyList? approvals = null, IReadOnlyList? releases = null) { var releaseItems = (releases ?? ReleaseEndpoints.SeedData.Releases) .OrderByDescending(release => release.CreatedAt) .ThenBy(release => release.Id, StringComparer.Ordinal) .ToArray(); var approvalItems = (approvals ?? ApprovalEndpoints.SeedData.Approvals) .OrderBy(approval => ParseTimestamp(approval.RequestedAt)) .ThenBy(approval => approval.Id, StringComparer.Ordinal) .ToArray(); var pendingApprovals = approvalItems .Where(approval => string.Equals(approval.Status, "pending", StringComparison.OrdinalIgnoreCase)) .Select(approval => new PendingApprovalItem( approval.Id, approval.ReleaseId, approval.ReleaseName, approval.ReleaseVersion, ToDisplayEnvironment(approval.SourceEnvironment), ToDisplayEnvironment(approval.TargetEnvironment), approval.RequestedBy, approval.RequestedAt, NormalizeUrgency(approval.Urgency))) .ToArray(); var activeDeployments = releaseItems .Where(release => string.Equals(release.Status, "deploying", StringComparison.OrdinalIgnoreCase)) .OrderByDescending(release => release.UpdatedAt) .ThenBy(release => release.Id, StringComparer.Ordinal) .Select((release, index) => { var progress = Math.Min(90, 45 + (index * 15)); var totalTargets = Math.Max(1, release.ComponentCount); var completedTargets = Math.Clamp( (int)Math.Round(totalTargets * (progress / 100d), MidpointRounding.AwayFromZero), 1, totalTargets); return new ActiveDeploymentItem( Id: $"dep-{release.Id}", ReleaseId: release.Id, ReleaseName: release.Name, ReleaseVersion: release.Version, Environment: ToDisplayEnvironment(release.TargetEnvironment ?? release.CurrentEnvironment ?? "staging"), Progress: progress, Status: "running", StartedAt: release.UpdatedAt.ToString("O"), CompletedTargets: completedTargets, TotalTargets: totalTargets); }) .ToArray(); var pipelineEnvironments = PipelineDefinitions .Select(definition => { var releaseCount = releaseItems.Count(release => string.Equals(NormalizeEnvironment(release.CurrentEnvironment), definition.NormalizedName, StringComparison.OrdinalIgnoreCase)); var pendingCount = pendingApprovals.Count(approval => string.Equals(NormalizeEnvironment(approval.TargetEnvironment), definition.NormalizedName, StringComparison.OrdinalIgnoreCase)); var hasActiveDeployment = activeDeployments.Any(deployment => string.Equals(NormalizeEnvironment(deployment.Environment), definition.NormalizedName, StringComparison.OrdinalIgnoreCase)); var healthStatus = hasActiveDeployment || pendingCount > 0 ? "degraded" : releaseCount > 0 ? "healthy" : "unknown"; return new PipelineEnvironmentItem( definition.Id, definition.NormalizedName, definition.DisplayName, definition.Order, releaseCount, pendingCount, healthStatus); }) .ToArray(); var pipelineConnections = PipelineDefinitions .Skip(1) .Select((definition, index) => new PipelineConnectionItem( PipelineDefinitions[index].Id, definition.Id)) .ToArray(); var recentReleases = releaseItems .Take(10) .Select(release => new RecentReleaseItem( release.Id, release.Name, release.Version, NormalizeReleaseStatus(release.Status), release.CurrentEnvironment is null ? null : ToDisplayEnvironment(release.CurrentEnvironment), release.CreatedAt.ToString("O"), string.IsNullOrWhiteSpace(release.CreatedBy) ? "system" : release.CreatedBy, release.ComponentCount)) .ToArray(); return new ReleaseDashboardSnapshot( new PipelineData(pipelineEnvironments, pipelineConnections), pendingApprovals, activeDeployments, recentReleases); } private static DateTimeOffset ParseTimestamp(string value) { if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed)) { return parsed; } return DateTimeOffset.MinValue; } private static string NormalizeEnvironment(string? value) { var normalized = value?.Trim().ToLowerInvariant() ?? string.Empty; return normalized switch { "dev" => "development", "stage" => "staging", "prod" => "production", _ => normalized, }; } private static string ToDisplayEnvironment(string? value) { return NormalizeEnvironment(value) switch { "development" => "Development", "staging" => "Staging", "uat" => "UAT", "production" => "Production", var other when string.IsNullOrWhiteSpace(other) => "Unknown", var other => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(other), }; } private static string NormalizeReleaseStatus(string value) { var normalized = value.Trim().ToLowerInvariant(); if (string.Equals(normalized, "deploying", StringComparison.OrdinalIgnoreCase)) { return "promoting"; } return AllowedReleaseStatuses.Contains(normalized) ? normalized : "draft"; } private static string NormalizeUrgency(string value) { var normalized = value.Trim().ToLowerInvariant(); return normalized switch { "low" or "normal" or "high" or "critical" => normalized, _ => "normal", }; } private sealed record PipelineDefinition(string Id, string NormalizedName, string DisplayName, int Order); } public sealed record ReleaseDashboardSnapshot( PipelineData PipelineData, IReadOnlyList PendingApprovals, IReadOnlyList ActiveDeployments, IReadOnlyList RecentReleases); public sealed record PipelineData( IReadOnlyList Environments, IReadOnlyList Connections); public sealed record PipelineEnvironmentItem( string Id, string Name, string DisplayName, int Order, int ReleaseCount, int PendingCount, string HealthStatus); public sealed record PipelineConnectionItem(string From, string To); public sealed record PendingApprovalItem( string Id, string ReleaseId, string ReleaseName, string ReleaseVersion, string SourceEnvironment, string TargetEnvironment, string RequestedBy, string RequestedAt, string Urgency); public sealed record ActiveDeploymentItem( string Id, string ReleaseId, string ReleaseName, string ReleaseVersion, string Environment, int Progress, string Status, string StartedAt, int CompletedTargets, int TotalTargets); public sealed record RecentReleaseItem( string Id, string Name, string Version, string Status, string? CurrentEnvironment, string CreatedAt, string CreatedBy, int ComponentCount);