200 строки
10 KiB
PowerShell
200 строки
10 KiB
PowerShell
##############################################################
|
|
# Copy Azure DevOps work items to GitHub issues
|
|
##############################################################
|
|
|
|
# Customized for copying CodeQL work items.
|
|
|
|
# From: https://github.com/joshjohanning/ado_workitems_to_github_issues/blob/main/ado_workitems_to_github_issues.ps1
|
|
|
|
# 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. define under what area path you want to migrate
|
|
# - You can modify the WIQL if you want to use a different way to migrate work items, such as [TAG] = "migrate"
|
|
|
|
# How to run:
|
|
# ./AdoWorkitemsToGithubIssues.ps1 -ado_pat "xxx" -ado_org "fuselabs" -ado_project "SDK_v4" -ado_area_path "SDK_v4\Code Analysis" `
|
|
# -gh_pat "xxx" -gh_org "microsoft" -gh_repo "botbuilder-dotnet" -gh_update_assigned_to -gh_assignee "tracyboehrer" -gh_assigned_to_user_suffix "" -gh_add_ado_comments
|
|
|
|
#
|
|
# 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
|
|
# 7. Creates tag "copied-to-github" and a comment on the ADO work item. The tag prevents duplicate copying.
|
|
#
|
|
|
|
#
|
|
# 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_area_path, # Area path in Azure DevOps to migrate; uses the 'UNDER' operator)
|
|
[string]$gh_pat, # GitHub PAT
|
|
[string]$gh_org, # GitHub organization to create the issues in
|
|
[string]$gh_repo, # Default gitHub repository to create the issues in. This gets overridden using the value in the title
|
|
[switch]$gh_update_assigned_to, # try to update the assigned to field in GitHub
|
|
[string]$gh_assignee = "", # github user to assign to
|
|
[string]$gh_assigned_to_user_suffix = "", # the emu suffix, ie: "_corp"
|
|
[switch]$gh_add_ado_comments # try to get ado comments
|
|
)
|
|
|
|
# Set the auth token for az commands
|
|
$env:AZURE_DEVOPS_EXT_PAT = $ado_pat;
|
|
# Set the auth token for gh commands
|
|
$env:GH_TOKEN = $gh_pat;
|
|
|
|
az devops configure --defaults organization="https://dev.azure.com/$ado_org" project="$ado_project";
|
|
|
|
$wiql = "select [ID], [Title], [System.Tags] from workitems where [State] <> 'Done' and [State] <> 'Closed' and [State] <> 'Resolved' and [State] <> 'Removed' and [System.AreaPath] UNDER '$ado_area_path' and [System.Title] Contains 'CodeQL' and not [System.Tags] Contains 'copied-to-github' 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;
|
|
$count = 0;
|
|
|
|
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>";
|
|
|
|
$workitemId = $workitem.id;
|
|
$workitemTags = $workitem.fields.'System.Tags';
|
|
|
|
$details_json = az boards work-item show --id $workitem.id --output json;
|
|
$details = $details_json | ConvertFrom-Json;
|
|
|
|
# double quotes in the title must be escaped with \ to be passed to gh cli
|
|
$title = $details.fields.{System.Title} -replace "`"","`\`"";
|
|
|
|
# get the target github repo from the end of the work item title.
|
|
$index = $title.LastIndexOf('/');
|
|
if($index -gt 0) {
|
|
$repo = $title.Substring($index + 1);
|
|
if(![string]::IsNullOrEmpty($repo.Trim())) {
|
|
$gh_repo = $repo;
|
|
}
|
|
}
|
|
|
|
Write-Host "Copying work item $workitemId to $gh_org/$gh_repo on github";
|
|
|
|
$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})) {
|
|
# Fix line # reference in "Repository:" URL.
|
|
$reproSteps = ($details.fields.{Microsoft.VSTS.TCM.ReproSteps}).Replace('/tree/', '/blob/').Replace('?&path=', '').Replace('&line=', '#L');
|
|
$description += "## Repro Steps`n`n" + $reproSteps + "`n`n";
|
|
}
|
|
if(![string]::IsNullOrEmpty($details.fields.{Microsoft.VSTS.TCM.SystemInfo})) {
|
|
$workitemUrl = "https://dev.azure.com/$ado_org/$ado_project/_workitems/edit/$workitemId";
|
|
$copiedFromNotice = "This issue is a copy of [ADO work item $workitemId]($workitemUrl) created by CodeQL.";
|
|
$description+="## System Info`n`n" + $copiedFromNotice + "`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 -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;
|
|
|
|
# use empty string if there is no user is assigned
|
|
if ( $null -ne $details.fields.{System.AssignedTo}.displayName )
|
|
{
|
|
$ado_assigned_to_display_name = $details.fields.{System.AssignedTo}.displayName;
|
|
$ado_assigned_to_unique_name = $details.fields.{System.AssignedTo}.uniqueName;
|
|
}
|
|
else {
|
|
$ado_assigned_to_display_name = "";
|
|
$ado_assigned_to_unique_name = "";
|
|
}
|
|
|
|
# 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;
|
|
|
|
# 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;
|
|
|
|
# get comments if enabled
|
|
if($gh_add_ado_comments) {
|
|
$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 -Encoding ASCII;
|
|
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;
|
|
}
|
|
$ado_original_workitem_json_end="`n" + "`n</p></details>";
|
|
$ado_original_workitem_json_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII;
|
|
}
|
|
}
|
|
|
|
# set the label on the issue to be the work item type
|
|
$work_item_type = $details.fields.{System.WorkItemType}.ToLower();
|
|
|
|
# create the issue in Github
|
|
$issue_url = gh issue create --body-file ./temp_issue_body.txt --repo "$gh_org/$gh_repo" --title "$title" --label $work_item_type --label "ExemptFromDailyDRIReport";
|
|
|
|
if (![string]::IsNullOrEmpty($issue_url.Trim())) {
|
|
Write-Host " Issue created: $issue_url";
|
|
$count++;
|
|
}
|
|
else {
|
|
throw "Issue creation failed.";
|
|
}
|
|
|
|
# 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) {
|
|
write-host " Assigning issue to: $gh_assignee";
|
|
$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
|
|
$discussion = "This work item was copied to github as issue <a href=`"$issue_url`">$issue_url</a>";
|
|
az boards work-item update --id "$workitemId" --fields "System.Tags=copied-to-github; $workitemTags" --discussion "$discussion" | Out-Null;
|
|
}
|
|
Write-Host "Total items copied: $count" |