diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/Cake.Frosting.Issues.Recipe.Tests.csproj b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/Cake.Frosting.Issues.Recipe.Tests.csproj
new file mode 100644
index 00000000..7c0b2d43
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/Cake.Frosting.Issues.Recipe.Tests.csproj
@@ -0,0 +1,56 @@
+
+
+
+ net6.0;net7.0;net8.0
+ latest
+ true
+ true
+ Cake Issues contributors
+ Copyright © Cake Issues contributors
+ Cake.Issues
+
+ ..\Cake.Issues.Tests.ruleset
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ runtime; build; native; contentfiles; analyzers
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/FakeIssueProvider2.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/FakeIssueProvider2.cs
new file mode 100644
index 00000000..8baafebc
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/FakeIssueProvider2.cs
@@ -0,0 +1,34 @@
+namespace Cake.Frosting.Issues.Recipe.Tests;
+
+using System.Collections.Generic;
+using Cake.Core.Diagnostics;
+
+///
+/// Additional Implementation of a for use in test cases.
+///
+internal class FakeIssueProvider2 : FakeIssueProvider
+{
+ private readonly List issues = [];
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Cake log instance.
+ public FakeIssueProvider2(ICakeLog log)
+ : base(log)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Cake log instance.
+ /// Issues which should be returned by the issue provider.
+ public FakeIssueProvider2(ICakeLog log, IEnumerable issues)
+ : base(log, issues)
+ {
+ }
+
+ ///
+ public override string ProviderName => "Fake Issue Provider 2";
+}
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/PullRequestSystems/PullRequestStatusCalculatorTests.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/PullRequestSystems/PullRequestStatusCalculatorTests.cs
new file mode 100644
index 00000000..02fd1e9d
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.Tests/PullRequestSystems/PullRequestStatusCalculatorTests.cs
@@ -0,0 +1,535 @@
+namespace Cake.Frosting.Issues.Recipe.Tests.PullRequestSystems;
+
+public sealed class PullRequestStatusCalculatorTests
+{
+ public sealed class TheGetPullRequestStatesMethod
+ {
+ public sealed class TheShouldSetPullRequestStatusParameter
+ {
+ [Fact]
+ public void Should_Set_Suceeded_Status_If_Neither_Issues_Nor_Providers_Are_Reported()
+ {
+ // Given
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>();
+ var issues = new List();
+ var shouldSetPullRequestStatus = true;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Succeeded);
+ status.Description.ShouldBe("No issues found");
+ }
+
+ [Fact]
+ public void Should_Set_Succeeded_Status_If_No_Issue_Is_Reported()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var run = "Run 1";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run)
+ };
+ var issues = new List();
+ var shouldSetPullRequestStatus = true;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Succeeded);
+ status.Description.ShouldBe("No issues found");
+ }
+
+ [Fact]
+ public void Should_Set_Failed_Status_If_Issue_And_Provider_Is_Reported()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var run = "Run 1";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run)
+ };
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName)
+ .ForRun(run)
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = true;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Failed);
+ status.Description.ShouldBe("Found 1 issues");
+ }
+
+ [Fact]
+ public void Should_Set_Failed_Status_If_Issue_But_No_Provider_Is_Reported()
+ {
+ // Given
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>();
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", "ProviderType", "Provider Name")
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = true;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Failed);
+ status.Description.ShouldBe("Found 1 issues");
+ }
+ }
+
+ public sealed class TheShouldSetSeparatePullRequestStatusForEachIssueProviderAndRunParameter
+ {
+ [Fact]
+ public void Should_Set_Succeeded_Status_If_No_Issue_Is_Reported()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, null)
+ };
+ var issues = new List();
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues-Fake Issue Provider");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Succeeded);
+ status.Description.ShouldBe("No issues found for Fake Issue Provider");
+ }
+
+ [Fact]
+ public void Should_Set_Failed_Status_If_Issue_Is_Reported()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var run = "Run 1";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run)
+ };
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName)
+ .ForRun(run)
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues-Fake Issue Provider (Run 1)");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Failed);
+ status.Description.ShouldBe("Found 1 issues for Fake Issue Provider (Run 1)");
+ }
+
+ [Fact]
+ public void Should_Set_Status_For_Each_Provider()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var issueProvider2 = new FakeIssueProvider2(new FakeLog());
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, null),
+ new (issueProvider2, null)
+ };
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName)
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ result.Count().ShouldBe(2);
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Failed &&
+ x.Description == "Found 1 issues for Fake Issue Provider");
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider 2" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Succeeded &&
+ x.Description == "No issues found for Fake Issue Provider 2");
+ }
+
+ [Fact]
+ public void Should_Set_Status_For_Each_Run()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var run1 = "Run 1";
+ var run2 = "Run 2";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run1),
+ new (issueProvider, run2)
+ };
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName)
+ .ForRun(run1)
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ result.Count().ShouldBe(2);
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider (Run 1)" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Failed &&
+ x.Description == "Found 1 issues for Fake Issue Provider (Run 1)");
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider (Run 2)" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Succeeded &&
+ x.Description == "No issues found for Fake Issue Provider (Run 2)");
+ }
+
+ [Fact]
+ public void Should_Handle_Issue_Provider_With_Different_Type_But_Same_Run()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var issueProvider2 = new FakeIssueProvider2(new FakeLog());
+ var run = "Run 1";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run),
+ new (issueProvider2, run)
+ };
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", issueProvider.ProviderType, issueProvider.ProviderName)
+ .ForRun(run)
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ result.Count().ShouldBe(2);
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider (Run 1)" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Failed &&
+ x.Description == "Found 1 issues for Fake Issue Provider (Run 1)");
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider 2 (Run 1)" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Succeeded &&
+ x.Description == "No issues found for Fake Issue Provider 2 (Run 1)");
+ }
+
+ [Fact]
+ public void Should_Handle_Issue_Provider_With_Same_Type_But_Different_Name()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var run = "Run 1";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run)
+ };
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", issueProvider.ProviderType, "DifferentIssueProviderName")
+ .ForRun(run)
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues-DifferentIssueProviderName (Run 1)");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Failed);
+ status.Description.ShouldBe("Found 1 issues for DifferentIssueProviderName (Run 1)");
+ }
+
+ [Fact]
+ public void Should_Handle_Everything_Together()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var issueProvider2 = new FakeIssueProvider2(new FakeLog());
+ var run1 = "Run 1";
+ var run2 = "Run 2";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run1),
+ new (issueProvider, run2),
+ new (issueProvider2, run2)
+ };
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message 1", issueProvider.ProviderType, issueProvider.ProviderName)
+ .ForRun(run1)
+ .Create(),
+ IssueBuilder
+ .NewIssue("Message 2", issueProvider.ProviderType, issueProvider.ProviderName)
+ .ForRun(run1)
+ .Create(),
+ IssueBuilder
+ .NewIssue("Message", issueProvider2.ProviderType, "DifferentIssueProviderName")
+ .ForRun(run2)
+ .Create(),
+ IssueBuilder
+ .NewIssue("Message", "AnotherIssueProvider", "Another Issue Provider")
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier)
+ .ToList();
+
+ // Then
+ result.Count.ShouldBe(4);
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider (Run 1)" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Failed &&
+ x.Description == "Found 2 issues for Fake Issue Provider (Run 1)");
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Fake Issue Provider (Run 2)" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Succeeded &&
+ x.Description == "No issues found for Fake Issue Provider (Run 2)");
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-DifferentIssueProviderName (Run 2)" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Failed &&
+ x.Description == "Found 1 issues for DifferentIssueProviderName (Run 2)");
+
+ result.ShouldContain(x =>
+ x.Name == "Issues-Another Issue Provider" &&
+ x.Genre == "Cake.Issues.Recipe" &&
+ x.State == PullRequestStatusState.Failed &&
+ x.Description == "Found 1 issues for Another Issue Provider");
+ }
+
+ [Fact]
+ public void Should_Consider_Run()
+ {
+ // Given
+ var issueProvider = new FakeIssueProvider(new FakeLog());
+ var run = "Run 1";
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>()
+ {
+ new (issueProvider, run)
+ };
+ var issues = new List();
+ var shouldSetPullRequestStatus = false;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = true;
+ string buildIdentifier = null;
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues-Fake Issue Provider (Run 1)");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Succeeded);
+ status.Description.ShouldBe("No issues found for Fake Issue Provider (Run 1)");
+ }
+ }
+
+ public sealed class TheBuildIdentifierParameter
+ {
+ [Fact]
+ public void Should_Consider_Build_Identifier()
+ {
+ // Given
+ var issueProvidersAndRuns = new List<(IIssueProvider, string)>();
+ var issues = new List
+ {
+ IssueBuilder
+ .NewIssue("Message", "ProviderType", "Provider Name")
+ .Create(),
+ };
+ var shouldSetPullRequestStatus = true;
+ var shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun = false;
+ string buildIdentifier = "Build Identifier";
+
+ // When
+ var result =
+ PullRequestStatusCalculator.GetPullRequestStates(
+ issueProvidersAndRuns,
+ issues,
+ shouldSetPullRequestStatus,
+ shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ buildIdentifier);
+
+ // Then
+ var status = result.ShouldHaveSingleItem();
+ status.Name.ShouldBe("Issues-Build Identifier");
+ status.Genre.ShouldBe("Cake.Issues.Recipe");
+ status.State.ShouldBe(PullRequestStatusState.Failed);
+ status.Description.ShouldBe("Found 1 issues in build Build Identifier");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.lutconfig b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.lutconfig
new file mode 100644
index 00000000..ff7fdf3f
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.lutconfig
@@ -0,0 +1,6 @@
+
+ ..\
+ true
+ true
+ 180000
+
\ No newline at end of file
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln
index bcf8e62e..e0d77a4c 100644
--- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.2.32526.322
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cake.Frosting.Issues.Recipe", "Cake.Frosting.Issues.Recipe\Cake.Frosting.Issues.Recipe.csproj", "{EF4F2348-04B1-4BDD-87A3-1622845227A9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Frosting.Issues.Recipe.Tests", "Cake.Frosting.Issues.Recipe.Tests\Cake.Frosting.Issues.Recipe.Tests.csproj", "{946A333A-2713-4D77-93EE-342573E1F63E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +29,18 @@ Global
{EF4F2348-04B1-4BDD-87A3-1622845227A9}.Release|x64.Build.0 = Release|Any CPU
{EF4F2348-04B1-4BDD-87A3-1622845227A9}.Release|x86.ActiveCfg = Release|Any CPU
{EF4F2348-04B1-4BDD-87A3-1622845227A9}.Release|x86.Build.0 = Release|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x64.Build.0 = Debug|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Debug|x86.Build.0 = Debug|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x64.ActiveCfg = Release|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x64.Build.0 = Release|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x86.ActiveCfg = Release|Any CPU
+ {946A333A-2713-4D77-93EE-342573E1F63E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Properties/ProjectInfo.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Properties/ProjectInfo.cs
new file mode 100644
index 00000000..1f6ac330
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Properties/ProjectInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Cake.Frosting.Issues.Recipe.Tests")]
\ No newline at end of file
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs
index 0a9889e0..1c90d600 100644
--- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/AzureDevOpsPullRequestSystem.cs
@@ -41,55 +41,22 @@ public override void SetPullRequestIssuesState(IIssuesContext context)
return;
}
- // Set status across all issues
- if (context.Parameters.PullRequestSystem.ShouldSetPullRequestStatus)
+ var status = PullRequestStatusCalculator.GetPullRequestStates(context).ToList();
+ if (status.Count > 0)
{
- SetPullRequestStatus(
- context,
- context.State.Issues,
- null);
- }
-
- // Set status for individual issue providers
- if (context.Parameters.PullRequestSystem.ShouldSetSeparatePullRequestStatusForEachIssueProviderAndRun)
- {
- // Determine issue providers and runs from the list of issues providers from which issues were read
- // and issue providers and runs of the reported issues.
- var issueProvidersAndRuns =
- context.State.IssueProvidersAndRuns
- .Select(x =>
- new
- {
- x.Item1.ProviderName,
- Run = x.Item2
- })
- .Union(
- context.State.Issues
- .GroupBy(x =>
- new
- {
- x.ProviderName,
- x.Run
- })
- .Select(x =>
- new
- {
- x.Key.ProviderName,
- x.Key.Run
- }));
+ var pullRequestSettings =
+ new AzureDevOpsPullRequestSettings(
+ context.State.BuildServer.DetermineRepositoryRemoteUrl(context, context.State.RepositoryRootDirectory),
+ context.State.BuildServer.DeterminePullRequestId(context).Value,
+ context.AzureDevOpsAuthenticationOAuth(context.EnvironmentVariable("SYSTEM_ACCESSTOKEN")));
- foreach (var item in issueProvidersAndRuns)
+ foreach (var item in status)
{
- var issueProvider = item.ProviderName;
- if (!string.IsNullOrEmpty(item.Run))
- {
- issueProvider += $" ({item.Run})";
- }
+ context.Information("Set status {0} to {1}", item.Name, item.State);
- SetPullRequestStatus(
- context,
- context.State.Issues.Where(x => x.ProviderName == item.ProviderName && x.Run == item.Run),
- issueProvider);
+ context.AzureDevOpsSetPullRequestStatus(
+ pullRequestSettings,
+ item.ToAzureDevOpsPullRequestStatus());
}
}
}
@@ -106,62 +73,5 @@ public override FileLinkSettings GetFileLinkSettings(IIssuesContext context)
context.State.CommitId,
rootPath.FullPath);
}
-
- ///
- /// Reports a status to the pull request of the current build.
- ///
- /// The Cake context.
- /// Issues for which the status should be reported.
- /// Identifier for the issues passed in .
- private static void SetPullRequestStatus(
- IIssuesContext context,
- IEnumerable issues,
- string issueIdentifier)
- {
- var pullRequestSettings =
- new AzureDevOpsPullRequestSettings(
- context.State.BuildServer.DetermineRepositoryRemoteUrl(context, context.State.RepositoryRootDirectory),
- context.State.BuildServer.DeterminePullRequestId(context).Value,
- context.AzureDevOpsAuthenticationOAuth(context.EnvironmentVariable("SYSTEM_ACCESSTOKEN")));
-
- var issuesList = issues.ToList();
-
- var pullRequestStatusName = "Issues";
- var pullRequestDescriptionIfIssues = $"Found {issuesList.Count} issues";
- var pullRequestDescriptionIfNoIssues = "No issues found";
-
- if (!string.IsNullOrWhiteSpace(issueIdentifier))
- {
- pullRequestStatusName += $"-{issueIdentifier}";
- pullRequestDescriptionIfIssues += $" for {issueIdentifier}";
- pullRequestDescriptionIfNoIssues += $" for {issueIdentifier}";
- }
-
- if (!string.IsNullOrWhiteSpace(context.Parameters.BuildIdentifier))
- {
- pullRequestStatusName += $"-{context.Parameters.BuildIdentifier}";
- pullRequestDescriptionIfIssues += $" in build {context.Parameters.BuildIdentifier}";
- pullRequestDescriptionIfNoIssues += $" in build {context.Parameters.BuildIdentifier}";
- }
-
- var state =
- issuesList.Count != 0 ?
- AzureDevOpsPullRequestStatusState.Failed :
- AzureDevOpsPullRequestStatusState.Succeeded;
-
- context.Information("Set status {0} to {1}", pullRequestStatusName, state);
-
- var pullRequestStatus =
- new AzureDevOpsPullRequestStatus(pullRequestStatusName)
- {
- Genre = "Cake.Issues.Recipe",
- State = state,
- Description = issuesList.Count != 0 ? pullRequestDescriptionIfIssues : pullRequestDescriptionIfNoIssues
- };
-
- context.AzureDevOpsSetPullRequestStatus(
- pullRequestSettings,
- pullRequestStatus);
- }
}
}
\ No newline at end of file
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatus.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatus.cs
new file mode 100644
index 00000000..e5888320
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatus.cs
@@ -0,0 +1,11 @@
+namespace Cake.Frosting.Issues.Recipe
+{
+ ///
+ /// Represents a pull request status.
+ ///
+ /// Name of the status entry.
+ /// Category of the status entry.
+ /// Sate of the status entry.
+ /// Description of the status entry.
+ internal record PullRequestStatus(string Name, string Genre, PullRequestStatusState State, string Description);
+}
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusCalculator.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusCalculator.cs
new file mode 100644
index 00000000..502703c4
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusCalculator.cs
@@ -0,0 +1,158 @@
+namespace Cake.Frosting.Issues.Recipe
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ ///
+ /// Class to calculate the status which should be reported to the pull request.
+ ///
+ internal class PullRequestStatusCalculator
+ {
+ ///
+ /// Returns the status which should be reported to the pull request.
+ ///
+ /// Context of the build.
+ /// Status which should be reported to the pull request.
+ public static IEnumerable GetPullRequestStates(IIssuesContext context)
+ {
+ return GetPullRequestStates(
+ context.State.IssueProvidersAndRuns,
+ context.State.Issues,
+ context.Parameters.PullRequestSystem.ShouldSetPullRequestStatus,
+ context.Parameters.PullRequestSystem.ShouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ context.Parameters.BuildIdentifier);
+ }
+
+ ///
+ /// Returns the status which should be reported to the pull request.
+ ///
+ /// List of issue providers and runs.
+ /// List of issues.
+ /// Value which indicates whether overall status for all issues should
+ /// be reported.
+ /// Value which indicates whether
+ /// individual status for each issue provder and run should be reported.
+ /// Identifier of the build run.
+ /// Status which should be reported to the pull request.
+ public static IEnumerable GetPullRequestStates(
+ IList<(IIssueProvider, string)> issueProvidersAndRuns,
+ IEnumerable issues,
+ bool shouldSetPullRequestStatus,
+ bool shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ string buildIdentifier)
+ {
+ var result = new List();
+
+ // Set status across all issues
+ if (shouldSetPullRequestStatus)
+ {
+ result.Add(PullRequestStatusCalculator.CreatePullRequestState(issues, null, buildIdentifier));
+ }
+
+ // Set status for individual issue providers
+ if (shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun)
+ {
+ // Determine issue providers and runs from the list of issues providers from which issues were read
+ // and issue providers and runs of the reported issues.
+ var comparer = new IssueProviderAndRunComparer();
+ var pullrequestStateIssues =
+ issues
+ .GroupBy(x =>
+ new
+ {
+ x.ProviderType,
+ x.ProviderName,
+ x.Run
+ })
+ .Select(x =>
+ new PullRequestStateIssue(
+ x.Key.ProviderType,
+ x.Key.ProviderName,
+ x.Key.Run))
+ .Union(
+ issueProvidersAndRuns
+ .Select(x =>
+ new PullRequestStateIssue(
+ x.Item1.ProviderType,
+ x.Item1.ProviderName,
+ x.Item2)), comparer);
+
+ foreach (var item in pullrequestStateIssues)
+ {
+ var issueProvider = item.ProviderName;
+ if (!string.IsNullOrEmpty(item.Run))
+ {
+ issueProvider += $" ({item.Run})";
+ }
+
+ result.Add(
+ PullRequestStatusCalculator.CreatePullRequestState(
+ issues.Where(x => x.ProviderName == item.ProviderName && x.Run == item.Run),
+ issueProvider,
+ buildIdentifier));
+ }
+ }
+
+ return result;
+ }
+
+ private record PullRequestStateIssue(string ProviderType, string ProviderName, string Run);
+
+ private class IssueProviderAndRunComparer : IEqualityComparer
+ {
+ public bool Equals(PullRequestStateIssue x, PullRequestStateIssue y)
+ {
+ return x.ProviderType == y.ProviderType && x.Run == y.Run;
+ }
+
+ public int GetHashCode(PullRequestStateIssue obj)
+ {
+ var result = obj.ProviderType.GetHashCode(StringComparison.OrdinalIgnoreCase);
+
+ if (obj.Run != null)
+ {
+ result ^= obj.Run.GetHashCode(StringComparison.OrdinalIgnoreCase);
+ }
+
+ return result;
+ }
+ }
+
+ private static PullRequestStatus CreatePullRequestState(
+ IEnumerable issues,
+ string issueIdentifier,
+ string buildIdentifier)
+ {
+ var issuesList = issues.ToList();
+
+ var pullRequestStatusName = "Issues";
+ var pullRequestDescriptionIfIssues = $"Found {issuesList.Count} issues";
+ var pullRequestDescriptionIfNoIssues = "No issues found";
+
+ if (!string.IsNullOrWhiteSpace(issueIdentifier))
+ {
+ pullRequestStatusName += $"-{issueIdentifier}";
+ pullRequestDescriptionIfIssues += $" for {issueIdentifier}";
+ pullRequestDescriptionIfNoIssues += $" for {issueIdentifier}";
+ }
+
+ if (!string.IsNullOrWhiteSpace(buildIdentifier))
+ {
+ pullRequestStatusName += $"-{buildIdentifier}";
+ pullRequestDescriptionIfIssues += $" in build {buildIdentifier}";
+ pullRequestDescriptionIfNoIssues += $" in build {buildIdentifier}";
+ }
+
+ var state =
+ issuesList.Count != 0 ?
+ PullRequestStatusState.Failed :
+ PullRequestStatusState.Succeeded;
+
+ return new PullRequestStatus(
+ pullRequestStatusName,
+ "Cake.Issues.Recipe",
+ state,
+ issuesList.Count != 0 ? pullRequestDescriptionIfIssues : pullRequestDescriptionIfNoIssues);
+ }
+ }
+}
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusExtensions.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusExtensions.cs
new file mode 100644
index 00000000..d706e245
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusExtensions.cs
@@ -0,0 +1,25 @@
+namespace Cake.Frosting.Issues.Recipe
+{
+ using Cake.AzureDevOps.Repos.PullRequest;
+
+ ///
+ /// Extension methods for .
+ ///
+ internal static class PullRequestStatusExtensions
+ {
+ ///
+ /// Converts the to .
+ ///
+ /// Status to convert.
+ /// Converted status.
+ public static AzureDevOpsPullRequestStatus ToAzureDevOpsPullRequestStatus(this PullRequestStatus status)
+ {
+ return new AzureDevOpsPullRequestStatus(status.Name)
+ {
+ Genre = status.Genre,
+ State = status.State.ToAzureDevOpsPullRequestStatusState(),
+ Description = status.Description
+ };
+ }
+ }
+}
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusState.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusState.cs
new file mode 100644
index 00000000..ea5658ee
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusState.cs
@@ -0,0 +1,18 @@
+namespace Cake.Frosting.Issues.Recipe
+{
+ ///
+ /// Possible states of a pull request status.
+ ///
+ internal enum PullRequestStatusState
+ {
+ ///
+ /// Status succeeded.
+ ///
+ Succeeded,
+
+ ///
+ /// Status failed.
+ ///
+ Failed
+ }
+}
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusStateExtensions.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusStateExtensions.cs
new file mode 100644
index 00000000..5c8d88ff
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/PullRequestSystems/PullRequestStatusStateExtensions.cs
@@ -0,0 +1,25 @@
+namespace Cake.Frosting.Issues.Recipe
+{
+ using Cake.AzureDevOps.Repos.PullRequest;
+
+ ///
+ /// Extension methods for .
+ ///
+ internal static class PullRequestStatusStateExtensions
+ {
+ ///
+ /// Converts the to .
+ ///
+ /// Status to convert.
+ /// Converted status.
+ public static AzureDevOpsPullRequestStatusState ToAzureDevOpsPullRequestStatusState(this PullRequestStatusState status)
+ {
+ return status switch
+ {
+ PullRequestStatusState.Succeeded => AzureDevOpsPullRequestStatusState.Succeeded,
+ PullRequestStatusState.Failed => AzureDevOpsPullRequestStatusState.Failed,
+ _ => AzureDevOpsPullRequestStatusState.Succeeded,
+ };
+ }
+ }
+}
diff --git a/Cake.Frosting.Issues.Recipe/Cake.Issues.Tests.ruleset b/Cake.Frosting.Issues.Recipe/Cake.Issues.Tests.ruleset
new file mode 100644
index 00000000..ce420cdb
--- /dev/null
+++ b/Cake.Frosting.Issues.Recipe/Cake.Issues.Tests.ruleset
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/AzureDevOpsPullRequestSystem.cake b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/AzureDevOpsPullRequestSystem.cake
index 5b380f1d..b326171e 100644
--- a/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/AzureDevOpsPullRequestSystem.cake
+++ b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/AzureDevOpsPullRequestSystem.cake
@@ -36,57 +36,22 @@ public class AzureDevOpsPullRequestSystem : BasePullRequestSystem
return;
}
- // Set status across all issues
- if (IssuesParameters.PullRequestSystem.ShouldSetPullRequestStatus)
+ var status = PullRequestStatusCalculator.GetPullRequestStates(context).ToList();
+ if (status.Count > 0)
{
- SetPullRequestStatus(
- context,
- data,
- data.Issues,
- null);
- }
-
- // Set status for individual issue providers
- if (IssuesParameters.PullRequestSystem.ShouldSetSeparatePullRequestStatusForEachIssueProviderAndRun)
- {
- // Determine issue providers and runs from the list of issues providers from which issues were read
- // and issue providers and runs of the reported issues.
- var issueProvidersAndRuns =
- data.IssueProvidersAndRuns
- .Select(x =>
- new
- {
- x.Item1.ProviderName,
- Run = x.Item2
- })
- .Union(
- data.Issues
- .GroupBy(x =>
- new
- {
- x.ProviderName,
- x.Run
- })
- .Select(x =>
- new
- {
- x.Key.ProviderName,
- x.Key.Run
- }));
+ var pullRequestSettings =
+ new AzureDevOpsPullRequestSettings(
+ data.BuildServer.DetermineRepositoryRemoteUrl(context, context.State.RepositoryRootDirectory),
+ data.BuildServer.DeterminePullRequestId(context).Value,
+ context.AzureDevOpsAuthenticationOAuth(context.EnvironmentVariable("SYSTEM_ACCESSTOKEN")));
- foreach (var item in issueProvidersAndRuns)
+ foreach (var item in status)
{
- var issueProvider = item.ProviderName;
- if (!string.IsNullOrEmpty(item.Run))
- {
- issueProvider += $" ({item.Run})";
- }
+ context.Information("Set status {0} to {1}", item.Name, item.State);
- SetPullRequestStatus(
- context,
- data,
- data.Issues.Where(x => x.ProviderName == item.ProviderName && x.Run == item.Run),
- issueProvider);
+ context.AzureDevOpsSetPullRequestStatus(
+ pullRequestSettings,
+ item.ToAzureDevOpsPullRequestStatus());
}
}
}
@@ -104,61 +69,4 @@ public class AzureDevOpsPullRequestSystem : BasePullRequestSystem
data.CommitId,
rootPath.FullPath);
}
-
- ///
- /// Reports a status to the pull request of the current build.
- ///
- /// The Cake context.
- /// Object containing information about the build.
- /// Issues for which the status should be reported.
- /// Identifier for the issues passed in .
- private static void SetPullRequestStatus(
- ICakeContext context,
- IssuesData data,
- IEnumerable issues,
- string issueIdentifier)
- {
- var pullRequestSettings =
- new AzureDevOpsPullRequestSettings(
- data.BuildServer.DetermineRepositoryRemoteUrl(context, data.RepositoryRootDirectory),
- data.BuildServer.DeterminePullRequestId(context).Value,
- context.AzureDevOpsAuthenticationOAuth(context.EnvironmentVariable("SYSTEM_ACCESSTOKEN")));
-
- var pullRequestStatusName = "Issues";
- var pullRequestDescriptionIfIssues = $"Found {issues.Count()} issues";
- var pullRequestDescriptionIfNoIssues = "No issues found";
-
- if (!string.IsNullOrWhiteSpace(issueIdentifier))
- {
- pullRequestStatusName += $"-{issueIdentifier}";
- pullRequestDescriptionIfIssues += $" for {issueIdentifier}";
- pullRequestDescriptionIfNoIssues += $" for {issueIdentifier}";
- }
-
- if (!string.IsNullOrWhiteSpace(IssuesParameters.BuildIdentifier))
- {
- pullRequestStatusName += $"-{IssuesParameters.BuildIdentifier}";
- pullRequestDescriptionIfIssues += $" in build {IssuesParameters.BuildIdentifier}";
- pullRequestDescriptionIfNoIssues += $" in build {IssuesParameters.BuildIdentifier}";
- }
-
- var state =
- issues.Any() ?
- AzureDevOpsPullRequestStatusState.Failed :
- AzureDevOpsPullRequestStatusState.Succeeded;
-
- context.Information("Set status {0} to {1}", pullRequestStatusName, state);
-
- var pullRequestStatus =
- new AzureDevOpsPullRequestStatus(pullRequestStatusName)
- {
- Genre = "Cake.Issues.Recipe",
- State = state,
- Description = issues.Any() ? pullRequestDescriptionIfIssues : pullRequestDescriptionIfNoIssues
- };
-
- context.AzureDevOpsSetPullRequestStatus(
- pullRequestSettings,
- pullRequestStatus);
- }
-}
\ No newline at end of file
+ }
\ No newline at end of file
diff --git a/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatus.cake b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatus.cake
new file mode 100644
index 00000000..d5e9f518
--- /dev/null
+++ b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatus.cake
@@ -0,0 +1,8 @@
+///
+/// Represents a pull request status.
+///
+/// Name of the status entry.
+/// Category of the status entry.
+/// Sate of the status entry.
+/// Description of the status entry.
+internal record PullRequestStatus(string Name, string Genre, PullRequestStatusState State, string Description);
\ No newline at end of file
diff --git a/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusCalculator.cake b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusCalculator.cake
new file mode 100644
index 00000000..d9185ddb
--- /dev/null
+++ b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusCalculator.cake
@@ -0,0 +1,135 @@
+///
+/// Class to calculate the status which should be reported to the pull request.
+///
+internal class PullRequestStatusCalculator
+{
+ ///
+ /// Returns the status which should be reported to the pull request.
+ ///
+ /// Object containing information about the build.
+ /// Status which should be reported to the pull request.
+ public static IEnumerable GetPullRequestStates(IssuesData data)
+ {
+ return GetPullRequestStates(
+ data.IssueProvidersAndRuns,
+ data.Issues,
+ IssuesParameters.PullRequestSystem.ShouldSetPullRequestStatus,
+ IssuesParameters.PullRequestSystem.ShouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ IssuesParameters.BuildIdentifier);
+ }
+ ///
+ /// Returns the status which should be reported to the pull request.
+ ///
+ /// List of issue providers and runs.
+ /// List of issues.
+ /// Value which indicates whether overall status for all issues should
+ /// be reported.
+ /// Value which indicates whether
+ /// individual status for each issue provder and run should be reported.
+ /// Identifier of the build run.
+ /// Status which should be reported to the pull request.
+ public static IEnumerable GetPullRequestStates(
+ IList<(IIssueProvider, string)> issueProvidersAndRuns,
+ IEnumerable issues,
+ bool shouldSetPullRequestStatus,
+ bool shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun,
+ string buildIdentifier)
+ {
+ var result = new List();
+ // Set status across all issues
+ if (shouldSetPullRequestStatus)
+ {
+ result.Add(PullRequestStatusCalculator.CreatePullRequestState(issues, null, buildIdentifier));
+ }
+ // Set status for individual issue providers
+ if (shouldSetSeparatePullRequestStatusForEachIssueProviderAndRun)
+ {
+ // Determine issue providers and runs from the list of issues providers from which issues were read
+ // and issue providers and runs of the reported issues.
+ var comparer = new IssueProviderAndRunComparer();
+ var pullrequestStateIssues =
+ issues
+ .GroupBy(x =>
+ new
+ {
+ x.ProviderType,
+ x.ProviderName,
+ x.Run
+ })
+ .Select(x =>
+ new PullRequestStateIssue(
+ x.Key.ProviderType,
+ x.Key.ProviderName,
+ x.Key.Run))
+ .Union(
+ issueProvidersAndRuns
+ .Select(x =>
+ new PullRequestStateIssue(
+ x.Item1.ProviderType,
+ x.Item1.ProviderName,
+ x.Item2)), comparer);
+ foreach (var item in pullrequestStateIssues)
+ {
+ var issueProvider = item.ProviderName;
+ if (!string.IsNullOrEmpty(item.Run))
+ {
+ issueProvider += $" ({item.Run})";
+ }
+ result.Add(
+ PullRequestStatusCalculator.CreatePullRequestState(
+ issues.Where(x => x.ProviderName == item.ProviderName && x.Run == item.Run),
+ issueProvider,
+ buildIdentifier));
+ }
+ }
+ return result;
+ }
+ private record PullRequestStateIssue(string ProviderType, string ProviderName, string Run);
+ private class IssueProviderAndRunComparer : IEqualityComparer
+ {
+ public bool Equals(PullRequestStateIssue x, PullRequestStateIssue y)
+ {
+ return x.ProviderType == y.ProviderType && x.Run == y.Run;
+ }
+ public int GetHashCode(PullRequestStateIssue obj)
+ {
+ var result = obj.ProviderType.GetHashCode(StringComparison.OrdinalIgnoreCase);
+ if (obj.Run != null)
+ {
+ result ^= obj.Run.GetHashCode(StringComparison.OrdinalIgnoreCase);
+ }
+ return result;
+ }
+ }
+ private static PullRequestStatus CreatePullRequestState(
+ IEnumerable issues,
+ string issueIdentifier,
+ string buildIdentifier)
+ {
+ var issuesList = issues.ToList();
+ var pullRequestStatusName = "Issues";
+ var pullRequestDescriptionIfIssues = $"Found {issuesList.Count} issues";
+ var pullRequestDescriptionIfNoIssues = "No issues found";
+ if (!string.IsNullOrWhiteSpace(issueIdentifier))
+ {
+ pullRequestStatusName += $"-{issueIdentifier}";
+ pullRequestDescriptionIfIssues += $" for {issueIdentifier}";
+ pullRequestDescriptionIfNoIssues += $" for {issueIdentifier}";
+ }
+ if (!string.IsNullOrWhiteSpace(buildIdentifier))
+ {
+ pullRequestStatusName += $"-{buildIdentifier}";
+ pullRequestDescriptionIfIssues += $" in build {buildIdentifier}";
+ pullRequestDescriptionIfNoIssues += $" in build {buildIdentifier}";
+ }
+ var state =
+ issuesList.Count != 0 ?
+ PullRequestStatusState.Failed :
+ PullRequestStatusState.Succeeded;
+ return new PullRequestStatus(
+ pullRequestStatusName,
+ "Cake.Issues.Recipe",
+ state,
+ issuesList.Count != 0 ? pullRequestDescriptionIfIssues : pullRequestDescriptionIfNoIssues);
+ }
+}
\ No newline at end of file
diff --git a/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusExtensions.cake b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusExtensions.cake
new file mode 100644
index 00000000..3519e9d6
--- /dev/null
+++ b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusExtensions.cake
@@ -0,0 +1,20 @@
+///
+/// Extension methods for .
+///
+internal static class PullRequestStatusExtensions
+{
+ ///
+ /// Converts the to .
+ ///
+ /// Status to convert.
+ /// Converted status.
+ public static AzureDevOpsPullRequestStatus ToAzureDevOpsPullRequestStatus(this PullRequestStatus status)
+ {
+ return new AzureDevOpsPullRequestStatus(status.Name)
+ {
+ Genre = status.Genre,
+ State = status.State.ToAzureDevOpsPullRequestStatusState(),
+ Description = status.Description
+ };
+ }
+}
\ No newline at end of file
diff --git a/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusState.cake b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusState.cake
new file mode 100644
index 00000000..d5eea947
--- /dev/null
+++ b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusState.cake
@@ -0,0 +1,14 @@
+///
+/// Possible states of a pull request status.
+///
+internal enum PullRequestStatusState
+{
+ ///
+ /// Status succeeded.
+ ///
+ Succeeded,
+ ///
+ /// Status failed.
+ ///
+ Failed
+}
diff --git a/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusStateExtensions.cake b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusStateExtensions.cake
new file mode 100644
index 00000000..03c9d982
--- /dev/null
+++ b/Cake.Issues.Recipe/Content/tasks/pullrequestsystems/PullRequestStatusStateExtensions.cake
@@ -0,0 +1,20 @@
+///
+/// Extension methods for .
+///
+internal static class PullRequestStatusStateExtensions
+{
+ ///
+ /// Converts the to .
+ ///
+ /// Status to convert.
+ /// Converted status.
+ public static AzureDevOpsPullRequestStatusState ToAzureDevOpsPullRequestStatusState(this PullRequestStatusState status)
+ {
+ return status switch
+ {
+ PullRequestStatusState.Succeeded => AzureDevOpsPullRequestStatusState.Succeeded,
+ PullRequestStatusState.Failed => AzureDevOpsPullRequestStatusState.Failed,
+ _ => AzureDevOpsPullRequestStatusState.Succeeded,
+ };
+ }
+}
\ No newline at end of file