Skip to content

Commit 9a37263

Browse files
fix(#72): Async metric create. (#73)
1 parent 8f147c7 commit 9a37263

File tree

2 files changed

+250
-0
lines changed

2 files changed

+250
-0
lines changed

sentry/metric_alerts.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sentry
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"time"
78
)
@@ -23,6 +24,15 @@ type MetricAlert struct {
2324
Projects []string `json:"projects,omitempty"`
2425
Owner *string `json:"owner,omitempty"`
2526
DateCreated *time.Time `json:"dateCreated,omitempty"`
27+
TaskUUID *string `json:"uuid,omitempty"` // This is actually the UUID of the async task that can be spawned to create the metric
28+
}
29+
30+
// MetricAlertTaskDetail represents the inline struct Sentry defines for task details
31+
// https://github.com/getsentry/sentry/blob/22.12.0/src/sentry/incidents/endpoints/project_alert_rule_task_details.py#L31
32+
type MetricAlertTaskDetail struct {
33+
Status *string `json:"status,omitempty"`
34+
AlertRule *MetricAlert `json:"alertRule,omitempty"`
35+
Error *string `json:"error,omitempty"`
2636
}
2737

2838
// MetricAlertTrigger represents a metric alert trigger.
@@ -104,6 +114,15 @@ func (s *MetricAlertsService) Create(ctx context.Context, organizationSlug strin
104114
if err != nil {
105115
return nil, resp, err
106116
}
117+
118+
if resp.StatusCode == 202 {
119+
if alert.TaskUUID == nil {
120+
return nil, resp, errors.New("missing task uuid")
121+
}
122+
// We just received a reference to an async task, we need to check another endpoint to retrieve the metric alert we created
123+
return s.getMetricAlertFromMetricAlertTaskDetail(ctx, organizationSlug, projectSlug, *alert.TaskUUID)
124+
}
125+
107126
return alert, resp, nil
108127
}
109128

@@ -120,9 +139,53 @@ func (s *MetricAlertsService) Update(ctx context.Context, organizationSlug strin
120139
if err != nil {
121140
return nil, resp, err
122141
}
142+
143+
if resp.StatusCode == 202 {
144+
if alert.TaskUUID == nil {
145+
return nil, resp, errors.New("missing task uuid")
146+
}
147+
// We just received a reference to an async task, we need to check another endpoint to retrieve the metric alert we created
148+
return s.getMetricAlertFromMetricAlertTaskDetail(ctx, organizationSlug, projectSlug, *alert.TaskUUID)
149+
}
150+
123151
return alert, resp, nil
124152
}
125153

