From 00a99b99292d9a91df3593169226337884f3be00 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 14 Jul 2024 01:24:15 +0100 Subject: [PATCH] AddPipelineGlobalHooks --- src/ModularPipelines.Build/ReleaseNotes.md | 2 +- .../Extensions/GitHubExtensions.cs | 2 + .../GitHubMarkdownSummaryGenerator.cs | 121 ++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/ModularPipelines.GitHub/GitHubMarkdownSummaryGenerator.cs diff --git a/src/ModularPipelines.Build/ReleaseNotes.md b/src/ModularPipelines.Build/ReleaseNotes.md index ec747fa47d..ff26158bfd 100644 --- a/src/ModularPipelines.Build/ReleaseNotes.md +++ b/src/ModularPipelines.Build/ReleaseNotes.md @@ -1 +1 @@ -null \ No newline at end of file +- GitHub Actions Markdown Summary - Thanks to @MattParkerDev ! \ No newline at end of file diff --git a/src/ModularPipelines.GitHub/Extensions/GitHubExtensions.cs b/src/ModularPipelines.GitHub/Extensions/GitHubExtensions.cs index 373f614593..ea3619e6bb 100644 --- a/src/ModularPipelines.GitHub/Extensions/GitHubExtensions.cs +++ b/src/ModularPipelines.GitHub/Extensions/GitHubExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using ModularPipelines.Context; using ModularPipelines.Engine; +using ModularPipelines.Extensions; namespace ModularPipelines.GitHub.Extensions; @@ -23,6 +24,7 @@ public static IServiceCollection RegisterGitHubContext(this IServiceCollection s services.TryAddScoped(); services.TryAddScoped(); services.TryAddSingleton(); + services.AddPipelineGlobalHooks(); return services; } diff --git a/src/ModularPipelines.GitHub/GitHubMarkdownSummaryGenerator.cs b/src/ModularPipelines.GitHub/GitHubMarkdownSummaryGenerator.cs new file mode 100644 index 0000000000..a17d8c0024 --- /dev/null +++ b/src/ModularPipelines.GitHub/GitHubMarkdownSummaryGenerator.cs @@ -0,0 +1,121 @@ +using ModularPipelines.Context; +using ModularPipelines.Enums; +using ModularPipelines.Interfaces; +using ModularPipelines.Models; + +namespace ModularPipelines.GitHub; + +internal class GitHubMarkdownSummaryGenerator : IPipelineGlobalHooks +{ + public Task OnStartAsync(IPipelineHookContext pipelineContext) + { + return Task.CompletedTask; + } + + public async Task OnEndAsync(IPipelineHookContext pipelineContext, PipelineSummary pipelineSummary) + { + var mermaid = await GenerateMermaidSummary(pipelineSummary); + var table = await GenerateTableSummary(pipelineSummary); + + var stepSummaryVariable = pipelineContext.Environment.EnvironmentVariables.GetEnvironmentVariable("GITHUB_STEP_SUMMARY"); + + if (string.IsNullOrEmpty(stepSummaryVariable)) + { + return; + } + + await pipelineContext.FileSystem.GetFile(stepSummaryVariable).WriteAsync($"{mermaid}\n\n{table}"); + } + + private async Task GenerateMermaidSummary(PipelineSummary pipelineSummary) + { + var results = await pipelineSummary.GetModuleResultsAsync(); + + var stepStringList = results + .OrderBy(x => x.ModuleEnd) + .ThenBy(s => s.ModuleStart) + .Select(x => + { + var (startTime, endTime) = (x.ModuleStart, x.ModuleEnd); + return $"{x.ModuleName} :{AddCritIfFailed(x)} {startTime:mm:ss:fff}, {endTime:mm:ss:fff}"; + }).ToList(); + + var text = $""" + ```mermaid + --- + config: + theme: base + themeVariables: + primaryColor: "#007d15" + primaryTextColor: "#fff" + primaryBorderColor: "#02ad1e" + lineColor: "#F8B229" + secondaryColor: "#006100" + tertiaryColor: "#fff" + darkmode: "true" + titleColor: "#fff" + gantt: + leftPadding: 40 + rightPadding: 120 + --- + + gantt + dateFormat mm:ss:SSS + title Run Summary + axisFormat %M:%S + + {string.Join("\n", stepStringList)} + ``` + """; + + return text; + } + + private async Task GenerateTableSummary(PipelineSummary pipelineSummary) + { + var results = await pipelineSummary.GetModuleResultsAsync(); + + var stepStringList = results.OrderBy(x => x.ModuleEnd) + .ThenBy(s => s.ModuleStart) + .Select(stepContainer => + { + var (startTime, endTime, duration) = (stepContainer.ModuleStart, stepContainer.ModuleEnd, stepContainer.ModuleDuration); + var text = $"| {stepContainer.ModuleName} | {GetStatusString(stepContainer.ModuleStatus)} | {startTime:HH:mm:ss} | {endTime:HH:mm:ss} | {duration} |"; + return text; + } + ).ToList(); + + var (globalStartTime, globalEndTime, globalDuration) = (pipelineSummary.Start, pipelineSummary.End, pipelineSummary.TotalDuration); + var pipelineStatusString = GetStatusString(pipelineSummary.Status); + var overallSummaryString = $"| **Total** | **{pipelineStatusString}** | **{globalStartTime:HH:mm:ss}** | **{globalEndTime:HH:mm:ss}** | **{globalDuration}** |"; + var text = $""" + ### Run Summary + | Step | Status | Start | End | Duration | + | --- | --- | --- | --- | --- | + {string.Join("\n", stepStringList)} + {overallSummaryString} + """; + + return text; + } + + private static string AddCritIfFailed(IModuleResult moduleResult) + { + return moduleResult.ModuleResultType is ModuleResultType.Failure + ? "crit," + : string.Empty; + } + + private static string GetStatusString(Status status) + { + return status switch + { + Status.Successful or Status.UsedHistory => $$$"""${\textsf{\color{lightgreen}{{{status}}}}}$""", + Status.NotYetStarted or Status.IgnoredFailure or Status.Processing or Status.Skipped => + $$$"""${\textsf{\color{orange}{{{status}}}}}$""", + Status.PipelineTerminated or Status.TimedOut or Status.Failed or Status.Unknown => + $$$"""${\textsf{\color{red}{{{status}}}}}$""", + _ => throw new ArgumentOutOfRangeException(nameof(status), status, null), + }; + } +} \ No newline at end of file