Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6da408fcb9 | |||
| 55b94e6df6 |
@@ -170,7 +170,7 @@ This example prints the `$value` variable:
|
||||
When using multiple data sources, it would display something like this:
|
||||
|
||||
```
|
||||
[ var='A' labels={instance=instance1} value=81.234, , [ var='B' labels={instance=instance2} value=1 ] ]: CPU usage has exceeded 80% for the last 5 minutes.
|
||||
[ var='A' labels={instance=instance1} type='reduce' value=81.234 ], [ var='B' labels={instance=instance2} type='threshold' value=1 ]: CPU usage has exceeded 80% for the last 5 minutes.
|
||||
```
|
||||
|
||||
But with a single data source, it would display just the value of the query:
|
||||
|
||||
@@ -116,7 +116,7 @@ Grafana-managed alerts include these additional properties:
|
||||
| `PanelURL` | string | A link to the panel if the alert has a Panel ID annotation, with time range from `1h` before alert start to end (or now if firing). |
|
||||
| `SilenceURL` | string | A link to silence the alert. |
|
||||
| `Values` | [KV](#kv) | The values of expressions used to evaluate the alert condition. Only relevant values are included. |
|
||||
| `ValueString` | string | A string that contains the labels and value of each reduced expression in the alert. |
|
||||
| `ValueString` | string | A string containing the labels, expression type, and value of each reduced expression in the alert. |
|
||||
| `OrgID` | integer | The ID of the organization that owns the alert. |
|
||||
|
||||
This example iterates over the list of firing and resolved alerts (`.Alerts`) in the notification and prints the data for each alert:
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
title: 'JSON alert object'
|
||||
---
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------- | ------ | ----------------------------------------------------------------------------------- |
|
||||
| `status` | string | Current status of the alert, `firing` or `resolved`. |
|
||||
| `labels` | object | Labels that are part of this alert, map of string keys to string values. |
|
||||
| `annotations` | object | Annotations that are part of this alert, map of string keys to string values. |
|
||||
| `startsAt` | string | Start time of the alert. |
|
||||
| `endsAt` | string | End time of the alert, default value when not resolved is `0001-01-01T00:00:00Z`. |
|
||||
| `values` | object | Values that triggered the current status. |
|
||||
| `generatorURL` | string | URL of the alert rule in the Grafana UI. |
|
||||
| `fingerprint` | string | The labels fingerprint, alarms with the same labels will have the same fingerprint. |
|
||||
| `silenceURL` | string | URL to silence the alert rule in the Grafana UI. |
|
||||
| `dashboardURL` | string | A link to the Grafana Dashboard if the alert has a Dashboard UID annotation. |
|
||||
| `panelURL` | string | A link to the panel if the alert has a Panel ID annotation. |
|
||||
| `imageURL` | string | URL of a screenshot of a panel assigned to the rule that created this notification. |
|
||||
| Key | Type | Description |
|
||||
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `status` | string | Current status of the alert, `firing` or `resolved`. |
|
||||
| `labels` | object | Labels that are part of this alert, map of string keys to string values. |
|
||||
| `annotations` | object | Annotations that are part of this alert, map of string keys to string values. |
|
||||
| `startsAt` | string | Start time of the alert. |
|
||||
| `endsAt` | string | End time of the alert, default value when not resolved is `0001-01-01T00:00:00Z`. |
|
||||
| `values` | object | Values that triggered the current status. |
|
||||
| `valueString` | string | A string representation of expression values including variable names, labels, expression types, and values. |
|
||||
| `generatorURL` | string | URL of the alert rule in the Grafana UI. |
|
||||
| `fingerprint` | string | The labels fingerprint, alarms with the same labels will have the same fingerprint. |
|
||||
| `silenceURL` | string | URL to silence the alert rule in the Grafana UI. |
|
||||
| `dashboardURL` | string | A link to the Grafana Dashboard if the alert has a Dashboard UID annotation. |
|
||||
| `panelURL` | string | A link to the panel if the alert has a Panel ID annotation. |
|
||||
| `imageURL` | string | URL of a screenshot of a panel assigned to the rule that created this notification. |
|
||||
|
||||
@@ -437,6 +437,7 @@ type NumberValueCapture struct {
|
||||
Var string // RefID
|
||||
IsDatasourceNode bool
|
||||
Labels data.Labels
|
||||
Type string // Expression type (reduce, threshold, classic_conditions, etc.)
|
||||
|
||||
Value *float64
|
||||
}
|
||||
@@ -462,17 +463,33 @@ func IsNoData(res backend.DataResponse) bool {
|
||||
func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.QueryDataResponse) ExecutionResults {
|
||||
// captures contains the values of all instant queries and expressions for each dimension
|
||||
captures := make(map[string]map[data.Fingerprint]NumberValueCapture)
|
||||
|
||||
// Build a lookup table for expression types by RefID
|
||||
expressionTypes := make(map[string]string)
|
||||
for _, query := range c.Data {
|
||||
if exprType, err := query.GetExpressionType(); err == nil {
|
||||
expressionTypes[query.RefID] = exprType
|
||||
}
|
||||
}
|
||||
|
||||
captureFn := func(refID string, datasourceType expr.NodeType, labels data.Labels, value *float64) {
|
||||
m := captures[refID]
|
||||
if m == nil {
|
||||
m = make(map[data.Fingerprint]NumberValueCapture)
|
||||
}
|
||||
fp := labels.Fingerprint()
|
||||
|
||||
exprType := expressionTypes[refID]
|
||||
if exprType == "" && datasourceType == expr.TypeDatasourceNode {
|
||||
exprType = "query"
|
||||
}
|
||||
|
||||
m[fp] = NumberValueCapture{
|
||||
Var: refID,
|
||||
IsDatasourceNode: datasourceType == expr.TypeDatasourceNode,
|
||||
Value: value,
|
||||
Labels: labels.Copy(),
|
||||
Type: exprType,
|
||||
}
|
||||
captures[refID] = m
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func extractEvalString(frame *data.Frame) (s string) {
|
||||
sb.WriteString(fmt.Sprintf("var='%s%v' ", frame.RefID, i))
|
||||
sb.WriteString(fmt.Sprintf("metric='%s' ", m.Metric))
|
||||
sb.WriteString(fmt.Sprintf("labels={%s} ", m.Labels))
|
||||
sb.WriteString("type='classic_conditions' ")
|
||||
|
||||
valString := "null"
|
||||
if m.Value != nil {
|
||||
@@ -53,6 +54,9 @@ func extractEvalString(frame *data.Frame) (s string) {
|
||||
sb.WriteString("[ ")
|
||||
sb.WriteString(fmt.Sprintf("var='%s' ", capture.Var))
|
||||
sb.WriteString(fmt.Sprintf("labels={%s} ", capture.Labels))
|
||||
if capture.Type != "" {
|
||||
sb.WriteString(fmt.Sprintf("type='%s' ", capture.Type))
|
||||
}
|
||||
valString := "null"
|
||||
if capture.Value != nil {
|
||||
valString = fmt.Sprintf("%v", *capture.Value)
|
||||
@@ -92,6 +96,7 @@ func extractValues(frame *data.Frame) map[string]NumberValueCapture {
|
||||
Var: frame.RefID,
|
||||
Labels: match.Labels,
|
||||
Value: match.Value,
|
||||
Type: "classic_conditions",
|
||||
}
|
||||
}
|
||||
return v
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestExtractEvalString(t *testing.T) {
|
||||
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(32.3)},
|
||||
}, util.Pointer(1.0)),
|
||||
outString: `[ var='0' metric='Test' labels={host=foo} value=32.3 ]`,
|
||||
outString: `[ var='0' metric='Test' labels={host=foo} type='classic_conditions' value=32.3 ]`,
|
||||
},
|
||||
{
|
||||
desc: "2 EvalMatches",
|
||||
@@ -29,7 +29,7 @@ func TestExtractEvalString(t *testing.T) {
|
||||
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(32.3)},
|
||||
{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: util.Pointer(10.0)},
|
||||
}, util.Pointer(1.0), withRefID("A")),
|
||||
outString: `[ var='A0' metric='Test' labels={host=foo} value=32.3 ], [ var='A1' metric='Test' labels={host=baz} value=10 ]`,
|
||||
outString: `[ var='A0' metric='Test' labels={host=foo} type='classic_conditions' value=32.3 ], [ var='A1' metric='Test' labels={host=baz} type='classic_conditions' value=10 ]`,
|
||||
},
|
||||
{
|
||||
desc: "3 EvalMatches",
|
||||
@@ -38,15 +38,15 @@ func TestExtractEvalString(t *testing.T) {
|
||||
{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: util.Pointer(10.0)},
|
||||
{Metric: "TestA", Labels: data.Labels{"host": "zip"}, Value: util.Pointer(11.0)},
|
||||
}, util.Pointer(1.0), withRefID("A")),
|
||||
outString: `[ var='A0' metric='Test' labels={host=foo} value=32.3 ], [ var='A1' metric='Test' labels={host=baz} value=10 ], [ var='A2' metric='TestA' labels={host=zip} value=11 ]`,
|
||||
outString: `[ var='A0' metric='Test' labels={host=foo} type='classic_conditions' value=32.3 ], [ var='A1' metric='Test' labels={host=baz} type='classic_conditions' value=10 ], [ var='A2' metric='TestA' labels={host=zip} type='classic_conditions' value=11 ]`,
|
||||
},
|
||||
{
|
||||
desc: "Captures are sorted in ascending order of var",
|
||||
inFrame: newMetaFrame([]NumberValueCapture{
|
||||
{Var: "B", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
|
||||
{Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(10.0)},
|
||||
{Var: "B", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0), Type: "reduce"},
|
||||
{Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(10.0), Type: "threshold"},
|
||||
}, util.Pointer(1.0)),
|
||||
outString: `[ var='A' labels={host=foo} value=10 ], [ var='B' labels={host=foo} value=1 ]`,
|
||||
outString: `[ var='A' labels={host=foo} type='threshold' value=10 ], [ var='B' labels={host=foo} type='reduce' value=1 ]`,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@@ -71,7 +71,7 @@ func TestExtractValues(t *testing.T) {
|
||||
{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
|
||||
}, util.Pointer(1.0), withRefID("A")),
|
||||
values: map[string]NumberValueCapture{
|
||||
"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
|
||||
"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0), Type: "classic_conditions"},
|
||||
},
|
||||
}, {
|
||||
desc: "Classic condition frame with multiple matches",
|
||||
@@ -80,8 +80,8 @@ func TestExtractValues(t *testing.T) {
|
||||
{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(3.0)},
|
||||
}, util.Pointer(1.0), withRefID("A")),
|
||||
values: map[string]NumberValueCapture{
|
||||
"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
|
||||
"A1": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(3.0)},
|
||||
"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0), Type: "classic_conditions"},
|
||||
"A1": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(3.0), Type: "classic_conditions"},
|
||||
},
|
||||
}, {
|
||||
desc: "Nil value",
|
||||
|
||||
@@ -342,3 +342,31 @@ func (aq *AlertQuery) InitDefaults() error {
|
||||
aq.Model = model
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExpressionType returns the type of expression for this AlertQuery.
|
||||
// It returns "query" for regular datasource queries and the actual type for expressions.
|
||||
func (aq *AlertQuery) GetExpressionType() (string, error) {
|
||||
if aq.modelProps == nil {
|
||||
err := aq.setModelProps()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is an expression query
|
||||
isExpr, err := aq.IsExpression()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !isExpr {
|
||||
return "query", nil // Regular data source query
|
||||
}
|
||||
|
||||
// Extract type from model
|
||||
if exprType, ok := aq.modelProps["type"].(string); ok {
|
||||
return exprType, nil
|
||||
}
|
||||
|
||||
return "unknown", nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ var (
|
||||
}
|
||||
DefaultAnnotations = map[string]string{
|
||||
alertingModels.ValuesAnnotation: `{"B":22,"C":1}`,
|
||||
alertingModels.ValueStringAnnotation: `[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]`,
|
||||
alertingModels.ValueStringAnnotation: `[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='reduce' value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='threshold' value=1 ]`,
|
||||
alertingModels.OrgIDAnnotation: `1`,
|
||||
alertingModels.DashboardUIDAnnotation: `dashboard_uid`,
|
||||
alertingModels.PanelIDAnnotation: `1`,
|
||||
|
||||
@@ -137,7 +137,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
|
||||
"__dashboardUid__": "dashboard_uid",
|
||||
"__orgId__": "1",
|
||||
"__panelId__": "1",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='reduce' value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='threshold' value=1 ]",
|
||||
"__values__": "{\"B\":22,\"C\":1}"
|
||||
},
|
||||
"labels": {
|
||||
@@ -225,7 +225,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
|
||||
"__dashboardUid__": "dashboard_uid",
|
||||
"__orgId__": "1",
|
||||
"__panelId__": "1",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='reduce' value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='threshold' value=1 ]",
|
||||
"__values__": "{\"B\":22,\"C\":1}"
|
||||
},
|
||||
"labels": {
|
||||
@@ -307,7 +307,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
|
||||
"__dashboardUid__": "dashboard_uid",
|
||||
"__orgId__": "1",
|
||||
"__panelId__": "1",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='reduce' value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='threshold' value=1 ]",
|
||||
"__values__": "{\"B\":22,\"C\":1}"
|
||||
},
|
||||
"labels": {
|
||||
@@ -400,7 +400,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
|
||||
"__dashboardUid__": "dashboard_uid",
|
||||
"__orgId__": "1",
|
||||
"__panelId__": "1",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='reduce' value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='threshold' value=1 ]",
|
||||
"__values__": "{\"B\":22,\"C\":1}"
|
||||
},
|
||||
"labels": {
|
||||
@@ -506,7 +506,7 @@ func TestIntegrationTestReceivers(t *testing.T) {
|
||||
"__dashboardUid__": "dashboard_uid",
|
||||
"__orgId__": "1",
|
||||
"__panelId__": "1",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='reduce' value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='threshold' value=1 ]",
|
||||
"__values__": "{\"B\":22,\"C\":1}"
|
||||
},
|
||||
"labels": {
|
||||
@@ -805,7 +805,7 @@ func TestIntegrationTestReceiversAlertCustomization(t *testing.T) {
|
||||
"__dashboardUid__": "dashboard_uid",
|
||||
"__orgId__": "1",
|
||||
"__panelId__": "1",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} value=1 ]",
|
||||
"__value_string__": "[ var='B' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='reduce' value=22 ], [ var='C' labels={__name__=go_threads, instance=host.docker.internal:3000, job=grafana} type='threshold' value=1 ]",
|
||||
"__values__": "{\"B\":22,\"C\":1}"
|
||||
},
|
||||
"labels": {
|
||||
@@ -2440,7 +2440,7 @@ var expEmailNotifications = []*notifications.SendEmailCommandSync{
|
||||
PanelURL: "",
|
||||
OrgID: util.Pointer(int64(1)),
|
||||
Values: map[string]float64{"A": 1},
|
||||
ValueString: "[ var='A' labels={} value=1 ]",
|
||||
ValueString: "[ var='A' labels={} type='math' value=1 ]",
|
||||
},
|
||||
},
|
||||
"GroupLabels": template.KV{"alertname": "EmailAlert"},
|
||||
@@ -2594,10 +2594,10 @@ var expNonEmailNotifications = map[string][]string{
|
||||
"grafana_folder": "default"
|
||||
},
|
||||
"annotations": {},
|
||||
"startsAt": "%s",
|
||||
"startsAt": "%s",
|
||||
"values": {"A": 1},
|
||||
"valueString": "[ var='A' labels={} value=1 ]",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"valueString": "[ var='A' labels={} type='math' value=1 ]",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"generatorURL": "http://localhost:3000/alerting/grafana/UID_WebhookAlert/view?orgId=1",
|
||||
"fingerprint": "15c59b0a380bd9f1",
|
||||
"silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_WebhookAlert&orgId=1",
|
||||
@@ -2768,10 +2768,10 @@ var expNonEmailNotifications = map[string][]string{
|
||||
"alertname": "AlertmanagerAlert",
|
||||
"grafana_folder": "default"
|
||||
},
|
||||
"annotations": {
|
||||
"__orgId__":"1",
|
||||
"annotations": {
|
||||
"__orgId__":"1",
|
||||
"__values__": "{\"A\":1}",
|
||||
"__value_string__": "[ var='A' labels={} value=1 ]"
|
||||
"__value_string__": "[ var='A' labels={} type='math' value=1 ]"
|
||||
},
|
||||
"startsAt": "%s",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
|
||||
@@ -150,8 +150,8 @@ func TestGrafanaRuleConfig(t *testing.T) {
|
||||
for i, alert := range result {
|
||||
require.NotEmpty(t, alert.Annotations["values.B"])
|
||||
require.NotEmpty(t, alert.Annotations["values.C"])
|
||||
valueB := fmt.Sprintf("[ var='B' labels={state=%s} value=%s ]", dynamicLabels[i], alert.Annotations["values.B"])
|
||||
valueC := fmt.Sprintf("[ var='C' labels={state=%s} value=%s ]", dynamicLabels[i], alert.Annotations["values.C"])
|
||||
valueB := fmt.Sprintf("[ var='B' labels={state=%s} type='reduce' value=%s ]", dynamicLabels[i], alert.Annotations["values.B"])
|
||||
valueC := fmt.Sprintf("[ var='C' labels={state=%s} type='threshold' value=%s ]", dynamicLabels[i], alert.Annotations["values.C"])
|
||||
require.Contains(t, alert.Annotations["value"], valueB)
|
||||
require.Contains(t, alert.Annotations["value"], valueC)
|
||||
}
|
||||
@@ -172,8 +172,8 @@ func TestGrafanaRuleConfig(t *testing.T) {
|
||||
for i, alert := range result {
|
||||
require.NotEmpty(t, alert.Labels["values.B"])
|
||||
require.NotEmpty(t, alert.Labels["values.C"])
|
||||
valueB := fmt.Sprintf("[ var='B' labels={state=%s} value=%s ]", dynamicLabels[i], alert.Labels["values.B"])
|
||||
valueC := fmt.Sprintf("[ var='C' labels={state=%s} value=%s ]", dynamicLabels[i], alert.Labels["values.C"])
|
||||
valueB := fmt.Sprintf("[ var='B' labels={state=%s} type='reduce' value=%s ]", dynamicLabels[i], alert.Labels["values.B"])
|
||||
valueC := fmt.Sprintf("[ var='C' labels={state=%s} type='threshold' value=%s ]", dynamicLabels[i], alert.Labels["values.C"])
|
||||
require.Contains(t, alert.Labels["value"], valueB)
|
||||
require.Contains(t, alert.Labels["value"], valueC)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user