Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
joshjohanning authored Mar 10, 2022
1 parent ff03ecc commit 77c8286
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 1 deletion.
73 changes: 73 additions & 0 deletions .github/workflows/migrate-work-items.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Migrate Work Items

on:
workflow_dispatch:
inputs:
ado-org:
description: 'ado-org'
required: true
default: 'jjohanning0798'
ado-project:
description: 'ado-project'
required: true
default: 'PartsUnlimited'
ado-tag:
description: 'ADO tag to migrate'
required: true
default: 'migrate'
gh-org:
description: 'gh-org'
required: true
default: 'joshjohanning-org'
gh-repo:
description: 'gh-org'
required: true
default: 'migrate-ado-workitems'
gh_update_assigned_to:
description: 'Update Assigned To'
required: true
type: boolean
default: 'true'
gh_assigned_to_user_suffix:
description: 'EMU suffix'
required: true
default: '_corp'
gh_add_ado_comments:
description: 'Add ADO Comments'
required: true
type: boolean
default: 'true'

jobs:
migrate:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@main

- uses: tibdex/github-app-token@v1
id: get_installation_token
with:
app_id: 179484
installation_id: 23995058
private_key: ${{ secrets.PRIVATE_KEY }}

- name: run migration
shell: pwsh
run: |
# run migration script
# cleaning up bools
$gh_update_assigned_to=$false
$gh_add_ado_comments=$false
if("${{ github.event.inputs.gh_update_assigned_to }}" -eq "true") {
$gh_update_assigned_to=$true
}
if("${{ github.event.inputs.gh_add_ado_comments }}" -eq "true") {
$gh_add_ado_comments=$true
}
./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_tag "${{ github.event.inputs.ado-tag }}" -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
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,51 @@
# ado_workitems_to_github_issues
Migrate Azure DevOps work items to GitHub Issues

## Notes

### Prerequisites
1. Install az devops and github cli where this is running (ie: action or locally; GitHub-hosted runners already have)
2. Create a label for EACH work item type that is being migrated (as lower case)
- ie: "user story", "bug", "task", "feature"
3. Add a tag to eaach work item you want to migrate - ie: "migrate"
- You can modify the WIQL if you want to use a different way to migrate work items, such as UNDER [Area Path]

