Public/Add-DhStatusGrid.ps1
|
function Add-DhStatusGrid { <# .SYNOPSIS Add a status-matrix / health-grid block — rows × columns of RAG cells. .DESCRIPTION Renders a 2-D grid where each cell carries a status (ok / warn / danger / nodata / info), per the IT-infrastructure KPI dashboard specification (§2 Widget Types — "Status Matrix / Health Grid"). Use it as a compact at-a-glance overview of binary or low-cardinality health state across many entities — services × regions, DCs × replication links, hosts × probes. Distinct from Add-DhTable: a status grid is a colour-first matrix where the individual cell labels don't matter; a table is text-first with rich sorting, filtering, and per-cell content. Cells are sparse — declare only the (Row, Column) pairs that exist; missing cells render as a faint neutral state. .PARAMETER Report Dashboard object from New-DhDashboard. .PARAMETER Id Unique identifier (alphanumeric, dash, underscore). .PARAMETER Title Block heading shown above the grid. .PARAMETER Rows Array of row labels (Y-axis). Each cell's Row must match one of these. .PARAMETER Columns Array of column labels (X-axis). Each cell's Column must match one of these. .PARAMETER Cells Array of cell hashtables: @{ Row = 'Auth' # REQUIRED — must appear in -Rows Column = 'us-east' # REQUIRED — must appear in -Columns Status = 'ok' # REQUIRED — ok | warn | danger | nodata | info Tooltip = '99.98% / 24h' # optional — native hover tooltip LinkTableId = 'incidents' # optional — click jumps to a registered table LinkFilter = 'auth-eu' # optional — text filter applied on jump } .PARAMETER RowLabel Optional caption for the Y-axis (top-left corner). .PARAMETER ColumnLabel Optional caption for the X-axis (above column headers). .PARAMETER CellSize Cell side length preset: 'small' (32 px) | 'normal' (48 px, default) | 'large' (64 px). On a narrow viewport (< 600 px) the grid collapses to a stacked list regardless of this setting. .PARAMETER MaxCells Hard ceiling on Rows × Columns. Default 2000. Above this the cmdlet throws to protect against accidental 10k-cell matrices from broad `Get-AzResource`-style queries. A soft warning fires at 200 cells. .PARAMETER NavGroup Primary nav group label (enables two-tier nav). .PARAMETER NavSubGroup Optional second-level group under NavGroup. .EXAMPLE Add-DhStatusGrid -Report $report -Id 'service-health' -Title 'Service Health' ` -RowLabel 'Service' -ColumnLabel 'Region' ` -Rows @('Auth','Billing','Search','Mail') ` -Columns @('us-east','us-west','eu-west','ap-south') ` -Cells @( @{ Row='Auth'; Column='us-east'; Status='ok'; Tooltip='99.98% / 24h' } @{ Row='Auth'; Column='eu-west'; Status='warn'; Tooltip='latency spike'; LinkTableId='incidents'; LinkFilter='auth-eu' } @{ Row='Billing'; Column='us-east'; Status='danger'; Tooltip='down 12m' } ) #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $Report, [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9_-]+$')] [string] $Id, [Parameter(Mandatory)] [string] $Title, [Parameter(Mandatory)] [object[]] $Rows, [Parameter(Mandatory)] [object[]] $Columns, [Parameter(Mandatory)] [object[]] $Cells, [string] $RowLabel = '', [string] $ColumnLabel = '', [ValidateSet('small','normal','large')] [string] $CellSize = 'normal', [ValidateRange(1, 100000)] [int] $MaxCells = 2000, [string] $NavGroup = '', [string] $NavSubGroup = '' ) if (-not $Report.Contains('Blocks')) { $Report['Blocks'] = [System.Collections.Generic.List[hashtable]]::new() } foreach ($existing in $Report.Blocks) { if ($existing.Id -eq $Id) { throw "Add-DhStatusGrid: A block with Id '$Id' already exists in this report. Use a unique Id." } } if ($Rows.Count -lt 1) { throw "Add-DhStatusGrid: -Rows must contain at least one label." } if ($Columns.Count -lt 1) { throw "Add-DhStatusGrid: -Columns must contain at least one label." } # Size guard — per docs/v1.4-plan.md §3 Q3 $totalCells = $Rows.Count * $Columns.Count if ($totalCells -gt $MaxCells) { throw "Add-DhStatusGrid: Rows × Columns = $totalCells exceeds -MaxCells ($MaxCells). Reduce the axes or raise -MaxCells deliberately." } if ($totalCells -gt 200) { Write-Warning "Add-DhStatusGrid: $totalCells cells is dense — consider splitting into smaller grouped grids for legibility (Rows × Columns = $($Rows.Count) × $($Columns.Count))." } # Normalise axes to strings; build lookup sets for membership checks $rowList = @($Rows | ForEach-Object { [string]$_ }) $colList = @($Columns | ForEach-Object { [string]$_ }) $rowSet = @{} $colSet = @{} foreach ($r in $rowList) { $rowSet[$r] = $true } foreach ($c in $colList) { $colSet[$c] = $true } $allowedStatus = @('ok','warn','danger','nodata','info') $normCells = foreach ($cell in $Cells) { if ($cell -isnot [hashtable] -and $cell -isnot [System.Collections.Specialized.OrderedDictionary]) { throw "Add-DhStatusGrid: each cell must be a hashtable. Got: $($cell.GetType().Name)" } foreach ($req in 'Row','Column','Status') { if (-not $cell.Contains($req) -or [string]::IsNullOrWhiteSpace([string]$cell[$req])) { throw "Add-DhStatusGrid: each cell must have a non-empty '$req' key." } } $r = [string]$cell['Row'] $c = [string]$cell['Column'] if (-not $rowSet.ContainsKey($r)) { throw "Add-DhStatusGrid: cell Row '$r' is not in -Rows. Add it to the axis or fix the typo." } if (-not $colSet.ContainsKey($c)) { throw "Add-DhStatusGrid: cell Column '$c' is not in -Columns. Add it to the axis or fix the typo." } $st = ([string]$cell['Status']).ToLowerInvariant() if ($st -notin $allowedStatus) { throw "Add-DhStatusGrid: cell ($r, $c) — Status must be 'ok', 'warn', 'danger', 'nodata', or 'info'. Got: $($cell['Status'])" } # Action validation — matches Add-DhAlertBanner semantics $linkTableId = if ($cell.Contains('LinkTableId') -and -not [string]::IsNullOrWhiteSpace([string]$cell['LinkTableId'])) { [string]$cell['LinkTableId'] } else { '' } $linkFilter = if ($cell.Contains('LinkFilter') -and -not [string]::IsNullOrWhiteSpace([string]$cell['LinkFilter'])) { [string]$cell['LinkFilter'] } else { '' } if ($linkFilter -and -not $linkTableId) { throw "Add-DhStatusGrid: cell ($r, $c) — LinkFilter requires LinkTableId." } @{ Row = $r Column = $c Status = $st Tooltip = if ($cell.Contains('Tooltip') -and $null -ne $cell['Tooltip']) { [string]$cell['Tooltip'] } else { '' } LinkTableId = $linkTableId LinkFilter = $linkFilter } } $Report.Blocks.Add([ordered]@{ BlockType = 'statusgrid' Id = $Id Title = $Title Rows = $rowList Columns = $colList Cells = @($normCells) RowLabel = $RowLabel ColumnLabel = $ColumnLabel CellSize = $CellSize NavGroup = $NavGroup NavSubGroup = $NavSubGroup }) Write-Verbose "Add-DhStatusGrid: '$Id' ($($rowList.Count) rows × $($colList.Count) cols, $(@($normCells).Count) cells)." } |