Skip to content

Instantly share code, notes, and snippets.

@EitanBlumin
Last active October 15, 2023 15:26
Show Gist options
  • Save EitanBlumin/f4ead30a188e04c806f3f6bebf20fcf0 to your computer and use it in GitHub Desktop.
Save EitanBlumin/f4ead30a188e04c806f3f6bebf20fcf0 to your computer and use it in GitHub Desktop.
Import GitHub Issues from Trello Cards using Powershell ( https://eitanblumin.com/2019/05/07/import-github-issues-from-trello-cards-using-powershell/ )
param (
[Parameter(Mandatory)][string] $GitHubToken,
[Parameter(Mandatory)][string] $GitHubOwner,
[Parameter(Mandatory)][string] $GitHubRepo,
[Parameter(Mandatory)][string] $SourceTrelloJsonFile,
[string[]] $TrelloLists,
[bool] $UpdateExistingIssuesByTitle = $true,
[bool] $AddNonExistingIssues = $true,
[bool] $Logging = $true
)
$ErrorActionPreference = "Stop"
# Function for converting Unicode to Ascii strings
function Convert-FromUnicode {
Param([string]$a)
$b=[system.text.encoding]::UTF8.GetBytes($a)
$c=[system.text.encoding]::convert([text.encoding]::UTF8,[text.encoding]::ASCII,$b)
-join [system.text.encoding]::ASCII.GetChars($c)
}
# clear screen for logging
if ($Logging) { cls }
# init empty issue variable
$newIssue = New-Object PSObject
$newIssue | add-member Noteproperty title ""
$newIssue | add-member Noteproperty body ""
$newIssue | add-member Noteproperty labels @("enhancement")
# init empty label variable
$newLabel = New-Object PSObject
$newLabel | add-member Noteproperty name ""
$newLabel | add-member Noteproperty description ""
$newLabel | add-member Noteproperty color ""
# load json file into variable
$j = gc $SourceTrelloJsonFile | ConvertFrom-Json
# save all non-archived lists in a hash variable
$lists = @{}
$j.lists | select id, name, closed | where closed -NE "True" | % {$lists.add($_.id, $_.name)}
# if $TrelloLists was specified, filter only requested trello lists
if ($TrelloLists.Count -gt 0) {
# first, trim whitespaces for each item in the parameter
for ($i = 0; $i -lt $TrelloLists.Count; $i++) {
$TrelloLists[$i] = $TrelloLists[$i].Trim()
}
# next, remove from $lists items that weren't specified in the parameter
# must clone the list first, otherwise we won't be able to modify it while iterating through it
$listsClone = $lists.Clone()
$listsClone.Keys | foreach { if ($TrelloLists -notcontains $lists[$_]) { $lists.Remove($_) } }
}
# save all labels with HEX color representation in a hash variable
$labels = @{}
$j.labels | select name, color | % {$labels.add($_.name, ([Convert]::ToString([System.Drawing.ColorTranslator]::FromHtml($_.color).ToArgb(), 16).ToLower()).Substring(2))}
# add lists as labels
$lists.Keys | foreach { if ($labels.Keys -notcontains $lists[$_]) {
# generate random color for new label and add it
$randcolor = Get-Random -Minimum ([System.Drawing.ColorTranslator]::FromHtml("black").ToArgb()) -Maximum ([System.Drawing.ColorTranslator]::FromHtml("white").ToArgb())
$labels.Add($lists[$_], ([Convert]::ToString($randcolor, 16).ToLower()).Substring(2))
} }
# get existing labels in GitHub and save in an array
$githubUri = "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/labels" + "?access_token=$GitHubToken"
$githubResponse = (Invoke-WebRequest -Uri $githubUri -Method GET).Content | ConvertFrom-Json
$githubLabels = [string[]]@()
$githubResponse | select name | foreach { $githubLabels += $_.name }
# add any missing labels to the GitHub repo
$labels.Keys | foreach {
if ($githubLabels -notcontains $_) {
$currLabel = $newLabel
$currLabel.name = $_
$currLabel.description = "Label imported from Trello"
$currLabel.color = $labels[$_]
$currLabel = $currLabel | ConvertTo-Json
if ($Logging) { "adding missing label: " + $currLabel }
$githubResponse = Invoke-WebRequest -Uri $githubUri -Body $currLabel -Method "POST"
#$githubResponse.Content | ConvertFrom-Json
}
}
# if UpdateExistingIssuesByTitle is enabled, get all existing GitHub issues
if ($UpdateExistingIssuesByTitle -eq $true) {
$page = 1
$githubUri = "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/issues?access_token=$GitHubToken&page=$page"
$githubResponse = (Invoke-WebRequest -Uri $githubUri -Method GET).Content | ConvertFrom-Json
$existingIssues = @()
while ($githubResponse.Count -gt 0) {
if ($Logging) {"loaded github issues page $page : " + $githubResponse.Count + " issue(s)"}
$existingIssues += $githubResponse | foreach {$_} | select url, title, state, body, labels | where state -EQ "open"
$page += 1
$githubUri = "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/issues?access_token=$GitHubToken&page=$page"
$githubResponse = (Invoke-WebRequest -Uri $githubUri -Method GET).Content | ConvertFrom-Json
}
if ($Logging) {"found " + $existingIssues.Count + " issues total `n"}
}
# process per list
foreach ($listid in $lists.Keys) {
if ($Logging) {" `nProcessing List: " + $lists[$listid]}
# inspect cards for current list
foreach ($card in ($j.cards | select name, desc, labels, idList, id, shortUrl, attachments, closed, @{name="list"; expression={$lists[$_.idList]}} | where idList -eq $listid | where closed -NE "True")) {
# init empty variable for issue
$issue = $null
# if updating of existing issues is enabled
if ($UpdateExistingIssuesByTitle -eq $true)
{
# check if issue already exists based on title
$issue = $existingIssues | where title -eq (Convert-FromUnicode $card.name)
if ($issue) {
if (($issue.GetType()).Name -eq "Object[]") { $issue = $issue[0] }
$githubUri = $issue.url + "?access_token=$GitHubToken"
$issue.PSObject.Properties.Remove('url')
$method = "PATCH"
}
}
# if no existing issue was found, and adding of new issues is enabled, init variables accordingly
if ($issue -eq $null -and $AddNonExistingIssues -eq $true) {
$githubUri = "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/issues?access_token=$GitHubToken"
$issue = $newIssue
$issue.title = $card.name
$method = "POST"
}
if ($issue -ne $null) {
# init issue new body
$issue.body = $card.desc
# save checklists as checklists in body
$j.checklists | select id, name, idCard, checkItems | where idCard -EQ $card.id | foreach {
$issue.body += " `n### " + $_.name
foreach ($currCheckItem in $_.checkItems) {
$issue.body += " `n"
if ($currCheckItem.state -eq "incomplete") {
$issue.body += "- [ ] "
} else {
$issue.body += "- [X] "
}
$issue.body += $currCheckItem.name
}
}
# save attachments as unordered list of links in body
if ($card.attachments.Count -ge 1) {
$issue.body += " `n `n## Attachments `n"
}
$card.attachments | select name, url | foreach {
$issue.body += " `n- [" + $_.name + "](" + $_.url + ")"
}
# save original Trello source URL in body
$issue.body += " `n `n> Trello Source: " + $card.shortUrl
# add labels
$issue.labels = @($lists[$listid])
$card.labels | foreach { $issue.labels += $_.name }
$issue.labels = @() + ($issue.labels | select -uniq) # adding @() ensures that single items are still formatted as array
# convert issue object to JSON and send to GitHub
$newIssueBody = $issue | ConvertTo-Json
$newIssueBody = Convert-FromUnicode $newIssueBody
$githubResponse = Invoke-WebRequest -Uri $githubUri -Body $newIssueBody -Method $method
$issue = $githubResponse.Content | ConvertFrom-Json
if ($Logging) {$method + " " + $issue.title + ": " + $issue.url}
} else {
if ($Logging) {"SKIP " + $card.name}
}
}
}
@EitanBlumin
Copy link
Author

$AddNonExistingIssues doesn't work right if not combined with $UpdateExistingIssuesByTitle

Need to fix

@serbasii
Copy link

serbasii commented Jun 15, 2022

% : Unable to find type [System.Drawing.ColorTranslator].
At C:\Users\User\Desktop\trello_cards_to_github_issues.ps1:60 char:34

  • ... me, color | % {$labels.add($_.name, ([Convert]::ToString([System.Draw ...
  •             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : InvalidOperation: (System.Drawing.ColorTranslator:TypeName) [ForEach-Object], RuntimeException
    • FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.ForEachObjectCommand

@Cueball
Copy link

Cueball commented Oct 15, 2023

ForEach-Object: /Users/userid/trello_cards_to_github_issues.ps1:60
Line |
  60 |  … me, color | % {$labels.add($_.name, ([Convert]::ToString([System.Draw …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Exception calling "Add" with "2" argument(s): "Item has already been added. Key in dictionary: ''  Key being added: ''"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment