Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
e06abb30
Commit
e06abb30
authored
Jun 06, 2019
by
Thibault Chataigner
Committed by
Carl Bergquist
Jun 06, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Alerting: Add tags to alert rules (#10989)
Ref #6552
parent
34f31455
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
201 additions
and
70 deletions
+201
-70
docs/sources/alerting/notifications.md
+27
-19
pkg/models/alert.go
+15
-0
pkg/models/alert_test.go
+23
-0
pkg/services/alerting/notifiers/alertmanager.go
+4
-1
pkg/services/alerting/rule.go
+2
-0
pkg/services/sqlstore/alert.go
+19
-0
pkg/services/sqlstore/annotation.go
+2
-22
pkg/services/sqlstore/annotation_test.go
+0
-28
pkg/services/sqlstore/migrations/alert_mig.go
+14
-0
pkg/services/sqlstore/tags.go
+23
-0
pkg/services/sqlstore/tags_test.go
+26
-0
public/app/features/alerting/AlertTabCtrl.ts
+14
-0
public/app/features/alerting/partials/alert_tab.html
+32
-0
No files found.
docs/sources/alerting/notifications.md
View file @
e06abb30
...
...
@@ -167,26 +167,26 @@ Notifications can be sent by setting up an incoming webhook in Google Hangouts c
### All supported notifiers
Name | Type | Supports images
Name | Type | Supports images
|Support alert rule tags
-----|------------ | ------
DingDing |
`dingding`
| yes, external only
Discord |
`discord`
| yes
Email |
`email`
| yes
Google Hangouts Chat |
`googlechat`
| yes, external only
Hipchat |
`hipchat`
| yes, external only
Kafka |
`kafka`
| yes, external only
Line |
`line`
| yes, external only
Microsoft Teams |
`teams`
| yes, external only
OpsGenie |
`opsgenie`
| yes, external only
Pagerduty |
`pagerduty`
| yes, external only
Prometheus Alertmanager |
`prometheus-alertmanager`
| yes, external only
Pushover |
`pushover`
| yes
Sensu |
`sensu`
| yes, external only
Slack |
`slack`
| yes
Telegram |
`telegram`
| yes
Threema |
`threema`
| yes, external only
VictorOps |
`victorops`
| yes, external only
Webhook |
`webhook`
| yes, external only
DingDing |
`dingding`
| yes, external only
| no
Discord |
`discord`
| yes
| no
Email |
`email`
| yes
| no
Google Hangouts Chat |
`googlechat`
| yes, external only
| no
Hipchat |
`hipchat`
| yes, external only
| no
Kafka |
`kafka`
| yes, external only
| no
Line |
`line`
| yes, external only
| no
Microsoft Teams |
`teams`
| yes, external only
| no
OpsGenie |
`opsgenie`
| yes, external only
| no
Pagerduty |
`pagerduty`
| yes, external only
| no
Prometheus Alertmanager |
`prometheus-alertmanager`
| yes, external only
| yes
Pushover |
`pushover`
| yes
| no
Sensu |
`sensu`
| yes, external only
| no
Slack |
`slack`
| yes
| no
Telegram |
`telegram`
| yes
| no
Threema |
`threema`
| yes, external only
| no
VictorOps |
`victorops`
| yes, external only
| no
Webhook |
`webhook`
| yes, external only
| no
# Enable images in notifications {#external-image-store}
...
...
@@ -197,6 +197,14 @@ Be aware that some notifiers requires public access to the image to be able to i
Notification services which need public image access are marked as 'external only'.
# Use alert rule tags in notifications {#alert-rule-tags}
Grafana can include a list of tags (key/value) in the notification.
It's called alert rule tags to contrast with tags parsed from timeseries.
It currently supports only the Prometheus Alertmanager notifier.
This is an optional feature. You can get notifications without using alert rule tags.
# Configure the link back to Grafana from alert notifications
All alert notifications contain a link back to the triggered alert in the Grafana instance.
...
...
pkg/models/alert.go
View file @
e06abb30
...
...
@@ -117,6 +117,21 @@ func (this *Alert) ContainsUpdates(other *Alert) bool {
return
result
}
func
(
alert
*
Alert
)
GetTagsFromSettings
()
[]
*
Tag
{
tags
:=
[]
*
Tag
{}
if
alert
.
Settings
!=
nil
{
if
data
,
ok
:=
alert
.
Settings
.
CheckGet
(
"alertRuleTags"
);
ok
{
for
tagNameString
,
tagValue
:=
range
data
.
MustMap
()
{
// MustMap() already guarantees the return of a `map[string]interface{}`.
// Therefore we only need to verify that tagValue is a String.
tagValueString
:=
simplejson
.
NewFromAny
(
tagValue
)
.
MustString
()
tags
=
append
(
tags
,
&
Tag
{
Key
:
tagNameString
,
Value
:
tagValueString
})
}
}
}
return
tags
}
type
AlertingClusterInfo
struct
{
ServerId
string
ClusterSize
int
...
...
pkg/models/alert_test.go
View file @
e06abb30
...
...
@@ -35,5 +35,28 @@ func TestAlertingModelTest(t *testing.T) {
rule1
.
Settings
=
json2
So
(
rule1
.
ContainsUpdates
(
rule2
),
ShouldBeTrue
)
})
Convey
(
"Should parse alertRule tags correctly"
,
func
()
{
json2
,
_
:=
simplejson
.
NewJson
([]
byte
(
`{
"field": "value",
"alertRuleTags": {
"foo": "bar",
"waldo": "fred",
"tagMap": { "mapValue": "value" }
}
}`
))
rule1
.
Settings
=
json2
expectedTags
:=
[]
*
Tag
{
{
Id
:
0
,
Key
:
"foo"
,
Value
:
"bar"
},
{
Id
:
0
,
Key
:
"waldo"
,
Value
:
"fred"
},
{
Id
:
0
,
Key
:
"tagMap"
,
Value
:
""
},
}
actualTags
:=
rule1
.
GetTagsFromSettings
()
So
(
len
(
actualTags
),
ShouldEqual
,
len
(
expectedTags
))
for
_
,
tag
:=
range
expectedTags
{
So
(
ContainsTag
(
actualTags
,
tag
),
ShouldBeTrue
)
}
})
})
}
pkg/services/alerting/notifiers/alertmanager.go
View file @
e06abb30
...
...
@@ -93,7 +93,7 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
alertJSON
.
SetPath
([]
string
{
"annotations"
,
"image"
},
evalContext
.
ImagePublicURL
)
}
// Labels (from metrics tags + mandatory alertname).
// Labels (from metrics tags +
AlertRuleTags +
mandatory alertname).
tags
:=
make
(
map
[
string
]
string
)
if
match
!=
nil
{
if
len
(
match
.
Tags
)
==
0
{
...
...
@@ -104,6 +104,9 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
}
}
}
for
_
,
tag
:=
range
evalContext
.
Rule
.
AlertRuleTags
{
tags
[
tag
.
Key
]
=
tag
.
Value
}
tags
[
"alertname"
]
=
evalContext
.
Rule
.
Name
alertJSON
.
Set
(
"labels"
,
tags
)
return
alertJSON
...
...
pkg/services/alerting/rule.go
View file @
e06abb30
...
...
@@ -35,6 +35,7 @@ type Rule struct {
State
models
.
AlertStateType
Conditions
[]
Condition
Notifications
[]
string
AlertRuleTags
[]
*
models
.
Tag
StateChanges
int64
}
...
...
@@ -145,6 +146,7 @@ func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
model
.
Notifications
=
append
(
model
.
Notifications
,
uid
)
}
}
model
.
AlertRuleTags
=
ruleDef
.
GetTagsFromSettings
()
for
index
,
condition
:=
range
ruleDef
.
Settings
.
Get
(
"conditions"
)
.
MustArray
()
{
conditionModel
:=
simplejson
.
NewFromAny
(
condition
)
...
...
pkg/services/sqlstore/alert.go
View file @
e06abb30
...
...
@@ -64,6 +64,10 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) erro
return
err
}
if
_
,
err
:=
sess
.
Exec
(
"DELETE FROM alert_rule_tag WHERE alert_id = ?"
,
alertId
);
err
!=
nil
{
return
err
}
return
nil
}
...
...
@@ -215,6 +219,21 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
sqlog
.
Debug
(
"Alert inserted"
,
"name"
,
alert
.
Name
,
"id"
,
alert
.
Id
)
}
tags
:=
alert
.
GetTagsFromSettings
()
if
_
,
err
:=
sess
.
Exec
(
"DELETE FROM alert_rule_tag WHERE alert_id = ?"
,
alert
.
Id
);
err
!=
nil
{
return
err
}
if
tags
!=
nil
{
tags
,
err
:=
EnsureTagsExist
(
sess
,
tags
)
if
err
!=
nil
{
return
err
}
for
_
,
tag
:=
range
tags
{
if
_
,
err
:=
sess
.
Exec
(
"INSERT INTO alert_rule_tag (alert_id, tag_id) VALUES(?,?)"
,
alert
.
Id
,
tag
.
Id
);
err
!=
nil
{
return
err
}
}
}
}
return
nil
...
...
pkg/services/sqlstore/annotation.go
View file @
e06abb30
...
...
@@ -29,7 +29,7 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
}
if
item
.
Tags
!=
nil
{
tags
,
err
:=
r
.
e
nsureTagsExist
(
sess
,
tags
)
tags
,
err
:=
E
nsureTagsExist
(
sess
,
tags
)
if
err
!=
nil
{
return
err
}
...
...
@@ -44,26 +44,6 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
})
}
// Will insert if needed any new key/value pars and return ids
func
(
r
*
SqlAnnotationRepo
)
ensureTagsExist
(
sess
*
DBSession
,
tags
[]
*
models
.
Tag
)
([]
*
models
.
Tag
,
error
)
{
for
_
,
tag
:=
range
tags
{
var
existingTag
models
.
Tag
// check if it exists
if
exists
,
err
:=
sess
.
Table
(
"tag"
)
.
Where
(
dialect
.
Quote
(
"key"
)
+
"=? AND "
+
dialect
.
Quote
(
"value"
)
+
"=?"
,
tag
.
Key
,
tag
.
Value
)
.
Get
(
&
existingTag
);
err
!=
nil
{
return
nil
,
err
}
else
if
exists
{
tag
.
Id
=
existingTag
.
Id
}
else
{
if
_
,
err
:=
sess
.
Table
(
"tag"
)
.
Insert
(
tag
);
err
!=
nil
{
return
nil
,
err
}
}
}
return
tags
,
nil
}
func
(
r
*
SqlAnnotationRepo
)
Update
(
item
*
annotations
.
Item
)
error
{
return
inTransaction
(
func
(
sess
*
DBSession
)
error
{
var
(
...
...
@@ -94,7 +74,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
}
if
item
.
Tags
!=
nil
{
tags
,
err
:=
r
.
e
nsureTagsExist
(
sess
,
models
.
ParseTagPairs
(
item
.
Tags
))
tags
,
err
:=
E
nsureTagsExist
(
sess
,
models
.
ParseTagPairs
(
item
.
Tags
))
if
err
!=
nil
{
return
err
}
...
...
pkg/services/sqlstore/annotation_test.go
View file @
e06abb30
...
...
@@ -5,37 +5,9 @@ import (
.
"github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
)
func
TestSavingTags
(
t
*
testing
.
T
)
{
InitTestDB
(
t
)
Convey
(
"Testing annotation saving/loading"
,
t
,
func
()
{
repo
:=
SqlAnnotationRepo
{}
Convey
(
"Can save tags"
,
func
()
{
Reset
(
func
()
{
_
,
err
:=
x
.
Exec
(
"DELETE FROM annotation_tag WHERE 1=1"
)
So
(
err
,
ShouldBeNil
)
})
tagPairs
:=
[]
*
models
.
Tag
{
{
Key
:
"outage"
},
{
Key
:
"type"
,
Value
:
"outage"
},
{
Key
:
"server"
,
Value
:
"server-1"
},
{
Key
:
"error"
},
}
tags
,
err
:=
repo
.
ensureTagsExist
(
newSession
(),
tagPairs
)
So
(
err
,
ShouldBeNil
)
So
(
len
(
tags
),
ShouldEqual
,
4
)
})
})
}
func
TestAnnotations
(
t
*
testing
.
T
)
{
InitTestDB
(
t
)
...
...
pkg/services/sqlstore/migrations/alert_mig.go
View file @
e06abb30
...
...
@@ -45,6 +45,20 @@ func addAlertMigrations(mg *Migrator) {
mg
.
AddMigration
(
"add index alert state"
,
NewAddIndexMigration
(
alertV1
,
alertV1
.
Indices
[
1
]))
mg
.
AddMigration
(
"add index alert dashboard_id"
,
NewAddIndexMigration
(
alertV1
,
alertV1
.
Indices
[
2
]))
alertRuleTagTable
:=
Table
{
Name
:
"alert_rule_tag"
,
Columns
:
[]
*
Column
{
{
Name
:
"alert_id"
,
Type
:
DB_BigInt
,
Nullable
:
false
},
{
Name
:
"tag_id"
,
Type
:
DB_BigInt
,
Nullable
:
false
},
},
Indices
:
[]
*
Index
{
{
Cols
:
[]
string
{
"alert_id"
,
"tag_id"
},
Type
:
UniqueIndex
},
},
}
mg
.
AddMigration
(
"Create alert_rule_tag table v1"
,
NewAddTableMigration
(
alertRuleTagTable
))
mg
.
AddMigration
(
"Add unique index alert_rule_tag.alert_id_tag_id"
,
NewAddIndexMigration
(
alertRuleTagTable
,
alertRuleTagTable
.
Indices
[
0
]))
alert_notification
:=
Table
{
Name
:
"alert_notification"
,
Columns
:
[]
*
Column
{
...
...
pkg/services/sqlstore/tags.go
0 → 100644
View file @
e06abb30
package
sqlstore
import
"github.com/grafana/grafana/pkg/models"
// Will insert if needed any new key/value pars and return ids
func
EnsureTagsExist
(
sess
*
DBSession
,
tags
[]
*
models
.
Tag
)
([]
*
models
.
Tag
,
error
)
{
for
_
,
tag
:=
range
tags
{
var
existingTag
models
.
Tag
// check if it exists
if
exists
,
err
:=
sess
.
Table
(
"tag"
)
.
Where
(
"`key`=? AND `value`=?"
,
tag
.
Key
,
tag
.
Value
)
.
Get
(
&
existingTag
);
err
!=
nil
{
return
nil
,
err
}
else
if
exists
{
tag
.
Id
=
existingTag
.
Id
}
else
{
if
_
,
err
:=
sess
.
Table
(
"tag"
)
.
Insert
(
tag
);
err
!=
nil
{
return
nil
,
err
}
}
}
return
tags
,
nil
}
pkg/services/sqlstore/tags_test.go
0 → 100644
View file @
e06abb30
package
sqlstore
import
(
"testing"
.
"github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models"
)
func
TestSavingTags
(
t
*
testing
.
T
)
{
Convey
(
"Testing tags saving"
,
t
,
func
()
{
InitTestDB
(
t
)
tagPairs
:=
[]
*
models
.
Tag
{
{
Key
:
"outage"
},
{
Key
:
"type"
,
Value
:
"outage"
},
{
Key
:
"server"
,
Value
:
"server-1"
},
{
Key
:
"error"
},
}
tags
,
err
:=
EnsureTagsExist
(
newSession
(),
tagPairs
)
So
(
err
,
ShouldBeNil
)
So
(
len
(
tags
),
ShouldEqual
,
4
)
})
}
public/app/features/alerting/AlertTabCtrl.ts
View file @
e06abb30
...
...
@@ -28,6 +28,7 @@ export class AlertTabCtrl {
error
:
string
;
appSubUrl
:
string
;
alertHistory
:
any
;
newAlertRuleTag
:
any
;
/** @ngInject */
constructor
(
...
...
@@ -158,6 +159,18 @@ export class AlertTabCtrl {
_
.
remove
(
this
.
alertNotifications
,
(
n
:
any
)
=>
n
.
uid
===
an
.
uid
||
n
.
id
===
an
.
id
);
}
addAlertRuleTag
()
{
if
(
this
.
newAlertRuleTag
.
name
)
{
this
.
alert
.
alertRuleTags
[
this
.
newAlertRuleTag
.
name
]
=
this
.
newAlertRuleTag
.
value
;
}
this
.
newAlertRuleTag
.
name
=
''
;
this
.
newAlertRuleTag
.
value
=
''
;
}
removeAlertRuleTag
(
tagName
)
{
delete
this
.
alert
.
alertRuleTags
[
tagName
];
}
initModel
()
{
const
alert
=
(
this
.
alert
=
this
.
panel
.
alert
);
if
(
!
alert
)
{
...
...
@@ -175,6 +188,7 @@ export class AlertTabCtrl {
alert
.
handler
=
alert
.
handler
||
1
;
alert
.
notifications
=
alert
.
notifications
||
[];
alert
.
for
=
alert
.
for
||
'0m'
;
alert
.
alertRuleTags
=
alert
.
alertRuleTags
||
{};
const
defaultName
=
this
.
panel
.
title
+
' alert'
;
alert
.
name
=
alert
.
name
||
defaultName
;
...
...
public/app/features/alerting/partials/alert_tab.html
View file @
e06abb30
...
...
@@ -149,6 +149,38 @@
<textarea
class=
"gf-form-input"
rows=
"10"
ng-model=
"ctrl.alert.message"
placeholder=
"Notification message details..."
></textarea>
</div>
<div
class=
"gf-form"
>
<span
class=
"gf-form-label width-8"
>
Tags
</span>
<div
class=
"gf-form-group"
>
<div
class=
"gf-form-inline"
ng-repeat=
"(name, value) in ctrl.alert.alertRuleTags"
>
<label
class=
"gf-form-label width-15"
>
{{ name }}
</label>
<input
class=
"gf-form-input width-15"
placeholder=
"Tag value..."
ng-model=
"ctrl.alert.alertRuleTags[name]"
type=
"text"
/>
<label
class=
"gf-form-label"
>
<a
class=
"pointer"
tabindex=
"1"
ng-click=
"ctrl.removeAlertRuleTag(name)"
>
<i
class=
"fa fa-trash"
></i>
</a>
</label>
</div>
<div
class=
"gf-form-group"
>
<div
class=
"gf-form-inline"
>
<div
class=
"gf-form"
>
<input
class=
"gf-form-input width-15"
placeholder=
"New tag name..."
ng-model=
"ctrl.newAlertRuleTag.name"
type=
"text"
>
<input
class=
"gf-form-input width-15"
placeholder=
"New tag value..."
ng-model=
"ctrl.newAlertRuleTag.value"
type=
"text"
>
</div>
</div>
<div
class=
"gf-form"
>
<label
class=
"gf-form-label"
>
<a
class=
"pointer"
tabindex=
"1"
ng-click=
"ctrl.addAlertRuleTag()"
>
<i
class=
"fa fa-plus"
></i>
Add Tag
</a>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment