Skip to content

Commit 4ae2fb2

Browse files
CopilotMalcolmnixon
andcommitted
Add GraphQL releases query with pagination support
Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
1 parent 93cb236 commit 4ae2fb2

File tree

4 files changed

+456
-1
lines changed

4 files changed

+456
-1
lines changed

src/DemaConsulting.BuildMark/RepoConnectors/GitHub/GitHubGraphQLClient.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,78 @@ ... on Commit {
172172
}
173173
}
174174

175+
/// <summary>
176+
/// Gets all releases for a repository using GraphQL with pagination.
177+
/// </summary>
178+
/// <param name="owner">Repository owner.</param>
179+
/// <param name="repo">Repository name.</param>
180+
/// <returns>List of release tag names.</returns>
181+
public async Task<List<string>> GetReleasesAsync(
182+
string owner,
183+
string repo)
184+
{
185+
try
186+
{
187+
var allReleaseTagNames = new List<string>();
188+
string? afterCursor = null;
189+
bool hasNextPage;
190+
191+
// Paginate through all releases
192+
do
193+
{
194+
// Create GraphQL request to get releases for a repository with pagination support
195+
var request = new GraphQLRequest
196+
{
197+
Query = @"
198+
query($owner: String!, $repo: String!, $after: String) {
199+
repository(owner: $owner, name: $repo) {
200+
releases(first: 100, after: $after, orderBy: {field: CREATED_AT, direction: DESC}) {
201+
nodes {
202+
tagName
203+
}
204+
pageInfo {
205+
hasNextPage
206+
endCursor
207+
}
208+
}
209+
}
210+
}",
211+
Variables = new
212+
{
213+
owner,
214+
repo,
215+
after = afterCursor
216+
}
217+
};
218+
219+
// Execute GraphQL query
220+
var response = await _graphqlClient.SendQueryAsync<GetReleasesResponse>(request);
221+
222+
// Extract release tag names from the GraphQL response, filtering out null or invalid values
223+
var pageReleaseTagNames = response.Data?.Repository?.Releases?.Nodes?
224+
.Where(n => !string.IsNullOrEmpty(n.TagName))
225+
.Select(n => n.TagName!)
226+
.ToList() ?? [];
227+
228+
allReleaseTagNames.AddRange(pageReleaseTagNames);
229+
230+
// Check if there are more pages
231+
var pageInfo = response.Data?.Repository?.Releases?.PageInfo;
232+
hasNextPage = pageInfo?.HasNextPage ?? false;
233+
afterCursor = pageInfo?.EndCursor;
234+
}
235+
while (hasNextPage);
236+
237+
// Return list of all release tag names
238+
return allReleaseTagNames;
239+
}
240+
catch
241+
{
242+
// If GraphQL query fails, return empty list
243+
return [];
244+
}
245+
}
246+
175247
/// <summary>
176248
/// Finds issue IDs linked to a pull request via closingIssuesReferences.
177249
/// </summary>

src/DemaConsulting.BuildMark/RepoConnectors/GitHub/GitHubGraphQLTypes.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,33 @@ internal record CommitHistoryData(
109109
/// <param name="Oid">Git object ID (SHA).</param>
110110
internal record CommitNode(
111111
string? Oid);
112+
113+
/// <summary>
114+
/// Response for getting releases from a repository.
115+
/// </summary>
116+
/// <param name="Repository">Repository data containing release information.</param>
117+
internal record GetReleasesResponse(
118+
ReleaseRepositoryData? Repository);
119+
120+
/// <summary>
121+
/// Repository data containing releases information.
122+
/// </summary>
123+
/// <param name="Releases">Releases connection data.</param>
124+
internal record ReleaseRepositoryData(
125+
ReleasesConnectionData? Releases);
126+
127+
/// <summary>
128+
/// Releases connection data containing nodes and page info.
129+
/// </summary>
130+
/// <param name="Nodes">Release nodes.</param>
131+
/// <param name="PageInfo">Pagination information.</param>
132+
internal record ReleasesConnectionData(
133+
List<ReleaseNode>? Nodes,
134+
PageInfo? PageInfo);
135+
136+
/// <summary>
137+
/// Release node containing release information.
138+
/// </summary>
139+
/// <param name="TagName">Tag name associated with the release.</param>
140+
internal record ReleaseNode(
141+
string? TagName);

src/DemaConsulting.BuildMark/RepoConnectors/GitHubRepoConnector.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ private static async Task<GitHubData> FetchGitHubDataAsync(
166166
{
167167
// Fetch all data from GitHub in parallel
168168
var commitsTask = GetAllCommitsAsync(graphqlClient, owner, repo, branch);
169-
var releasesTask = client.Repository.Release.GetAll(owner, repo);
169+
var releasesTask = GetAllReleasesAsync(graphqlClient, owner, repo);
170170
var tagsTask = client.Repository.GetAllTags(owner, repo);
171171
var pullRequestsTask = client.PullRequest.GetAllForRepository(owner, repo, new PullRequestRequest { State = ItemStateFilter.All });
172172
var issuesTask = client.Issue.GetAllForRepository(owner, repo, new RepositoryIssueRequest { State = ItemStateFilter.All });
@@ -572,6 +572,30 @@ private static async Task<IReadOnlyList<Commit>> GetAllCommitsAsync(
572572
return commitShas.Select(sha => new Commit(sha)).ToList();
573573
}
574574

575+
/// <summary>
576+
/// Gets all releases for a repository using GraphQL pagination.
577+
/// </summary>
578+
/// <param name="graphqlClient">GitHub GraphQL client.</param>
579+
/// <param name="owner">Repository owner.</param>
580+
/// <param name="repo">Repository name.</param>
581+
/// <returns>List of all releases.</returns>
582+
private static async Task<IReadOnlyList<Release>> GetAllReleasesAsync(
583+
GitHubGraphQLClient graphqlClient,
584+
string owner,
585+
string repo)
586+
{
587+
// Fetch all release tag names for the repository using GraphQL
588+
var releaseTagNames = await graphqlClient.GetReleasesAsync(owner, repo);
589+
590+
// Convert tag names to Release objects using JSON deserialization
591+
// This creates minimal Release objects with only TagName populated
592+
return releaseTagNames.Select(tagName =>
593+
{
594+
var json = $$"""{"tag_name":"{{tagName}}"}""";
595+
return System.Text.Json.JsonSerializer.Deserialize<Release>(json) ?? throw new InvalidOperationException($"Failed to create Release object for tag {tagName}");
596+
}).ToList();
597+
}
598+
575599
/// <summary>
576600
/// Gets commits in the range from fromHash (exclusive) to toHash (inclusive).
577601
/// </summary>

0 commit comments

Comments
 (0)