Skip to content

Commit 1e88c70

Browse files
Jerichogep13
authored andcommitted
Injectable GraphQlClient
1 parent 626abee commit 1e88c70

File tree

8 files changed

+96
-47
lines changed

8 files changed

+96
-47
lines changed

src/GitReleaseManager.Cli/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
using GitReleaseManager.Core.Provider;
1414
using GitReleaseManager.Core.ReleaseNotes;
1515
using GitReleaseManager.Core.Templates;
16+
using GraphQL.Client.Http;
17+
using GraphQL.Client.Serializer.SystemTextJson;
1618
using Microsoft.Extensions.DependencyInjection;
1719
using NGitLab;
1820
using Octokit;
@@ -211,6 +213,12 @@ private static void RegisterVcsProvider(BaseVcsOptions vcsOptions, IServiceColle
211213
// default to Github
212214
serviceCollection
213215
.AddSingleton<IGitHubClient>((_) => new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(vcsOptions.Token) })
216+
.AddSingleton<GraphQL.Client.Abstractions.IGraphQLClient>(_ =>
217+
{
218+
var client = new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri("https://api.github.com/graphql") }, new SystemTextJsonSerializer());
219+
client.HttpClient.DefaultRequestHeaders.Add("Authorization", $"bearer {vcsOptions.Token}");
220+
return client;
221+
})
214222
.AddSingleton<IVcsProvider, GitHubProvider>();
215223
}
216224
}

src/GitReleaseManager.Core/MappingProfiles/GitHubProfile.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ public GitHubProfile()
4040
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.GetProperty("url").GetString()))
4141
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => src.GetProperty("url").GetString().Contains("/pull/", StringComparison.OrdinalIgnoreCase)))
4242
.ForMember(dest => dest.User, act => act.MapFrom(src => src.GetProperty("author")))
43-
.ForMember(dest => dest.Labels, act => act.MapFrom(src => src.GetProperty("labels")))
44-
.ForMember(dest => dest.LinkedIssues, act => act.MapFrom(src => src.GetProperty("linked_issues")))
43+
.ForMember(dest => dest.Labels, act => act.MapFrom(src => src.GetJsonElement("labels.nodes").EnumerateArray()))
4544
.ReverseMap();
4645

4746
CreateMap<JsonElement, Model.Label>()
@@ -52,7 +51,7 @@ public GitHubProfile()
5251

5352
CreateMap<JsonElement, Model.User>()
5453
.ForMember(dest => dest.Login, act => act.MapFrom(src => src.GetProperty("login").GetString()))
55-
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.GetProperty("resourcePath").GetString()))
54+
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => $"https://github.com{src.GetProperty("resourcePath").GetString()}")) // The resourcePath contains a value similar to "/jericho". That's why we must manually prepend "https://github.com
5655
.ForMember(dest => dest.AvatarUrl, act => act.MapFrom(src => src.GetProperty("avatarUrl").GetString()))
5756
.ReverseMap();
5857
}

