From 802d67eecaecb129308b0b3d966c73ec01358ca6 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Mon, 10 Oct 2022 13:40:21 +0100 Subject: [PATCH] Alerting: Support values in notification templates (#56457) We have received a lot of feedback regarding the ValueString in alert notifications. Perhaps one of the most frequent complaints about ValueString is that it is difficult to read because it contains a lot of information, and the information is shown as a JSON-like string. Users have often asked how it can be templated and the answer is that it can't. Until now users have been able to add custom annotations to their alert rules which contains values via the $values variable added in previous versions of Grafana. However, these custom annotations must be added for each of the user's alert rule, instead of once in a template that all of their alerts can be notified via. This commit adds then the much requested feature to support values in notification templates. Users can then create a single template that prints the annotations, labels and values of their alerts in a format of their choice! --- emails/templates/ng_alert_notification.html | 6 +- pkg/services/ngalert/models/alert_rule.go | 3 + .../notifier/channels/default_template.go | 14 +++- .../channels/default_template_test.go | 24 +++--- .../notifier/channels/dingding_test.go | 4 +- .../notifier/channels/template_data.go | 36 +++++---- pkg/services/ngalert/schedule/compat.go | 10 ++- pkg/services/ngalert/schedule/compat_test.go | 1 + pkg/services/ngalert/state/cache.go | 12 +++ pkg/services/ngalert/state/manager_test.go | 79 +++++++++++++------ pkg/services/ngalert/state/state.go | 1 + .../alerting/api_notification_channel_test.go | 39 ++++----- .../components/receivers/TemplateData.ts | 6 ++ public/emails/ng_alert_notification.html | 6 +- 14 files changed, 165 insertions(+), 76 deletions(-) diff --git a/emails/templates/ng_alert_notification.html b/emails/templates/ng_alert_notification.html index f97757e7869..093c75cc21e 100644 --- a/emails/templates/ng_alert_notification.html +++ b/emails/templates/ng_alert_notification.html @@ -3,6 +3,10 @@ [[Subject .Subject "[[.Title]]"]] +[[ define "__text_values_list" ]][[ $len := len .Values ]][[ if $len ]][[ $first := gt $len 1 ]][[ range $refID, $value := .Values -]] +[[ $refID ]]=[[ $value ]][[ if $first ]], [[ end ]][[ $first = false ]][[ end -]] +[[ else ]][no value][[ end ]][[ end ]] + [[ define "alert" ]] [[ if ne .ImageURL "" ]] @@ -21,7 +25,7 @@ [[ end ]] - Value: [[ .ValueString ]] + Value: [[ template "__text_values_list" . ]] [[ if gt (len .Annotations.SortedPairs) 0 ]] diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 8d55f7e3828..d540204c157 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -101,6 +101,9 @@ const ( // StateReasonAnnotation is the name of the annotation that explains the difference between evaluation state and alert state (i.e. changing state when NoData or Error). StateReasonAnnotation = GrafanaReservedLabelPrefix + "state_reason" + + ValuesAnnotation = "__values__" + ValueStringAnnotation = "__value_string__" ) var ( diff --git a/pkg/services/ngalert/notifier/channels/default_template.go b/pkg/services/ngalert/notifier/channels/default_template.go index 36dc5cb6095..d2cf9e5a1a9 100644 --- a/pkg/services/ngalert/notifier/channels/default_template.go +++ b/pkg/services/ngalert/notifier/channels/default_template.go @@ -16,8 +16,12 @@ const ( var DefaultTemplateString = ` {{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ if gt (.Alerts.Resolved | len) 0 }}, RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }} +{{ define "__text_values_list" }}{{ $len := len .Values }}{{ if $len }}{{ $first := gt $len 1 }}{{ range $refID, $value := .Values -}} +{{ $refID }}={{ $value }}{{ if $first }}, {{ end }}{{ $first = false }}{{ end -}} +{{ else }}[no value]{{ end }}{{ end }} + {{ define "__text_alert_list" }}{{ range . }} -Value: {{ or .ValueString "[no value]" }} +Value: {{ template "__text_values_list" . }} Labels: {{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} {{ end }}Annotations: @@ -38,7 +42,7 @@ Labels: {{ define "__teams_text_alert_list" }}{{ range . }} -Value: {{ or .ValueString "[no value]" }} +Value: {{ template "__text_values_list" . }} Labels: {{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} {{ end }} @@ -70,8 +74,12 @@ Annotations: const TemplateForTestsString = ` {{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }} +{{ define "__text_values_list" }}{{ $len := len .Values }}{{ if $len }}{{ $first := gt $len 1 }}{{ range $refID, $value := .Values -}} +{{ $refID }}={{ $value }}{{ if $first }}, {{ end }}{{ $first = false }}{{ end -}} +{{ else }}[no value]{{ end }}{{ end }} + {{ define "__text_alert_list" }}{{ range . }} -Value: {{ or .ValueString "[no value]" }} +Value: {{ template "__text_values_list" . }} Labels: {{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} {{ end }}Annotations: diff --git a/pkg/services/ngalert/notifier/channels/default_template_test.go b/pkg/services/ngalert/notifier/channels/default_template_test.go index 6f2827c9076..55fae4b6198 100644 --- a/pkg/services/ngalert/notifier/channels/default_template_test.go +++ b/pkg/services/ngalert/notifier/channels/default_template_test.go @@ -20,7 +20,7 @@ func TestDefaultTemplateString(t *testing.T) { Alert: model.Alert{ Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, Annotations: model.LabelSet{ - "ann1": "annv1", "__dashboardUid__": "dbuid123", "__panelId__": "puid123", "__value_string__": "1234", + "ann1": "annv1", "__dashboardUid__": "dbuid123", "__panelId__": "puid123", "__values__": "{\"A\": 1234}", "__value_string__": "1234", }, StartsAt: time.Now(), EndsAt: time.Now().Add(1 * time.Hour), @@ -29,7 +29,7 @@ func TestDefaultTemplateString(t *testing.T) { }, { // Firing without dashboard and panel ID. Alert: model.Alert{ Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"}, - Annotations: model.LabelSet{"ann1": "annv2", "__value_string__": "1234"}, + Annotations: model.LabelSet{"ann1": "annv2", "__values__": "{\"A\": 1234}", "__value_string__": "1234"}, StartsAt: time.Now(), EndsAt: time.Now().Add(2 * time.Hour), GeneratorURL: "http://localhost/alert2", @@ -38,7 +38,7 @@ func TestDefaultTemplateString(t *testing.T) { Alert: model.Alert{ Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val3"}, Annotations: model.LabelSet{ - "ann1": "annv3", "__dashboardUid__": "dbuid456", "__panelId__": "puid456", "__value_string__": "1234", + "ann1": "annv3", "__dashboardUid__": "dbuid456", "__panelId__": "puid456", "__values__": "{\"A\": 1234}", "__value_string__": "1234", }, StartsAt: time.Now().Add(-1 * time.Hour), EndsAt: time.Now().Add(-30 * time.Minute), @@ -47,7 +47,7 @@ func TestDefaultTemplateString(t *testing.T) { }, { // Resolved without dashboard and panel ID. Alert: model.Alert{ Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val4"}, - Annotations: model.LabelSet{"ann1": "annv4", "__value_string__": "1234"}, + Annotations: model.LabelSet{"ann1": "annv4", "__values__": "{\"A\": 1234}", "__value_string__": "1234"}, StartsAt: time.Now().Add(-2 * time.Hour), EndsAt: time.Now().Add(-3 * time.Hour), GeneratorURL: "http://localhost/alert4", @@ -91,7 +91,7 @@ func TestDefaultTemplateString(t *testing.T) { templateString: DefaultMessageEmbed, expected: `**Firing** -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val1 @@ -102,7 +102,7 @@ Silence: http://localhost/grafana/alerting/silence/new?alertmanager=grafana&matc Dashboard: http://localhost/grafana/d/dbuid123 Panel: http://localhost/grafana/d/dbuid123?viewPanel=puid123 -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val2 @@ -114,7 +114,7 @@ Silence: http://localhost/grafana/alerting/silence/new?alertmanager=grafana&matc **Resolved** -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val3 @@ -125,7 +125,7 @@ Silence: http://localhost/grafana/alerting/silence/new?alertmanager=grafana&matc Dashboard: http://localhost/grafana/d/dbuid456 Panel: http://localhost/grafana/d/dbuid456?viewPanel=puid456 -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val4 @@ -139,7 +139,7 @@ Silence: http://localhost/grafana/alerting/silence/new?alertmanager=grafana&matc templateString: `{{ template "teams.default.message" .}}`, expected: `**Firing** -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val1 @@ -157,7 +157,7 @@ Panel: [http://localhost/grafana/d/dbuid123?viewPanel=puid123](http://localhost/ -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val2 @@ -174,7 +174,7 @@ Silence: [http://localhost/grafana/alerting/silence/new?alertmanager=grafana&mat **Resolved** -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val3 @@ -192,7 +192,7 @@ Panel: [http://localhost/grafana/d/dbuid456?viewPanel=puid456](http://localhost/ -Value: 1234 +Value: A=1234 Labels: - alertname = alert1 - lbl1 = val4 diff --git a/pkg/services/ngalert/notifier/channels/dingding_test.go b/pkg/services/ngalert/notifier/channels/dingding_test.go index db1a927aea4..573cbcdaa5f 100644 --- a/pkg/services/ngalert/notifier/channels/dingding_test.go +++ b/pkg/services/ngalert/notifier/channels/dingding_test.go @@ -36,7 +36,7 @@ func TestDingdingNotifier(t *testing.T) { { Alert: model.Alert{ Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, - Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__value_string__": "1234"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__values__": "{\"A\": 1234}", "__value_string__": "1234"}, }, }, }, @@ -44,7 +44,7 @@ func TestDingdingNotifier(t *testing.T) { "msgtype": "link", "link": map[string]interface{}{ "messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%2Falerting%2Flist", - "text": "**Firing**\n\nValue: 1234\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n", + "text": "**Firing**\n\nValue: A=1234\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n", "title": "[FIRING:1] (val1)", }, }, diff --git a/pkg/services/ngalert/notifier/channels/template_data.go b/pkg/services/ngalert/notifier/channels/template_data.go index c021d8ecc37..06044738b36 100644 --- a/pkg/services/ngalert/notifier/channels/template_data.go +++ b/pkg/services/ngalert/notifier/channels/template_data.go @@ -2,6 +2,7 @@ package channels import ( "context" + "encoding/json" "net/url" "path" "sort" @@ -18,19 +19,20 @@ import ( ) type ExtendedAlert struct { - Status string `json:"status"` - Labels template.KV `json:"labels"` - Annotations template.KV `json:"annotations"` - StartsAt time.Time `json:"startsAt"` - EndsAt time.Time `json:"endsAt"` - GeneratorURL string `json:"generatorURL"` - Fingerprint string `json:"fingerprint"` - SilenceURL string `json:"silenceURL"` - DashboardURL string `json:"dashboardURL"` - PanelURL string `json:"panelURL"` - ValueString string `json:"valueString"` - ImageURL string `json:"imageURL,omitempty"` - EmbeddedImage string `json:"embeddedImage,omitempty"` + Status string `json:"status"` + Labels template.KV `json:"labels"` + Annotations template.KV `json:"annotations"` + StartsAt time.Time `json:"startsAt"` + EndsAt time.Time `json:"endsAt"` + GeneratorURL string `json:"generatorURL"` + Fingerprint string `json:"fingerprint"` + SilenceURL string `json:"silenceURL"` + DashboardURL string `json:"dashboardURL"` + PanelURL string `json:"panelURL"` + Values map[string]float64 `json:"values"` + ValueString string `json:"valueString"` // TODO: Remove in Grafana 10 + ImageURL string `json:"imageURL,omitempty"` + EmbeddedImage string `json:"embeddedImage,omitempty"` } type ExtendedAlerts []ExtendedAlert @@ -90,7 +92,13 @@ func extendAlert(alert template.Alert, externalURL string, logger log.Logger) *E } if alert.Annotations != nil { - extended.ValueString = alert.Annotations[`__value_string__`] + if s, ok := alert.Annotations[ngmodels.ValuesAnnotation]; ok { + if err := json.Unmarshal([]byte(s), &extended.Values); err != nil { + logger.Warn("failed to unmarshal values annotation", "err", err) + } + } + // TODO: Remove in Grafana 10 + extended.ValueString = alert.Annotations[ngmodels.ValueStringAnnotation] } matchers := make([]string, 0) diff --git a/pkg/services/ngalert/schedule/compat.go b/pkg/services/ngalert/schedule/compat.go index 4aa597a9c28..ffa6b381444 100644 --- a/pkg/services/ngalert/schedule/compat.go +++ b/pkg/services/ngalert/schedule/compat.go @@ -1,6 +1,7 @@ package schedule import ( + "encoding/json" "fmt" "net/url" "path" @@ -35,8 +36,15 @@ func stateToPostableAlert(alertState *state.State, appURL *url.URL) *models.Post nL := alertState.Labels.Copy() nA := data.Labels(alertState.Annotations).Copy() + // encode the values as JSON where it will be expanded later + if len(alertState.Values) > 0 { + if b, err := json.Marshal(alertState.Values); err == nil { + nA[ngModels.ValuesAnnotation] = string(b) + } + } + if alertState.LastEvaluationString != "" { - nA["__value_string__"] = alertState.LastEvaluationString + nA[ngModels.ValueStringAnnotation] = alertState.LastEvaluationString } if alertState.Image != nil { diff --git a/pkg/services/ngalert/schedule/compat_test.go b/pkg/services/ngalert/schedule/compat_test.go index 51d101e2f08..510bff94f7c 100644 --- a/pkg/services/ngalert/schedule/compat_test.go +++ b/pkg/services/ngalert/schedule/compat_test.go @@ -276,5 +276,6 @@ func randomState(evalState eval.State) *state.State { LastSentAt: randomTimeInPast(), Annotations: make(map[string]string), Labels: make(map[string]string), + Values: make(map[string]float64), } } diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index 2e748325cca..1371c1a8262 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -3,6 +3,7 @@ package state import ( "context" "fmt" + "math" "net/url" "strings" "sync" @@ -50,6 +51,15 @@ func (c *cache) getOrCreate(ctx context.Context, log log.Logger, alertRule *ngMo func (rs *ruleStates) getOrCreate(ctx context.Context, log log.Logger, alertRule *ngModels.AlertRule, result eval.Result, extraLabels data.Labels, externalURL *url.URL) *State { ruleLabels, annotations := rs.expandRuleLabelsAndAnnotations(ctx, log, alertRule, result, extraLabels, externalURL) + values := make(map[string]float64) + for _, v := range result.Values { + if v.Value != nil { + values[v.Var] = *v.Value + } else { + values[v.Var] = math.NaN() + } + } + lbs := make(data.Labels, len(extraLabels)+len(ruleLabels)+len(result.Instance)) dupes := make(data.Labels) for key, val := range extraLabels { @@ -102,6 +112,7 @@ func (rs *ruleStates) getOrCreate(ctx context.Context, log log.Logger, alertRule } } state.Annotations = annotations + state.Values = values rs.states[id] = state return state } @@ -115,6 +126,7 @@ func (rs *ruleStates) getOrCreate(ctx context.Context, log log.Logger, alertRule Labels: lbs, Annotations: annotations, EvaluationDuration: result.EvaluationDuration, + Values: values, } if result.State == eval.Alerting { newState.StartsAt = result.EvaluatedAt diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index ebe1d76fe8b..53d572b4a8f 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -125,7 +125,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -179,7 +180,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label_1": "test", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -202,7 +204,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label_2": "test", }, - State: eval.Alerting, + Values: make(map[string]float64), + State: eval.Alerting, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -259,7 +262,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -320,7 +324,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Alerting, + Values: make(map[string]float64), + State: eval.Alerting, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -392,7 +397,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Alerting, + Values: make(map[string]float64), + State: eval.Alerting, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -486,7 +492,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Pending, + Values: make(map[string]float64), + State: eval.Pending, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(30 * time.Second), @@ -567,7 +574,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.NoData, + Values: make(map[string]float64), + State: eval.NoData, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(20 * time.Second), @@ -631,7 +639,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Pending, + Values: make(map[string]float64), + State: eval.Pending, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -695,7 +704,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Pending, + Values: make(map[string]float64), + State: eval.Pending, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -759,6 +769,7 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, + Values: make(map[string]float64), State: eval.Alerting, StateReason: eval.NoData.String(), Results: []state.Evaluation{ @@ -824,7 +835,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.NoData, + Values: make(map[string]float64), + State: eval.NoData, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -888,7 +900,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -912,7 +925,8 @@ func TestProcessEvalResults(t *testing.T) { "alertname": "test_title", "label": "test", }, - State: eval.NoData, + Values: make(map[string]float64), + State: eval.NoData, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(10 * time.Second), @@ -977,7 +991,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test-1", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -1002,7 +1017,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test-2", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -1026,7 +1042,8 @@ func TestProcessEvalResults(t *testing.T) { "alertname": "test_title", "label": "test", }, - State: eval.NoData, + Values: make(map[string]float64), + State: eval.NoData, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(10 * time.Second), @@ -1093,7 +1110,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -1122,7 +1140,8 @@ func TestProcessEvalResults(t *testing.T) { "alertname": "test_title", "label": "test", }, - State: eval.NoData, + Values: make(map[string]float64), + State: eval.NoData, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(10 * time.Second), @@ -1181,6 +1200,7 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, + Values: make(map[string]float64), State: eval.Normal, StateReason: eval.NoData.String(), Results: []state.Evaluation{ @@ -1247,6 +1267,7 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, + Values: make(map[string]float64), State: eval.Alerting, StateReason: eval.NoData.String(), @@ -1314,6 +1335,7 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, + Values: make(map[string]float64), State: eval.Pending, StateReason: eval.Error.String(), Results: []state.Evaluation{ @@ -1404,6 +1426,7 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, + Values: make(map[string]float64), State: eval.Alerting, StateReason: eval.Error.String(), Results: []state.Evaluation{ @@ -1485,7 +1508,8 @@ func TestProcessEvalResults(t *testing.T) { "datasource_uid": "datasource_uid_1", "ref_id": "A", }, - State: eval.Error, + Values: make(map[string]float64), + State: eval.Error, Error: expr.QueryError{ RefID: "A", Err: errors.New("this is an error"), @@ -1562,6 +1586,7 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, + Values: make(map[string]float64), State: eval.Normal, StateReason: eval.Error.String(), Error: nil, @@ -1635,6 +1660,7 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, + Values: make(map[string]float64), State: eval.Normal, StateReason: eval.Error.String(), Error: nil, @@ -1734,7 +1760,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Error, + Values: make(map[string]float64), + State: eval.Error, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(40 * time.Second), @@ -1815,7 +1842,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.Alerting, + Values: make(map[string]float64), + State: eval.Alerting, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(30 * time.Second), @@ -1902,7 +1930,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "instance_label": "test", }, - State: eval.NoData, + Values: make(map[string]float64), + State: eval.NoData, Results: []state.Evaluation{ { EvaluationTime: evaluationTime.Add(30 * time.Second), @@ -1964,7 +1993,8 @@ func TestProcessEvalResults(t *testing.T) { "label": "test", "job": "prod/grafana", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, @@ -2117,7 +2147,8 @@ func TestStaleResultsHandler(t *testing.T) { "alertname": rule.Title, "test1": "testValue1", }, - State: eval.Normal, + Values: make(map[string]float64), + State: eval.Normal, Results: []state.Evaluation{ { EvaluationTime: evaluationTime, diff --git a/pkg/services/ngalert/state/state.go b/pkg/services/ngalert/state/state.go index b8a084eba3b..994945b55de 100644 --- a/pkg/services/ngalert/state/state.go +++ b/pkg/services/ngalert/state/state.go @@ -32,6 +32,7 @@ type State struct { Resolved bool Annotations map[string]string Labels data.Labels + Values map[string]float64 Image *models.Image Error error } diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index 152bf6c38d6..e31bd6884d0 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -2275,6 +2275,7 @@ var expEmailNotifications = []*models.SendEmailCommandSync{ SilenceURL: "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DEmailAlert&matcher=grafana_folder%3Ddefault", DashboardURL: "", PanelURL: "", + Values: map[string]float64{"A": 1}, ValueString: "[ var='A' labels={} value=1 ]", }, }, @@ -2332,7 +2333,7 @@ var expNonEmailNotifications = map[string][]string{ { "title": "[FIRING:1] SlackAlert2 (default)", "title_link": "http://localhost:3000/alerting/list", - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = SlackAlert2\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SlackAlert2/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSlackAlert2&matcher=grafana_folder%%3Ddefault\n", + "text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SlackAlert2\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SlackAlert2/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSlackAlert2&matcher=grafana_folder%%3Ddefault\n", "fallback": "[FIRING:1] SlackAlert2 (default)", "footer": "Grafana v", "footer_icon": "https://grafana.com/assets/img/fav32.png", @@ -2365,7 +2366,7 @@ var expNonEmailNotifications = map[string][]string{ "component": "Integration Test", "group": "testgroup", "custom_details": { - "firing": "\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = PagerdutyAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PagerdutyAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DPagerdutyAlert&matcher=grafana_folder%%3Ddefault\n", + "firing": "\nValue: A=1\nLabels:\n - alertname = PagerdutyAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PagerdutyAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DPagerdutyAlert&matcher=grafana_folder%%3Ddefault\n", "num_firing": "1", "num_resolved": "0", "resolved": "" @@ -2385,7 +2386,7 @@ var expNonEmailNotifications = map[string][]string{ `{ "link": { "messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%3A3000%2Falerting%2Flist", - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = DingDingAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DingDingAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDingDingAlert&matcher=grafana_folder%3Ddefault\n", + "text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DingDingAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DingDingAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDingDingAlert&matcher=grafana_folder%3Ddefault\n", "title": "[FIRING:1] DingDingAlert (default)" }, "msgtype": "link" @@ -2406,7 +2407,7 @@ var expNonEmailNotifications = map[string][]string{ "weight": "bolder", "wrap": true }, { - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = TeamsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TeamsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DTeamsAlert\u0026matcher=grafana_folder%3Ddefault\n", + "text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = TeamsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TeamsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DTeamsAlert\u0026matcher=grafana_folder%3Ddefault\n", "type": "TextBlock", "wrap": true }, { @@ -2447,7 +2448,8 @@ var expNonEmailNotifications = map[string][]string{ }, "annotations": {}, "startsAt": "%s", - "valueString": "[ var='A' labels={} value=1 ]", + "values": {"A": 1}, + "valueString": "[ var='A' labels={} value=1 ]", "endsAt": "0001-01-01T00:00:00Z", "generatorURL": "http://localhost:3000/alerting/grafana/UID_WebhookAlert/view", "fingerprint": "15c59b0a380bd9f1", @@ -2470,12 +2472,12 @@ var expNonEmailNotifications = map[string][]string{ "truncatedAlerts": 0, "title": "[FIRING:1] WebhookAlert (default)", "state": "alerting", - "message": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = WebhookAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_WebhookAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert&matcher=grafana_folder%%3Ddefault\n" + "message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = WebhookAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_WebhookAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert&matcher=grafana_folder%%3Ddefault\n" }`, }, "discord_recv/discord_test": { `{ - "content": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = DiscordAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DiscordAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDiscordAlert&matcher=grafana_folder%3Ddefault\n", + "content": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DiscordAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DiscordAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDiscordAlert&matcher=grafana_folder%3Ddefault\n", "embeds": [ { "color": 14037554, @@ -2503,7 +2505,7 @@ var expNonEmailNotifications = map[string][]string{ }, "name": "default" }, - "output": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = SensuGoAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SensuGoAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSensuGoAlert&matcher=grafana_folder%%3Ddefault\n", + "output": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SensuGoAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SensuGoAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSensuGoAlert&matcher=grafana_folder%%3Ddefault\n", "status": 2 }, "entity": { @@ -2516,10 +2518,10 @@ var expNonEmailNotifications = map[string][]string{ }`, }, "pushover_recv/pushover_test": { - "--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert (default)\r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = PushoverAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PushoverAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DPushoverAlert&matcher=grafana_folder%3Ddefault\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n", + "--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert (default)\r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = PushoverAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PushoverAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DPushoverAlert&matcher=grafana_folder%3Ddefault\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n", }, "telegram_recv/bot6sh027hs034h": { - "--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nhtml\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = TelegramAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TelegramAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTelegramAlert&matcher=grafana_folder%3Ddefault\n\r\n--abcd--\r\n", + "--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nhtml\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = TelegramAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TelegramAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTelegramAlert&matcher=grafana_folder%3Ddefault\n\r\n--abcd--\r\n", }, "googlechat_recv/googlechat_test": { `{ @@ -2535,7 +2537,7 @@ var expNonEmailNotifications = map[string][]string{ "widgets": [ { "textParagraph": { - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = GoogleChatAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_GoogleChatAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DGoogleChatAlert&matcher=grafana_folder%%3Ddefault\n" + "text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = GoogleChatAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_GoogleChatAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DGoogleChatAlert&matcher=grafana_folder%%3Ddefault\n" } }, { @@ -2573,7 +2575,7 @@ var expNonEmailNotifications = map[string][]string{ "client": "Grafana", "client_url": "http://localhost:3000/alerting/list", "description": "[FIRING:1] KafkaAlert (default)", - "details": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = KafkaAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_KafkaAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DKafkaAlert&matcher=grafana_folder%3Ddefault\n", + "details": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = KafkaAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_KafkaAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DKafkaAlert&matcher=grafana_folder%3Ddefault\n", "incident_key": "35c0bdb1715f9162a20d7b2a01cb2e3a4c5b1dc663571701e3f67212b696332f" } } @@ -2581,10 +2583,10 @@ var expNonEmailNotifications = map[string][]string{ }`, }, "line_recv/line_test": { - `message=%5BFIRING%3A1%5D+LineAlert+%28default%29%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+%5B+var%3D%27A%27+labels%3D%7B%7D+value%3D1+%5D%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_LineAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DLineAlert%26matcher%3Dgrafana_folder%253Ddefault%0A`, + `message=%5BFIRING%3A1%5D+LineAlert+%28default%29%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_LineAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DLineAlert%26matcher%3Dgrafana_folder%253Ddefault%0A`, }, "threema_recv/threema_test": { - `from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%28default%29%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+%5B+var%3D%27A%27+labels%3D%7B%7D+value%3D1+%5D%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_ThreemaAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DThreemaAlert%26matcher%3Dgrafana_folder%253Ddefault%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`, + `from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%28default%29%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_ThreemaAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DThreemaAlert%26matcher%3Dgrafana_folder%253Ddefault%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`, }, "victorops_recv/victorops_test": { `{ @@ -2593,14 +2595,14 @@ var expNonEmailNotifications = map[string][]string{ "entity_id": "633ae988fa7074bcb51f3d1c5fef2ba1c5c4ccb45b3ecbf681f7d507b078b1ae", "message_type": "CRITICAL", "monitoring_tool": "Grafana v", - "state_message": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = VictorOpsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_VictorOpsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DVictorOpsAlert&matcher=grafana_folder%%3Ddefault\n", + "state_message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = VictorOpsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_VictorOpsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DVictorOpsAlert&matcher=grafana_folder%%3Ddefault\n", "timestamp": %s }`, }, "opsgenie_recv/opsgenie_test": { `{ "alias": "47e92f0f6ef9fe99f3954e0d6155f8d09c4b9a038d8c3105e82c0cee4c62956e", - "description": "[FIRING:1] OpsGenieAlert (default)\nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = OpsGenieAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_OpsGenieAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DOpsGenieAlert&matcher=grafana_folder%3Ddefault\n", + "description": "[FIRING:1] OpsGenieAlert (default)\nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = OpsGenieAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_OpsGenieAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DOpsGenieAlert&matcher=grafana_folder%3Ddefault\n", "details": { "url": "http://localhost:3000/alerting/list" }, @@ -2619,8 +2621,9 @@ var expNonEmailNotifications = map[string][]string{ "grafana_folder": "default" }, "annotations": { - "__value_string__": "[ var='A' labels={} value=1 ]" - }, + "__values__": "{\"A\":1}", + "__value_string__": "[ var='A' labels={} value=1 ]" + }, "startsAt": "%s", "endsAt": "0001-01-01T00:00:00Z", "generatorURL": "http://localhost:3000/alerting/grafana/UID_AlertmanagerAlert/view", diff --git a/public/app/features/alerting/unified/components/receivers/TemplateData.ts b/public/app/features/alerting/unified/components/receivers/TemplateData.ts index 4f00e4931e0..7580bda6fba 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateData.ts +++ b/public/app/features/alerting/unified/components/receivers/TemplateData.ts @@ -75,6 +75,12 @@ export const AlertTemplateData: TemplateDataItem[] = [ type: 'KeyValue', notes: 'Set of annotations attached to the alert.', }, + { + name: 'Values', + type: 'KeyValue', + notes: + 'The values of all instant queries, reduce and math expressions, and classic conditions for the alert. It does not contain time series data.', + }, { name: 'StartsAt', type: 'time.Time', diff --git a/public/emails/ng_alert_notification.html b/public/emails/ng_alert_notification.html index 938457f0335..cfdfe91aeab 100644 --- a/public/emails/ng_alert_notification.html +++ b/public/emails/ng_alert_notification.html @@ -209,6 +209,10 @@ text-decoration: underline; {{Subject .Subject "{{.Title}}"}} +{{ define "__text_values_list" }}{{ $len := len .Values }}{{ if $len }}{{ $first := gt $len 1 }}{{ range $refID, $value := .Values -}} +{{ $refID }}={{ $value }}{{ if $first }}, {{ end }}{{ $first = false }}{{ end -}} +{{ else }}[no value]{{ end }}{{ end }} + {{ define "alert" }} {{ if ne .ImageURL "" }} @@ -227,7 +231,7 @@ text-decoration: underline; {{ end }} - Value: {{ .ValueString }} + Value: {{ template "__text_values_list" . }} {{ if gt (len .Annotations.SortedPairs) 0 }}