Include mentions in resolved webhook templates (#1118)
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Core E2E Tests / Playwright Core E2E (push) Waiting to run

This commit is contained in:
rcourtman 2026-03-26 00:32:07 +00:00
parent ca66581b6e
commit 0a7b93a842
2 changed files with 130 additions and 2 deletions

View file

@ -339,6 +339,56 @@ func TestSendResolvedWebhookUsesServiceTemplate(t *testing.T) {
}
}
func TestSendResolvedWebhookDiscordIncludesMention(t *testing.T) {
var gotBody []byte
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
gotBody = body
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
manager := NewNotificationManager("https://pulse.local")
defer manager.Stop()
manager.webhookClient = server.Client()
if err := manager.UpdateAllowedPrivateCIDRs("127.0.0.1/32"); err != nil {
t.Fatalf("allowlist: %v", err)
}
alertList := []*alerts.Alert{{
ID: "a1",
Type: "cpu",
Level: alerts.AlertLevelWarning,
ResourceID: "vm100",
ResourceName: "web-server",
Node: "pve1",
Message: "CPU usage high",
Value: 95,
Threshold: 90,
StartTime: time.Now().Add(-5 * time.Minute),
}}
webhook := WebhookConfig{
Name: "discord-test",
URL: server.URL + "/discord",
Enabled: true,
Service: "discord",
Mention: "@everyone",
}
if err := manager.sendResolvedWebhook(webhook, alertList, time.Now()); err != nil {
t.Fatalf("sendResolvedWebhook error: %v", err)
}
var payload map[string]interface{}
if err := json.Unmarshal(gotBody, &payload); err != nil {
t.Fatalf("unmarshal payload: %v", err)
}
if payload["content"] != "@everyone" {
t.Fatalf("expected discord mention in content, got %v", payload["content"])
}
}
func TestSendResolvedWebhookGenericFallback(t *testing.T) {
var gotBody []byte
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -422,6 +472,7 @@ func TestSendResolvedWebhookSlackTemplate(t *testing.T) {
URL: server.URL + "/slack",
Enabled: true,
Service: "slack",
Mention: "@channel",
}
if err := manager.sendResolvedWebhook(webhook, alertList, time.Now()); err != nil {
@ -439,6 +490,74 @@ func TestSendResolvedWebhookSlackTemplate(t *testing.T) {
if !hasBlocks && !hasText {
t.Fatalf("expected Slack payload to contain 'blocks' or 'text', got keys: %v", payload)
}
if payload["text"] != "@channel Resolved: db-server" {
t.Fatalf("expected slack mention in text, got %v", payload["text"])
}
blocks, ok := payload["blocks"].([]interface{})
if !ok || len(blocks) == 0 {
t.Fatalf("expected slack blocks, got %v", payload["blocks"])
}
firstBlock, ok := blocks[0].(map[string]interface{})
if !ok {
t.Fatalf("expected first block object, got %T", blocks[0])
}
text, ok := firstBlock["text"].(map[string]interface{})
if !ok || text["text"] != "@channel" {
t.Fatalf("expected first slack block to carry mention, got %v", firstBlock["text"])
}
}
func TestSendResolvedWebhookMattermostIncludesMention(t *testing.T) {
var gotBody []byte
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
gotBody = body
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
manager := NewNotificationManager("https://pulse.local")
defer manager.Stop()
manager.webhookClient = server.Client()
if err := manager.UpdateAllowedPrivateCIDRs("127.0.0.1/32"); err != nil {
t.Fatalf("allowlist: %v", err)
}
alertList := []*alerts.Alert{{
ID: "a1",
Type: "memory",
Level: alerts.AlertLevelCritical,
ResourceID: "vm200",
ResourceName: "db-server",
Node: "pve2",
Message: "Memory usage critical",
Value: 98,
Threshold: 95,
StartTime: time.Now().Add(-10 * time.Minute),
}}
webhook := WebhookConfig{
Name: "mattermost-test",
URL: server.URL + "/mattermost",
Enabled: true,
Service: "mattermost",
Mention: "@channel",
}
if err := manager.sendResolvedWebhook(webhook, alertList, time.Now()); err != nil {
t.Fatalf("sendResolvedWebhook error: %v", err)
}
var payload map[string]interface{}
if err := json.Unmarshal(gotBody, &payload); err != nil {
t.Fatalf("unmarshal payload: %v", err)
}
text, _ := payload["text"].(string)
if !strings.HasPrefix(text, "@channel\n\n:white_check_mark: **RESOLVED**") {
t.Fatalf("expected mattermost text to start with mention, got %q", text)
}
}
func TestSendResolvedWebhookPagerDutyResolve(t *testing.T) {

View file

@ -44,6 +44,7 @@ func GetWebhookTemplates() []WebhookTemplate {
}`,
ResolvedPayloadTemplate: `{
"username": "Pulse Monitoring",
{{if .Mention}}"content": "{{.Mention | jsonString}}",{{end}}
"embeds": [{
"title": "Resolved: {{.ResourceName | jsonString}}",
"description": "{{.Message | jsonString}}",
@ -137,8 +138,15 @@ func GetWebhookTemplates() []WebhookTemplate {
]
}`,
ResolvedPayloadTemplate: `{
"text": "Resolved: {{.ResourceName | jsonString}}",
"text": "{{if .Mention}}{{.Mention | jsonString}} {{end}}Resolved: {{.ResourceName | jsonString}}",
"blocks": [
{{if .Mention}}{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "{{.Mention | jsonString}}"
}
},{{end}}
{
"type": "header",
"text": {
@ -217,6 +225,7 @@ func GetWebhookTemplates() []WebhookTemplate {
"@context": "http://schema.org/extensions",
"themeColor": "2DC72D",
"summary": "Resolved: {{.ResourceName | jsonString}}",
{{if .Mention}}"text": "{{.Mention | jsonString}}",{{end}}
"sections": [{
"activityTitle": "Resolved: {{.ResourceName | jsonString}}",
"activitySubtitle": "{{.Message | jsonString}}",
@ -478,7 +487,7 @@ View in Pulse: {{.Instance}}`,
ResolvedPayloadTemplate: `{
"username": "Pulse Monitoring",
"icon_url": "https://raw.githubusercontent.com/rcourtman/Pulse/main/frontend-modern/public/android-chrome-192x192.png",
"text": ":white_check_mark: **RESOLVED**\n\n**{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n| Detail | Value |\n|:-------|:------|\n| Resource | {{.ResourceName | jsonString}} |\n| Node | {{.Node | jsonString}} |\n| Type | {{.Type | title | jsonString}} |\n| Duration | {{.Duration | jsonString}} |\n| Resolved At | {{.ResolvedAt | jsonString}} |\n| Alert ID | {{.ID | jsonString}} |"
"text": "{{if .Mention}}{{.Mention | jsonString}}\n\n{{end}}:white_check_mark: **RESOLVED**\n\n**{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n| Detail | Value |\n|:-------|:------|\n| Resource | {{.ResourceName | jsonString}} |\n| Node | {{.Node | jsonString}} |\n| Type | {{.Type | title | jsonString}} |\n| Duration | {{.Duration | jsonString}} |\n| Resolved At | {{.ResolvedAt | jsonString}} |\n| Alert ID | {{.ID | jsonString}} |"
}`,
Instructions: "1. In Mattermost, go to Integrations > Incoming Webhooks\n2. Create a new webhook and select the channel\n3. Copy the webhook URL and paste it here\n\nNote: This template uses Markdown formatting which is fully supported by Mattermost.",
},