Skip to content

Commit 4a2f2fb

Browse files
committed
feat(alerting): show panel specific alert annotations, grafana#5694
1 parent 0ac4ece commit 4a2f2fb

File tree

15 files changed

+156
-114
lines changed

15 files changed

+156
-114
lines changed

pkg/api/annotations.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import (
99
func GetAnnotations(c *middleware.Context) Response {
1010

1111
query := &annotations.ItemQuery{
12-
From: c.QueryInt64("from") / 1000,
13-
To: c.QueryInt64("to") / 1000,
14-
Type: annotations.ItemType(c.Query("type")),
15-
OrgId: c.OrgId,
16-
Limit: c.QueryInt64("limit"),
12+
From: c.QueryInt64("from") / 1000,
13+
To: c.QueryInt64("to") / 1000,
14+
Type: annotations.ItemType(c.Query("type")),
15+
OrgId: c.OrgId,
16+
AlertId: c.QueryInt64("alertId"),
17+
DashboardId: c.QueryInt64("dashboardId"),
18+
PanelId: c.QueryInt64("panelId"),
19+
Limit: c.QueryInt64("limit"),
1720
}
1821

1922
repo := annotations.GetRepository()

pkg/api/dtos/annotations.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package dtos
33
import "github.com/grafana/grafana/pkg/components/simplejson"
44

55
type Annotation struct {
6-
AlertId int64 `json:"alertId"`
7-
NewState string `json:"newState"`
8-
PrevState string `json:"prevState"`
9-
Time int64 `json:"time"`
10-
Title string `json:"title"`
11-
Text string `json:"text"`
12-
Metric string `json:"metric"`
6+
AlertId int64 `json:"alertId"`
7+
DashboardId int64 `json:"dashboardId"`
8+
PanelId int64 `json:"panelId"`
9+
NewState string `json:"newState"`
10+
PrevState string `json:"prevState"`
11+
Time int64 `json:"time"`
12+
Title string `json:"title"`
13+
Text string `json:"text"`
14+
Metric string `json:"metric"`
1315

1416
Data *simplejson.Json `json:"data"`
1517
}

pkg/services/alerting/result_handler.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,17 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
6666

6767
// save annotation
6868
item := annotations.Item{
69-
OrgId: ctx.Rule.OrgId,
70-
Type: annotations.AlertType,
71-
AlertId: ctx.Rule.Id,
72-
Title: ctx.Rule.Name,
73-
Text: ctx.GetStateModel().Text,
74-
NewState: string(ctx.Rule.State),
75-
PrevState: string(oldState),
76-
Epoch: time.Now().Unix(),
77-
Data: annotationData,
69+
OrgId: ctx.Rule.OrgId,
70+
DashboardId: ctx.Rule.DashboardId,
71+
PanelId: ctx.Rule.PanelId,
72+
Type: annotations.AlertType,
73+
AlertId: ctx.Rule.Id,
74+
Title: ctx.Rule.Name,
75+
Text: ctx.GetStateModel().Text,
76+
NewState: string(ctx.Rule.State),
77+
PrevState: string(oldState),
78+
Epoch: time.Now().Unix(),
79+
Data: annotationData,
7880
}
7981

8082
annotationRepo := annotations.GetRepository()

pkg/services/annotations/annotations.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ type Repository interface {
88
}
99

1010
type ItemQuery struct {
11-
OrgId int64 `json:"orgId"`
12-
From int64 `json:"from"`
13-
To int64 `json:"from"`
14-
Type ItemType `json:"type"`
15-
AlertId int64 `json:"alertId"`
11+
OrgId int64 `json:"orgId"`
12+
From int64 `json:"from"`
13+
To int64 `json:"from"`
14+
Type ItemType `json:"type"`
15+
AlertId int64 `json:"alertId"`
16+
DashboardId int64 `json:"dashboardId"`
17+
PanelId int64 `json:"panelId"`
1618

1719
Limit int64 `json:"alertId"`
1820
}
@@ -34,17 +36,19 @@ const (
3436
)
3537

3638
type Item struct {
37-
Id int64 `json:"id"`
38-
OrgId int64 `json:"orgId"`
39-
Type ItemType `json:"type"`
40-
Title string `json:"title"`
41-
Text string `json:"text"`
42-
Metric string `json:"metric"`
43-
AlertId int64 `json:"alertId"`
44-
UserId int64 `json:"userId"`
45-
PrevState string `json:"prevState"`
46-
NewState string `json:"newState"`
47-
Epoch int64 `json:"epoch"`
39+
Id int64 `json:"id"`
40+
OrgId int64 `json:"orgId"`
41+
DashboardId int64 `json:"dashboardId"`
42+
PanelId int64 `json:"panelId"`
43+
Type ItemType `json:"type"`
44+
Title string `json:"title"`
45+
Text string `json:"text"`
46+
Metric string `json:"metric"`
47+
AlertId int64 `json:"alertId"`
48+
UserId int64 `json:"userId"`
49+
PrevState string `json:"prevState"`
50+
NewState string `json:"newState"`
51+
Epoch int64 `json:"epoch"`
4852

4953
Data *simplejson.Json `json:"data"`
5054
}

pkg/services/sqlstore/annotation.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
3838
params = append(params, query.AlertId)
3939
}
4040

41+
if query.AlertId != 0 {
42+
sql.WriteString(` AND alert_id = ?`)
43+
params = append(params, query.AlertId)
44+
}
45+
46+
if query.DashboardId != 0 {
47+
sql.WriteString(` AND dashboard_id = ?`)
48+
params = append(params, query.DashboardId)
49+
}
50+
51+
if query.PanelId != 0 {
52+
sql.WriteString(` AND panel_id = ?`)
53+
params = append(params, query.PanelId)
54+
}
55+
4156
sql.WriteString(` AND epoch BETWEEN ? AND ?`)
4257
params = append(params, query.From, query.To)
4358

pkg/services/sqlstore/migrations/annotation_mig.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ func addAnnotationMig(mg *Migrator) {
1313
{Name: "org_id", Type: DB_BigInt, Nullable: false},
1414
{Name: "alert_id", Type: DB_BigInt, Nullable: true},
1515
{Name: "user_id", Type: DB_BigInt, Nullable: true},
16+
{Name: "dashboard_id", Type: DB_BigInt, Nullable: true},
17+
{Name: "panel_id", Type: DB_BigInt, Nullable: true},
1618
{Name: "type", Type: DB_NVarchar, Length: 25, Nullable: false},
1719
{Name: "title", Type: DB_Text, Nullable: false},
1820
{Name: "text", Type: DB_Text, Nullable: false},
@@ -25,17 +27,18 @@ func addAnnotationMig(mg *Migrator) {
2527
Indices: []*Index{
2628
{Cols: []string{"org_id", "alert_id"}, Type: IndexType},
2729
{Cols: []string{"org_id", "type"}, Type: IndexType},
30+
{Cols: []string{"dashboard_id", "panel_id"}, Type: IndexType},
2831
{Cols: []string{"epoch"}, Type: IndexType},
2932
},
3033
}
3134

32-
mg.AddMigration("Drop old annotation table v2", NewDropTableMigration("annotation"))
35+
mg.AddMigration("Drop old annotation table v3", NewDropTableMigration("annotation"))
3336

34-
mg.AddMigration("create annotation table v3", NewAddTableMigration(table))
37+
mg.AddMigration("create annotation table v4", NewAddTableMigration(table))
3538

3639
// create indices
37-
mg.AddMigration("add index annotation org_id & alert_id v2", NewAddIndexMigration(table, table.Indices[0]))
38-
39-
mg.AddMigration("add index annotation org_id & type v2", NewAddIndexMigration(table, table.Indices[1]))
40-
mg.AddMigration("add index annotation epoch", NewAddIndexMigration(table, table.Indices[2]))
40+
mg.AddMigration("add index annotation org_id & alert_id v3", NewAddIndexMigration(table, table.Indices[0]))
41+
mg.AddMigration("add index annotation org_id & type v3", NewAddIndexMigration(table, table.Indices[1]))
42+
mg.AddMigration("add index annotation dashboard_id panel_id", NewAddIndexMigration(table, table.Indices[2]))
43+
mg.AddMigration("add index annotation epoch v3", NewAddIndexMigration(table, table.Indices[3]))
4144
}

public/app/features/annotations/annotations_srv.ts

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class AnnotationsSrv {
1414
constructor(private $rootScope,
1515
private $q,
1616
private datasourceSrv,
17+
private backendSrv,
1718
private timeSrv) {
1819
$rootScope.onAppEvent('refresh', this.clearCache.bind(this), $rootScope);
1920
$rootScope.onAppEvent('dashboard-initialized', this.clearCache.bind(this), $rootScope);
@@ -23,9 +24,41 @@ export class AnnotationsSrv {
2324
this.globalAnnotationsPromise = null;
2425
}
2526

26-
getAnnotations(dashboard) {
27+
getAnnotations(options) {
28+
return this.$q.all([
29+
this.getGlobalAnnotations(options),
30+
this.getPanelAnnotations(options)
31+
]).then(allResults => {
32+
return _.flatten(allResults);
33+
}).catch(err => {
34+
this.$rootScope.appEvent('alert-error', ['Annotations failed', (err.message || err)]);
35+
});
36+
}
37+
38+
getPanelAnnotations(options) {
39+
var panel = options.panel;
40+
var dashboard = options.dashboard;
41+
42+
if (panel && panel.alert && panel.alert.enabled) {
43+
return this.backendSrv.get('/api/annotations', {
44+
from: options.range.from.valueOf(),
45+
to: options.range.to.valueOf(),
46+
limit: 100,
47+
panelId: panel.id,
48+
dashboardId: dashboard.id,
49+
}).then(results => {
50+
return this.translateQueryResult({iconColor: '#AA0000', name: 'panel-alert'}, results);
51+
});
52+
}
53+
54+
return this.$q.when([]);
55+
}
56+
57+
getGlobalAnnotations(options) {
58+
var dashboard = options.dashboard;
59+
2760
if (dashboard.annotations.list.length === 0) {
28-
return this.$q.when(null);
61+
return this.$q.when([]);
2962
}
3063

3164
if (this.globalAnnotationsPromise) {
@@ -34,57 +67,38 @@ export class AnnotationsSrv {
3467

3568
var annotations = _.where(dashboard.annotations.list, {enable: true});
3669
var range = this.timeSrv.timeRange();
37-
var rangeRaw = this.timeSrv.timeRange(false);
3870

3971
this.globalAnnotationsPromise = this.$q.all(_.map(annotations, annotation => {
4072
if (annotation.snapshotData) {
41-
return this.translateQueryResult(annotation.snapshotData);
73+
return this.translateQueryResult(annotation, annotation.snapshotData);
4274
}
4375

4476
return this.datasourceSrv.get(annotation.datasource).then(datasource => {
4577
// issue query against data source
46-
return datasource.annotationQuery({
47-
range: range,
48-
rangeRaw:
49-
rangeRaw,
50-
annotation: annotation
51-
});
78+
return datasource.annotationQuery({range: range, rangeRaw: range.raw, annotation: annotation});
5279
})
5380
.then(results => {
5481
// store response in annotation object if this is a snapshot call
5582
if (dashboard.snapshot) {
5683
annotation.snapshotData = angular.copy(results);
5784
}
5885
// translate result
59-
return this.translateQueryResult(results);
86+
return this.translateQueryResult(annotation, results);
6087
});
61-
}))
62-
.then(allResults => {
63-
return _.flatten(allResults);
64-
}).catch(err => {
65-
this.$rootScope.appEvent('alert-error', ['Annotations failed', (err.message || err)]);
66-
});
88+
}));
6789

6890
return this.globalAnnotationsPromise;
6991
}
7092

71-
translateQueryResult(results) {
72-
var translated = [];
73-
93+
translateQueryResult(annotation, results) {
7494
for (var item of results) {
75-
translated.push({
76-
annotation: item.annotation,
77-
min: item.time,
78-
max: item.time,
79-
eventType: item.annotation.name,
80-
title: item.title,
81-
tags: item.tags,
82-
text: item.text,
83-
score: 1
84-
});
95+
item.source = annotation;
96+
item.min = item.time;
97+
item.max = item.time;
98+
item.scope = 1;
99+
item.eventType = annotation.name;
85100
}
86-
87-
return translated;
101+
return results;
88102
}
89103
}
90104

public/app/features/annotations/partials/editor.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ <h2 class="tabbed-view-title">
4040
<td style="width: 1%"><i ng-click="_.move(ctrl.annotations,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
4141

4242
<td style="width: 1%">
43-
<a ng-click="edit(annotation)" class="btn btn-inverse btn-mini">
43+
<a ng-click="ctrl.edit(annotation)" class="btn btn-inverse btn-mini">
4444
<i class="fa fa-edit"></i>
4545
Edit
4646
</a>

public/app/features/dashboard/timeSrv.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,28 +119,28 @@ define([
119119
};
120120

121121
this.timeRangeForUrl = function() {
122-
var range = this.timeRange(false);
123-
if (_.isString(range.to) && range.to.indexOf('now')) {
124-
range = this.timeRange();
125-
}
122+
var range = this.timeRange().raw;
126123

127124
if (moment.isMoment(range.from)) { range.from = range.from.valueOf(); }
128125
if (moment.isMoment(range.to)) { range.to = range.to.valueOf(); }
129126

130127
return range;
131128
};
132129

133-
this.timeRange = function(parse) {
130+
this.timeRange = function() {
134131
// make copies if they are moment (do not want to return out internal moment, because they are mutable!)
135-
var from = moment.isMoment(this.time.from) ? moment(this.time.from) : this.time.from ;
136-
var to = moment.isMoment(this.time.to) ? moment(this.time.to) : this.time.to ;
132+
var range = {
133+
from: moment.isMoment(this.time.from) ? moment(this.time.from) : this.time.from,
134+
to: moment.isMoment(this.time.to) ? moment(this.time.to) : this.time.to,
135+
};
137136

138-
if (parse !== false) {
139-
from = dateMath.parse(from, false);
140-
to = dateMath.parse(to, true);
141-
}
137+
range = {
138+
from: dateMath.parse(range.from, false),
139+
to: dateMath.parse(range.to, true),
140+
raw: range
141+
};
142142

143-
return {from: from, to: to};
143+
return range;
144144
};
145145

146146
this.zoomOut = function(factor) {

public/app/features/dashboard/timepicker/timepicker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class TimePickerCtrl {
4646
this.firstDayOfWeek = moment.localeData().firstDayOfWeek();
4747

4848
var time = angular.copy(this.timeSrv.timeRange());
49-
var timeRaw = angular.copy(this.timeSrv.timeRange(false));
49+
var timeRaw = angular.copy(time.raw);
5050

5151
if (!this.dashboard.isTimezoneUtc()) {
5252
time.from.local();

0 commit comments

Comments
 (0)