This commit is contained in:
@@ -57,7 +57,7 @@ $Config = @{
|
||||
MaxDiscordEmbeds = 10
|
||||
RetryCount = 3
|
||||
RetryDelaySeconds = 2
|
||||
AuthoringTestsEnabled = $false
|
||||
AuthoringTestsEnabled = $true
|
||||
AuthoringTestsPattern = 'test_authoring_server.py'
|
||||
AuthoringTestsDirectory = Join-Path $PSScriptRoot 'tests'
|
||||
AuthoringTestTimeoutSec = 120
|
||||
@@ -75,6 +75,7 @@ $Config = @{
|
||||
|
||||
# When true, completed successful runs are reported too.
|
||||
NotifySuccessfulBuilds = $true
|
||||
MaxPreviousFailures = 5
|
||||
|
||||
# When true, successful but slow/warned jobs are reported too.
|
||||
# Keep this false if Discord should only receive notifications for failed/cancelled/timed-out jobs.
|
||||
@@ -547,15 +548,35 @@ function Get-Severity {
|
||||
return 'Info'
|
||||
}
|
||||
|
||||
function Get-BuildStatus {
|
||||
param([Parameter(Mandatory)][object]$Build)
|
||||
|
||||
$conclusion = Get-ObjectValue -Object $Build -Name 'conclusion'
|
||||
if ([string]::IsNullOrWhiteSpace([string]$conclusion)) {
|
||||
$conclusion = Get-ObjectValue -Object $Build -Name 'result'
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace([string]$conclusion)) {
|
||||
$conclusion = Get-ObjectValue -Object $Build -Name 'outcome'
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$conclusion)) {
|
||||
return [string]$conclusion
|
||||
}
|
||||
|
||||
$status = Get-ObjectValue -Object $Build -Name 'status'
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$status)) {
|
||||
return [string]$status
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
function Analyze-Build {
|
||||
param(
|
||||
[Parameter(Mandatory)][hashtable]$Repository,
|
||||
[Parameter(Mandatory)][object]$Job
|
||||
)
|
||||
|
||||
$conclusion = Get-ObjectValue -Object $Job -Name 'conclusion'
|
||||
$jobStatus = Get-ObjectValue -Object $Job -Name 'status'
|
||||
$status = if ($conclusion) { [string]$conclusion } elseif ($jobStatus) { [string]$jobStatus } else { 'unknown' }
|
||||
$status = Get-BuildStatus -Build $Job
|
||||
$isSuccessfulStatus = Test-IsSuccessfulStatus -Status $status
|
||||
$duration = Get-Duration -Job $Job
|
||||
$isSlow = $duration.TotalMinutes -ge [double]$Config.SlowBuildThresholdMins
|
||||
@@ -728,6 +749,128 @@ function Send-DiscordNotification {
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BuildStatusEmoji {
|
||||
param([Parameter(Mandatory)][string]$Status)
|
||||
|
||||
if (Test-IsSuccessfulStatus -Status $Status) {
|
||||
return '✅'
|
||||
}
|
||||
|
||||
$normalized = $Status.ToLowerInvariant()
|
||||
if ($normalized -in @('failure', 'failed', 'cancelled', 'canceled', 'timed_out')) {
|
||||
return '❌'
|
||||
}
|
||||
|
||||
return 'ℹ️'
|
||||
}
|
||||
|
||||
function Send-BuildStatusNotification {
|
||||
param([Parameter(Mandatory)][object[]]$Analyses)
|
||||
|
||||
$latestByRepository = @($Analyses |
|
||||
Group-Object Repository |
|
||||
ForEach-Object {
|
||||
$_.Group | Sort-Object StartedAt -Descending | Select-Object -First 1
|
||||
} |
|
||||
Sort-Object Repository)
|
||||
|
||||
if ($latestByRepository.Count -eq 0) {
|
||||
Write-Log -Message "No completed builds found in the last $($Config.LookbackMinutes) minute(s)."
|
||||
return
|
||||
}
|
||||
|
||||
$embeds = New-Object System.Collections.Generic.List[object]
|
||||
$failedLatestCount = 0
|
||||
$successfulLatestCount = 0
|
||||
$previousFailureCount = 0
|
||||
|
||||
foreach ($latest in $latestByRepository) {
|
||||
$latestStatus = [string]$latest.Status
|
||||
$emoji = Get-BuildStatusEmoji -Status $latestStatus
|
||||
$isLatestSuccessful = Test-IsSuccessfulStatus -Status $latestStatus
|
||||
if ($isLatestSuccessful) {
|
||||
$successfulLatestCount++
|
||||
}
|
||||
elseif ($latest.Severity -eq 'Critical') {
|
||||
$failedLatestCount++
|
||||
}
|
||||
|
||||
$previousFailures = @($Analyses |
|
||||
Where-Object {
|
||||
$_.Repository -eq $latest.Repository -and
|
||||
$_.RunId -ne $latest.RunId -and
|
||||
$_.Severity -eq 'Critical'
|
||||
} |
|
||||
Sort-Object StartedAt -Descending |
|
||||
Select-Object -First ([int]$Config.MaxPreviousFailures))
|
||||
|
||||
$previousFailureCount += $previousFailures.Count
|
||||
$previousFailureText = if ($previousFailures.Count -gt 0) {
|
||||
($previousFailures | ForEach-Object {
|
||||
$shortSha = if ($_.CommitSha -and $_.CommitSha.Length -ge 8) { $_.CommitSha.Substring(0, 8) } else { $_.CommitSha }
|
||||
$commitPart = if ($shortSha) { " ``$shortSha``" } else { '' }
|
||||
"#{0} {1}{2}" -f $_.RunId, $_.Status, $commitPart
|
||||
}) -join "`n"
|
||||
}
|
||||
else {
|
||||
'None in this lookback window.'
|
||||
}
|
||||
|
||||
if ($previousFailureText.Length -gt 950) {
|
||||
$previousFailureText = $previousFailureText.Substring(0, 947) + '...'
|
||||
}
|
||||
|
||||
$shortLatestSha = if ($latest.CommitSha -and $latest.CommitSha.Length -ge 8) { $latest.CommitSha.Substring(0, 8) } else { $latest.CommitSha }
|
||||
$latestCommit = if ($shortLatestSha) {
|
||||
if ($latest.CommitMessage) { "``$shortLatestSha`` $($latest.CommitMessage)" } else { "``$shortLatestSha``" }
|
||||
}
|
||||
else {
|
||||
'unknown'
|
||||
}
|
||||
|
||||
$color = if ($isLatestSuccessful) { 3066993 } elseif ($latest.Severity -eq 'Critical') { 15158332 } else { 3447003 }
|
||||
$embeds.Add(@{
|
||||
title = "$emoji Latest build: $($latest.Repository) #$($latest.RunId)"
|
||||
url = $latest.BuildUrl
|
||||
color = $color
|
||||
description = if ($isLatestSuccessful) { 'Latest completed build is successful.' } elseif ($latest.Severity -eq 'Critical') { 'Latest completed build failed.' } else { 'Latest completed build has a non-success status.' }
|
||||
fields = @(
|
||||
@{ name = 'Latest status'; value = "$emoji $latestStatus"; inline = $true },
|
||||
@{ name = 'Branch'; value = $latest.Branch; inline = $true },
|
||||
@{ name = 'Duration'; value = $latest.DurationText; inline = $true },
|
||||
@{ name = 'Commit'; value = $latestCommit; inline = $false },
|
||||
@{ name = 'Previous failed builds'; value = $previousFailureText; inline = $false }
|
||||
)
|
||||
footer = @{
|
||||
text = "Run ID $($latest.RunId)"
|
||||
}
|
||||
timestamp = (Get-Date).ToUniversalTime().ToString('o')
|
||||
})
|
||||
}
|
||||
|
||||
$payload = @{
|
||||
username = 'Gitea Build Monitor'
|
||||
content = if ($failedLatestCount -gt 0) {
|
||||
"❌ Latest Gitea build status: $failedLatestCount failed, $successfulLatestCount successful. Previous failures in window: $previousFailureCount."
|
||||
}
|
||||
elseif ($successfulLatestCount -gt 0) {
|
||||
"✅ Latest Gitea build status: $successfulLatestCount successful. Previous failures in window: $previousFailureCount."
|
||||
}
|
||||
else {
|
||||
"ℹ️ Latest Gitea build status reported. Previous failures in window: $previousFailureCount."
|
||||
}
|
||||
embeds = @($embeds)
|
||||
}
|
||||
|
||||
Invoke-DiscordWebhook -Payload $payload
|
||||
if ($DryRun) {
|
||||
Write-Log -Message "Prepared build status notification with $($embeds.Count) embed(s) in dry-run mode."
|
||||
}
|
||||
else {
|
||||
Write-Log -Message "Sent build status notification with $($embeds.Count) embed(s)."
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PythonCommand {
|
||||
$linuxVenvPython = Join-Path $PSScriptRoot '.venv/bin/python'
|
||||
$windowsVenvPython = Join-Path $PSScriptRoot '.venv/Scripts/python.exe'
|
||||
@@ -921,6 +1064,25 @@ function Get-CacheKey {
|
||||
return "$(Get-RepositorySlug -Repository $Repository):$kind`:$jobId`:state:$state"
|
||||
}
|
||||
|
||||
function Get-BuildSummaryCacheKey {
|
||||
param([Parameter(Mandatory)][object[]]$Analyses)
|
||||
|
||||
$parts = @($Analyses |
|
||||
Group-Object Repository |
|
||||
ForEach-Object {
|
||||
$latest = $_.Group | Sort-Object StartedAt -Descending | Select-Object -First 1
|
||||
$previousFailureIds = @($_.Group |
|
||||
Where-Object { $_.RunId -ne $latest.RunId -and $_.Severity -eq 'Critical' } |
|
||||
Sort-Object StartedAt -Descending |
|
||||
Select-Object -First ([int]$Config.MaxPreviousFailures) |
|
||||
ForEach-Object { $_.RunId })
|
||||
'{0}:latest:{1}:{2}:prevfail:{3}' -f $latest.Repository, $latest.RunId, $latest.Status, ($previousFailureIds -join ',')
|
||||
} |
|
||||
Sort-Object)
|
||||
|
||||
return 'build-summary:' + ($parts -join '|')
|
||||
}
|
||||
|
||||
function Start-BuildMonitor {
|
||||
Assert-Config
|
||||
|
||||
@@ -961,17 +1123,9 @@ function Start-BuildMonitor {
|
||||
continue
|
||||
}
|
||||
|
||||
$cacheKey = Get-CacheKey -Repository $repository -Job $job
|
||||
if ($cache.ContainsKey($cacheKey)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$analysis = Analyze-Build -Repository $repository -Job $job
|
||||
$analyses.Add($analysis)
|
||||
if (-not $DryRun) {
|
||||
$cache[$cacheKey] = Get-Date
|
||||
}
|
||||
Write-Log -Message ("Analyzed job {0} for {1}: severity={2}, status={3}, duration={4}" -f (Get-ObjectValue -Object $job -Name 'id'), $analysis.Repository, $analysis.Severity, $analysis.Status, $analysis.DurationText)
|
||||
}
|
||||
catch {
|
||||
@@ -981,10 +1135,19 @@ function Start-BuildMonitor {
|
||||
}
|
||||
|
||||
if ($analyses.Count -gt 0) {
|
||||
Send-DiscordNotification -Analyses $analyses.ToArray()
|
||||
$summaryCacheKey = Get-BuildSummaryCacheKey -Analyses $analyses.ToArray()
|
||||
if ($cache.ContainsKey($summaryCacheKey)) {
|
||||
Write-Log -Message 'Build status summary has already been sent.'
|
||||
}
|
||||
else {
|
||||
Send-BuildStatusNotification -Analyses $analyses.ToArray()
|
||||
if (-not $DryRun) {
|
||||
$cache[$summaryCacheKey] = Get-Date
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Message "No new jobs found in the last $($Config.LookbackMinutes) minute(s)."
|
||||
Write-Log -Message "No completed builds found in the last $($Config.LookbackMinutes) minute(s)."
|
||||
}
|
||||
|
||||
if (-not $DryRun) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
See the categories: @@html:<a href="../home/categories.html">Categories</a>@@
|
||||
|
||||
* Posts:
|
||||
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">08-05-2026 15:52</span>@@
|
||||
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">08-05-2026 16:04</span>@@
|
||||
- [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:<span class="post-date">14-04-2026 16:36</span>@@
|
||||
- [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:<span class="post-date">11-03-2026 17:18</span>@@ @@html:<a href="/tags/learning.html"><span class="post-tag">learning</span></a>@@ @@html:<a href="/tags/notes.html"><span class="post-tag">notes</span></a>@@
|
||||
- [[file:career/javascript.org][Understands the Javascript language]] @@html:<span class="post-date">11-03-2026 16:52</span>@@ @@html:<a href="/tags/learning.html"><span class="post-tag">learning</span></a>@@ @@html:<a href="/tags/notes.html"><span class="post-tag">notes</span></a>@@
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
- [[file:blogs/2026/04-april/ending-the-week-17-04-26.org][End of week thoughts]] @@html:<span class="post-date">2026-04-17 16:17</span>@@
|
||||
- [[file:blogs/2026/04-april/16-04-26.org][Rambles]] @@html:<span class="post-date">2026-04-16 16:02</span>@@
|
||||
- [[file:posts/posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:<span class="post-date">2026-04-14 16:36</span>@@
|
||||
- [[file:home/categories.sync-conflict-20260417-233429-VT6366A.org][Categories]] @@html:<span class="post-date">2026-04-14 16:36</span>@@
|
||||
- [[file:blogs/2026/04-april/comparison-14-04.org][Comparison is the thief of joy]] @@html:<span class="post-date">2026-04-14 09:55</span>@@
|
||||
- [[file:blogs/2026/04-april/12-04-week-review.org][[12-04-2026] - Weekly Review]] @@html:<span class="post-date">2026-04-12 12:00</span>@@
|
||||
- [[file:blogs/2026/04-april/starting-new-rotation-08-04-26.org][Starting new rotation]] @@html:<span class="post-date">2026-04-08 16:15</span>@@
|
||||
@@ -28,3 +27,4 @@
|
||||
- [[file:blogs/2026/03-march/intellectually-challenging-myself-30-03-26.org][Intellectually challenging oneself]] @@html:<span class="post-date">2026-03-31 11:14</span>@@
|
||||
- [[file:blogs/2026/03-march/cooking-dinner-29-03-26.org][Cooking Dinner (num)]] @@html:<span class="post-date">2026-03-30 11:49</span>@@
|
||||
- [[file:blogs/2026/03-march/29-03-week-review.org][[29-03-2026] - Weekly Review]] @@html:<span class="post-date">2026-03-29 12:00</span>@@
|
||||
- [[file:blogs/2026/03-march/training-on-friday-afternoon-27-03-26.org][Training time on Friday afternoon]] @@html:<span class="post-date">2026-03-27 15:09</span>@@
|
||||
|
||||
29
sitemap.org
29
sitemap.org
@@ -13,24 +13,11 @@
|
||||
- [[file:tags/website.org][Tag: website]]
|
||||
- [[file:tags/life.org][Tag: life]]
|
||||
- [[file:tags/update.org][Tag: update]]
|
||||
- [[file:tags/insights.org][Tag: insights]]
|
||||
- [[file:tags/emacs.org][Tag: emacs]]
|
||||
- [[file:tags/education.org][Tag: education]]
|
||||
- [[file:tags/insights.org][Tag: insights]]
|
||||
- [[file:tags/reading.org][Tag: reading]]
|
||||
- [[file:tags/emacs.org][Tag: emacs]]
|
||||
- [[file:tags/maths.org][Tag: maths]]
|
||||
- home
|
||||
- [[file:home/countdown.org][Countdown]]
|
||||
- [[file:home/contact.org][Contact]]
|
||||
- [[file:home/backlog.org][Backlog]]
|
||||
- [[file:home/notes.org][Notes]]
|
||||
- [[file:home/services.org][Service]]
|
||||
- [[file:home/status.org][Competency Status Board]]
|
||||
- [[file:home/categories.sync-conflict-20260417-233429-VT6366A.org][Categories]]
|
||||
- [[file:home/wird-tracker.org][Wird Tracker]]
|
||||
- [[file:home/categories.org][Categories]]
|
||||
- guide
|
||||
- [[file:home/guide/setup.org][Setup]]
|
||||
- [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]]
|
||||
- posts
|
||||
- [[file:posts/posts-intro.org][Posts Introduction]]
|
||||
- [[file:posts/posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]]
|
||||
@@ -65,3 +52,15 @@
|
||||
- [[file:books/clean-code/clean-code-notes.org][Clean Code Notes]]
|
||||
- lima
|
||||
- [[file:lima/lima-list.org][Lima]]
|
||||
- home
|
||||
- [[file:home/countdown.org][Countdown]]
|
||||
- [[file:home/contact.org][Contact]]
|
||||
- [[file:home/backlog.org][Backlog]]
|
||||
- [[file:home/notes.org][Notes]]
|
||||
- [[file:home/services.org][Service]]
|
||||
- [[file:home/status.org][Competency Status Board]]
|
||||
- [[file:home/wird-tracker.org][Wird Tracker]]
|
||||
- [[file:home/categories.org][Categories]]
|
||||
- guide
|
||||
- [[file:home/guide/setup.org][Setup]]
|
||||
- [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]]
|
||||
Reference in New Issue
Block a user