Skip to content

Commit 12de07f

Browse files
authored
Merge pull request #669 from glennsarti/automate-projects
(maint) Adds tool to update puppet-vscode projects
2 parents 1075ca6 + 1171713 commit 12de07f

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed

tools/SyncProjects.ps1

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
[CmdletBinding()]
2+
param()
3+
4+
# Needs PowerShell 5.1+
5+
$ErrorActionPreference = 'Stop'
6+
7+
$script:GithubToken = $ENV:GITHUB_TOKEN
8+
$script:GithubUsername = $ENV:GITHUB_USERNAME
9+
10+
if ($null -eq $script:GithubToken) { Throw "This script requires the GITHUB_TOKEN environment variable to be set"; Exit 1 }
11+
if ($null -eq $script:GithubUsername) { Throw "This script requires the GITHUB_USERNAME environment variable to be set"; Exit 1 }
12+
13+
Function Invoke-GithubAPI {
14+
[CmdletBinding()]
15+
16+
Param(
17+
[Parameter(Mandatory = $True, ParameterSetName = 'RelativeURI')]
18+
[String]$RelativeUri,
19+
20+
[Parameter(Mandatory = $True, ParameterSetName = 'AbsoluteURI')]
21+
[String]$AbsoluteUri,
22+
23+
[Parameter(Mandatory = $False)]
24+
[switch]$Raw,
25+
26+
[String]$Method = 'GET',
27+
28+
[Object]$Body = $null
29+
)
30+
31+
if ($PsCmdlet.ParameterSetName -eq 'RelativeURI') {
32+
$uri = "https://api.github.com" + $RelativeUri
33+
}
34+
else {
35+
$uri = $AbsoluteUri
36+
}
37+
38+
$result = ""
39+
40+
$oldPreference = $ProgressPreference
41+
42+
$auth = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($script:GithubUsername + ':' + $script:GithubToken));
43+
44+
$ProgressPreference = 'SilentlyContinue'
45+
$Headers = @{
46+
'Accept' = 'application/vnd.github.inertia-preview+json' # Needed for project API
47+
'Authorization' = $auth;
48+
}
49+
$splat = @{
50+
'Uri' = $uri
51+
'UseBasicParsing' = $True
52+
'Headers' = $Headers
53+
'Method' = $Method
54+
}
55+
if ($null -ne $Body) { $splat['Body'] = ConvertTo-Json $Body -Compress }
56+
try {
57+
$result = Invoke-WebRequest @splat -ErrorAction 'Stop'
58+
} catch {
59+
Write-Verbose "Invoke-WebRequest arguments were $($splat | ConvertTo-JSON -Depth 10)"
60+
Throw $_
61+
}
62+
$ProgressPreference = $oldPreference
63+
64+
if ($Raw) {
65+
Write-Output $result
66+
}
67+
else {
68+
Write-Output $result.Content | ConvertFrom-JSON
69+
}
70+
}
71+
72+
Function Invoke-GithubAPIWithPaging($RelativeUri) {
73+
$response = Invoke-GithubAPI -RelativeUri $RelativeUri -Raw
74+
$result = $response.Content | ConvertFrom-Json
75+
if (!($result -is [Array])) { $result = @($result) }
76+
$nextLink = $response.RelationLink.next
77+
do {
78+
if ($null -ne $nextLink) {
79+
$response = Invoke-GithubAPI -AbsoluteUri $nextLink -Raw
80+
$result = $result + ($response.Content | ConvertFrom-Json)
81+
$nextLink = $response.RelationLink.next
82+
}
83+
}
84+
while ($null -ne $nextLink)
85+
86+
Write-Output $result
87+
}
88+
89+
Function Get-VSCodeProjects {
90+
Invoke-GithubAPIWithPaging -RelativeUri '/repos/puppetlabs/puppet-vscode/projects?state=open'
91+
}
92+
93+
Function Convert-GHIssueToNote {
94+
param(
95+
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
96+
[Object]$Issue,
97+
98+
[String]$Prefix = ""
99+
)
100+
101+
Begin {
102+
if ($Prefix -ne "") { $Prefix = $Prefix + " " }
103+
}
104+
105+
Process {
106+
# We only add the title, because github fills in the description as a rich-link
107+
$hash = @{
108+
'cardId' = -1
109+
'note' = @"
110+
[$($Prefix)GitHub Issue $($issue.number)]($($issue.html_url))
111+
112+
$($issue.title)
113+
"@
114+
'expectedColumn' = 'To do'
115+
}
116+
117+
# Consider all open pull requests as "in progress"
118+
if ($Issue.pull_request -ne $null) { $hash['expectedColumn'] = 'In progress' }
119+
if ($Issue.state -eq 'closed') { $hash['expectedColumn'] = 'Done' }
120+
121+
Write-Output ([PSCustomObject]$hash)
122+
}
123+
}
124+
125+
Function Convert-GHCardToNote {
126+
param(
127+
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
128+
[Object]$Card,
129+
130+
[String]$ColumnName
131+
)
132+
133+
Process {
134+
# Write-Host ($Card | ConvertTo-JSON) -ForegroundColor Red
135+
$hash = @{
136+
'foundMatchingIssue' = $false
137+
'note' = $Card.note
138+
'id' = $Card.id
139+
'currentColumn' = $ColumnName
140+
'expectedColumn' = '????'
141+
}
142+
Write-Output ([PSCustomObject]$hash)
143+
}
144+
}
145+
146+
Function Resize-String($Value, $MaxLength) {
147+
if ($Value.Length -gt $MaxLength) {
148+
Write-Output ($Value.SubString(0, $MaxLength) + "...")
149+
}
150+
else {
151+
Write-Output $Value
152+
}
153+
}
154+
155+
Function Invoke-ParseVSCodeProject($project) {
156+
if ($project.name -notmatch '^([\w\. ]+) Marketplace Release$') {
157+
Write-Verbose "Project '$($project.name)' is not a release"
158+
return
159+
}
160+
161+
Write-Verbose "Parsing Project $($project.name) ..."
162+
$ProjectVersion = $Matches[1]
163+
$MilestoneName = $ProjectVersion
164+
165+
$EditorServicesMilestone = ''
166+
if ($project.body -match '(?m)editor-services:\s+([0-9.]+)') {
167+
$EditorServicesMilestone = $Matches[1]
168+
Write-Verbose "Using Editor Services Milestone $EditorServicesMilestone"
169+
}
170+
171+
$EditorSyntaxMilestone = ''
172+
if ($project.body -match '(?m)editor-syntax:\s+([0-9.]+)') {
173+
$EditorSyntaxMilestone = $Matches[1]
174+
Write-Verbose "Using Editor Syntax Milestone $EditorSyntaxMilestone"
175+
}
176+
177+
# Get all columns
178+
$ProjectColumns = Invoke-GithubAPIWithPaging -RelativeUri "/projects/$($project.id)/columns"
179+
180+
# Get all cards, including archived
181+
Write-Verbose "Getting existing cards ..."
182+
$ProjectCards = @()
183+
$ColumnIds = @{}
184+
$ProjectColumns | ForEach-Object {
185+
$ColumnName = $_.name
186+
$ColumnIds[$_.name] = $_.id
187+
Invoke-GithubAPIWithPaging -RelativeUri "/projects/columns/$($_.Id)/cards?archived_state=all" | ForEach-Object { $ProjectCards += (Convert-GHCardToNote -Card $_ -ColumnName $ColumnName) }
188+
}
189+
190+
# Sanity check for columns we need
191+
if ($null -eq $ColumnIds['To do']) { Write-Warning "Project $($Project.number) is missing expected column 'To do'"; Return}
192+
if ($null -eq $ColumnIds['In progress']) { Write-Warning "Project $($Project.number) is missing expected column 'In progress'"; Return}
193+
if ($null -eq $ColumnIds['Done']) { Write-Warning "Project $($Project.number) is missing expected column 'Done'"; Return}
194+
195+
# Get all of the Github issues for the VSCode version milestone
196+
$GHIssues = @()
197+
198+
# puppetlabs/puppet-vscode
199+
Write-Verbose "Getting current issues in the puppet-vscode repo for milestone ${MilestoneName} ..."
200+
$GHIssues += ((Invoke-GithubAPIWithPaging -RelativeUri "/search/issues?q=repo:puppetlabs/puppet-vscode+milestone:`"${MilestoneName}`"").items | Convert-GHIssueToNote -Prefix 'VSCode')
201+
202+
if ($EditorServicesMilestone -ne '') {
203+
Write-Verbose "Getting current issues in the puppet-editor-services repo for milestone ${EditorServicesMilestone} ..."
204+
$GHIssues += ((Invoke-GithubAPIWithPaging -RelativeUri "/search/issues?q=repo:puppetlabs/puppet-editor-services+milestone:`"${EditorServicesMilestone}`"").items | Convert-GHIssueToNote -Prefix 'Editor Services')
205+
}
206+
207+
if ($EditorSyntaxMilestone -ne '') {
208+
Write-Verbose "Getting current issues in the puppet-editor-syntax repo for milestone ${EditorSyntaxMilestone} ..."
209+
$GHIssues += ((Invoke-GithubAPIWithPaging -RelativeUri "/search/issues?q=repo:puppetlabs/puppet-editor-syntax+milestone:`"${EditorSyntaxMilestone}`"").items | Convert-GHIssueToNote -Prefix 'Editor Syntax')
210+
}
211+
212+
Write-Verbose "Matching Issues to Notes ..."
213+
# Match things up
214+
$ProjectCards | ForEach-Object -Process {
215+
$thisCard = $_
216+
# Try and find a matching GHIssue
217+
$GHIssues | Where-Object { $_.cardId -eq -1} | ForEach-Object -Process {
218+
if ($_.note -eq $thisCard.note) {
219+
Write-Verbose "Found a match for card $($thisCard.Id)"
220+
$_.cardId = $thisCard.Id
221+
$thisCard.expectedColumn = $_.expectedColumn
222+
$thisCard.foundMatchingIssue = $true
223+
}
224+
}
225+
if (!$thisCard.foundMatchingIssue) {
226+
$JiraTickets | Where-Object { $_.cardId -eq -1} | ForEach-Object -Process {
227+
if ($_.note -eq $thisCard.note) {
228+
Write-Verbose "Found a match for card $($thisCard.Id)"
229+
$_.cardId = $thisCard.Id
230+
$thisCard.expectedColumn = $_.expectedColumn
231+
$thisCard.foundMatchingIssue = $true
232+
}
233+
}
234+
}
235+
}
236+
237+
# Create new cards from GH Issues
238+
$GHIssues | Where-Object { $_.cardId -eq -1 } | ForEach-Object {
239+
$Issue = $_
240+
$body = @{ 'note' = $Issue.note }
241+
Write-Host "Adding card for `"$(Resize-String -Value $Issue.note -MaxLength 50)`""
242+
Invoke-GithubAPI -RelativeUri "/projects/columns/$($ColumnIds[$Issue.expectedColumn])/cards" -Method 'POST' -Body $body | Out-Null
243+
}
244+
245+
# Remove cards which can not be matched
246+
$ProjectCards | Where-Object { -not $_.foundMatchingIssue } | ForEach-Object -Process {
247+
Write-Host "Removing card `"$(Resize-String -Value $_.note -MaxLength 50)`""
248+
Invoke-GithubAPI -RelativeUri "/projects/columns/cards/$($_.id)" -Method 'DELETE' | Out-Null
249+
}
250+
# Move cards which are in the wrong column
251+
$ProjectCards | Where-Object { $_.foundMatchingIssue -and ($_.expectedColumn -ne $_.currentColumn) } | ForEach-Object -Process {
252+
$body = @{ 'position' = 'top'; 'column_id' = $ColumnIds[$_.expectedColumn] }
253+
Write-Host "Moving card `"$(Resize-String -Value $_.note -MaxLength 50)`" to `"$($_.expectedColumn)`""
254+
Invoke-GithubAPI -RelativeUri "/projects/columns/cards/$($_.id)/moves" -Method 'POST' -Body $Body | Out-Null
255+
}
256+
}
257+
258+
Get-VSCodeProjects | ForEach-Object { Invoke-ParseVSCodeProject $_ }

0 commit comments

Comments
 (0)