Private/Write-AACPSRuleHtml.ps1

function Write-AACPSRuleHtml {
    <#
    .SYNOPSIS
        Writes Invoke-AACPSRule's -HtmlPath report: tiles for the outcome,
        charts of the failures by pillar, rule, resource and severity, and
        every result in one interactive table - opening on the failures,
        grouped by rule - with Azure portal and rule documentation links.
    #>

    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [object[]] $Result,

        [Parameter(Mandatory)]
        [string] $Path,

        [string] $Title = 'PSRule for Azure',

        [System.Collections.IDictionary] $Detail,

        [int] $Rules,

        [int] $Objects,

        [string[]] $Warning = @(),

        # List only failures (and rules that couldn't evaluate) in the
        # table; the tiles still count every result.
        [switch] $FailedOnly
    )

    $passed = @($Result | Where-Object Outcome -EQ 'Pass').Count
    $failed = @($Result | Where-Object Outcome -EQ 'Fail')
    $errors = @($Result | Where-Object Outcome -EQ 'Error').Count
    $problems = @($Result | Where-Object Outcome -NE 'Pass')
    $decided = $passed + $failed.Count
    $pillarTone = @{ Security = 'bad'; Reliability = 'info'; 'Cost Optimization' = 'good'; 'Operational Excellence' = 'violet'; 'Performance Efficiency' = 'warn' }
    $severityTone = @{ Critical = 'bad'; Error = 'bad'; Important = 'warn'; Warning = 'warn'; Awareness = 'neutral' }

    $tiles = @(
        @{ Value = '{0:N0}' -f $Objects; Label = 'objects checked'; Tone = 'info' }
        @{ Value = '{0:N0}' -f $Rules; Label = 'rules'; Tone = 'violet' }
        @{ Value = '{0:N0}' -f $passed; Label = 'passed'; Tone = 'good'; Table = 'results'; Filters = @{ Outcome = 'Pass' } }
        @{ Value = '{0:N0}' -f $failed.Count; Label = 'failed'; Tone = $(if ($failed.Count) { 'bad' } else { 'neutral' }); Table = 'results'; Filters = @{ Outcome = 'Fail' } }
        @{ Value = '{0:N0}' -f $errors; Label = 'could not evaluate'; Tone = $(if ($errors) { 'warn' } else { 'neutral' }); Table = 'results'; Filters = @{ Outcome = 'Error' } }
        @{ Value = $(if ($decided) { '{0:N1}%' -f (100 * $passed / $decided) } else { '-' }); Label = 'pass rate'; Tone = 'info' }
    )
    $top = {
        param([string] $Property, [int] $First = 10)
        @($failed | Group-Object -Property $Property | Sort-Object -Property @{ Expression = 'Count'; Descending = $true }, Name | Select-Object -First $First | ForEach-Object {
                @{ Label = $(if ($_.Name) { $_.Name } else { '(none)' }); Value = $_.Count; Tone = $pillarTone[$_.Name] }
            })
    }
    $bySeverity = @(foreach ($severity in 'Critical', 'Important', 'Awareness', 'Error', 'Warning') {
            $n = @($failed | Where-Object Severity -EQ $severity).Count
            if ($n) { @{ Label = $severity; Value = $n; Tone = $severityTone[$severity] } }
        })
    $onlyFailed = @{ Outcome = 'Fail' }
    $charts = @(
        @{ Title = 'Failed by Well-Architected pillar'; Items = @(& $top 'Pillar'); Table = 'results'; Column = 'Pillar'; BaseFilters = $onlyFailed }
        @{ Title = 'Failed by severity'; Items = $bySeverity; Table = 'results'; Column = 'Severity'; BaseFilters = $onlyFailed }
        @{ Title = 'Resources with most failures'; Items = @(& $top 'ResourceName'); Table = 'results'; Column = 'ResourceName'; BaseFilters = $onlyFailed; Tone = 'violet' }
        @{ Title = 'Rules that fail most'; Items = @(& $top 'RuleName'); Table = 'results'; Column = 'RuleName'; BaseFilters = $onlyFailed; Wide = $true }
    )
    $notices = @(foreach ($line in $Warning | Select-Object -First 8) { @{ Tone = 'warn'; Text = $line } })

    $table = @{
        Id      = 'results'
        Title   = 'Results'
        Note    = $(if ($FailedOnly) { 'One row per rule and resource that failed (passed results are counted above, not listed).' } else { 'One row per rule and resource. It opens on the failures - Reset shows every result.' })
        Noun    = 'results'
        File    = 'PSRuleResults'
        Rows    = $(if ($FailedOnly) { $problems } else { $Result })
        Group   = 'RuleName'
        Filters = $(if ($problems.Count) { $onlyFailed } else { @{} })
        GroupBy = @('RuleName', 'ResourceName', 'Pillar', 'ResourceGroup', 'SubscriptionName')
        Columns = @(
            @{ Key = 'Outcome'; Label = 'Outcome'; Type = 'badge'; Facet = $true; Tones = @{ Pass = 'good'; Fail = 'bad'; Error = 'warn' } }
            @{ Key = 'Severity'; Label = 'Severity'; Type = 'badge'; Facet = $true; Tones = $severityTone }
            @{ Key = 'RuleName'; Label = 'Rule'; Facet = $true; Nowrap = $true }
            @{ Key = 'Title'; Label = 'Title'; Type = 'wide' }
            @{ Key = 'ResourceName'; Label = 'Resource'; Type = 'resource' }
            @{ Key = 'ResourceType'; Label = 'Type'; Facet = $true; Nowrap = $true }
            @{ Key = 'ResourceGroup'; Label = 'Resource group'; Facet = $true; Nowrap = $true }
            @{ Key = 'SubscriptionName'; Label = 'Subscription'; Facet = $true; Nowrap = $true }
            @{ Key = 'Reason'; Label = 'Why'; Type = 'wide' }
            @{ Key = 'Link'; Label = 'Docs'; Type = 'link'; Text = 'Rule docs' }
            @{ Key = 'Pillar'; Label = 'Pillar'; Facet = $true; Nowrap = $true }
            @{ Key = 'Source'; Label = 'Source'; Facet = $true; Nowrap = $true }
            @{ Key = 'Recommendation'; Label = 'Recommendation'; Hidden = $true }
            @{ Key = 'Ref'; Label = 'Reference'; Hidden = $true }
            @{ Key = 'SubscriptionId'; Label = 'Subscription ID'; Hidden = $true }
            @{ Key = 'ResourceId'; Label = 'Resource ID'; Hidden = $true }
        )
    }

    Write-AACHtmlReport -Path $Path -Title $Title -Subtitle "$('{0:N0}' -f $Rules) rules on $('{0:N0}' -f $Objects) objects - $('{0:N0}' -f $failed.Count) failed" -Fact $Detail -Tile $tiles -Chart $charts -Table @($table) -Notice $notices
}