Private/Write-AACCostHtml.ps1
|
function Write-AACCostHtml { <# .SYNOPSIS Writes Show-AACCost's -HtmlPath report: tiles for month to date, last month and the period per currency; charts by month, subscription, service and resource group; a subscription-by-month table; and every detail row (subscription, month, resource group, service) in an interactive table that sums what is shown. .DESCRIPTION Amounts are never converted: each billing currency gets its own tiles and charts. Subscriptions Cost Management couldn't read are listed as notices. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Cost, [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $CostDetail, [Parameter(Mandatory)] [datetime[]] $MonthStart, [Parameter(Mandatory)] [string] $Path, [string] $Title = 'Azure cost', [string] $Period, [System.Collections.IDictionary] $Detail ) $invariant = [cultureinfo]::InvariantCulture $monthKeys = @($MonthStart | ForEach-Object { $_.ToString('yyyy-MM', $invariant) }) $thisMonth = $monthKeys[-1] $lastMonth = if ($monthKeys.Count -gt 1) { $monthKeys[-2] } else { $null } $sum = { param($Items, [string] $Property) $total = 0.0 foreach ($item in @($Items)) { $value = Get-AACPropertyValue -InputObject $item -Name $Property; if ($null -ne $value) { $total += [double]$value } } [Math]::Round($total, 2) } $money = { param([double] $Value, [string] $Currency) ('{0} {1:N2}' -f $Currency, $Value).Trim() } $read = @($Cost | Where-Object Status -eq 'OK') $tiles = [System.Collections.Generic.List[object]]::new() $charts = [System.Collections.Generic.List[object]]::new() $currencyGroups = @($read | Group-Object -Property Currency | Sort-Object -Property Count -Descending) foreach ($currencyGroup in $currencyGroups) { $currency = $currencyGroup.Name $group = @($currencyGroup.Group) $suffix = if ($currencyGroups.Count -gt 1) { " ($currency)" } else { '' } $ids = @($group | ForEach-Object { $_.SubscriptionId }) $rows = @($CostDetail | Where-Object { $_.SubscriptionId -in $ids }) $thisMonthRows = @($rows | Where-Object Month -eq $thisMonth) $tiles.Add(@{ Value = (& $money (& $sum $group 'MonthToDate') $currency); Label = "month to date$suffix"; Tone = 'good'; Table = 'detail'; Filters = @{ Month = $thisMonth; Currency = $currency } }) if ($lastMonth) { $tiles.Add(@{ Value = (& $money (& $sum $group $lastMonth) $currency); Label = "last month$suffix"; Tone = 'info'; Table = 'detail'; Filters = @{ Month = $lastMonth; Currency = $currency } }) } $tiles.Add(@{ Value = (& $money (& $sum $group 'Total') $currency); Label = "last $($monthKeys.Count) months$suffix"; Tone = 'violet'; Table = 'detail'; Filters = @{ Currency = $currency } }) $byMonth = @(foreach ($key in $monthKeys) { @{ Label = $(if ($key -eq $thisMonth) { "$key (to date)" } else { $key }); Value = (& $sum $group $key); Filter = $key; Tone = $(if ($key -eq $thisMonth) { 'good' } else { '' }) } }) $charts.Add(@{ Title = "By month$suffix"; Items = $byMonth; Format = 'N2'; Suffix = $currency; Table = 'detail'; Column = 'Month' }) $top = { param([string] $Property, [int] $First = 10) @($thisMonthRows | Group-Object -Property $Property | ForEach-Object { @{ Label = $(if ($_.Name) { $_.Name } else { '(none)' }); Value = (& $sum $_.Group 'Cost') } } | Where-Object { $_.Value -gt 0 } | Sort-Object -Property { $_.Value } -Descending | Select-Object -First $First) } if ($group.Count -gt 1) { $charts.Add(@{ Title = "Month to date by subscription$suffix"; Items = @(& $top 'SubscriptionName'); Format = 'N2'; Suffix = $currency; Table = 'detail'; Column = 'SubscriptionName'; Tone = 'violet' }) } $charts.Add(@{ Title = "Month to date by service$suffix"; Items = @(& $top 'Service'); Format = 'N2'; Suffix = $currency; Table = 'detail'; Column = 'Service'; Tone = 'warn' }) $charts.Add(@{ Title = "Month to date by resource group$suffix"; Items = @(& $top 'ResourceGroup'); Format = 'N2'; Suffix = $currency; Table = 'detail'; Column = 'ResourceGroup'; Tone = 'info' }) } $noCost = @($Cost | Where-Object Status -EQ 'No cost') $tiles.Add(@{ Value = '{0:N0}' -f $read.Count; Label = 'subscriptions with cost'; Tone = 'neutral'; Table = 'subscriptions' }) if ($noCost.Count) { $tiles.Add(@{ Value = '{0:N0}' -f $noCost.Count; Label = 'with no cost'; Tone = 'neutral'; Table = 'subscriptions'; Filters = @{ Status = 'No cost' } }) } # A subscription with no cost is a note, not a warning; one that couldn't # be read is a warning with the reason. $notices = @( foreach ($item in $noCost) { @{ Tone = 'info'; Text = "$($item.SubscriptionName): no cost since $($monthKeys[0]). Its usage may be billed to another subscription, be covered by credits, or not be processed yet." } } foreach ($failed in @($Cost | Where-Object { $_.Status -notin 'OK', 'No cost' })) { @{ Tone = 'warn'; Text = "$($failed.SubscriptionName): $($failed.Status)" } }) # Subscription by month: one money column per month. $monthColumns = @(foreach ($key in $monthKeys) { @{ Key = $key; Label = $(if ($key -eq $thisMonth) { "$key *" } else { $key }); Type = 'money'; Sum = $true; CurrencyKey = 'Currency' } }) $subscriptionTable = @{ Id = 'subscriptions' Title = 'By subscription and month' Note = '* month to date. Totals are per currency.' Noun = 'subscriptions' File = 'CostBySubscription' Rows = @($Cost) Sort = @{ Key = 'Total'; Desc = $true } PageSize = 500 Columns = @( @(@{ Key = 'SubscriptionName'; Label = 'Subscription'; Nowrap = $true }) $monthColumns @( @{ Key = 'Total'; Label = 'Total'; Type = 'money'; Sum = $true; CurrencyKey = 'Currency'; Tone = 'good' } @{ Key = 'Currency'; Label = 'Currency'; Facet = $true } @{ Key = 'TopServices'; Label = 'Top services this month'; Type = 'wide' } @{ Key = 'Status'; Label = 'Status'; Type = 'wide' } @{ Key = 'SubscriptionId'; Label = 'Subscription ID'; Hidden = $true } ) ) } $detailTable = @{ Id = 'detail' Title = 'Cost detail' Note = 'One row per subscription, month, resource group and service. The total of the rows shown is under the filters.' Noun = 'detail rows' File = 'CostDetail' Rows = @($CostDetail) Sort = @{ Key = 'Cost'; Desc = $true } GroupBy = @('Service', 'ResourceGroup', 'SubscriptionName', 'Month') Columns = @( @{ Key = 'Month'; Label = 'Month'; Facet = $true; Nowrap = $true } @{ Key = 'SubscriptionName'; Label = 'Subscription'; Facet = $true; Nowrap = $true } @{ Key = 'ResourceGroup'; Label = 'Resource group'; Facet = $true } @{ Key = 'Service'; Label = 'Service'; Facet = $true } @{ Key = 'Cost'; Label = 'Cost'; Type = 'money'; Sum = $true; CurrencyKey = 'Currency' } @{ Key = 'Currency'; Label = 'Currency'; Facet = $true } @{ Key = 'SubscriptionId'; Label = 'Subscription ID'; Hidden = $true } ) } Write-AACHtmlReport -Path $Path -Title $Title -Subtitle "Actual cost$(if ($Period) { ", $Period" })" -Fact $Detail -Tile $tiles.ToArray() -Chart $charts.ToArray() -Table @($subscriptionTable, $detailTable) -Notice $notices } |