Skip to content
This repository was archived by the owner on Jan 15, 2024. It is now read-only.

Commit 1cb3f2e

Browse files
authored
Merge pull request #104 from grafana/alexweav/alerting-serialization
Alerting: Convenience translations for For field in alert rules
2 parents 4467425 + 948f89b commit 1cb3f2e

File tree

2 files changed

+132
-90
lines changed

2 files changed

+132
-90
lines changed

alerting_alert_rule.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ type AlertRule struct {
2222
Title string `json:"title"`
2323
UID string `json:"uid,omitempty"`
2424
Updated time.Time `json:"updated"`
25-
ForDuration time.Duration `json:"for"`
25+
For string `json:"for"`
26+
ForDuration time.Duration `json:"-"`
2627
Provenance string `json:"provenance"`
2728
}
2829

@@ -82,6 +83,7 @@ func (c *Client) AlertRuleGroup(folderUID string, name string) (RuleGroup, error
8283

8384
// SetAlertRuleGroup overwrites an existing rule group on the server.
8485
func (c *Client) SetAlertRuleGroup(group RuleGroup) error {
86+
syncCalculatedRuleGroupFields(&group)
8587
folderUID := group.FolderUID
8688
name := group.Title
8789
req, err := json.Marshal(group)
@@ -95,6 +97,7 @@ func (c *Client) SetAlertRuleGroup(group RuleGroup) error {
9597

9698
// NewAlertRule creates a new alert rule and returns its UID.
9799
func (c *Client) NewAlertRule(ar *AlertRule) (string, error) {
100+
syncCalculatedRuleFields(ar)
98101
req, err := json.Marshal(ar)
99102
if err != nil {
100103
return "", err
@@ -109,6 +112,7 @@ func (c *Client) NewAlertRule(ar *AlertRule) (string, error) {
109112

110113
// UpdateAlertRule replaces an alert rule, identified by the alert rule's UID.
111114
func (c *Client) UpdateAlertRule(ar *AlertRule) error {
115+
syncCalculatedRuleFields(ar)
112116
uri := fmt.Sprintf("/api/v1/provisioning/alert-rules/%s", ar.UID)
113117
req, err := json.Marshal(ar)
114118
if err != nil {
@@ -123,3 +127,51 @@ func (c *Client) DeleteAlertRule(uid string) error {
123127
uri := fmt.Sprintf("/api/v1/provisioning/alert-rules/%s", uid)
124128
return c.request("DELETE", uri, nil, nil, nil)
125129
}
130+
131+
func syncCalculatedRuleGroupFields(group *RuleGroup) {
132+
for i := range group.Rules {
133+
syncCalculatedRuleFields(&group.Rules[i])
134+
}
135+
}
136+
137+
func syncCalculatedRuleFields(rule *AlertRule) {
138+
// rule.For is the newer field. Older systems may not provide the value.
139+
// If the user provided a For, prefer that over whatever we might calculate.
140+
// Otherwise, translate the time.Duration-based field to the format that the rule API expects.
141+
if rule.For == "" {
142+
rule.For = timeDurationToRuleDuration(rule.ForDuration)
143+
}
144+
}
145+
146+
// timeDurationToRuleDuration converts a typical time.Duration to the string-based format that alert rules expect.
147+
// This code is adapted from Prometheus: https://github.com/prometheus/common/blob/dfbc25bd00225c70aca0d94c3c4bb7744f28ace0/model/time.go#L236
148+
func timeDurationToRuleDuration(d time.Duration) string {
149+
ms := int64(d / time.Millisecond)
150+
if ms == 0 {
151+
return "0s"
152+
}
153+
154+
r := ""
155+
f := func(unit string, mult int64, exact bool) {
156+
if exact && ms%mult != 0 {
157+
return
158+
}
159+
if v := ms / mult; v > 0 {
160+
r += fmt.Sprintf("%d%s", v, unit)
161+
ms -= v * mult
162+
}
163+
}
164+
165+
// Only format years and weeks if the remainder is zero, as it is often
166+
// easier to read 90d than 12w6d.
167+
f("y", 1000*60*60*24*365, true)
168+
f("w", 1000*60*60*24*7, true)
169+
170+
f("d", 1000*60*60*24, false)
171+
f("h", 1000*60*60, false)
172+
f("m", 1000*60, false)
173+
f("s", 1000, false)
174+
f("ms", 1, false)
175+
176+
return r
177+
}

alerting_alert_rule_test.go

Lines changed: 79 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gapi
33
import (
44
"encoding/json"
55
"testing"
6+
"time"
67

78
"github.com/gobs/pretty"
89
)
@@ -26,17 +27,17 @@ func TestAlertRules(t *testing.T) {
2627
server, client := gapiTestTools(t, 200, getAlertRuleGroupJSON)
2728
defer server.Close()
2829

29-
group, err := client.AlertRuleGroup("d8-gk06nz", "test")
30+
group, err := client.AlertRuleGroup("project_test", "eval_group_1")
3031

3132
if err != nil {
3233
t.Error(err)
3334
}
3435
t.Log(pretty.PrettyFormat(group))
35-
if group.Title != "test" {
36-
t.Errorf("incorrect title - expected %s got %s", "test", group.Title)
36+
if group.Title != "eval_group_1" {
37+
t.Errorf("incorrect title - expected %s got %s", "eval_group_1", group.Title)
3738
}
38-
if group.FolderUID != "d8-gk06nz" {
39-
t.Errorf("incorrect folderUID - expected %s got %s", "d8-gk06nz", group.FolderUID)
39+
if group.FolderUID != "project_test" {
40+
t.Errorf("incorrect folderUID - expected %s got %s", "project_test", group.FolderUID)
4041
}
4142
if len(group.Rules) != 1 {
4243
t.Errorf("wrong number of rules, got %d", len(group.Rules))
@@ -138,7 +139,7 @@ func createAlertRule() AlertRule {
138139
OrgID: 1,
139140
RuleGroup: "eval_group_1",
140141
Title: "Always in alarm",
141-
ForDuration: 0,
142+
ForDuration: 60 * time.Second,
142143
}
143144
}
144145

@@ -165,7 +166,7 @@ const writeAlertRuleJSON = `
165166
"orgId": 1,
166167
"ruleGroup": "eval_group_1",
167168
"title": "Always in alarm",
168-
"for": 0
169+
"for": "1m"
169170
}
170171
`
171172

@@ -180,95 +181,84 @@ const getAlertRuleJSON = `
180181
"uid": "123abcd",
181182
"ruleGroup": "eval_group_1",
182183
"title": "Always in alarm",
183-
"for": 0
184+
"for": "1m"
184185
}
185186
`
186187

187188
const getAlertRuleGroupJSON = `
188189
{
189-
"title": "test",
190-
"folderUid": "d8-gk06nz",
190+
"title": "eval_group_1",
191+
"folderUid": "project_test",
191192
"interval": 60,
192193
"rules": [
193-
{
194-
"ID": 1,
195-
"OrgID": 1,
196-
"Title": "abc",
197-
"Condition": "B",
198-
"Data": [
199-
{
200-
"refId": "A",
201-
"queryType": "",
202-
"relativeTimeRange": {
203-
"from": 600,
204-
"to": 0
194+
{
195+
"id": 212,
196+
"uid": "HW7RYci4z",
197+
"orgID": 1,
198+
"folderUID": "project_test",
199+
"ruleGroup": "eval_group_1",
200+
"title": "Always in alarm",
201+
"condition": "A",
202+
"data": [
203+
{
204+
"refId": "A",
205+
"queryType": "",
206+
"relativeTimeRange": {
207+
"from": 0,
208+
"to": 0
209+
},
210+
"datasourceUid": "-100",
211+
"model": {
212+
"datasourceUid": "-100",
213+
"intervalMs": 1000,
214+
"maxDataPoints": 43200,
215+
"model": {
216+
"conditions": [
217+
{
218+
"evaluator": {
219+
"params": [
220+
0,
221+
0
222+
],
223+
"type": "gt"
205224
},
206-
"datasourceUid": "PD8C576611E62080A",
207-
"model": {
208-
"hide": false,
209-
"intervalMs": 1000,
210-
"maxDataPoints": 43200,
211-
"refId": "A"
212-
}
213-
},
214-
{
215-
"refId": "B",
216-
"queryType": "",
217-
"relativeTimeRange": {
218-
"from": 0,
219-
"to": 0
225+
"operator": {
226+
"type": "and"
220227
},
221-
"datasourceUid": "-100",
222-
"model": {
223-
"conditions": [
224-
{
225-
"evaluator": {
226-
"params": [
227-
3
228-
],
229-
"type": "gt"
230-
},
231-
"operator": {
232-
"type": "and"
233-
},
234-
"query": {
235-
"params": [
236-
"A"
237-
]
238-
},
239-
"reducer": {
240-
"params": [],
241-
"type": "last"
242-
},
243-
"type": "query"
244-
}
245-
],
246-
"datasource": {
247-
"type": "__expr__",
248-
"uid": "-100"
249-
},
250-
"hide": false,
251-
"intervalMs": 1000,
252-
"maxDataPoints": 43200,
253-
"refId": "B",
254-
"type": "classic_conditions"
255-
}
256-
}
257-
],
258-
"Updated": "2022-07-07T16:23:56-05:00",
259-
"IntervalSeconds": 60,
260-
"Version": 1,
261-
"UID": "hsXgz0enz",
262-
"NamespaceUID": "d8-gk06nz",
263-
"DashboardUID": null,
264-
"PanelID": null,
265-
"RuleGroup": "test",
266-
"RuleGroupIndex": 1,
267-
"NoDataState": "NoData",
268-
"ExecErrState": "Alerting",
269-
"For": 300000000000,
270-
"Annotations": {},
271-
"Labels": {}
272-
}
228+
"query": {
229+
"params": []
230+
},
231+
"reducer": {
232+
"params": [],
233+
"type": "avg"
234+
},
235+
"type": "query"
236+
}
237+
],
238+
"datasource": {
239+
"type": "__expr__",
240+
"uid": "__expr__"
241+
},
242+
"expression": "1 == 1",
243+
"hide": false,
244+
"intervalMs": 1000,
245+
"maxDataPoints": 43200,
246+
"refId": "A",
247+
"type": "math"
248+
},
249+
"queryType": "",
250+
"refId": "A",
251+
"relativeTimeRange": {
252+
"from": 0,
253+
"to": 0
254+
}
255+
}
256+
}
257+
],
258+
"updated": "2022-08-12T15:44:43-05:00",
259+
"noDataState": "OK",
260+
"execErrState": "OK",
261+
"for": "2m"
262+
}
273263
]
274-
}`
264+
}`

0 commit comments

Comments
 (0)