Assets/Get-OpsJobHistory.ps1

<#
.SYNOPSIS
    Gets the history of Ops Jobs based on the NDJSON history logs.
.PARAMETER JobName
    Optional specific job name. Supports wildcards.
.PARAMETER Count
    Number of recent entries to return.
#>

param (
  [string]$JobName = "*",
  [ValidateRange(1, [int]::MaxValue)]
  [int]$Count = 10
)

# The Ops root defaults to C:\Ops. OPS_ROOT overrides it for development and
# testing, matching the dashboard server's existing convention.
$OpsRoot = if ([string]::IsNullOrWhiteSpace($env:OPS_ROOT)) { "C:\Ops" } else { $env:OPS_ROOT }
$LogDir = Join-Path $OpsRoot "Logs"
if (-not (Test-Path -Path $LogDir)) {
  return @()
}

$Files = @(Get-ChildItem -Path $LogDir -Filter "$JobName.history.json" -File -ErrorAction SilentlyContinue)

$Results = [System.Collections.Generic.List[object]]::new()

foreach ($File in $Files) {
  # The history file is newline-delimited JSON, not a JSON document. Piping the
  # whole file into ConvertFrom-Json concatenates the lines and fails outright on
  # PowerShell 5.1; a single truncated line would also discard the entire file.
  # Parse line by line and skip anything unreadable.
  $Entries = [System.Collections.Generic.List[object]]::new()
  foreach ($Line in (Get-Content -Path $File.FullName -ErrorAction SilentlyContinue)) {
    if ([string]::IsNullOrWhiteSpace($Line)) { continue }
    try {
      $Entries.Add(($Line | ConvertFrom-Json))
    }
    catch {
      Write-Verbose "Skipping malformed history line in $($File.Name): $Line"
    }
  }

  # '.history' as a -replace pattern is a regex whose dot matches any character.
  $Name = $File.BaseName -replace '\.history$', ''

  $RelevantEntries = $Entries | Where-Object {
    $_.Message -match "Job completed successfully" -or $_.Level -eq "ERROR"
  } | Sort-Object Timestamp -Descending | Select-Object -First $Count

  foreach ($Entry in $RelevantEntries) {
    $Status = "Unknown"
    if ($Entry.Level -eq "ERROR") { $Status = "Failed" }
    elseif ($Entry.Message -match "Job completed successfully") { $Status = "Success" }

    $Results.Add([PSCustomObject]@{
        JobName   = $Name
        Timestamp = $Entry.Timestamp
        Status    = $Status
        Message   = $Entry.Message
      })
  }
}

return $Results | Sort-Object Timestamp -Descending | Select-Object -First $Count