src/GitReleaseManager.Core/Provider/GitHubProvider.cs

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using GitReleaseManager.Core.Extensions;
99
using GraphQL.Client.Abstractions;
1010
using GraphQL.Client.Http;
11-
using GraphQL.Client.Serializer.SystemTextJson;
1211
using Octokit;
1312
using ApiException = GitReleaseManager.Core.Exceptions.ApiException;
1413
using ForbiddenException = GitReleaseManager.Core.Exceptions.ForbiddenException;
@@ -53,11 +52,19 @@ query ClosingIssuesAndPullRequests($repoName: String!, $repoOwner: String!, $iss
5352
avatarUrl
5453
resourcePath
5554
}
56-
closedByPullRequestsReferences(includeClosedPrs: true, first: $pageSize) {
55+
closedByPullRequestsReferences(userLinkedOnly: false, includeClosedPrs: true, first: $pageSize) {
5756
nodes {
58-
number
59-
id
6057
title
58+
id
59+
number
60+
url
61+
labels(first: 100) {
62+
nodes {
63+
name
64+
color
65+
description
66+
}
67+
}
6168
author {
6269
login
6370
avatarUrl
@@ -69,10 +76,19 @@ query ClosingIssuesAndPullRequests($repoName: String!, $repoOwner: String!, $iss
6976
pullRequest(number: $issueNumber) {
7077
number
7178
title
72-
closingIssuesReferences(first: $pageSize) {
79+
closingIssuesReferences(userLinkedOnly: false, first: $pageSize) {
7380
nodes {
74-
number
7581
title
82+
id
83+
number
84+
url
85+
labels(first: 100) {
86+
nodes {
87+
name
88+
color
89+
description
90+
}
91+
}
7692
author {
7793
login
7894
avatarUrl
@@ -92,13 +108,11 @@ public GitHubProvider(IGitHubClient gitHubClient, IMapper mapper)
92108
{
93109
_gitHubClient = gitHubClient;
94110
_mapper = mapper;
111+
}
95112

96-
var graphQLClient = new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri("https://api.github.com/graphql") }, new SystemTextJsonSerializer());
97-
if (!string.IsNullOrEmpty(_gitHubClient?.Connection?.Credentials?.Password))
98-
{
99-
graphQLClient.HttpClient.DefaultRequestHeaders.Add("Authorization", $"bearer {_gitHubClient.Connection.Credentials.Password}");
100-
}
101-
113+
public GitHubProvider(IGitHubClient gitHubClient, IMapper mapper, IGraphQLClient graphQLClient)
114+
: this(gitHubClient, mapper)
115+
{
102116
_graphQLClient = graphQLClient;
103117
}
104118

@@ -423,8 +437,9 @@ public string GetIssueType(Issue issue)
423437
return issue.IsPullRequest ? "Pull Request" : "Issue";
424438
}
425439

426-
public async Task<IEnumerable<Issue>> GetLinkedIssuesAsync(string owner, string repository, Issue issue)
440+
public async Task<Issue[]> GetLinkedIssuesAsync(string owner, string repository, Issue issue)
427441
{
442+
ArgumentNullException.ThrowIfNull(_graphQLClient, nameof(_graphQLClient));
428443
ArgumentNullException.ThrowIfNull(issue, nameof(issue));
429444

430445
var request = new GraphQLHttpRequest
@@ -441,21 +456,16 @@ public async Task<IEnumerable<Issue>> GetLinkedIssuesAsync(string owner, string
441456

442457
var graphQLResponse = await _graphQLClient.SendQueryAsync<dynamic>(request).ConfigureAwait(false);
443458

444-
var rootNode = (JsonElement)graphQLResponse.Data;
445-
var issueNode = rootNode.GetFirstJsonElement(new[] { "repository.issue", "repository.pullRequest" });
446-
447-
if (issueNode.ValueKind == JsonValueKind.Null || issueNode.ValueKind == JsonValueKind.Undefined)
459+
var nodes = ((JsonElement)graphQLResponse.Data).GetFirstJsonElement(new[]
448460
{
449-
throw new NotFoundException($"Unable to find issue/pull request {issue.PublicNumber}");
450-
}
451-
452-
var nodes = issueNode.GetFirstJsonElement(new[] { "closedByPullRequestsReferences.nodes", "closingIssuesReferences.nodes" });
461+
"repository.issue.closedByPullRequestsReferences.nodes", // If issue.PublicNumber represents an issue, retrieve the linked PRs
462+
"repository.pullRequest.closingIssuesReferences.nodes", // If issue.PublicNumber represents a PR, retrieve the linked issues
463+
});
453464

454-
var linkedIssues = new List<Issue>();
455-
foreach (var node in nodes.EnumerateArray())
456-
{
457-
linkedIssues.Add(_mapper.Map<Issue>(node));
458-
}
465+
using var enumerator = nodes.EnumerateArray();
466+
var linkedIssues = enumerator
467+
.Select(element => _mapper.Map<Issue>(element))
468+
.ToArray();
459469

460470
return linkedIssues;
461471
}

src/GitReleaseManager.Core/Provider/GitLabProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,21 +396,21 @@ public string GetIssueType(Issue issue)
396396
return issue.IsPullRequest ? "Merge Request" : "Issue";
397397
}
398398

399-
public Task<IEnumerable<Issue>> GetLinkedIssuesAsync(string owner, string repository, Issue issue)
399+
public Task<Issue[]> GetLinkedIssuesAsync(string owner, string repository, Issue issue)
400400
{
401401
return ExecuteAsync(() =>
402402
{
403403
if (issue.IsPullRequest)
404404
{
405405
var closes = _gitLabClient.MergeRequests.ClosesIssues(issue.PublicNumber);
406406
var issues = _mapper.Map<IEnumerable<Issue>>(closes);
407-
return Task.FromResult(issues);
407+
return Task.FromResult(issues.ToArray());
408408
}
409409
else
410410
{
411411
var relatedTo = _gitLabClient.Issues.RelatedTo(GetGitLabProjectId(owner, repository), issue.PublicNumber);
412412
var issues = _mapper.Map<IEnumerable<Issue>>(relatedTo);
413-
return Task.FromResult(issues);
413+
return Task.FromResult(issues.ToArray());
414414
}
415415
});
416416
}

src/GitReleaseManager.Core/Provider/IVcsProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ public interface IVcsProvider
5050

5151
string GetIssueType(Issue issue);
5252

53-
Task<IEnumerable<Issue>> GetLinkedIssuesAsync(string owner, string repository, Issue issue);
53+
Task<Issue[]> GetLinkedIssuesAsync(string owner, string repository, Issue issue);
5454
}
5555
}

src/GitReleaseManager.Core/ReleaseNotes/ReleaseNotesBuilder.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,22 @@ public async Task<string> BuildReleaseNotesAsync(string user, string repository,
7171

7272
foreach (var issue in distinctValidIssues)
7373
{
74-
var linkedIssues = await _vcsProvider.GetLinkedIssuesAsync(_user, _repository, issue).ConfigureAwait(false);
75-
issue.LinkedIssues = linkedIssues?.ToList().AsReadOnly() ?? Array.AsReadOnly(Array.Empty<Issue>());
74+
// Linked issues are only necessary for figuring out who contributed to a given issue.
75+
// Therefore, we only need to fetch linked issues if IncludeContributors is enabled.
76+
if (_configuration.Create.IncludeContributors)
77+
{
78+
var linkedIssues = await _vcsProvider.GetLinkedIssuesAsync(_user, _repository, issue).ConfigureAwait(false);
79+
issue.LinkedIssues = Array.AsReadOnly(linkedIssues ?? Array.Empty<Issue>());
80+
}
81+
else
82+
{
83+
issue.LinkedIssues = Array.AsReadOnly(Array.Empty<Issue>());
84+
}
7685
}
7786

78-
var contributors = GetContributors(distinctValidIssues);
87+
var contributors = _configuration.Create.IncludeContributors
88+
? GetContributors(distinctValidIssues)
89+
: Array.Empty<User>();
7990

8091
var milestoneQueryString = _vcsProvider.GetMilestoneQueryString();
8192

@@ -88,7 +99,7 @@ public async Task<string> BuildReleaseNotesAsync(string user, string repository,
8899
},
89100
Contributors = new
90101
{
91-
Count = contributors.Count,
102+
Count = contributors.Length,
92103
Items = contributors,
93104
},
94105
Commits = new
@@ -128,7 +139,7 @@ private Dictionary<string, List<Issue>> GetIssuesDict(List<Issue> issues)
128139
return issuesByLabel;
129140
}
130141

131-
private static List<User> GetContributors(IEnumerable<Issue> issues)
142+
private static User[] GetContributors(IEnumerable<Issue> issues)
132143
{
133144
var contributors = issues.Select(i => i.User);
134145
var linkedContributors = issues.SelectMany(i => i.LinkedIssues).Select(i => i.User);
@@ -137,7 +148,7 @@ private static List<User> GetContributors(IEnumerable<Issue> issues)
137148
.Union(linkedContributors)
138149
.Where(u => u != null)
139150
.DistinctBy(u => u.Login)
140-
.ToList();
151+
.ToArray();
141152

142153
return allContributors;
143154
}

src/GitReleaseManager.IntegrationTests/GitHubProviderIntegrationTests.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
using AutoMapper;
55
using GitReleaseManager.Core;
66
using GitReleaseManager.Core.Provider;
7+
using GraphQL.Client.Abstractions;
8+
using GraphQL.Client.Http;
9+
using GraphQL.Client.Serializer.SystemTextJson;
710
using NUnit.Framework;
811
using Octokit;
912
using Shouldly;
@@ -22,6 +25,7 @@ public class GitHubProviderIntegrationTests
2225

2326
private GitHubProvider _gitHubProvider;
2427
private IGitHubClient _gitHubClient;
28+
private IGraphQLClient _graphQlClient;
2529
private IMapper _mapper;
2630

2731
private string _token;
@@ -33,16 +37,25 @@ public class GitHubProviderIntegrationTests
3337
[OneTimeSetUp]
3438
public void OneTimeSetUp()
3539
{
36-
_token = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
40+
_token = Environment.GetEnvironmentVariable("GITTOOLS_GITHUB_TOKEN");
3741

3842
if (string.IsNullOrWhiteSpace(_token))
3943
{
4044
Assert.Inconclusive("Unable to locate credentials for accessing GitHub API");
4145
}
4246

47+
_graphQlClient = new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri("https://api.github.com/graphql") }, new SystemTextJsonSerializer());
48+
((GraphQLHttpClient)_graphQlClient).HttpClient.DefaultRequestHeaders.Add("Authorization", $"bearer {_token}");
49+
4350
_mapper = AutoMapperConfiguration.Configure();
4451
_gitHubClient = new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(_token) };
45-
_gitHubProvider = new GitHubProvider(_gitHubClient, _mapper);
52+
_gitHubProvider = new GitHubProvider(_gitHubClient, _mapper, _graphQlClient);
53+
}
54+
55+
[OneTimeTearDown]
56+
public void OneTimeTearDown()
57+
{
58+
((IDisposable)_graphQlClient)?.Dispose();
4659
}
4760

4861
[Test]
@@ -108,20 +121,21 @@ public async Task GetLinkedIssues()
108121
// Assert that issue 113 in the GitTools/GitReleaseManager repo is linked to pull request 369
109122
var result0 = await _gitHubProvider.GetLinkedIssuesAsync("GitTools", "GitReleaseManager", new Issue() { PublicNumber = 113 }).ConfigureAwait(false);
110123
Assert.That(result0, Is.Not.Null);
111-
Assert.That(result0.Count(), Is.EqualTo(1));
124+
Assert.That(result0.Length, Is.EqualTo(1));
112125
Assert.That(result0.Count(r => r.PublicNumber == 369), Is.EqualTo(1));
113126

114-
// Assert that pull request 43 in the jericho/_testing repo is linked to issues 107 and 108
127+
// Assert that pull request 43 in the jericho/_testing repo is linked to issues 42, 107 and 108
115128
var result1 = await _gitHubProvider.GetLinkedIssuesAsync("jericho", "_testing", new Issue() { PublicNumber = 43 }).ConfigureAwait(false);
116129
Assert.That(result1, Is.Not.Null);
117-
Assert.That(result1.Count(), Is.EqualTo(2));
130+
Assert.That(result1.Length, Is.EqualTo(3));
131+
Assert.That(result1.Count(r => r.PublicNumber == 42), Is.EqualTo(1));
118132
Assert.That(result1.Count(r => r.PublicNumber == 107), Is.EqualTo(1));
119133
Assert.That(result1.Count(r => r.PublicNumber == 108), Is.EqualTo(1));
120134

121135
// Assert that issue 108 in the jericho/_testing repo is linked to pull request 7, 43 and 109
122136
var result2 = await _gitHubProvider.GetLinkedIssuesAsync("jericho", "_testing", new Issue() { PublicNumber = 108 }).ConfigureAwait(false);
123137
Assert.That(result2, Is.Not.Null);
124-
Assert.That(result2.Count(), Is.EqualTo(3));
138+
Assert.That(result2.Length, Is.EqualTo(3));
125139
Assert.That(result2.Count(r => r.PublicNumber == 7), Is.EqualTo(1));
126140
Assert.That(result2.Count(r => r.PublicNumber == 43), Is.EqualTo(1));
127141
Assert.That(result2.Count(r => r.PublicNumber == 109), Is.EqualTo(1));

src/GitReleaseManager.IntegrationTests/ReleaseNotesBuilderIntegrationTests.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
using GitReleaseManager.Core.Provider;
1010
using GitReleaseManager.Core.ReleaseNotes;
1111
using GitReleaseManager.Core.Templates;
12+
using GraphQL.Client.Abstractions;
13+
using GraphQL.Client.Http;
14+
using GraphQL.Client.Serializer.SystemTextJson;
1215
using NUnit.Framework;
1316
using Octokit;
1417
using Serilog;
@@ -20,6 +23,7 @@ namespace GitReleaseManager.IntegrationTests
2023
public class ReleaseNotesBuilderIntegrationTests
2124
{
2225
private IGitHubClient _gitHubClient;
26+
private IGraphQLClient _graphQlClient;
2327
#pragma warning disable NUnit1032 // An IDisposable field/property should be Disposed in a TearDown method
2428
private ILogger _logger;
2529
#pragma warning restore NUnit1032 // An IDisposable field/property should be Disposed in a TearDown method
@@ -42,13 +46,16 @@ public void Configure()
4246
}
4347

4448
_gitHubClient = new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(_token) };
49+
_graphQlClient = new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri("https://api.github.com/graphql") }, new SystemTextJsonSerializer());
50+
((GraphQLHttpClient)_graphQlClient).HttpClient.DefaultRequestHeaders.Add("Authorization", $"bearer {_token}");
4551
}
4652

