From c9cbcc5067a0dfdfad2e31ffe5648ef984e5c6d6 Mon Sep 17 00:00:00 2001 From: Robert McLeod Date: Tue, 24 Mar 2020 16:53:49 +1100 Subject: [PATCH] Add `Get-SatisfactionRating` & `New-SatisfactionRating` +semver: feature --- PwshZendesk.psd1 | 2 + functions/Get-SatisfactionRating.ps1 | 102 ++++++++++++++++++ functions/New-SatisfactionRating.ps1 | 75 +++++++++++++ tests/Routes-SatisfactionRatings.tests.ps1 | 117 +++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 functions/Get-SatisfactionRating.ps1 create mode 100644 functions/New-SatisfactionRating.ps1 create mode 100644 tests/Routes-SatisfactionRatings.tests.ps1 diff --git a/PwshZendesk.psd1 b/PwshZendesk.psd1 index 4b874a1..2c7d3f1 100644 --- a/PwshZendesk.psd1 +++ b/PwshZendesk.psd1 @@ -95,6 +95,7 @@ 'Get-OrganizationMembership', 'Get-ZendeskOrganizationMembership' 'Get-OrganizationRelated', 'Get-ZendeskOrganizationRelated' 'Get-Problem', 'Get-ZendeskProblem' + 'Get-SatisfactionRating', 'Get-ZendeskSatisfactionRating' 'Get-SearchCount', 'Get-ZendeskSearchCount' 'Get-SharingAgreement', 'Get-ZendeskSharingAgreement' 'Get-SuspendedTicket', 'Get-ZendeskSuspendedTicket' @@ -119,6 +120,7 @@ 'New-GroupMembership', 'New-ZendeskGroupMembership' 'New-Organization', 'New-ZendeskOrganization' 'New-OrganizationMembership', 'New-ZendeskOrganizationMembership' + 'New-SatisfactionRating', 'New-ZendeskSatisfactionRating' 'New-SharingAgreement', 'New-ZendeskSharingAgreement' 'New-Ticket', 'New-ZendeskTicket' 'New-UserIdentity', 'New-ZendeskUserIdentity' diff --git a/functions/Get-SatisfactionRating.ps1 b/functions/Get-SatisfactionRating.ps1 new file mode 100644 index 0000000..675e22a --- /dev/null +++ b/functions/Get-SatisfactionRating.ps1 @@ -0,0 +1,102 @@ +function Get-SatisfactionRating { + <# + .SYNOPSIS + Retrieves Satisfaction Ratings + .DESCRIPTION + Retrieves Satisfaction Ratings + .EXAMPLE + PS C:\> Get-ZendeskSatisfactionRating + + Retrieves all satisfaction ratings + .EXAMPLE + PS C:\> Get-ZendeskSatisfactionRating -Id 1 + + Retrieves satisfaction rating with id 1 + .EXAMPLE + PS C:\> Get-ZendeskSatisfactionRating -Score 'good_with_comment' + + Retrieves all good satisfaction ratings that include a comment + .EXAMPLE + PS C:\> Get-ZendeskSatisfactionRating -Score 'good' + + Retrieves all good satisfaction ratings regardless of whether a comment was included + .EXAMPLE + PS C:\> Get-ZendeskSatisfactionRating -Score 'received' + + Retrieves all satisfaction ratings that have been received whether the rating is good or bad. + .EXAMPLE + PS C:\> Get-ZendeskSatisfactionRating -Score 'offered' + + Retrieves all satisfaction ratings that have been offered but not responded to. + .EXAMPLE + PS C:\> Get-ZendeskSatisfactionRating -StartTime [DateTime]::Now.AddHours(-1) + + Retrieves all satisfaction ratings from the past hour. + .NOTES + If you specify an unqualified score such as good, the results include all the records with and without comments. + #> + [CmdletBinding(DefaultParameterSetName = 'Filter')] + Param ( + # Unique Id of the satisfaction rating to retrieve + [Parameter(Mandatory = $false, + ParameterSetName = 'Id')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Parameter help description + [Parameter(Mandatory = $false, + ParameterSetName = 'Filter')] + [ValidateSet('offered', 'unoffered', + 'received', 'received_with_comment', 'received_without_comment', + 'good', 'good_with_comment', 'good_without_comment', + 'bad', 'bad_with_comment', 'bad_without_comment')] + [String] + $Score, + + # Time of the oldest satisfaction rating, as a Unix epoch time + [Parameter(Mandatory = $false, + ParameterSetName = 'Filter')] + [DateTimeOffset] + $StartTime, + + # Time of the most recent satisfaction rating, as a Unix epoch time + [Parameter(Mandatory = $false, + ParameterSetName = 'Filter')] + [DateTimeOffset] + $EndTime, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + Assert-IsAdmin -Context $Context + + if ($PSBoundParameters.ContainsKey('Id')) { + $path = "/api/v2/satisfaction_ratings/$Id.json" + $key = 'satisfaction_rating' + } else { + $path = '/api/v2/satisfaction_ratings.json' + $key = 'satisfaction_ratings' + } + + $parameters = @() + if ($PSBoundParameters.ContainsKey('Score')) { + $parameters += "score=$Score" + } + if ($PSBoundParameters.ContainsKey('StartTime')) { + $parameters += 'start_time={0}' -f $StartTime.ToUnixTimeSeconds() + } + if ($PSBoundParameters.ContainsKey('EndTime')) { + $parameters += 'end_time={0}' -f $EndTime.ToUnixTimeSeconds() + } + if ($parameters.Count -gt 0) { + $path += '?' + $parameters -join '&' + } + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand $key +} diff --git a/functions/New-SatisfactionRating.ps1 b/functions/New-SatisfactionRating.ps1 new file mode 100644 index 0000000..c944551 --- /dev/null +++ b/functions/New-SatisfactionRating.ps1 @@ -0,0 +1,75 @@ +function New-SatisfactionRating { + <# + .SYNOPSIS + Creates a CSAT rating for a solved ticket, or for a ticket that was previously solved and then reopened. + .DESCRIPTION + Creates a CSAT rating for a solved ticket, or for a ticket that was previously solved and then reopened. + .EXAMPLE + PS C:\> New-ZendeskSatisfactionRating -TicketId 1 -Score 'good' + + Creates a good satisfaction rating for ticket with id 1 + .EXAMPLE + PS C:\> New-ZendeskSatisfactionRating -TicketId 1 -Score 'good' -Comment 'fast' + + Creates a good satisfaction rating for ticket with id 1 including a comment. + .EXAMPLE + PS C:\> New-ZendeskSatisfactionRating -TicketId 1 -Score 'bad' -ReasonCode 5 + + Creates a bad satisfaction rating for ticket with id 1 including a reason for the bad rating. + .NOTES + Only the end user listed as the ticket requester can create a satisfaction rating for the ticket. + #> + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param ( + # The internal id of the ticket to create a rating for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # The rating + [Parameter(Mandatory = $true)] + [ValidateSet('good', 'bad')] + [String] + $Score, + + # The comment + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [String] + $Comment, + + # The reason for a bad rating given by the requester + [Parameter(Mandatory = $false)] + [Int] + $ReasonCode = 0, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + Assert-IsEndUser -Context $Context + + $path = "/api/v2/tickets/$TicketId/satisfaction_rating.json" + $body = @{ + satisfaction_rating = @{ + score = $Score + } + } + + if ($PSBoundParameters.ContainsKey('Comment')) { + $body.satisfaction_rating.comment = $Comment + } + + if ($Score -eq 'bad') { + $body.satisfaction_rating.reason_code = $ReasonCode + } + + if ($PSCmdlet.ShouldProcess($TicketId, 'Create Group')) { + $result = Invoke-Method -Context $Context -Method 'Post' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } +} diff --git a/tests/Routes-SatisfactionRatings.tests.ps1 b/tests/Routes-SatisfactionRatings.tests.ps1 new file mode 100644 index 0000000..ffe811b --- /dev/null +++ b/tests/Routes-SatisfactionRatings.tests.ps1 @@ -0,0 +1,117 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +Param() + +Import-Module "$PSScriptRoot/../PwshZendesk.psm1" -Force + +Describe 'Satisfaction Rating Routes' { + + InModuleScope PwshZendesk { + + $IsInteractive = [Environment]::GetCommandLineArgs() -join ' ' -notmatch '-NonI' + + $context = @{ + Organization = 'company' + BaseUrl = 'https://company.testdesk.com' + Credential = [System.Management.Automation.PSCredential]::New('email', ('api-key' | ConvertTo-SecureString -AsPlainText -Force)) + User = [PSCustomObject]@{ role = '' } + } + $context | Add-Member -TypeName 'ZendeskContext' + + Mock -ModuleName PwshZendesk Invoke-RestMethod { [PSCustomObject]@{ satisfaction_rating = $null; satisfaction_ratings = $null } } + + Context 'List Satisfaction Ratings' { + It 'Matches the endpoint' { + if ($IsInteractive) { + throw 'Please run test in non-interactive mode' + } + + $context.User.role = 'admin' + + { Get-SatisfactionRating -Context $context } | Should -Not -Throw + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { $Method -eq 'Get' -and $Uri -match '/api/v2/satisfaction_ratings\.json' } -Scope It + } + + It 'Does not allow end users to call' { + $context.User.role = 'end-user' + + { Get-SatisfactionRating -Context $context } | Should -Throw 'Authenticated user must have role' + } + + It 'Does not allow agents to call' { + $context.User.role = 'agent' + + { Get-SatisfactionRating -Context $context } | Should -Throw 'Authenticated user must have role' + } + + It 'Allows admins to call' { + $context.User.role = 'admin' + + { Get-SatisfactionRating -Context $context } | Should -Not -Throw + } + } + + Context 'Show Satisfaction Rating' { + It 'Matches the endpoint' { + if ($IsInteractive) { + throw 'Please run test in non-interactive mode' + } + + $context.User.role = 'admin' + + { Get-SatisfactionRating -Context $context -Id 1 } | Should -Not -Throw + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { $Method -eq 'Get' -and $Uri -match '/api/v2/satisfaction_ratings/\d+\.json' } -Scope It + } + + It 'Does not allow end users to call' { + $context.User.role = 'end-user' + + { Get-SatisfactionRating -Context $context -Id 1 } | Should -Throw 'Authenticated user must have role' + } + + It 'Does not allow agents to call' { + $context.User.role = 'agent' + + { Get-SatisfactionRating -Context $context -Id 1 } | Should -Throw 'Authenticated user must have role' + } + + It 'Allows admins to call' { + $context.User.role = 'admin' + + { Get-SatisfactionRating -Context $context -Id 1 } | Should -Not -Throw + } + } + + Context 'Create a Satisfaction Rating' { + It 'Matches the endpoint' { + if ($IsInteractive) { + throw 'Please run test in non-interactive mode' + } + + $context.User.role = 'end-user' + + { New-SatisfactionRating -Context $context -TicketId 1 -Score 'good' -Confirm:$false } | Should -Not -Throw + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { $Method -eq 'Post' -and $Uri -match '/api/v2/tickets/\d+/satisfaction_rating\.json' } -Scope It + } + + It 'Allows end users to call' { + $context.User.role = 'end-user' + + { New-SatisfactionRating -Context $context -TicketId 1 -Score 'good' -Confirm:$false } | Should -Not -Throw + } + + It 'Does not allow agents to call' { + $context.User.role = 'agent' + + { New-SatisfactionRating -Context $context -TicketId 1 -Score 'good' -Confirm:$false } | Should -Throw 'Authenticated user must have role' + } + + It 'Does not allow admins to call' { + $context.User.role = 'admin' + + { New-SatisfactionRating -Context $context -TicketId 1 -Score 'good' -Confirm:$false } | Should -Throw 'Authenticated user must have role' + } + } + + } + +}