### Things it migrates
1. Title
2. Description (or repro steps + system info for a bug
3. State (if the work item is done / closed, it will be closed in GitHub)
4. It will try to assign the work item to the correct user in GitHub - based on ADO email (-gh_update_assigned_to and -gh_assigned_to_user_suffix options) - they of course have to be in GitHub already
5. Migrate acceptance criteria as part of issue body (if present)
6. Adds in the following as a comment to the issue:
a. Original work item url
b. Basic details in a collapsed markdown table
c. Entire work item as JSON in a collapsed section

### To Do
1. Create a comment on the Azure DevOps work item that says "Migrated to GitHub Issue #"

## Things it won't ever migrate
1. Created date/update dates

## Instructions for Running in Actions

1. Create GitHub App with (can use this [reference](https://josh-ops.com/posts/github-apps/#creating-a-github-app)). Use the following permissions:
+ Repo: `Contents:Read`
+ Repo: `Issues:Read and write`
+ Org: `Members:Read`
1. Create Private Key for GitHub App
1. Obtain App ID and Installation ID - see [the instructions for using smee.io](https://josh-ops.com/posts/github-apps/#creating-a-github-app)
1. Create the following action secrets:
+ `ADO_PAT`: Azure DevOps PAT with appropriate permissions to read work items
+ `PRIVATE_KEY`: The contents of the private key created and downloaded in step #2
1. Use the [action](.github/workflows/migrate-work-items.yml) and update the App ID and Installation ID obtained in step #3
1. Update any defaults in the [action](.github/workflows/migrate-work-items.yml) (ie: Azure DevOps organization and project, GitHub organization and repo)
1. Ensure the action exists in the repo's default branch
1. Run the workflow

## Instructions for Running Locally

Using the GitHub app might be better so you don't reach a limit on your GitHub account on creating new issues 😀

```pwsh
./ado_workitems_to_github_issues.ps1 -ado_pat "xxx" -ado_org "jjohanning0798" -ado_project "PartsUnlimited" -ado_tag "migrate" -gh_pat "ghp_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
```
157 changes: 157 additions & 0 deletions ado_workitems_to_github_issues.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
##############################################################
# Migrate Azure DevOps work items to GitHub Issues
##############################################################

# Prerequisites:
# 1. Install az devops and github cli
# 2. create a label for EACH work item type that is being migrated (as lower case)
# - ie: "user story", "bug", "task", "feature"
# 3. add a tag to eaach work item you want to migrate - ie: "migrate"
# - You can modify the WIQL if you want to use a different way to migrate work items, such as UNDER [Area Path]

# How to run:
# ./ado_workitems_to_github_issues.ps1 -ado_pat "xxx" -ado_org "jjohanning0798" -ado_project "PartsUnlimited" -ado_tag "migrate" -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

#
# Things it migrates:
# 1. Title
# 2. Description (or repro steps + system info for a bug
# 3. State (if the work item is done / closed, it will be closed in GitHub)
# 4. It will try to assign the work item to the correct user in GitHub - based on ADO email (-gh_update_assigned_to and -gh_assigned_to_user_suffix options) - they of course have to be in GitHub already
# 5. Migrate acceptance criteria as part of issue body (if present)
# 6. Adds in the following as a comment to the issue:
# a. Original work item url
# b. Basic details in a collapsed markdown table
# c. Entire work item as JSON in a collapsed section
#

#
# To Do:
# 1. Create a comment on the Azure DevOps work item that says "Migrated to GitHub Issue #"
#

#
# Things it won't ever migrate:
# 1. Created date/update dates
#

[CmdletBinding()]
param (
[string]$ado_pat, # Azure DevOps PAT
[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_tag, # only one tag is supported, would have to add another clause in the $wiql, eg: "migrate")
[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
[string]$gh_assigned_to_user_suffix = "", # the emu suffix, ie: "_corp"
[bool]$gh_add_ado_comments = $false # try to get ado comments
)

echo "$ado_pat" | az devops login --organization "https://dev.azure.com/$ado_org"
az devops configure --defaults organization="https://dev.azure.com/$ado_org" project="$ado_project"
echo $gh_pat | gh auth login --with-token

$wiql="select [ID], [Title] from workitems where [Tags] CONTAINS '$ado_tag' order by [ID]"

$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

ForEach($workitem in $query) {
$original_workitem_json_beginning="`n`n<details><summary>Original Work Item JSON</summary><p>" + "`n`n" + '```json'
$original_workitem_json_end="`n" + '```' + "`n</p></details>"

$details_json = az boards work-item show --id $workitem.id --output json
$details = $details_json | ConvertFrom-Json

$description=""

# bug doesn't have Description field - add repro steps and/or system info
if ($details.fields.{System.WorkItemType} -eq "Bug") {
if(![string]::IsNullOrEmpty($details.fields.{Microsoft.VSTS.TCM.ReproSteps})) {
$description+="## Repro Steps`n`n" + $details.fields.{Microsoft.VSTS.TCM.ReproSteps} + "`n`n"
}
if(![string]::IsNullOrEmpty($details.fields.{Microsoft.VSTS.TCM.SystemInfo})) {
$description+="## System Info`n`n" + $details.fields.{Microsoft.VSTS.TCM.SystemInfo} + "`n`n"
}
} else {
$description+=$details.fields.{System.Description}
# add in acceptance criteria if it has it
if(![string]::IsNullOrEmpty($details.fields.{Microsoft.VSTS.Common.AcceptanceCriteria})) {
$description+="`n`n## Acceptance Criteria`n`n" + $details.fields.{Microsoft.VSTS.Common.AcceptanceCriteria}
}
}

$description | Out-File -FilePath ./temp_issue_body.txt

$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

# create the details chart
$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
$ado_details= "| Created date | Created by | Changed date | Changed By | 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) | $($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
$ado_details_end="`n" + "`n</p></details>"
$ado_details_end | Add-Content -Path ./temp_comment_body.txt

# prepare the comment
$original_workitem_json_beginning | Add-Content -Path ./temp_comment_body.txt
$details_json | Add-Content -Path ./temp_comment_body.txt
$original_workitem_json_end | Add-Content -Path ./temp_comment_body.txt

# getting comments if enabled
if($gh_add_ado_comments -eq $true) {
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$base64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$ado_pat"))
$headers.Add("Authorization", "Basic $base64")
$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
ForEach($comment in $response.comments) {
# $ado_comments_details= "> **Created date**: $($comment.createdDate)`n**Created by**: $($comment.createdBy.displayName)`n**[Comment URL JSON]($($comment.url))**`n**Comment text**:$($comment.text)`n`n-----------`n`n"
$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
}
$ado_original_workitem_json_end="`n" + "`n</p></details>"
$ado_original_workitem_json_end | Add-Content -Path ./temp_comment_body.txt
}
}

# 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 $details.fields.{System.Title} --label $work_item_type
write-host "issue created: $issue_url"

# 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

# close out the issue if it's closed on the Azure Devops side
if ($details.fields.{System.State} -eq "Done" -or $details.fields.{System.State} -eq "Closed") {
gh issue close $issue_url
}

# update assigned to in GitHub if the option is set - tries to use ado email to map to github username
if ($gh_update_assigned_to -eq $true) {
$ado_assignee=$details.fields.{System.AssignedTo}.uniqueName
$gh_assignee=$ado_assignee.Split("@")[0]
$gh_assignee=$gh_assignee.Replace(".", "-") + $gh_assigned_to_user_suffix
write-host "trying to assign to: $gh_assignee"
$assigned=gh issue edit $issue_url --add-assignee "$gh_assignee"
}
}

0 comments on commit 77c8286

Please sign in to comment.