From 0a35fe31f7a305a4c7774aaf50532ec3b4c39b00 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Fri, 16 Jun 2023 16:50:40 +0100 Subject: [PATCH 1/2] Use issue migration api --- .gitignore | 3 + ado_workitems_to_github_issues.ps1 | 95 +++++++++++++++++++----------- 2 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8db0cba --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +temp_issue_body.txt +temp_comment_body.txt + diff --git a/ado_workitems_to_github_issues.ps1 b/ado_workitems_to_github_issues.ps1 index f9d85bc..3476634 100644 --- a/ado_workitems_to_github_issues.ps1 +++ b/ado_workitems_to_github_issues.ps1 @@ -63,8 +63,6 @@ $wiql = "select [ID], [Title], [System.Tags] from workitems where $closed_wiql [ $query=az boards query --wiql $wiql | ConvertFrom-Json -Remove-Item -Path ./temp_comment_body.txt -ErrorAction SilentlyContinue -Remove-Item -Path ./temp_issue_body.txt -ErrorAction SilentlyContinue $count = 0; ForEach($workitem in $query) { @@ -102,11 +100,8 @@ ForEach($workitem in $query) { } } - $description | Out-File -FilePath ./temp_issue_body.txt -Encoding ASCII; - - $url="[Original Work Item URL](https://dev.azure.com/$ado_org/$ado_project/_workitems/edit/$($workitem.id))" - $url | Out-File -FilePath ./temp_comment_body.txt -Encoding ASCII; - + $gh_comment="[Original Work Item URL](https://dev.azure.com/$ado_org/$ado_project/_workitems/edit/$($workitem.id))" + # use empty string if there is no user is assigned if ( $null -ne $details.fields.{System.AssignedTo}.displayName ) { @@ -119,18 +114,12 @@ ForEach($workitem in $query) { } # create the details table - $ado_details_beginning="`n`n
Original Work Item Details

" + "`n`n" - $ado_details_beginning | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; - $ado_details= "| Created date | Created by | Changed date | Changed By | Assigned To | State | Type | Area Path | Iteration Path|`n|---|---|---|---|---|---|---|---|---|`n" - $ado_details+="| $($details.fields.{System.CreatedDate}) | $($details.fields.{System.CreatedBy}.displayName) | $($details.fields.{System.ChangedDate}) | $($details.fields.{System.ChangedBy}.displayName) | $ado_assigned_to_display_name | $($details.fields.{System.State}) | $($details.fields.{System.WorkItemType}) | $($details.fields.{System.AreaPath}) | $($details.fields.{System.IterationPath}) |`n`n" - $ado_details | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; - $ado_details_end="`n" + "`n

" - $ado_details_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; + $gh_comment+="`n`n
Original Work Item Details

" + "`n`n" + $gh_comment+= "| Created date | Created by | Changed date | Changed By | Assigned To | State | Type | Area Path | Iteration Path|`n|---|---|---|---|---|---|---|---|---|`n" + $gh_comment+="| $($details.fields.{System.CreatedDate}) | $($details.fields.{System.CreatedBy}.displayName) | $($details.fields.{System.ChangedDate}) | $($details.fields.{System.ChangedBy}.displayName) | $ado_assigned_to_display_name | $($details.fields.{System.State}) | $($details.fields.{System.WorkItemType}) | $($details.fields.{System.AreaPath}) | $($details.fields.{System.IterationPath}) |`n`n" + $gh_comment+="`n" + "`n

" # prepare the comment - $original_workitem_json_beginning | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; - $details_json | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; - $original_workitem_json_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; # getting comments if enabled if($gh_add_ado_comments -eq $true) { @@ -140,25 +129,68 @@ ForEach($workitem in $query) { $response = Invoke-RestMethod "https://dev.azure.com/$ado_org/$ado_project/_apis/wit/workItems/$($workitem.id)/comments?api-version=7.1-preview.3" -Method 'GET' -Headers $headers if($response.count -gt 0) { - $ado_comments_details="" - $ado_original_workitem_json_beginning="`n`n
Work Item Comments ($($response.count))

" + "`n`n" - $ado_original_workitem_json_beginning | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; + $gh_comment+="`n`n

Work Item Comments ($($response.count))

" + "`n`n" ForEach($comment in $response.comments) { - $ado_comments_details= "| Created date | Created by | JSON URL |`n|---|---|---|`n" - $ado_comments_details+="| $($comment.createdDate) | $($comment.createdBy.displayName) | [URL]($($comment.url)) |`n`n" - $ado_comments_details+="**Comment text**: $($comment.text)`n`n-----------`n`n" - $ado_comments_details | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; + $gh_comment+= "| Created date | Created by | JSON URL |`n|---|---|---|`n" + $gh_comment+="| $($comment.createdDate) | $($comment.createdBy.displayName) | [URL]($($comment.url)) |`n`n" + $gh_comment+="**Comment text**: $($comment.text)`n`n-----------`n`n" } - $ado_original_workitem_json_end="`n" + "`n

" - $ado_original_workitem_json_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; + $gh_comment+="`n" + "`n

" } } # setting the label on the issue to be the work item type $work_item_type = $details.fields.{System.WorkItemType}.ToLower() - # create the issue - $issue_url=gh issue create --body-file ./temp_issue_body.txt --repo "$gh_org/$gh_repo" --title "$title" --label $work_item_type + $issueHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $issueHeaders.Add("Authorization", "token $gh_pat") + $issueHeaders.Add("Accept", "application/vnd.github.golden-comet-preview+json") + $issueHeaders.Add("Content-Type", "application/json") + + Write-Host "Migrating https://dev.azure.com/$ado_org/$ado_project/_workitems/edit/$($workitem.id)" + + if ([string]::IsNullOrEmpty($description)){ + # Can't have an empty body on this API, so add work item url + $description = "[Original Work Item URL](https://dev.azure.com/$ado_org/$ado_project/_workitems/edit/$($workitem.id))" + } + + $params = @{ + issue = @{ + title = "$title" + body = "$description" + } + comments = @(@{ + body = "$gh_comment" + }) + } | ConvertTo-Json + + Write-Host " Issue creation request: $params"; + $issueMigrateResponse = Invoke-RestMethod https://api.github.com/repos/$gh_org/$gh_repo/import/issues -Method 'POST' -Body $params -Headers $issueHeaders + + Write-Host " Issue creation response: $issueMigrateResponse"; + $issue_url = "" + while($true) { + Write-Host "Sleeping for 1 seconds..." + Start-Sleep -Seconds 1 + $issueCreationResponse = Invoke-RestMethod $issueMigrateResponse.url -Method 'GET' -Headers $issueHeaders -StatusCodeVariable 'statusCode' + + Write-Host " Issue creation response: $issueCreationResponse"; + Write-Host " Status code: $statusCode"; + if ($statusCode -eq 404) { + continue + } + + if ($statusCode -ne 200) { + throw "Issue creation failed with status code $statusCode" + } + + if ($issueCreationResponse.status -eq "imported") { + $issue_url = $issueCreationResponse.issue_url + break + } elseif ($issueCreationResponse.status -eq "failed") { + throw "Issue creation failed with message $issueCreationResponse" + } + } if (![string]::IsNullOrEmpty($issue_url.Trim())) { Write-Host " Issue created: $issue_url"; @@ -176,13 +208,6 @@ ForEach($workitem in $query) { $assigned=gh issue edit $issue_url --add-assignee "$gh_assignee" } - # add the comment - $comment_url=gh issue comment $issue_url --body-file ./temp_comment_body.txt - write-host " comment created: $comment_url" - - Remove-Item -Path ./temp_comment_body.txt -ErrorAction SilentlyContinue - Remove-Item -Path ./temp_issue_body.txt -ErrorAction SilentlyContinue - # Add the tag "copied-to-github" plus a comment to the work item if ($ado_production_run) { $workitemTags = $workitem.fields.'System.Tags'; From 307b27188066438824fb5ac47d14fb19a4ae6b57 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Wed, 13 Nov 2024 16:54:07 -0600 Subject: [PATCH 2/2] feat: convert from [bool] to [switch] --- .github/workflows/migrate-work-items.yml | 19 +++++++++---------- README.md | 14 +++++++------- ado_workitems_to_github_issues.ps1 | 18 ++++++++++++------ 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/.github/workflows/migrate-work-items.yml b/.github/workflows/migrate-work-items.yml index 043ee35..482284f 100644 --- a/.github/workflows/migrate-work-items.yml +++ b/.github/workflows/migrate-work-items.yml @@ -67,27 +67,26 @@ jobs: run: | # run migration script - # cleaning up bools - $ado_migrate_closed_workitems=$false - $ado_production_run=$false - $gh_update_assigned_to=$false - $gh_add_ado_comments=$false + $ado_migrate_closed_workitems_param="" + $ado_production_run_param="" + $gh_update_assigned_to_param="" + $gh_add_ado_comments_param="" if("${{ github.event.inputs.ado_migrate_closed_workitems }}" -eq "true") { - $ado_migrate_closed_workitems=$true + $ado_migrate_closed_workitems_param="-ado_migrate_closed_workitems" } if("${{ github.event.inputs.ado_production_run }}" -eq "true") { - $ado_production_run=$true + $ado_production_run_param="-ado_production_run" } if("${{ github.event.inputs.gh_update_assigned_to }}" -eq "true") { - $gh_update_assigned_to=$true + $gh_update_assigned_to_param="-gh_update_assigned_to" } if("${{ github.event.inputs.gh_add_ado_comments }}" -eq "true") { - $gh_add_ado_comments=$true + $gh_add_ado_comments_param="-gh_add_ado_comments" } - ./ado_workitems_to_github_issues.ps1 -ado_pat "${{ SECRETS.ADO_PAT }}" -ado_org "${{ github.event.inputs.ado-org }}" -ado_project "${{ github.event.inputs.ado-project }}" -ado_area_path "${{ github.event.inputs.ado_area_path }}" -ado_migrate_closed_workitems $ado_migrate_closed_workitems -ado_production_run $ado_production_run -gh_pat "${{ steps.get_installation_token.outputs.token }}" -gh_org "${{ github.event.inputs.gh-org }}" -gh_repo "${{ github.event.inputs.gh-repo }}" -gh_update_assigned_to $gh_update_assigned_to -gh_assigned_to_user_suffix "${{ github.event.inputs.gh_assigned_to_user_suffix }}" -gh_add_ado_comments $gh_add_ado_comments + ./ado_workitems_to_github_issues.ps1 -ado_pat "${{ SECRETS.ADO_PAT }}" -ado_org "${{ github.event.inputs.ado-org }}" -ado_project "${{ github.event.inputs.ado-project }}" -ado_area_path "${{ github.event.inputs.ado_area_path }}" $ado_migrate_closed_workitems_param $ado_production_run_param -gh_pat "${{ steps.get_installation_token.outputs.token }}" -gh_org "${{ github.event.inputs.gh-org }}" -gh_repo "${{ github.event.inputs.gh-repo }}" $gh_update_assigned_to_param -gh_assigned_to_user_suffix "${{ github.event.inputs.gh_assigned_to_user_suffix }}" $gh_add_ado_comments_param \ No newline at end of file diff --git a/README.md b/README.md index 6bda3c2..5772658 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ PowerShell script to migrate Azure DevOps work items to GitHub Issues - Original work item url - Basic details in a collapsed markdown table - Entire work item as JSON in a collapsed section -7. Creates tag "copied-to-github" and a comment on the ADO work item with `-$ado_production_run $true"`. The tag prevents duplicate copying. +7. Creates tag "copied-to-github" and a comment on the ADO work item with `-$ado_production_run"`. The tag prevents duplicate copying. ### To Do 1. Provide user mapping option @@ -64,21 +64,21 @@ Using the GitHub app might be better so you don't reach a limit on your GitHub a -ado_org "jjohanning0798" ` -ado_project "PartsUnlimited" ` -ado_area_path "PartsUnlimited\migrate" ` - -ado_migrate_closed_workitems $false ` - -ado_production_run $false ` + -ado_migrate_closed_workitems ` + -ado_production_run # IMPORTANT - USING THIS WILL UPDATE THE WORK ITEM IN ADO!` -gh_pat "ghp_xxx" ` -gh_org "joshjohanning-org" ` -gh_repo "migrate-ado-workitems" ` - -gh_update_assigned_to $true ` + -gh_update_assigned_to ` -gh_assigned_to_user_suffix "" ` - -gh_add_ado_comments $true + -gh_add_ado_comments ``` ## Script Options | Parameter | Required | Default | Description | |---------------------------------|----------|----------|---------------------------------------------------------------------------------------------------------------------------------------------| -| `-ado_pat` | Yes | | Azure DevOps Personal Access Token (PAT) with appropriate permissions to read work items (and update, with `-ado_production_run $true`) | +| `-ado_pat` | Yes | | Azure DevOps Personal Access Token (PAT) with appropriate permissions to read work items (and update, with `-ado_production_run`) | | `-ado_org` | Yes | | Azure DevOps organization to migrate from | | `-ado_project` | Yes | | Azure DevOps project to migrate from | | `-ado_area_path` | Yes | | Azure DevOps area path to migrate from - uses the `UNDER` operator | @@ -91,4 +91,4 @@ Using the GitHub app might be better so you don't reach a limit on your GitHub a | `-gh_assigned_to_user_suffix` | No | `""` | Used in conjunction with `-gh_update_assigned_to`, used to suffix the username, e.g. if using GitHub Enterprise Managed User (EMU) instance | | `-gh_add_ado_comments` | No | `$false` | Switch to add ADO comments as a section with the migrated work item | -+ **Note**: With `-gh_update_assigned_to $true`, you/your users will receive a lot of emails from GitHub when the user is assigned to the issue \ No newline at end of file ++ **Note**: With `-gh_update_assigned_to`, you/your users will receive a lot of emails from GitHub when the user is assigned to the issue \ No newline at end of file diff --git a/ado_workitems_to_github_issues.ps1 b/ado_workitems_to_github_issues.ps1 index 3476634..b5790bd 100644 --- a/ado_workitems_to_github_issues.ps1 +++ b/ado_workitems_to_github_issues.ps1 @@ -10,7 +10,13 @@ # - You can modify the WIQL if you want to use a different way to migrate work items, such as [TAG] = "migrate" # How to run: -# ./ado_workitems_to_github_issues.ps1 -ado_pat "xxx" -ado_org "jjohanning0798" -ado_project "PartsUnlimited" -ado_area_path "PartsUnlimited\migrate" -ado_migrate_closed_workitems $false -ado_production_run $true -gh_pat "xxx" -gh_org "joshjohanning-org" -gh_repo "migrate-ado-workitems" -gh_update_assigned_to $true -gh_assigned_to_user_suffix "_corp" -gh_add_ado_comments $true +# ./ado_workitems_to_github_issues.ps1 -ado_pat "xxx" -ado_org "jjohanning0798" -ado_project "PartsUnlimited" -ado_area_path "PartsUnlimited\migrate" -gh_pat "xxx" -gh_org "joshjohanning-org" -gh_repo "migrate-ado-workitems" -gh_assigned_to_user_suffix "_corp" + +# Optional switches to add - if you add this parameter, this means you want it set to TRUE (for false, simply do not provide) +# -ado_migrate_closed_workitems +# -ado_production_run +# -gh_update_assigned_to +# -gh_add_ado_comments # # Things it migrates: @@ -23,7 +29,7 @@ # a. Original work item url # b. Basic details in a collapsed markdown table # c. Entire work item as JSON in a collapsed section -# 7. Creates tag "copied-to-github" and a comment on the ADO work item with `-$ado_production_run $true` . The tag prevents duplicate copying. +# 7. Creates tag "copied-to-github" and a comment on the ADO work item with `-$ado_production_run` . The tag prevents duplicate copying. # # @@ -37,14 +43,14 @@ param ( [string]$ado_org, # Azure devops org without the URL, eg: "MyAzureDevOpsOrg" [string]$ado_project, # Team project name that contains the work items, eg: "TailWindTraders" [string]$ado_area_path, # Area path in Azure DevOps to migrate; uses the 'UNDER' operator) - [bool]$ado_migrate_closed_workitems = $false, # migrate work items with the state of done, closed, resolved, and removed - [bool]$ado_production_run = $false, # tag migrated work items with 'migrated-to-github' and add discussion comment + [switch]$ado_migrate_closed_workitems, # migrate work items with the state of done, closed, resolved, and removed + [switch]$ado_production_run, # tag migrated work items with 'migrated-to-github' and add discussion comment [string]$gh_pat, # GitHub PAT [string]$gh_org, # GitHub organization to create the issues in [string]$gh_repo, # GitHub repository to create the issues in - [bool]$gh_update_assigned_to = $false, # try to update the assigned to field in GitHub + [switch]$gh_update_assigned_to, # try to update the assigned to field in GitHub [string]$gh_assigned_to_user_suffix = "", # the emu suffix, ie: "_corp" - [bool]$gh_add_ado_comments = $false # try to get ado comments + [switch]$gh_add_ado_comments # try to get ado comments ) # Set the auth token for az commands