Public/Update-JiraTicket.ps1

function Update-JiraTicket {
    <#
    .SYNOPSIS
        Updates a Jira Cloud issue: transitions it, changes fields and/or adds a comment.
    .DESCRIPTION
        Performs the requested changes in this order, each as a separate REST call:
        1. -MarkDone / -MarkResolved: finds the 'Done' or 'Resolved' transition
           (GET /rest/api/3/issue/<key>/transitions) and performs it.
        2. -Summary / -OptionalFields: PUT /rest/api/3/issue/<key> with the fields.
        3. -Comment: POST /rest/api/3/issue/<key>/comment (the text is sent as an ADF paragraph).

        A failed transition or field update writes a non-terminating error and the remaining
        changes are still attempted; use -ErrorAction Stop to stop on the first failure. A failed
        comment is a terminating error. Supports -WhatIf and -Confirm.
    .PARAMETER IssueKey
        The issue key, for example PROJ-123.
    .PARAMETER Summary
        A new summary for the issue.
    .PARAMETER Comment
        A plain-text comment to add to the issue.
    .PARAMETER OptionalFields
        A hashtable of other fields to set, keyed by field id, for example @{ labels = @('ops') }.
    .PARAMETER MarkDone
        Transitions the issue to Done.
    .PARAMETER MarkResolved
        Transitions the issue to Resolved.
    .EXAMPLE
        Update-JiraTicket -IssueKey 'PROJ-123' -Comment 'Deployed to production' -MarkDone

        Transitions the issue to Done and adds a comment.
    .EXAMPLE
        Update-JiraTicket -IssueKey 'PROJ-123' -Summary 'New title' -OptionalFields @{ labels = @('ops', 'urgent') }

        Changes the summary and labels.
    .OUTPUTS
        None.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Default')]
        [Parameter(Mandatory = $true, ParameterSetName = 'MarkDone')]
        [Parameter(Mandatory = $true, ParameterSetName = 'MarkResolved')]
        [ValidateNotNullOrEmpty()]
        [string]$IssueKey,

        [Parameter(ParameterSetName = 'Default', HelpMessage = 'A new summary for the ticket.')]
        [Parameter(ParameterSetName = 'MarkDone', HelpMessage = 'A new summary for the ticket.')]
        [Parameter(ParameterSetName = 'MarkResolved', HelpMessage = 'A new summary for the ticket.')]
        [string]$Summary,

        [Parameter(ParameterSetName = 'Default', HelpMessage = 'A new comment to add to the ticket.')]
        [Parameter(ParameterSetName = 'MarkDone', HelpMessage = 'A new comment to add to the ticket.')]
        [Parameter(ParameterSetName = 'MarkResolved', HelpMessage = 'A new comment to add to the ticket.')]
        [string]$Comment,

        [Parameter(ParameterSetName = 'Default', HelpMessage = 'A hashtable of other fields to update.')]
        [Parameter(ParameterSetName = 'MarkDone', HelpMessage = 'A hashtable of other fields to update.')]
        [Parameter(ParameterSetName = 'MarkResolved', HelpMessage = 'A hashtable of other fields to update.')]
        [hashtable]$OptionalFields,

        [Parameter(Mandatory = $true, ParameterSetName = 'MarkDone', HelpMessage = 'Mark the ticket as Done (transition to Done status)')]
        [switch]$MarkDone,

        [Parameter(Mandatory = $true, ParameterSetName = 'MarkResolved', HelpMessage = 'Mark the ticket as Resolved (transition to Resolved status)')]
        [switch]$MarkResolved
    )

    $TelemetryArgs = @{
        ModuleName    = $MyInvocation.MyCommand.Module.Name
        ModuleVersion = [string]$MyInvocation.MyCommand.Module.Version
        CommandName   = $MyInvocation.MyCommand.Name
        ExecutionID   = [guid]::NewGuid().ToString()
    }
    Invoke-TelemetryCollection @TelemetryArgs -Stage Start -ClearTimer
    try {
        if (-not $Summary -and -not $Comment -and -not $OptionalFields -and -not $MarkDone -and -not $MarkResolved) {
            throw 'You must provide at least one of -Summary, -Comment, -OptionalFields, -MarkDone or -MarkResolved to update a ticket.'
        }
        Write-Verbose "Starting Update-JiraTicket for '$IssueKey'"

        # --- Transition (Done / Resolved) ---
        $targetStatus = $null
        if ($MarkDone) { $targetStatus = 'Done' }
        if ($MarkResolved) { $targetStatus = 'Resolved' }
        if ($targetStatus -and $PSCmdlet.ShouldProcess($IssueKey, "Transition to $targetStatus")) {
            try {
                $transitions = Invoke-JiraRequest -Method Get -Resource 'issue' -Id "$IssueKey/transitions" -ErrorAction Stop
                $transition = Select-JiraTransition -Transition @($transitions.transitions) -Name $targetStatus
                if ($transition) {
                    $body = @{ transition = @{ id = [string]$transition.id } } | ConvertTo-Json
                    Write-Verbose "Transitioning $IssueKey to $targetStatus using transition id $($transition.id)"
                    $null = Invoke-JiraRequest -Method Post -Resource 'issue' -Id "$IssueKey/transitions" -Body $body -ErrorAction Stop
                    Write-Verbose "Ticket $IssueKey transitioned to $targetStatus."
                }
                else {
                    Write-Warning "No '$targetStatus' transition found for ticket $IssueKey."
                }
            }
            catch {
                Write-Error -Message "Failed to transition ticket $IssueKey to $targetStatus. $($_.Exception.Message)"
            }
        }

        # --- Update fields ---
        $fieldsToUpdate = @{}
        if ($PSBoundParameters.ContainsKey('Summary')) {
            $fieldsToUpdate['summary'] = $Summary
        }
        if ($OptionalFields) {
            foreach ($key in $OptionalFields.Keys) {
                $fieldsToUpdate[$key] = $OptionalFields[$key]
            }
        }
        if ($fieldsToUpdate.Count -gt 0 -and $PSCmdlet.ShouldProcess($IssueKey, "Update fields: $(@($fieldsToUpdate.Keys) -join ', ')")) {
            try {
                $jsonUpdateBody = @{ fields = $fieldsToUpdate } | ConvertTo-Json -Depth 10
                $null = Invoke-JiraRequest -Method Put -Resource 'issue' -Id $IssueKey -Body $jsonUpdateBody -ErrorAction Stop
                Write-Verbose "Field update request for $IssueKey completed."
            }
            catch {
                Write-Error -Message "Failed to update fields for ticket $IssueKey. $($_.Exception.Message)"
            }
        }

        # --- Add comment ---
        if ($PSBoundParameters.ContainsKey('Comment') -and $PSCmdlet.ShouldProcess($IssueKey, 'Add comment')) {
            try {
                $jsonCommentBody = @{ body = (ConvertTo-JiraDocument -Text $Comment) } | ConvertTo-Json -Depth 10
                $null = Invoke-JiraRequest -Method Post -Resource 'issue' -Id "$IssueKey/comment" -Body $jsonCommentBody -ErrorAction Stop
                Write-Verbose "Comment added to $IssueKey."
            }
            catch {
                throw "Failed to add comment to ticket $IssueKey. $($_.Exception.Message)"
            }
        }

        Write-Verbose "Finished Update-JiraTicket for '$IssueKey'"
        Invoke-TelemetryCollection @TelemetryArgs -Stage End
    }
    catch {
        Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $_
        throw
    }
}