4753
[OneTimeTearDown]
4854
public void TearDown()
4955
{
5056
Log.CloseAndFlush();
5157
(_logger as IDisposable)?.Dispose();
58+
((IDisposable)_graphQlClient)?.Dispose();
5259
}
5360

5461
[Test]
@@ -75,7 +82,7 @@ public async Task SingleMilestone()
7582
? ReleaseTemplates.CONTRIBUTORS_NAME
7683
: ReleaseTemplates.DEFAULT_NAME;
7784

78-
var vcsProvider = new GitHubProvider(_gitHubClient, _mapper);
85+
var vcsProvider = new GitHubProvider(_gitHubClient, _mapper, _graphQlClient);
7986
var releaseNotesBuilder = new ReleaseNotesBuilder(vcsProvider, _logger, fileSystem, configuration, new TemplateFactory(fileSystem, configuration, TemplateKind.Create));
8087
var result = await releaseNotesBuilder.BuildReleaseNotesAsync("GitTools", "GitReleaseManager", "0.12.0", templatePath).ConfigureAwait(false); // 0.12.0 contains a mix of issues and PRs
8188
Debug.WriteLine(result);
@@ -91,7 +98,7 @@ public async Task SingleMilestone3()
9198
var currentDirectory = Environment.CurrentDirectory;
9299
var configuration = ConfigurationProvider.Provide(currentDirectory, fileSystem);
93100

94-
var vcsProvider = new GitHubProvider(_gitHubClient, _mapper);
101+
var vcsProvider = new GitHubProvider(_gitHubClient, _mapper, _graphQlClient);
95102
var releaseNotesBuilder = new ReleaseNotesBuilder(vcsProvider, _logger, fileSystem, configuration, new TemplateFactory(fileSystem, configuration, TemplateKind.Create));
96103
var result = await releaseNotesBuilder.BuildReleaseNotesAsync("Chocolatey", "ChocolateyGUI", "0.13.0", ReleaseTemplates.DEFAULT_NAME).ConfigureAwait(false);
97104
Debug.WriteLine(result);

0 commit comments

Comments
 (0)