Skip to content

Commit a8b0427

Browse files
committed
feat: use graphql for querying
1 parent 31b5ce6 commit a8b0427

2 files changed

Lines changed: 280 additions & 7 deletions

File tree

src/Action/Issue.psm1

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,44 @@
11
Join-Path $PSScriptRoot '..\Helpers.psm1' | Import-Module
22
Join-Path $PSScriptRoot 'Issue' | Get-ChildItem -Filter '*.psm1' | Select-Object -ExpandProperty Fullname | Import-Module
33

4+
function Invoke-GithubGraphQLQuery {
5+
<#
6+
.SYNOPSIS
7+
Execute a GraphQL query to GitHub API.
8+
.PARAMETER Query
9+
GraphQL query string.
10+
.PARAMETER Variables
11+
Hashtable of variables for the query.
12+
#>
13+
param(
14+
[Parameter(Mandatory)]
15+
[String] $Query,
16+
[Hashtable] $Variables
17+
)
18+
19+
$graphqlUrl = 'https://api.github.com/graphql'
20+
$body = @{ 'query' = $Query }
21+
if ($Variables) { $body['variables'] = $Variables }
22+
23+
$parameters = @{
24+
'Headers' = @{
25+
'Authorization' = "Bearer $env:GITHUB_TOKEN"
26+
'Accept' = 'application/json'
27+
}
28+
'Method' = 'Post'
29+
'Uri' = $graphqlUrl
30+
'Body' = (ConvertTo-Json $body -Depth 10)
31+
'ContentType' = 'application/json'
32+
}
33+
34+
Write-Log 'GraphQL Request' $parameters.Uri
35+
36+
$response = Invoke-WebRequest @parameters
37+
$env:GH_REQUEST_COUNTER = ([int] $env:GH_REQUEST_COUNTER) + 1
38+
39+
return $response
40+
}
41+
442
function Test-Hash {
543
param (
644
[Parameter(Mandatory = $true)]
@@ -63,12 +101,56 @@ function Test-Hash {
63101
} else {
64102
Write-Log 'Hash mismatch confirmed.'
65103

66-
$masterBranch = ((Invoke-GithubRequest "repos/$REPOSITORY").Content | ConvertFrom-Json).default_branch
104+
# Use GraphQL to fetch repository info and PRs in parallel
105+
$owner, $repo = $REPOSITORY -split '/'
106+
$graphqlQuery = @"
107+
query(`$owner:String!, `$repo:String!) {
108+
repository(owner:`$owner, name:`$repo) {
109+
defaultBranchRef {
110+
name
111+
}
112+
pullRequests(states:OPEN, first:100, orderBy:{field:UPDATED_AT, direction:DESC}) {
113+
nodes {
114+
number
115+
title
116+
body
117+
}
118+
}
119+
}
120+
rateLimit {
121+
remaining
122+
}
123+
}
124+
"@
125+
126+
$masterBranch = $null
127+
$prs = $null
128+
129+
try {
130+
Write-Log 'Attempting GraphQL query for repository and PRs...'
131+
$response = Invoke-GithubGraphQLQuery -Query $graphqlQuery -Variables @{
132+
owner = $owner
133+
repo = $repo
134+
}
135+
136+
$content = $response.Content | ConvertFrom-Json
137+
if ($content.data -and $content.data.repository) {
138+
$masterBranch = $content.data.repository.defaultBranchRef.name
139+
$prs = $content.data.repository.pullRequests.nodes
140+
Write-Log "GraphQL query succeeded. Remaining rate limit: $($content.data.rateLimit.remaining)"
141+
} else {
142+
throw 'GraphQL returned no data'
143+
}
144+
} catch {
145+
Write-Log "GraphQL query failed, falling back to REST API: $($_.Exception.Message)"
146+
# Fallback to REST API
147+
$masterBranch = ((Invoke-GithubRequest "repos/$REPOSITORY").Content | ConvertFrom-Json).default_branch
148+
$prs = (Invoke-GithubRequest "repos/$REPOSITORY/pulls?state=open&base=$masterBranch&sorting=updated").Content | ConvertFrom-Json
149+
}
150+
67151
$message = @('You are right. Thank you for reporting.')
68152
# TODO: Post labels at the end of function
69153
Add-Label -ID $IssueID -Label 'verified', 'hash-fix-needed'
70-
$prs = (Invoke-GithubRequest "repos/$REPOSITORY/pulls?state=open&base=$masterBranch&sorting=updated").Content | ConvertFrom-Json
71-
$titleToBePosted = "$manifestNameAsInBucket@$($man.version): Fix hash"
72154
$prs = $prs | Where-Object { $_.title -eq $titleToBePosted }
73155

74156
# There is alreay PR for
@@ -88,9 +170,16 @@ function Test-Hash {
88170
Invoke-GithubRequest "repos/$REPOSITORY/pulls/$prID" -Method Patch -Body @{ 'body' = (@("- Closes #$IssueID", $pr.body) -join "`r`n") }
89171
Add-Label -ID $IssueID -Label 'duplicate'
90172
} else {
91-
# Check if default branch is protected
92-
if (((Invoke-GithubRequest "repos/$REPOSITORY/branches/$masterBranch").Content | ConvertFrom-Json).protected) {
93-
Write-Log 'PR - Create new branch and post PR'
173+
# Check if default branch is protected (need REST API for this as GraphQL doesn't expose it)
174+
$isProtected = $false
175+
try {
176+
$isProtected = ((Invoke-GithubRequest "repos/$REPOSITORY/branches/$masterBranch").Content | ConvertFrom-Json).protected
177+
} catch {
178+
Write-Log "Failed to check branch protection status: $($_.Exception.Message)"
179+
$isProtected = $false
180+
}
181+
182+
if ($isProtected) {
94183

95184
$branch = "$manifestNameAsInBucket-hash-fix-$(Get-Random -Maximum 258258258)"
96185

src/Github.psm1

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,189 @@
11
Join-Path $PSScriptRoot 'Helpers.psm1' | Import-Module
22

3+
function Invoke-GithubGraphQL {
4+
<#
5+
.SYNOPSIS
6+
Invoke authenticated GitHub GraphQL API request with parallel query support and fallback.
7+
.PARAMETER Query
8+
GraphQL query string.
9+
.PARAMETER Variables
10+
Hashtable of variables for the GraphQL query.
11+
.PARAMETER UseFallback
12+
If set, falls back to REST API on GraphQL failure.
13+
.PARAMETER MaxRetries
14+
Maximum number of retry attempts on rate limit. Default is 3.
15+
.EXAMPLE
16+
Invoke-GithubGraphQL -Query 'query { viewer { login } }'
17+
#>
18+
param(
19+
[Parameter(Mandatory, ValueFromPipeline)]
20+
[String] $Query,
21+
[Hashtable] $Variables,
22+
[Switch] $UseFallback,
23+
[Int] $MaxRetries = 3
24+
)
25+
26+
$graphqlUrl = 'https://api.github.com/graphql'
27+
$retryCount = 0
28+
$success = $false
29+
$response = $null
30+
31+
while ($retryCount -lt $MaxRetries -and -not $success) {
32+
try {
33+
$body = @{
34+
'query' = $Query
35+
}
36+
if ($Variables) {
37+
$body['variables'] = $Variables
38+
}
39+
40+
$parameters = @{
41+
'Headers' = @{
42+
'Authorization' = "Bearer $env:GITHUB_TOKEN"
43+
'Accept' = 'application/json'
44+
}
45+
'Method' = 'Post'
46+
'Uri' = $graphqlUrl
47+
'Body' = (ConvertTo-Json $body -Depth 10)
48+
'ContentType' = 'application/json'
49+
}
50+
51+
Write-Log 'GraphQL Request' $parameters.Uri
52+
Write-Log 'GraphQL Query' $Query
53+
54+
$response = Invoke-WebRequest @parameters
55+
$content = $response.Content | ConvertFrom-Json
56+
57+
if ($content.errors) {
58+
Write-Log 'GraphQL Errors' ($content.errors | ConvertTo-Json -Depth 10)
59+
60+
# Check for rate limit errors
61+
$isRateLimit = $content.errors | Where-Object { $_.type -eq 'RATE_LIMITED' }
62+
if ($isRateLimit) {
63+
$retryCount++
64+
if ($retryCount -lt $MaxRetries) {
65+
$waitTime = [Math]::Pow(2, $retryCount) * 3
66+
Write-Log "Rate limit hit, waiting $waitTime seconds before retry ($retryCount/$MaxRetries)"
67+
Start-Sleep -Seconds $waitTime
68+
continue
69+
} else {
70+
throw "Rate limit exceeded after $MaxRetries retries"
71+
}
72+
}
73+
74+
throw "GraphQL query failed: $($content.errors[0].message)"
75+
}
76+
77+
$env:GH_REQUEST_COUNTER = ([int] $env:GH_REQUEST_COUNTER) + 1
78+
$success = $true
79+
80+
return $response
81+
} catch {
82+
Write-Log "GraphQL request failed: $($_.Exception.Message)"
83+
84+
$retryCount++
85+
if ($retryCount -lt $MaxRetries -and $_.Exception.Message -match 'rate limit|timeout|temporary') {
86+
$waitTime = [Math]::Pow(2, $retryCount) * 3
87+
Write-Log "Retrying in $waitTime seconds..."
88+
Start-Sleep -Seconds $waitTime
89+
} else {
90+
throw
91+
}
92+
}
93+
}
94+
95+
if (-not $success) {
96+
throw "GraphQL request failed after $MaxRetries retries: $($response | Out-String)"
97+
}
98+
}
99+
100+
function Invoke-GithubGraphQLParallel {
101+
<#
102+
.SYNOPSIS
103+
Execute multiple GraphQL queries in parallel to avoid rate limits.
104+
.PARAMETER Queries
105+
Array of hashtables containing Query and Variables.
106+
.PARAMETER UseFallback
107+
If set, falls back to REST API on GraphQL failure.
108+
.EXAMPLE
109+
$queries = @(
110+
@{ Query = 'query($owner:String!, $name:String!) { repository(owner:$owner, name:$name) { defaultBranch } }'; Variables = @{ owner = 'octocat'; name = 'Hello-World' } }
111+
)
112+
Invoke-GithubGraphQLParallel -Queries $queries
113+
#>
114+
param(
115+
[Parameter(Mandatory)]
116+
[Hashtable[]] $Queries,
117+
[Switch] $UseFallback
118+
)
119+
120+
$results = @()
121+
$errors = @()
122+
123+
# Use Runspaces for parallel execution
124+
$runspacePool = [runspacefactory]::CreateRunspacePool(1, [Math]::Min(5, $Queries.Count))
125+
$runspacePool.Open()
126+
127+
$jobs = @()
128+
foreach ($q in $Queries) {
129+
$powershell = [powershell]::Create()
130+
$powershell.RunspacePool = $runspacePool
131+
$powershell.AddScript({
132+
param($Query, $Variables, $Token)
133+
$env:GITHUB_TOKEN = $Token
134+
$body = @{ 'query' = $Query }
135+
if ($Variables) { $body['variables'] = $Variables }
136+
137+
$parameters = @{
138+
'Headers' = @{ 'Authorization' = "Bearer $Token" }
139+
'Method' = 'Post'
140+
'Uri' = 'https://api.github.com/graphql'
141+
'Body' = (ConvertTo-Json $body -Depth 10)
142+
'ContentType' = 'application/json'
143+
}
144+
145+
try {
146+
$response = Invoke-WebRequest @parameters
147+
$env:GH_REQUEST_COUNTER = ([int] $env:GH_REQUEST_COUNTER) + 1
148+
return @{ Success = $true; Data = $response }
149+
} catch {
150+
return @{ Success = $false; Error = $_.Exception.Message }
151+
}
152+
}).AddArgument($q.Query).AddArgument($q.Variables).AddArgument($env:GITHUB_TOKEN) > $null
153+
154+
$jobs += @{
155+
PowerShell = $powershell
156+
AsyncResult = $powershell.BeginInvoke()
157+
Query = $q
158+
}
159+
}
160+
161+
foreach ($job in $jobs) {
162+
try {
163+
$result = $job.PowerShell.EndInvoke($job.AsyncResult)
164+
if ($result.Success) {
165+
$results += $result.Data
166+
} else {
167+
Write-Log "Parallel query failed: $($result.Error)"
168+
$errors += @{ Query = $job.Query; Error = $result.Error }
169+
}
170+
} finally {
171+
$job.PowerShell.Dispose()
172+
}
173+
}
174+
175+
$runspacePool.Close()
176+
$runspacePool.Dispose()
177+
178+
if ($errors.Count -gt 0 -and $UseFallback) {
179+
Write-Log "Some GraphQL queries failed, falling back to REST API"
180+
return @{ Results = $results; Errors = $errors; FallbackUsed = $true }
181+
}
182+
183+
return @{ Results = $results; Errors = $errors; FallbackUsed = $false }
184+
}
185+
186+
3187
function Invoke-GithubRequest {
4188
<#
5189
.SYNOPSIS
@@ -313,5 +497,5 @@ function Get-LogURL {
313497
return $logURL
314498
}
315499

316-
Export-ModuleMember -Function Invoke-GithubRequest, Add-Comment, Get-AllChangedFilesInPR, New-Issue, Close-Issue, `
500+
Export-ModuleMember -Function Invoke-GithubRequest, Invoke-GithubGraphQL, Invoke-GithubGraphQLParallel, Add-Comment, Get-AllChangedFilesInPR, New-Issue, Close-Issue, `
317501
Add-Label, Remove-Label, Get-RateLimit, Get-JobID, Get-LogURL

0 commit comments

Comments
 (0)