Skip to content

Commit 09e13c8

Browse files
authored
Refactor most of the remaining queries and mutations. (#281)
* Remove unused query type * Use new style query for default branch Use the updated ScalarQuery class for the default branch query. Write tests for the new navigation and the new query result type. * consolidate label queries There were four different sets of identical code that queried for labels and created a label object from them. Consolidate them in one GitHubLabel object that now supports both a scalar and an enumeration query. * Refactor What's New query Modify the What's New query for pull requests merged in a sprint to the new enumeration query. * Refactor Files in PR query Move the Files in PR query to the new interface driven format. * code review * Refactor mutations * Code review Clean up unnecessary usings, add Xml comments.
1 parent d592e36 commit 09e13c8

40 files changed

+792
-1302
lines changed

DotNet.DocsTools/GitHubObjects/BankruptcyIssue.cs

+16-10
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,26 @@ query FindIssuesForBankruptcyQuery($organization: String!, $repository: String!,
4848
}
4949
""";
5050

51-
public static GraphQLPacket GetQueryPacket(BankruptcyIssueVariables variables) =>
52-
new()
53-
{
54-
query = OpenIssuesForBankruptcyQuery,
55-
variables =
56-
{
57-
["organization"] = variables.Organization,
58-
["repository"] = variables.Repository,
59-
}
60-
};
51+
public static GraphQLPacket GetQueryPacket(BankruptcyIssueVariables variables, bool isScalar) => isScalar
52+
? throw new InvalidOperationException("This query doesn't support scalar queries")
53+
: new()
54+
{
55+
query = OpenIssuesForBankruptcyQuery,
56+
variables =
57+
{
58+
["organization"] = variables.Organization,
59+
["repository"] = variables.Repository,
60+
}
61+
};
6162

6263
public static BankruptcyIssue FromJsonElement(JsonElement element, BankruptcyIssueVariables unused) =>
6364
new(element);
6465

66+
public static IEnumerable<string> NavigationToNodes(bool isScalar) =>
67+
isScalar
68+
? throw new InvalidOperationException("This query doesn't support scalar queries")
69+
: ["repository", "issues"];
70+
6571
public BankruptcyIssue(JsonElement element) : base(element)
6672
{
6773
Author = Actor.FromJsonElement(ResponseExtractors.GetAuthorChildElement(element));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using DotNet.DocsTools.GraphQLQueries;
2+
using DotNetDocs.Tools.GitHubCommunications;
3+
4+
namespace DotNet.DocsTools.GitHubObjects;
5+
6+
/// <summary>
7+
/// Variables for the CloseBankruptcyIssueMutation.
8+
/// </summary>
9+
/// <param name="NodeID">The node ID for the issue to close.</param>
10+
/// <param name="LabelID">The label ID (typically for a "won't fix" label) to add before closing.</param>
11+
/// <param name="CommentText">The comment text to add to the issue as a close message.</param>
12+
public readonly record struct CloseBankruptcyIssueVariables(string NodeID, string LabelID, string CommentText);
13+
14+
/// <summary>
15+
/// Mutation to close an issue.
16+
/// </summary>
17+
public class CloseBankruptyIssueMutation : IGitHubMutation<CloseBankruptyIssueMutation, CloseBankruptcyIssueVariables>
18+
{
19+
private const string mutationPacketText = """
20+
mutation AddLabels($nodeID: ID!, $labelIDs: [ID!]!, $commentText: String!) {
21+
addLabelsToLabelable(input: {
22+
labelableId:$nodeID
23+
labelIds:$labelIDs
24+
clientMutationId:"dotnet-docs-tools"
25+
}) {
26+
labelable {
27+
__typename
28+
}
29+
clientMutationId
30+
}
31+
addComment(input: {
32+
subjectId:$nodeID,
33+
body:$commentText,
34+
clientMutationId: "dotnet-docs-tools"
35+
}) {
36+
clientMutationId
37+
}
38+
closeIssue(input: {
39+
issueId:$nodeID,
40+
clientMutationId: "dotnet-docs-tools"
41+
}) {
42+
clientMutationId
43+
}
44+
}
45+
""";
46+
47+
/// <summary>
48+
/// Construct the GraphQL packet for closing an issue.
49+
/// </summary>
50+
/// <param name="variables">The variables that determines which issue to close.</param>
51+
/// <returns>The GraphQL packet object.</returns>
52+
public static GraphQLPacket GetMutationPacket(CloseBankruptcyIssueVariables variables) =>
53+
new()
54+
{
55+
query = mutationPacketText,
56+
variables =
57+
{
58+
["nodeID"] = variables.NodeID,
59+
["labelIDs"] = new[] { variables.LabelID },
60+
["commentText"] = variables.CommentText
61+
}
62+
};
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using DotNetDocs.Tools.GitHubCommunications;
2+
using System.Text.Json;
3+
4+
namespace DotNet.DocsTools.GitHubObjects;
5+
6+
public readonly record struct DefaultBranchVariables(string Organization, string Repository);
7+
public record DefaultBranch : IGitHubQueryResult<DefaultBranch, DefaultBranchVariables>
8+
{
9+
private const string Query = """
10+
query GetDefaultBranch($organization: String!, $repository: String!) {
11+
repository(owner: $organization, name: $repository) {
12+
defaultBranchRef {
13+
name
14+
}
15+
}
16+
}
17+
""";
18+
19+
public static GraphQLPacket GetQueryPacket(DefaultBranchVariables variables, bool isScalar) => isScalar
20+
? new()
21+
{
22+
query = Query,
23+
variables =
24+
{
25+
["organization"] = variables.Organization,
26+
["repository"] = variables.Repository,
27+
}
28+
}
29+
: throw new InvalidOperationException("This query doesn't support enumerations");
30+
31+
public static IEnumerable<string> NavigationToNodes(bool isScalar) =>
32+
isScalar
33+
? ["repository", "defaultBranchRef"]
34+
: throw new InvalidOperationException("This query doesn't support array queries");
35+
36+
public string DefaultBranchName { get; }
37+
38+
private DefaultBranch(string defaultBranchName) => DefaultBranchName = defaultBranchName;
39+
40+
public static DefaultBranch FromJsonElement(JsonElement branchRefNode, DefaultBranchVariables _)
41+
{
42+
var branchName = ResponseExtractors.StringProperty(branchRefNode, "name");
43+
return new DefaultBranch(branchName);
44+
}
45+
}
+86-17
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,96 @@
1-
using System.Text.Json;
1+
using DotNetDocs.Tools.GitHubCommunications;
2+
using System.Text.Json;
23

34
namespace DotNet.DocsTools.GitHubObjects;
45

6+
/// <summary>
7+
/// The variables for finding a GitHub label query.
8+
/// </summary>
9+
/// <param name="Organization">The GitHub organization</param>
10+
/// <param name="Repository">The GitHub repository</param>
11+
/// <param name="labelText">The text of the label.</param>
12+
/// <remarks>For labels with emojis, the `:` prefix and suffix may be used to
13+
/// describe the emoji.
14+
/// </remarks>
15+
public readonly record struct FindLabelQueryVariables(string Organization, string Repository, string labelText);
16+
517
/// <summary>
618
/// Record type for a GitHub label
719
/// </summary>
8-
public sealed record GitHubLabel
20+
public sealed record GitHubLabel : IGitHubQueryResult<GitHubLabel, FindLabelQueryVariables>
921
{
22+
private static readonly string allLabels = """
23+
query EnumerateLabels($organization: String!, $repository: String!, $cursor:String) {
24+
repository(owner: $organization, name: $repository) {
25+
labels(first: 50, after: $cursor) {
26+
pageInfo {
27+
hasNextPage
28+
endCursor
29+
}
30+
nodes {
31+
name
32+
id
33+
}
34+
}
35+
}
36+
}
37+
""";
38+
39+
private const string findLabel = """
40+
query FindLabel($labelName: String!, $organization: String!, $repository: String!) {
41+
repository(owner:$organization, name:$repository) {
42+
label(name: $labelName) {
43+
id
44+
name
45+
}
46+
}
47+
}
48+
""";
49+
50+
public static GraphQLPacket GetQueryPacket(FindLabelQueryVariables variables, bool isScalar) => isScalar
51+
? new()
52+
{
53+
query = findLabel,
54+
variables =
55+
{
56+
["organization"] = variables.Organization,
57+
["repository"] = variables.Repository,
58+
["labelName"] = variables.labelText
59+
}
60+
}
61+
: new()
62+
{
63+
query = allLabels,
64+
variables =
65+
{
66+
["organization"] = variables.Organization,
67+
["repository"] = variables.Repository
68+
}
69+
};
70+
71+
public static IEnumerable<string> NavigationToNodes(bool isScalar) => isScalar
72+
? ["repository", "label"]
73+
: ["repository", "labels"];
74+
75+
/// <summary>
76+
/// Construct a GitHub label from a JsonElement
77+
/// </summary>
78+
/// <param name="labelElement"></param>
79+
/// <exception cref="ArgumentException"></exception>
80+
private GitHubLabel(string name, string id) =>
81+
(Name, Id) = (name, id);
82+
83+
public static GitHubLabel? FromJsonElement(JsonElement element, FindLabelQueryVariables variables) =>
84+
element.ValueKind switch
85+
{
86+
JsonValueKind.Null => null,
87+
JsonValueKind.Object => new GitHubLabel(
88+
name: ResponseExtractors.StringProperty(element, "name"),
89+
id: ResponseExtractors.StringProperty(element, "id")
90+
),
91+
_ => throw new ArgumentException("Must be an object", nameof(element))
92+
};
93+
1094
/// <summary>
1195
/// The name of the label.
1296
/// </summary>
@@ -20,20 +104,5 @@ public sealed record GitHubLabel
20104
/// all GH objects, this would be in the common base type.
21105
/// </remarks>
22106
public string Id { get; }
23-
24-
/// <summary>
25-
/// Construct a GitHub label from a JsonElement
26-
/// </summary>
27-
/// <param name="labelElement"></param>
28-
/// <exception cref="ArgumentException"></exception>
29-
public GitHubLabel(JsonElement labelElement)
30-
{
31-
if (labelElement.ValueKind != JsonValueKind.Object)
32-
{
33-
throw new ArgumentException("Must be an object", nameof(labelElement));
34-
}
35-
Name = ResponseExtractors.StringProperty(labelElement, "name")!;
36-
Id = ResponseExtractors.StringProperty(labelElement, "id")!;
37-
}
38107
}
39108

DotNet.DocsTools/GitHubObjects/IGitHubQueryResult.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,29 @@ public interface IGitHubQueryResult<TResult, TVariables> where TResult : IGitHub
2020
/// <param name="variables">
2121
/// An instance of a record whose members are transformed into the dictionary for the query.
2222
/// </param>
23+
/// <param name="isScalar">True to return the query for a scalar query. False for an enumeration query.</param>
2324
/// <returns>The Packet structure for the query.</returns>
24-
public static abstract GraphQLPacket GetQueryPacket(TVariables variables);
25+
public static abstract GraphQLPacket GetQueryPacket(TVariables variables, bool isScalar);
2526

2627
/// <summary>
2728
/// Construct the <typeparamref name="TResult"/> object from the JsonElement.
2829
/// </summary>
2930
/// <param name="element">The Json element representing one object.</param>
3031
/// <returns>An instance of the result type.</returns>
31-
public static abstract TResult FromJsonElement(JsonElement element, TVariables variables);
32+
public static abstract TResult? FromJsonElement(JsonElement element, TVariables variables);
3233

34+
/// <summary>
35+
/// Retrieve the path to the correct data node from the "data" JsonElement node.
36+
/// </summary>
37+
/// <param name="isScalar">True if the query is a scalar query. False if it's enumerating an array</param>
38+
/// <returns>
39+
/// The sequence of node names to traverse from the "data" node to the node representing
40+
/// the object being returned.
41+
/// </returns>
42+
/// <remarks>
43+
/// For scalar queries, this navigation should return the node containing the result.
44+
/// For array queries, this navigation should return the parent of the "nodes" element,
45+
/// where the "nodes" element contains the array being enumerated.
46+
/// </remarks>
47+
public static abstract IEnumerable<string> NavigationToNodes(bool isScalar);
3348
}

DotNet.DocsTools/GitHubObjects/Issue.cs

+4-24
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,18 @@
22

33
namespace DotNet.DocsTools.GitHubObjects;
44

5-
public abstract record Issue
5+
public abstract record Issue : IssueOrPullRequest
66
{
7-
public Issue(JsonElement element)
7+
public Issue(JsonElement element) : base(element)
88
{
9-
Id = ResponseExtractors.GetIdValue(element);
10-
Number = element.GetProperty("number").GetInt32();
11-
Title = ResponseExtractors.GetTitleValue(element);
129
Body = ResponseExtractors.GetBodyValue(element);
1310
}
1411

1512
/// <summary>
16-
/// The node ID for the issue.
13+
/// The body of the issue
1714
/// </summary>
1815
/// <remarks>
19-
/// Every GitHub object has a unique node ID. This is used to identify the issue.
20-
/// It may be that every query uses the nodeID. I haven't verified that yet. If that
21-
/// turns out to be true, then this property should be moved to the base class.
16+
/// This is the body of the issue as markdown text.
2217
/// </remarks>
23-
public string Id { get; }
24-
25-
/// <summary>
26-
/// Retrieve the issue number.
27-
/// </summary>
28-
public int Number { get; }
29-
30-
/// <summary>
31-
/// The title of the issue.
32-
/// </summary>
33-
public string Title { get; }
34-
35-
/// <summary>
36-
/// The body of the issue
37-
/// </summary>
3818
public string? Body { get; }
3919
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Text.Json;
2+
3+
namespace DotNet.DocsTools.GitHubObjects;
4+
5+
/// <summary>
6+
/// This is the base class for any query result representing an issue or pull request.
7+
/// </summary>
8+
/// <remarks>
9+
/// GitHub's object model has a common type for issue and PR, so we probably should too.
10+
/// This represents that common base type.
11+
/// </remarks>
12+
public abstract record IssueOrPullRequest
13+
{
14+
/// <summary>
15+
/// Construct this record from the JsonElement.
16+
/// </summary>
17+
/// <param name="element">The Json object that represents the issue or PR.</param>
18+
public IssueOrPullRequest(JsonElement element)
19+
{
20+
Id = ResponseExtractors.GetIdValue(element);
21+
Number = element.GetProperty("number").GetInt32();
22+
Title = ResponseExtractors.GetTitleValue(element);
23+
}
24+
25+
/// <summary>
26+
/// The node ID for the issue.
27+
/// </summary>
28+
/// <remarks>
29+
/// Every GitHub object has a unique node ID. This is used to identify the issue.
30+
/// It may be that every query uses the nodeID. I haven't verified that yet. If that
31+
/// turns out to be true, then this property should be moved to the base class.
32+
/// </remarks>
33+
public string Id { get; }
34+
35+
/// <summary>
36+
/// Retrieve the issue or PR number.
37+
/// </summary>
38+
public int Number { get; }
39+
40+
/// <summary>
41+
/// The title of the issue or PR.
42+
/// </summary>
43+
public string Title { get; }
44+
}

0 commit comments

Comments
 (0)