Skip to content

Commit

Permalink
Merge pull request #6 from timja/use-migration-api
Browse files Browse the repository at this point in the history
Use issue migration api
  • Loading branch information
joshjohanning authored Nov 13, 2024
2 parents d6726a5 + d8f2842 commit 3a893ad
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 58 deletions.
19 changes: 9 additions & 10 deletions .github/workflows/migrate-work-items.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
temp_issue_body.txt
temp_comment_body.txt

14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 |
Expand All @@ -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
**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
113 changes: 72 additions & 41 deletions ado_workitems_to_github_issues.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
#

#
Expand All @@ -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
Expand All @@ -63,8 +69,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) {
Expand Down Expand Up @@ -102,11 +106,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 )
{
Expand All @@ -119,18 +120,12 @@ ForEach($workitem in $query) {
}

# create the details table
$ado_details_beginning="`n`n<details><summary>Original Work Item Details</summary><p>" + "`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</p></details>"
$ado_details_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII;
$gh_comment+="`n`n<details><summary>Original Work Item Details</summary><p>" + "`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</p></details>"

# 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) {
Expand All @@ -140,25 +135,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<details><summary>Work Item Comments ($($response.count))</summary><p>" + "`n`n"
$ado_original_workitem_json_beginning | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII;
$gh_comment+="`n`n<details><summary>Work Item Comments ($($response.count))</summary><p>" + "`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</p></details>"
$ado_original_workitem_json_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII;
$gh_comment+="`n" + "`n</p></details>"
}
}

# 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";
Expand All @@ -176,13 +214,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';
Expand Down

0 comments on commit 3a893ad

Please sign in to comment.