|
function Get-GitlabIssues { |
|
[CmdletBinding()] |
|
param ( |
|
[parameter(Mandatory=$true)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$AccessToken, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$ContainerPath) |
|
Begin { |
|
$issues = $null; |
|
$result = $null; |
|
$page = 1; |
|
Write-Progress -Activity "Get-GitlabIssues" -Status "0 Complete:" -PercentComplete 0; |
|
} |
|
Process { |
|
$total = (Invoke-RestMethod -Method Get -Uri "https://gitlab.com/api/v4/$ContainerPath/issues_statistics" -Headers @{ 'Authorization' = "Bearer $AccessToken" }).statistics.counts.all; |
|
while($result -ne 0) { |
|
$result = Invoke-RestMethod -Method Get -Uri "https://gitlab.com/api/v4/$ContainerPath/issues?page=$page&per_page=100" -Headers @{ 'Authorization' = "Bearer $AccessToken" } |
|
$issues += $result; |
|
if($issues.Count -eq 0) { |
|
$count = 1; |
|
} else { |
|
$count = $issues.Count; |
|
} |
|
$percent = $count/$total*100; |
|
$page ++; |
|
Write-Progress -Activity "Get-GitlabIssues" -Status "$percent% Complete:" -PercentComplete $percent; |
|
} |
|
} |
|
End { |
|
return $issues; |
|
} |
|
} |
|
|
|
|
|
function Get-GitlabComments { |
|
[CmdletBinding()] |
|
param ( |
|
[parameter(Mandatory=$true)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$AccessToken, |
|
[parameter(Mandatory=$true, ValueFromPipeline)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$NotesLink) |
|
Begin { |
|
} |
|
Process { |
|
try{ |
|
Invoke-RestMethod -Method Get -Uri "$NotesLink`?page=$page&per_page=100" -Headers @{ 'Authorization' = "Bearer $AccessToken" }; |
|
} catch { |
|
$issueIid = $NotesLink.Split("/") | Select-Object -Last 2 | Select-Object -First 1 |
|
Write-Error -Message "Failed to get comments for issue: $issueIid" -ErrorId 500 -TargetObject $issueIid -Category InvalidResult; |
|
} |
|
} |
|
End { |
|
} |
|
} |
|
|
|
|
|
function Get-GitLabAttachment { |
|
[CmdletBinding( |
|
SupportsShouldProcess, |
|
ConfirmImpact="Medium" |
|
)] |
|
param ( |
|
[parameter(Mandatory)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$AccessToken, |
|
[parameter(Mandatory)] |
|
[System.String] |
|
$Root, |
|
[parameter(Mandatory)] |
|
[System.String] |
|
$Issue, |
|
[parameter(Mandatory)] |
|
[System.String] |
|
$File) |
|
Begin { |
|
} |
|
Process { |
|
$path = Join-Path -Path $Root -ChildPath $Issue | Join-Path -ChildPath "Attachments"; |
|
New-Item -Path $path -Type Directory -Force | Out-Null; |
|
$path = $path | Join-Path -ChildPath ("~"+([string]::Join("~", ($File.Split("/") | Select-Object -Last 3)))); |
|
if(-Not (Test-Path -Path $path)) { |
|
Try { |
|
Invoke-WebRequest -OutFile $path -Uri $File -Headers @{ 'Authorization' = "Bearer $AccessToken" }; |
|
} Catch { |
|
Write-Error -Message "Failed to get attachment: $File for issue: $Issue" -ErrorId 500 -TargetObject $Issue -Category InvalidResult; |
|
} |
|
} |
|
} |
|
End { |
|
} |
|
} |
|
|
|
|
|
function Add-DevopsWorkItem { |
|
[CmdletBinding( |
|
SupportsShouldProcess=$true, |
|
ConfirmImpact="Medium" |
|
)] |
|
param ( |
|
[parameter(Mandatory=$true)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$AccessToken, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Organization, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Project, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
[ValidateSet("Epic", "Feature", "User Story", "Bug", "Issue", "Task")] |
|
$Type, |
|
[System.Int32] |
|
$Id, |
|
[parameter(Mandatory=$true,ValueFromPipeline)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Data, |
|
[Switch] |
|
$Test) |
|
Begin { |
|
$basicAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f '', $AccessToken))) |
|
$uri = "https://dev.azure.com/$Organization/$Project/_apis/wit/workitems/`$$($Type)?api-version=5.1&bypassRules=true&suppressNotifications=true"; |
|
} |
|
Process { |
|
if ($PSCmdlet.ShouldProcess($Data)) { |
|
Try { |
|
if($Test) { |
|
Invoke-RestMethod -Method Patch -Uri $uri+"&validateOnly=true" -Headers @{ 'Authorization' = "Basic $basicAuth" } -ContentType 'application/json-patch+json' -Body $Data |
|
} else { |
|
Invoke-RestMethod -Method Patch -Uri $uri -Headers @{ 'Authorization' = "Basic $basicAuth" } -ContentType 'application/json-patch+json' -Body $Data |
|
} |
|
} Catch { |
|
Write-Error -Message "Failed to add issue for issue: $($Id)" -ErrorId 500 -TargetObject $Data -Category InvalidResult; |
|
} |
|
} |
|
} |
|
End { |
|
} |
|
} |
|
|
|
|
|
function Add-DevopsComment { |
|
[CmdletBinding( |
|
SupportsShouldProcess=$true, |
|
ConfirmImpact="Medium" |
|
)] |
|
param ( |
|
[parameter(Mandatory=$true)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$AccessToken, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Organization, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Project, |
|
[ValidateNotNullOrEmpty()] |
|
[System.Int32] |
|
$Id, |
|
[parameter(Mandatory=$true,ValueFromPipeline)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Data) |
|
Begin { |
|
$basicAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f '', $AccessToken))) |
|
$uri = "https://dev.azure.com/$Organization/$Project/_apis/wit/workItems/$Id/comments?api-version=5.1-preview.3"; |
|
} |
|
Process { |
|
if ($PSCmdlet.ShouldProcess($Data)) { |
|
Try{ |
|
Invoke-RestMethod -Method Post -Uri $uri -Headers @{ 'Authorization' = "Basic $basicAuth" } -ContentType 'application/json' -Body $Data |
|
} Catch { |
|
Write-Error -Message "Failed to add comments for issue: $($Id)" -ErrorId 500 -TargetObject $Id -Category InvalidResult; |
|
} |
|
} |
|
} |
|
End { |
|
} |
|
} |
|
|
|
|
|
function Add-DevopsAttachment { |
|
[CmdletBinding( |
|
SupportsShouldProcess=$true, |
|
ConfirmImpact="Medium" |
|
)] |
|
param ( |
|
[parameter(Mandatory=$true)] |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$AccessToken, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Organization, |
|
[ValidateNotNullOrEmpty()] |
|
[System.String] |
|
$Project, |
|
[parameter(Mandatory=$true,ValueFromPipeline)] |
|
[System.IO.FileInfo] |
|
$File) |
|
Begin { |
|
$basicAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f '', $AccessToken))); |
|
$uri = "https://dev.azure.com/$Organization/$Project/_apis/wit/attachments?api-version=5.0"; |
|
} |
|
Process { |
|
if ($PSCmdlet.ShouldProcess($FileName)) { |
|
Try{ |
|
$image = [System.IO.File]::ReadAllBytes($File.FullName); |
|
Invoke-RestMethod -Uri $uri+"&fileName=$([System.Web.HttpUtility]::UrlEncode($File.Name))" -Method Post -Headers @{ Authorization=("Basic {0}" -f $basicAuth) } -ContentType application/json -Body $image; |
|
} Catch { |
|
Write-Error -Message "Failed to add attachments for issue: $($File.FullName)" -ErrorId 500 -TargetObject $File -Category InvalidResult; |
|
} |
|
} |
|
} |
|
End { |
|
} |
|
} |
|
|
|
|
|
function Convert-IssueToWorkItem { |
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '', Scope='Function')] |
|
[CmdletBinding()] |
|
[OutputType([string])] |
|
param ( |
|
[parameter(Mandatory=$true)] |
|
$RootIterationPath, |
|
[parameter(Mandatory=$true,ValueFromPipeline)] |
|
$Issue |
|
) |
|
Begin { |
|
Import-Module MarkdownToHtml -Force; |
|
$userMap = @{ |
|
#https://gitlab.com/api/v4/groups/mygroupid/members GitLabUserUserId="AzureDevOpsDisplayName", |
|
123 ="John Doe"; |
|
}; |
|
} |
|
Process { |
|
if([String]::IsNullOrWhiteSpace($Issue.description)) { |
|
$content = "`" `""; |
|
} else { |
|
$content = ((Convert-MarkdownToHTMLFragment -Markdown ($Issue.description | Format-BadCharacters)).HtmlFragment).Replace("₹", "₹").Replace("'", "'") | ConvertTo-Json; |
|
} |
|
$title = [System.Web.HttpUtility]::HtmlEncode(($Issue.title | Format-BadCharacters)); |
|
if($title.Length -gt 255){$title = $title.Substring(0,247);} |
|
$author = $($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.author.id}).Value; |
|
$assignee = ($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.assignee.id}).Value; |
|
$closedby = ($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.closed_by.id}).Value; |
|
$tags = [String]::Join(";", $Issue.labels+$Issue.state); |
|
if($Issue.milestone) { |
|
$iteration = $RootIterationPath+"\\"+$Issue.milestone.title.Replace("?", "").Replace("&", "and"); |
|
} else { |
|
$iteration = $RootIterationPath; |
|
} |
|
if($null -ne $Issue.assignee){ |
|
$user = ($userMap.GetEnumerator() | Where-Object{$_.Name -eq $Issue.assignee.id}).Value; |
|
$assignee = ", |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.AssignedTo`", |
|
`"from`": null, |
|
`"value`": `"$($user)`" |
|
}" |
|
} else { |
|
$assignee = ""; |
|
} |
|
$type = @("User Story", "Bug")[$Issue.labels.Contains("Bug")]; |
|
if($type -eq "Bug") { |
|
$body = ", |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/Microsoft.VSTS.TCM.ReproSteps`", |
|
`"from`": null, |
|
`"value`": $content |
|
}" |
|
} else { |
|
$body = ", |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.Description`", |
|
`"from`": null, |
|
`"value`": $content |
|
}" |
|
} |
|
if($Issue.state -eq "closed"){ |
|
$closed = ", |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/Microsoft.VSTS.Common.ClosedBy`", |
|
`"from`": null, |
|
`"value`": `"$($closedby)`" |
|
}, |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/Microsoft.VSTS.Common.ClosedDate`", |
|
`"from`": null, |
|
`"value`": `"$($Issue.closed_at)`" |
|
}"; |
|
} else { |
|
$closed = ""; |
|
} |
|
return "[ |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.IterationPath`", |
|
`"from`": null, |
|
`"value`": `"$($iteration)`" |
|
}, |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.Title`", |
|
`"from`": null, |
|
`"value`": `"$($Issue.iid): $($title)`" |
|
}$($body), |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.State`", |
|
`"from`": null, |
|
`"value`": `"$(@{"opened"="New"; "closed" = "New"}[$Issue.state])`" |
|
}, |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.Tags`", |
|
`"from`": null, |
|
`"value`": `"$($tags)`" |
|
}, |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.CreatedBy`", |
|
`"from`": null, |
|
`"value`": `"$($author)`" |
|
}, |
|
{ |
|
`"op`": `"add`", |
|
`"path`": `"/fields/System.CreatedDate`", |
|
`"from`": null, |
|
`"value`": `"$($Issue.created_at)`" |
|
}$($assignee)$($closed) |
|
]"; |
|
} |
|
End { |
|
} |
|
} |
|
|
|
function Convert-NoteToComment { |
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '', Scope='Function')] |
|
[CmdletBinding()] |
|
[OutputType([string])] |
|
param( |
|
[parameter(Mandatory,ValueFromPipeline)] |
|
$Note |
|
) |
|
Begin { |
|
Import-Module MarkdownToHtml -Force; |
|
} |
|
Process { |
|
$header = "<p><b>$($Note.created_at.ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss')) by $($Note.author.name)</b></p>"; |
|
if([String]::IsNullOrWhiteSpace($Note.body)) { |
|
$content = "`" `""; |
|
} else { |
|
$content = ($header + (Convert-MarkdownToHTMLFragment -Markdown ($Note.body | Format-BadCharacters)).HtmlFragment).Replace("₹", "₹") | ConvertTo-Json; |
|
} |
|
return "{ `"text`": $content }"; |
|
} |
|
End { |
|
} |
|
} |
|
|
|
function Format-BadCharacters { |
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '', Scope='Function')] |
|
[CmdletBinding()] |
|
[OutputType([string])] |
|
param ( |
|
[parameter( |
|
Mandatory, |
|
ValueFromPipeline, |
|
ValueFromPipelineByPropertyName, |
|
Position = 0)] |
|
[string] $Input |
|
) |
|
Process { |
|
return $Input.Replace("`“", "`"").Replace("`”", "`"").Replace(" ", " ").Replace("ä", "a").Replace("ë", "e"); |
|
} |
|
} |