Skip to content

Commit d2eb88e

Browse files
feat: Automatically move closed issues to done in backlog
If an issue is closed, we want the card to be moved to the Done column in the board and not be left dangling somewhere. close #6
1 parent 969fdf8 commit d2eb88e

12 files changed

+305
-16
lines changed

src/Nullinside.Cicd.GitHub/.editorconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ root = true
44
[*]
55
indent_style = space
66

7+
# ReSharper properties
8+
resharper_csharp_wrap_lines = false
9+
10+
# We don't want to code comment auto-generated files
11+
[GraphQl/Model/*.cs]
12+
dotnet_diagnostic.CS1591.severity = none
13+
714
# Xml files
815
[*.xml]
916
indent_size = 2

src/Nullinside.Cicd.GitHub/Constants.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ public static class Constants {
99
/// </summary>
1010
public const string GITHUB_ORG = "nullinside-development-group";
1111

12+
/// <summary>
13+
/// The url to the graphql endpoint to post against.
14+
/// </summary>
15+
public const string GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
16+
17+
/// <summary>
18+
/// The name of the project that be sent in user agent headers.
19+
/// </summary>
20+
public const string PROJECT_NAME = "nullinside-cicd-github";
21+
1222
/// <summary>
1323
/// The github project's unique identifier on github.
1424
/// </summary>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Net.Http.Headers;
2+
using System.Text;
3+
4+
using Newtonsoft.Json;
5+
6+
using Nullinside.Cicd.GitHub.GraphQl.Model;
7+
8+
namespace Nullinside.Cicd.GitHub.GraphQl;
9+
10+
/// <summary>
11+
/// The contract for executing a simple graphql query.
12+
/// </summary>
13+
/// <typeparam name="T">The JSON response POCO to the query, <see cref="GraphQlGenericMutationResponse" /> for no response.</typeparam>
14+
public abstract class AbstractGitHubGraphQlQuery<T> {
15+
/// <summary>
16+
/// The query, typically coded into the object.
17+
/// </summary>
18+
public abstract string Query { get; }
19+
20+
/// <summary>
21+
/// The query variables, typically assembled in the constructor.
22+
/// </summary>
23+
public object? QueryVariables { get; set; }
24+
25+
/// <summary>
26+
/// Executes the request.
27+
/// </summary>
28+
/// <returns>The response object.</returns>
29+
public virtual async Task<T?> SendAsync() {
30+
using var httpClient = new HttpClient {
31+
BaseAddress = new Uri(Constants.GITHUB_GRAPHQL_URL),
32+
DefaultRequestHeaders = {
33+
UserAgent = { new ProductInfoHeaderValue(Constants.PROJECT_NAME, "0.0.0") },
34+
Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("GITHUB_PAT"))
35+
}
36+
};
37+
38+
var queryObject = new {
39+
query = Query,
40+
variables = QueryVariables
41+
};
42+
43+
var request = new HttpRequestMessage {
44+
Method = HttpMethod.Post,
45+
Content = new StringContent(JsonConvert.SerializeObject(queryObject), Encoding.UTF8, "application/json")
46+
};
47+
48+
using HttpResponseMessage response = await httpClient.SendAsync(request);
49+
response.EnsureSuccessStatusCode();
50+
string responseString = await response.Content.ReadAsStringAsync();
51+
52+
return JsonConvert.DeserializeObject<T>(responseString);
53+
}
54+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by a tool.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
using System.Globalization;
11+
12+
using Newtonsoft.Json;
13+
using Newtonsoft.Json.Converters;
14+
15+
namespace Nullinside.Cicd.GitHub.GraphQl.Model;
16+
17+
18+
public partial class GraphQlGenericMutationResponse
19+
{
20+
[JsonProperty("data")]
21+
public object Data { get; set; }
22+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by a tool.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
using Newtonsoft.Json;
11+
12+
namespace Nullinside.Cicd.GitHub.Model;
13+
14+
public class GraphQlProjectIssueResponse {
15+
[JsonProperty("data")] public Data Data { get; set; }
16+
}
17+
18+
public class Data {
19+
[JsonProperty("repository")] public Repository Repository { get; set; }
20+
}
21+
22+
public class Repository {
23+
[JsonProperty("id")] public string Id { get; set; }
24+
25+
[JsonProperty("issues")] public Issues Issues { get; set; }
26+
}
27+
28+
public class Issues {
29+
[JsonProperty("nodes")] public List<IssuesNode> Nodes { get; set; }
30+
31+
[JsonProperty("pageInfo")] public PageInfo PageInfo { get; set; }
32+
}
33+
34+
public class IssuesNode {
35+
[JsonProperty("number")] public long Number { get; set; }
36+
37+
[JsonProperty("closed")] public bool Closed { get; set; }
38+
39+
[JsonProperty("id")] public string Id { get; set; }
40+
41+
[JsonProperty("projectItems")] public ProjectItems ProjectItems { get; set; }
42+
}
43+
44+
public class ProjectItems {
45+
[JsonProperty("nodes")] public List<ProjectItemsNode> Nodes { get; set; }
46+
47+
[JsonProperty("pageInfo")] public PageInfo PageInfo { get; set; }
48+
}
49+
50+
public class ProjectItemsNode {
51+
[JsonProperty("fieldValueByName")] public FieldValueByName FieldValueByName { get; set; }
52+
53+
[JsonProperty("id")] public string Id { get; set; }
54+
}
55+
56+
public class FieldValueByName {
57+
[JsonProperty("field")] public Field Field { get; set; }
58+
59+
[JsonProperty("name")] public string Name { get; set; }
60+
}
61+
62+
public class Field {
63+
[JsonProperty("id")] public string Id { get; set; }
64+
}
65+
66+
public class PageInfo {
67+
[JsonProperty("hasNextPage")] public bool HasNextPage { get; set; }
68+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Nullinside.Cicd.GitHub.GraphQl.Model;
2+
3+
namespace Nullinside.Cicd.GitHub.GraphQl;
4+
5+
/// <summary>
6+
/// Changes the status of the issue on the GitHub project board. (ex: Backlog, Ready, In progress, In review, Done)
7+
/// </summary>
8+
public class MutateIssueProjectStatuses : AbstractGitHubGraphQlQuery<GraphQlGenericMutationResponse> {
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="MutateIssueProjectStatuses" /> class.
11+
/// </summary>
12+
/// <param name="projectId">The project id</param>
13+
/// <param name="projectIssueId">The status field's id (ex: PVTI_lADOCZOBm84AdCw7zgQfohA NOT I_kwDOLcGb986MVe7p)</param>
14+
/// <param name="statusFieldId">The status field's id (ex: PVTSSF_lADOCZOBm84AdCw7zgSzu_A)</param>
15+
/// <param name="selectionId">The unique identifier of the single selection option to change the status to.</param>
16+
public MutateIssueProjectStatuses(string projectId, string projectIssueId, string statusFieldId, string selectionId) {
17+
QueryVariables = new { project = projectId, item = projectIssueId, field = statusFieldId, selectionId };
18+
}
19+
20+
/// <inheritdoc />
21+
public override string Query => @"mutation($project: ID!, $item: ID!, $field: ID!, $selectionId: String!) {
22+
updateProjectV2ItemFieldValue(
23+
input: {projectId: $project, itemId: $item, fieldId: $field, value: {singleSelectOptionId: $selectionId}}
24+
) {
25+
clientMutationId
26+
}
27+
}";
28+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Nullinside.Cicd.GitHub.Model;
2+
3+
namespace Nullinside.Cicd.GitHub.GraphQl;
4+
5+
/// <summary>
6+
/// Queries the issues in a GitHub code base.
7+
/// </summary>
8+
public class QueryIssueProjectStatuses : AbstractGitHubGraphQlQuery<GraphQlProjectIssueResponse> {
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="QueryIssueProjectStatuses" /> class.
11+
/// </summary>
12+
/// <param name="repoOwner">The owner of the repo on github.</param>
13+
/// <param name="repoName">The name of the repo on github.</param>
14+
public QueryIssueProjectStatuses(string repoOwner, string repoName) {
15+
QueryVariables = new { owner = repoOwner, name = repoName };
16+
}
17+
18+
/// <inheritdoc />
19+
public override string Query => @"query ($name: String!, $owner: String!) {
20+
repository(name: $name, owner: $owner) {
21+
id
22+
issues(first: 100) {
23+
pageInfo {
24+
hasNextPage
25+
endCursor
26+
}
27+
nodes {
28+
id
29+
number
30+
closed
31+
projectItems(first: 100) {
32+
pageInfo {
33+
hasNextPage
34+
endCursor
35+
}
36+
nodes {
37+
id
38+
fieldValueByName(name: ""Status"") {
39+
... on ProjectV2ItemFieldSingleSelectValue {
40+
name
41+
field {
42+
... on ProjectV2SingleSelectField {
43+
id
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}";
54+
}

src/Nullinside.Cicd.GitHub/Nullinside.Cicd.GitHub.csproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@
1919
</PropertyGroup>
2020

2121
<ItemGroup>
22-
<PackageReference Include="log4net" Version="3.0.4" />
23-
<PackageReference Include="log4net.Ext.Json" Version="3.0.3" />
24-
<PackageReference Include="Octokit" Version="14.0.0" />
22+
<PackageReference Include="log4net" Version="3.0.4"/>
23+
<PackageReference Include="log4net.Ext.Json" Version="3.0.3"/>
24+
<PackageReference Include="Octokit" Version="14.0.0"/>
2525
</ItemGroup>
2626

2727
<ItemGroup>
28-
<ProjectReference Include="..\octokit.graphql.net\src\Octokit.GraphQL\Octokit.GraphQL.csproj" />
28+
<ProjectReference Include="..\octokit.graphql.net\src\Octokit.GraphQL\Octokit.GraphQL.csproj"/>
2929
</ItemGroup>
3030

3131
<ItemGroup>
32-
<None Remove="log4net.config" />
33-
<Content Include="log4net.config">
34-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
35-
</Content>
32+
<None Remove="log4net.config"/>
33+
<Content Include="log4net.config">
34+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
35+
</Content>
3636
</ItemGroup>
3737

3838
</Project>

src/Nullinside.Cicd.GitHub/Program.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
using log4net;
44
using log4net.Config;
5-
using log4net.Core;
65

76
using Nullinside.Cicd.GitHub;
87
using Nullinside.Cicd.GitHub.Rule;
@@ -16,7 +15,7 @@
1615
using Query = Octokit.GraphQL.Query;
1716

1817
XmlConfigurator.Configure(new FileInfo("log4net.config"));
19-
var log = LogManager.GetLogger(typeof(Program));
18+
ILog log = LogManager.GetLogger(typeof(Program));
2019

2120
IRepoRule?[] rules = AppDomain.CurrentDomain.GetAssemblies()
2221
.SelectMany(a => a.GetTypes())

src/Nullinside.Cicd.GitHub/Rule/AssociateIssuesWithProject.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ namespace Nullinside.Cicd.GitHub.Rule;
1515
/// </summary>
1616
public class AssociateIssuesWithProject : IRepoRule {
1717
/// <summary>
18-
/// The logger
18+
/// The logger
1919
/// </summary>
20-
private ILog _log = LogManager.GetLogger(typeof(AssociateIssuesWithProject));
21-
20+
private readonly ILog _log = LogManager.GetLogger(typeof(AssociateIssuesWithProject));
21+
2222
/// <inheritdoc />
2323
public async Task Handle(GitHubClient client, Connection graphQl, ID projectId, Repository repo) {
2424
if (!repo.HasIssues) {

0 commit comments

Comments
 (0)