diff --git a/notify/util.go b/notify/util.go index fe4c9ea508..50562f6aab 100644 --- a/notify/util.go +++ b/notify/util.go @@ -213,6 +213,11 @@ func GetTemplateData(ctx context.Context, tmpl *template.Template, alerts []*typ notificationReason = ReasonUnknown } data := tmpl.Data(recv, groupLabels, routeLabels, notificationReason.String(), alerts...) + // Group key is optional for some callers (e.g. tests); populate when present + // so templates and webhook-style consumers can access .GroupKey. + if groupKey, ok := GroupKey(ctx); ok { + data.GroupKey = groupKey + } // Route labels are pre-rendered by the dispatcher; don't execute them again. template.MarkRouteLabelsRendered(data) return data diff --git a/notify/util_test.go b/notify/util_test.go index 2c2d4922e8..9a1d2d1f39 100644 --- a/notify/util_test.go +++ b/notify/util_test.go @@ -244,6 +244,7 @@ func TestGetTemplateDataWithRouteLabels(t *testing.T) { data := GetTemplateData(ctx, tmpl, nil, promslog.NewNopLogger()) + require.Equal(t, "test-key", data.GroupKey) require.Equal(t, "ops", data.RouteLabels["team"]) require.Equal(t, "value is {{ $value }}", data.RouteLabels["desc"]) @@ -253,6 +254,34 @@ func TestGetTemplateDataWithRouteLabels(t *testing.T) { require.Equal(t, "ops|value is {{ $value }}", got) } +func TestGetTemplateDataGroupKey(t *testing.T) { + tmpl, err := template.New() + require.NoError(t, err) + tmpl.ExternalURL = &url.URL{Scheme: "http", Host: "example.com"} + + groupKey := `{}:{alertname="HighErrorRate"}` + ctx := context.Background() + ctx = WithReceiverName(ctx, "webhook") + ctx = WithGroupKey(ctx, groupKey) + ctx = WithGroupLabels(ctx, model.LabelSet{"alertname": "HighErrorRate"}) + ctx = WithNotificationReason(ctx, ReasonFirstNotification) + + data := GetTemplateData(ctx, tmpl, nil, promslog.NewNopLogger()) + require.Equal(t, groupKey, data.GroupKey) + + // Templates can reference .GroupKey like other Data fields. + got, err := tmpl.ExecuteTextString(`{{ .GroupKey }}`, data) + require.NoError(t, err) + require.Equal(t, groupKey, got) + + // Missing group key leaves GroupKey empty without failing. + ctxNoKey := WithReceiverName(context.Background(), "webhook") + ctxNoKey = WithGroupLabels(ctxNoKey, model.LabelSet{"alertname": "HighErrorRate"}) + ctxNoKey = WithNotificationReason(ctxNoKey, ReasonFirstNotification) + dataNoKey := GetTemplateData(ctxNoKey, tmpl, nil, promslog.NewNopLogger()) + require.Empty(t, dataNoKey.GroupKey) +} + func TestGetFailureReasonFromStatusCode(t *testing.T) { for _, tc := range []struct { statusCode int diff --git a/template/template.go b/template/template.go index 0f51864147..fd0d5a2a9b 100644 --- a/template/template.go +++ b/template/template.go @@ -485,6 +485,10 @@ type Data struct { Status string `json:"status"` Alerts Alerts `json:"alerts"` + // GroupKey is the grouping key of the alert group, matching the webhook + // payload field of the same name. + GroupKey string `json:"groupKey"` + NotificationReason string `json:"notification_reason"` GroupLabels KV `json:"groupLabels"`