154+
func (s *MetricAlertsService) getMetricAlertFromMetricAlertTaskDetail(ctx context.Context, organizationSlug string, projectSlug string, taskUUID string) (*MetricAlert, *Response, error) {
155+
u := fmt.Sprintf("0/projects/%v/%v/alert-rule-task/%v/", organizationSlug, projectSlug, taskUUID)
156+
req, err := s.client.NewRequest("GET", u, nil)
157+
if err != nil {
158+
return nil, nil, err
159+
}
160+
161+
var resp *Response
162+
for i := 0; i < 5; i++ {
163+
time.Sleep(5 * time.Second)
164+
165+
taskDetail := new(MetricAlertTaskDetail)
166+
resp, err := s.client.Do(ctx, req, taskDetail)
167+
if err != nil {
168+
return nil, resp, err
169+
}
170+
171+
if resp.StatusCode == 404 {
172+
return nil, resp, fmt.Errorf("cannot find metric alert creation task with UUID %v", taskUUID)
173+
}
174+
if taskDetail.Status != nil && taskDetail.AlertRule != nil {
175+
if *taskDetail.Status == "success" {
176+
return taskDetail.AlertRule, resp, err
177+
} else if *taskDetail.Status == "failed" {
178+
if taskDetail != nil {
179+
return taskDetail.AlertRule, resp, errors.New(*taskDetail.Error)
180+
}
181+
182+
return taskDetail.AlertRule, resp, errors.New("error while running the metric alert creation task")
183+
}
184+
}
185+
}
186+
return nil, resp, errors.New("getting the status of the metric alert creation from Sentry took too long")
187+
}
188+
126189
// Delete an Alert Rule.
127190
func (s *MetricAlertsService) Delete(ctx context.Context, organizationSlug string, projectSlug string, alertRuleID string) (*Response, error) {
128191
u := fmt.Sprintf("0/projects/%v/%v/alert-rules/%v/", organizationSlug, projectSlug, alertRuleID)

sentry/metric_alerts_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,193 @@ func TestMetricAlertService_Get(t *testing.T) {
211211
require.Equal(t, expected, alert)
212212
}
213213

214+
func TestMetricAlertsService_CreateWithAsyncTask(t *testing.T) {
215+
client, mux, _, teardown := setup()
216+
defer teardown()
217+
218+
mux.HandleFunc("/api/0/projects/the-interstellar-jurisdiction/pump-station/alert-rule-task/fakeuuid/", func(w http.ResponseWriter, r *http.Request) {
219+
w.Header().Set("Content-Type", "application/json")
220+
fmt.Fprint(w, `
221+
{
222+
"status": "success",
223+
"error": null,
224+
"alertRule": {
225+
"id": "12345",
226+
"name": "pump-station-alert",
227+
"environment": "production",
228+
"dataset": "transactions",
229+
"eventTypes": ["transaction"],
230+
"query": "http.url:http://service/unreadmessages",
231+
"aggregate": "p50(transaction.duration)",
232+
"timeWindow": 10,
233+
"thresholdType": 0,
234+
"resolveThreshold": 0,
235+
"triggers": [
236+
{
237+
"actions": [
238+
{
239+
"alertRuleTriggerId": "56789",
240+
"dateCreated": "2022-04-15T15:06:01.087054Z",
241+
"desc": "Send a Slack notification to #alert-rule-alerts",
242+
"id": "12389",
243+
"inputChannelId": "C0XXXFKLXXX",
244+
"integrationId": 111,
245+
"sentryAppId": null,
246+
"targetIdentifier": "#alert-rule-alerts",
247+
"targetType": "specific",
248+
"type": "slack"
249+
}
250+
],
251+
"alertRuleId": "12345",
252+
"alertThreshold": 10000,
253+
"dateCreated": "2022-04-15T15:06:01.079598Z",
254+
"id": "56789",
255+
"label": "critical",
256+
"resolveThreshold": 0,
257+
"thresholdType": 0
258+
}
259+
],
260+
"projects": [
261+
"pump-station"
262+
],
263+
"owner": "pump-station:12345",
264+
"dateCreated": "2022-04-15T15:06:01.05618Z"
265+
}
266+
}
267+
`)
268+
})
269+
270+
mux.HandleFunc("/api/0/projects/the-interstellar-jurisdiction/pump-station/alert-rules/", func(w http.ResponseWriter, r *http.Request) {
271+
assertMethod(t, "POST", r)
272+
assertPostJSONValue(t, map[string]interface{}{
273+
"id": "12345",
274+
"name": "pump-station-alert",
275+
"environment": "production",
276+
"dataset": "transactions",
277+
"eventTypes": []string{"transaction"},
278+
"query": "http.url:http://service/unreadmessages",
279+
"aggregate": "p50(transaction.duration)",
280+
"timeWindow": 10,
281+
"thresholdType": 0,
282+
"resolveThreshold": 0,
283+
"triggers": []map[string]interface{}{
284+
{
285+
"actions": []map[string]interface{}{
286+
{
287+
"alertRuleTriggerId": "56789",
288+
"dateCreated": "2022-04-15T15:06:01.087054Z",
289+
"desc": "Send a Slack notification to #alert-rule-alerts",
290+
"id": "12389",
291+
"inputChannelId": "C0XXXFKLXXX",
292+
"integrationId": 111,
293+
"sentryAppId": nil,
294+
"targetIdentifier": "#alert-rule-alerts",
295+
"targetType": "specific",
296+
"type": "slack",
297+
},
298+
},
299+
"alertRuleId": "12345",
300+
"alertThreshold": 10000,
301+
"dateCreated": "2022-04-15T15:06:01.079598Z",
302+
"id": "56789",
303+
"label": "critical",
304+
"resolveThreshold": 0,
305+
"thresholdType": 0,
306+
},
307+
},
308+
"projects": []string{"pump-station"},
309+
"owner": "pump-station:12345",
310+
"dateCreated": "2022-04-15T15:06:01.05618Z",
311+
}, r)
312+
313+
w.WriteHeader(http.StatusAccepted)
314+
w.Header().Set("Content-Type", "application/json")
315+
fmt.Fprint(w, `{"uuid": "fakeuuid"}`)
316+
})
317+
318+
params := &MetricAlert{
319+
Name: String("pump-station-alert"),
320+
Environment: String("production"),
321+
DataSet: String("transactions"),
322+
Query: String("http.url:http://service/unreadmessages"),
323+
Aggregate: String("p50(transaction.duration)"),
324+
TimeWindow: Float64(10.0),
325+
ThresholdType: Int(0),
326+
ResolveThreshold: Float64(0),
327+
Triggers: []*MetricAlertTrigger{
328+
{
329+
ID: String("56789"),
330+
AlertRuleID: String("12345"),
331+
Label: String("critical"),
332+
ThresholdType: Int(0),
333+
AlertThreshold: Float64(55501.0),
334+
ResolveThreshold: Float64(100.0),
335+
DateCreated: Time(mustParseTime("2022-04-15T15:06:01.079598Z")),
336+
Actions: []*MetricAlertTriggerAction{
337+
{
338+
ID: String("12389"),
339+
AlertRuleTriggerID: String("56789"),
340+
Type: String("slack"),
341+
TargetType: String("specific"),
342+
TargetIdentifier: String("#alert-rule-alerts"),
343+
InputChannelID: String("C0XXXFKLXXX"),
344+
IntegrationID: Int(123),
345+
DateCreated: Time(mustParseTime("2022-04-15T15:06:01.087054Z")),
346+
Description: String("Send a Slack notification to #alert-rule-alerts"),
347+
},
348+
},
349+
},
350+
},
351+
Projects: []string{"pump-station"},
352+
Owner: String("pump-station:12345"),
353+
}
354+
ctx := context.Background()
355+
alertRule, _, err := client.MetricAlerts.Create(ctx, "the-interstellar-jurisdiction", "pump-station", params)
356+
require.NoError(t, err)
357+
358+
expected := &MetricAlert{
359+
ID: String("12345"),
360+
Name: String("pump-station-alert"),
361+
Environment: String("production"),
362+
DataSet: String("transactions"),
363+
EventTypes: []string{"transaction"},
364+
Query: String("http.url:http://service/unreadmessages"),
365+
Aggregate: String("p50(transaction.duration)"),
366+
ThresholdType: Int(0),
367+
ResolveThreshold: Float64(0),
368+
TimeWindow: Float64(10.0),
369+
Triggers: []*MetricAlertTrigger{
370+
{
371+
ID: String("56789"),
372+
AlertRuleID: String("12345"),
373+
Label: String("critical"),
374+
ThresholdType: Int(0),
375+
AlertThreshold: Float64(10000.0),
376+
ResolveThreshold: Float64(0.0),
377+
DateCreated: Time(mustParseTime("2022-04-15T15:06:01.079598Z")),
378+
Actions: []*MetricAlertTriggerAction{
379+
{
380+
ID: String("12389"),
381+
AlertRuleTriggerID: String("56789"),
382+
Type: String("slack"),
383+
TargetType: String("specific"),
384+
TargetIdentifier: String("#alert-rule-alerts"),
385+
InputChannelID: String("C0XXXFKLXXX"),
386+
IntegrationID: Int(111),
387+
DateCreated: Time(mustParseTime("2022-04-15T15:06:01.087054Z")),
388+
Description: String("Send a Slack notification to #alert-rule-alerts"),
389+
},
390+
},
391+
},
392+
},
393+
Projects: []string{"pump-station"},
394+
Owner: String("pump-station:12345"),
395+
DateCreated: Time(mustParseTime("2022-04-15T15:06:01.05618Z")),
396+
}
397+
398+
require.Equal(t, expected, alertRule)
399+
}
400+
214401
func TestMetricAlertService_Create(t *testing.T) {
215402
client, mux, _, teardown := setup()
216403
defer teardown()

0 commit comments

Comments
 (0)