@@ -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.
8485func (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.
9799func (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.
111114func (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,50 @@ 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+ func timeDurationToRuleDuration (d time.Duration ) string {
148+ ms := int64 (d / time .Millisecond )
149+ if ms == 0 {
150+ return "0s"
151+ }
152+
153+ r := ""
154+ f := func (unit string , mult int64 , exact bool ) {
155+ if exact && ms % mult != 0 {
156+ return
157+ }
158+ if v := ms / mult ; v > 0 {
159+ r += fmt .Sprintf ("%d%s" , v , unit )
160+ ms -= v * mult
161+ }
162+ }
163+
164+ // Only format years and weeks if the remainder is zero, as it is often
165+ // easier to read 90d than 12w6d.
166+ f ("y" , 1000 * 60 * 60 * 24 * 365 , true )
167+ f ("w" , 1000 * 60 * 60 * 24 * 7 , true )
168+
169+ f ("d" , 1000 * 60 * 60 * 24 , false )
170+ f ("h" , 1000 * 60 * 60 , false )
171+ f ("m" , 1000 * 60 , false )
172+ f ("s" , 1000 , false )
173+ f ("ms" , 1 , false )
174+
175+ return r
176+ }
0 commit comments