|
1 | 1 | <# |
2 | 2 | .SYNOPSIS |
3 | | - Measures the percentage of work item types and the bug to rest of types ratio within an iteration |
| 3 | +Measures the percentage of work item types and the bug to rest of types ratio within an iteration |
| 4 | +
|
4 | 5 | .DESCRIPTION |
5 | | - A PowerShell script that utilizes the Azure DevOps Services REST API to fetch work items within a specified iteration and calculates the percentage of each work item type and the ratio of bugs to the rest of the work item types |
| 6 | +A PowerShell script that utilizes the Azure DevOps Services REST API to fetch work items within a specified iteration and calculates the percentage of each work item type and the ratio of bugs to the rest of the work item types |
| 7 | +
|
6 | 8 | .PARAMETER PersonalAccessToken |
7 | | - Azure DevOps personal access token (PAT) with the following scopes: Work Items (Read) |
| 9 | +Azure DevOps personal access token (PAT) with the following scopes: Work Items (Read) |
| 10 | +
|
8 | 11 | .PARAMETER OrganizationName |
9 | | - Name of the Azure DevOps organization |
| 12 | +Name of the Azure DevOps organization |
| 13 | +
|
10 | 14 | .PARAMETER ProjectName |
11 | | - Name of the Azure DevOps project |
| 15 | +Name of the Azure DevOps project |
| 16 | +
|
12 | 17 | .PARAMETER TeamName |
13 | | - Name of the Azure DevOps team |
| 18 | +Name of the Azure DevOps team |
| 19 | +
|
14 | 20 | .PARAMETER IterationPath |
15 | | - The iteration path to measure work item types in |
| 21 | +The iteration path to measure work item types in |
| 22 | +
|
16 | 23 | .PARAMETER WorkItemTypes |
17 | | - The work item types to be considered |
| 24 | +The work item types to be considered |
| 25 | +
|
18 | 26 | .INPUTS |
19 | | - None |
| 27 | +None |
| 28 | +
|
20 | 29 | .OUTPUTS |
21 | | - The calculated percentages and ratios - printed to the console |
| 30 | +The calculated percentages and ratios - printed to the console |
| 31 | +
|
22 | 32 | .NOTES |
23 | | - Version: 1.0 |
24 | | - Author: Marc Rufer & GitHub Copilot Workspace |
25 | | - Creation Date: 14.05.2024 |
26 | | - Purpose/Change: Initial script development |
| 33 | +Version: 1.0 |
| 34 | +Author: Marc Rufer & GitHub Copilot Workspace |
| 35 | +Creation Date: 14.05.2024 |
| 36 | +Purpose/Change: Initial script development |
| 37 | +
|
| 38 | +.EXAMPLE |
| 39 | +PS> .\Measure-WorkItemTypesInIteration.ps1 -PersonalAccessToken "PAT_HERE" -OrganizationName "ORG_NAME_HERE" -ProjectName "PROJECT_NAME_HERE" -TeamName "TEAM_NAME_HERE" -IterationPath "ITERATION_PATH_HERE" |
| 40 | +
|
27 | 41 | .EXAMPLE |
28 | | - .\Measure-WorkItemTypesInIteration.ps1 -PersonalAccessToken "PAT_HERE" -OrganizationName "ORG_NAME_HERE" -ProjectName "PROJECT_NAME_HERE" -TeamName "TEAM_NAME_HERE" -IterationPath "ITERATION_PATH_HERE" |
| 42 | +PS> .\Measure-WorkItemTypesInIteration.ps1 -PersonalAccessToken "PAT_HERE" -OrganizationName "ORG_NAME_HERE" -ProjectName "PROJECT_NAME_HERE" -TeamName "TEAM_NAME_HERE" -IterationPath "ITERATION_PATH_HERE" -WorkItemTypes "User Story" "Epic" "Bug" |
29 | 43 | #> |
30 | 44 | PARAM |
31 | 45 | ( |
32 | | - [Parameter(Mandatory = $true, Position = 0, HelpMessage="Azure DevOps personal access token (PAT) with scopes: Work Items (Read).")] |
33 | | - [string] $PersonalAccessToken |
34 | | - , |
35 | | - [Parameter(Mandatory = $true, Position = 1)] |
36 | | - [string] $OrganizationName |
37 | | - , |
38 | | - [Parameter(Mandatory = $true, Position = 2)] |
39 | | - [string] $ProjectName |
40 | | - , |
41 | | - [Parameter(Mandatory = $true, Position = 3)] |
42 | | - [string] $TeamName |
43 | | - , |
44 | | - [Parameter(Mandatory = $true, Position = 4)] |
45 | | - [string] $IterationPath |
46 | | - , |
47 | | - [Parameter(Mandatory = $false)] |
48 | | - [string[]] $WorkItemTypes = "Bug", "User Story" |
| 46 | + [Parameter(Mandatory = $true, Position = 0, HelpMessage="Azure DevOps personal access token (PAT) with scopes: Work Items (Read).")] |
| 47 | + [string] $PersonalAccessToken |
| 48 | + , |
| 49 | + [Parameter(Mandatory = $true, Position = 1)] |
| 50 | + [string] $OrganizationName |
| 51 | + , |
| 52 | + [Parameter(Mandatory = $true, Position = 2)] |
| 53 | + [string] $ProjectName |
| 54 | + , |
| 55 | + [Parameter(Mandatory = $true, Position = 3)] |
| 56 | + [string] $TeamName |
| 57 | + , |
| 58 | + [Parameter(Mandatory = $true, Position = 4)] |
| 59 | + [string] $IterationPath |
| 60 | + , |
| 61 | + [Parameter(Mandatory = $false)] |
| 62 | + [string[]] $WorkItemTypes = "Bug", "User Story" |
49 | 63 | ) |
50 | 64 |
|
51 | 65 | function Get-WorkItemsInIteration { |
52 | | - param ( |
53 | | - [string] $personalAccessToken, |
54 | | - [string] $organizationName, |
55 | | - [string] $projectName, |
56 | | - [string] $teamName, |
57 | | - [string] $iterationPath, |
58 | | - [string[]] $workItemTypes |
59 | | - ) |
60 | | - |
61 | | - $base64AuthInfo = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("`:$personalAccessToken")) |
62 | | - $workItemsUri = "https://dev.azure.com/$organizationName/$projectName/$teamName/_apis/wit/wiql?api-version=6.0" |
63 | | - $body = @{ |
64 | | - query = "SELECT [System.Id] FROM WorkItems WHERE [System.IterationPath] = '$iterationPath' AND [System.WorkItemType] IN ('$($workItemTypes -join "','")')" |
65 | | - } | ConvertTo-Json |
66 | | - |
67 | | - try { |
68 | | - $response = Invoke-RestMethod -Uri $workItemsUri -Method Post -Body $body -ContentType "application/json" -Headers @{Authorization=("Basic $base64AuthInfo")} |
69 | | - return $response.workItems.id |
70 | | - } |
71 | | - catch { |
72 | | - Write-Error "Failed to fetch work items: $_" |
73 | | - exit 1 |
74 | | - } |
| 66 | + param ( |
| 67 | + [string] $personalAccessToken, |
| 68 | + [string] $organizationName, |
| 69 | + [string] $projectName, |
| 70 | + [string] $teamName, |
| 71 | + [string] $iterationPath, |
| 72 | + [string[]] $workItemTypes |
| 73 | + ) |
| 74 | + |
| 75 | + $base64AuthInfo = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("`:$personalAccessToken")) |
| 76 | + $workItemsUri = "https://dev.azure.com/$organizationName/$projectName/$teamName/_apis/wit/wiql?api-version=6.0" |
| 77 | + $body = @{ |
| 78 | + query = "SELECT [System.Id] FROM WorkItems WHERE [System.IterationPath] = '$iterationPath' AND [System.WorkItemType] IN ('$($workItemTypes -join "','")')" |
| 79 | + } | ConvertTo-Json |
| 80 | + |
| 81 | + try { |
| 82 | + $response = Invoke-RestMethod -Uri $workItemsUri -Method Post -Body $body -ContentType "application/json" -Headers @{Authorization=("Basic $base64AuthInfo")} |
| 83 | + return $response.workItems.id |
| 84 | + } |
| 85 | + catch { |
| 86 | + Write-Error "Failed to fetch work items: $_" |
| 87 | + exit 1 |
| 88 | + } |
75 | 89 | } |
76 | 90 |
|
77 | 91 | function Calculate-WorkItemTypesPercentage { |
78 | | - param ( |
79 | | - [string] $personalAccessToken, |
80 | | - [string] $organizationName, |
81 | | - [string[]] $workItemIds |
82 | | - ) |
83 | | - |
84 | | - $base64AuthInfo = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("`:$personalAccessToken")) |
85 | | - $workItemTypes = @{} |
86 | | - $totalWorkItems = $workItemIds.Count |
87 | | - |
88 | | - $workItemsCountPerType = @{} |
89 | | - |
90 | | - foreach ($id in $workItemIds) { |
91 | | - $workItemUri = "https://dev.azure.com/$organizationName/_apis/wit/workitems/$($id)?api-version=6.0" |
92 | | - try { |
93 | | - $workItem = Invoke-RestMethod -Uri $workItemUri -Method Get -Headers @{Authorization=("Basic $base64AuthInfo")} |
94 | | - $type = $workItem.fields.'System.WorkItemType' |
95 | | - $workItemsCountPerType[$type] = $workItemsCountPerType[$type] + 1 |
96 | | - } |
97 | | - catch { |
98 | | - Write-Error "Failed to fetch work item details: $_" |
99 | | - continue |
100 | | - } |
101 | | - } |
102 | | - |
103 | | - foreach ($type in $workItemsCountPerType.Keys) { |
104 | | - $percentage = ($workItemsCountPerType[$type] / $totalWorkItems) * 100 |
105 | | - Write-Host "$($type): $($percentage)%" |
106 | | - } |
107 | | - |
108 | | - if ($workItemsCountPerType['Bug']) { |
109 | | - $bugRatio = ($workItemsCountPerType['Bug'] / ($totalWorkItems - $workItemsCountPerType['Bug'])) * 100 |
110 | | - Write-Host "Bug to rest of types ratio: $($bugRatio)%" |
111 | | - } |
112 | | - else { |
113 | | - Write-Host "No bugs found in the iteration." |
114 | | - } |
| 92 | + param ( |
| 93 | + [string] $personalAccessToken, |
| 94 | + [string] $organizationName, |
| 95 | + [string[]] $workItemIds |
| 96 | + ) |
| 97 | + |
| 98 | + $base64AuthInfo = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("`:$personalAccessToken")) |
| 99 | + $workItemTypes = @{} |
| 100 | + $totalWorkItems = $workItemIds.Count |
| 101 | + |
| 102 | + $workItemsCountPerType = @{} |
| 103 | + |
| 104 | + foreach ($id in $workItemIds) { |
| 105 | + $workItemUri = "https://dev.azure.com/$organizationName/_apis/wit/workitems/$($id)?api-version=6.0" |
| 106 | + try { |
| 107 | + $workItem = Invoke-RestMethod -Uri $workItemUri -Method Get -Headers @{Authorization=("Basic $base64AuthInfo")} |
| 108 | + $type = $workItem.fields.'System.WorkItemType' |
| 109 | + $workItemsCountPerType[$type] = $workItemsCountPerType[$type] + 1 |
| 110 | + } |
| 111 | + catch { |
| 112 | + Write-Error "Failed to fetch work item details: $_" |
| 113 | + continue |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + foreach ($type in $workItemsCountPerType.Keys) { |
| 118 | + $percentage = ($workItemsCountPerType[$type] / $totalWorkItems) * 100 |
| 119 | + Write-Host "$($type): $($percentage)%" |
| 120 | + } |
| 121 | + |
| 122 | + if ($workItemsCountPerType['Bug']) { |
| 123 | + $bugRatio = ($workItemsCountPerType['Bug'] / ($totalWorkItems - $workItemsCountPerType['Bug'])) * 100 |
| 124 | + Write-Host "Bug to rest of types ratio: $($bugRatio)%" |
| 125 | + } |
| 126 | + else { |
| 127 | + Write-Host "No bugs found in the iteration." |
| 128 | + } |
115 | 129 | } |
116 | 130 |
|
117 | 131 | Write-Host "Azure DevOps organization: $OrganizationName" -ForegroundColor Green |
|
0 commit comments