Skip to content

Commit 2c05237

Browse files
committed
Merge branch 'master' of github.com:grafana/grafana
2 parents c045907 + bc8d277 commit 2c05237

File tree

13 files changed

+114
-71
lines changed

13 files changed

+114
-71
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
* **LDAP**: Basic Auth now supports LDAP username and password, [#6940](https://github.com/grafana/grafana/pull/6940), thx [@utkarshcmu](https://github.com/utkarshcmu)
1616
* **LDAP**: Now works with Auth Proxy, role and organisation mapping & sync will regularly be performed. [#6895](https://github.com/grafana/grafana/pull/6895), thx [@Seuf](https://github.com/seuf)
1717
* **Alerting**: Adds OK as no data option. [#6866](https://github.com/grafana/grafana/issues/6866)
18+
* **Alert list**: Order alerts based on state. [#6676](https://github.com/grafana/grafana/issues/6676)
1819

1920
### Bugfixes
2021
* **API**: HTTP API for deleting org returning incorrect message for a non-existing org [#6679](https://github.com/grafana/grafana/issues/6679)
2122
* **Dashboard**: Posting empty dashboard result in corrupted dashboard [#5443](https://github.com/grafana/grafana/issues/5443)
2223
* **Logging**: Fixed logging level confing issue [#6978](https://github.com/grafana/grafana/issues/6978)
24+
* **Notifications**: Remove html escaping the email subject. [#6905](https://github.com/grafana/grafana/issues/6905)
2325

2426
# 4.0.2 (2016-12-08)
2527

docs/sources/datasources/cloudwatch.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Name | Description
7171
------- | --------
7272
`regions()` | Returns a list of regions AWS provides their service.
7373
`namespaces()` | Returns a list of namespaces CloudWatch support.
74-
`metrics(namespace)` | Returns a list of metrics in the namespace.
74+
`metrics(namespace, [region])` | Returns a list of metrics in the namespace. (specify region for custom metrics)
7575
`dimension_keys(namespace)` | Returns a list of dimension keys in the namespace.
7676
`dimension_values(region, namespace, metric, dimension_key)` | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
7777
`ebs_volume_ids(region, instance_id)` | Returns a list of volume id matching the specified `region`, `instance_id`.

pkg/api/cloudwatch/cloudwatch.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/aws/aws-sdk-go/service/ec2"
1919
"github.com/aws/aws-sdk-go/service/sts"
2020
"github.com/grafana/grafana/pkg/log"
21+
"github.com/grafana/grafana/pkg/metrics"
2122
"github.com/grafana/grafana/pkg/middleware"
2223
m "github.com/grafana/grafana/pkg/models"
2324
)
@@ -194,14 +195,12 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
194195
json.Unmarshal(req.Body, reqParam)
195196

196197
params := &cloudwatch.GetMetricStatisticsInput{
197-
Namespace: aws.String(reqParam.Parameters.Namespace),
198-
MetricName: aws.String(reqParam.Parameters.MetricName),
199-
Dimensions: reqParam.Parameters.Dimensions,
200-
Statistics: reqParam.Parameters.Statistics,
201-
ExtendedStatistics: reqParam.Parameters.ExtendedStatistics,
202-
StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
203-
EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
204-
Period: aws.Int64(reqParam.Parameters.Period),
198+
Namespace: aws.String(reqParam.Parameters.Namespace),
199+
MetricName: aws.String(reqParam.Parameters.MetricName),
200+
Dimensions: reqParam.Parameters.Dimensions,
201+
StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
202+
EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
203+
Period: aws.Int64(reqParam.Parameters.Period),
205204
}
206205
if len(reqParam.Parameters.Statistics) != 0 {
207206
params.Statistics = reqParam.Parameters.Statistics
@@ -215,6 +214,7 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
215214
c.JsonApiErr(500, "Unable to call AWS API", err)
216215
return
217216
}
217+
metrics.M_Aws_CloudWatch_GetMetricStatistics.Inc(1)
218218

219219
c.JSON(200, resp)
220220
}
@@ -241,6 +241,7 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
241241
var resp cloudwatch.ListMetricsOutput
242242
err := svc.ListMetricsPages(params,
243243
func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
244+
metrics.M_Aws_CloudWatch_ListMetrics.Inc(1)
244245
metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
245246
for _, metric := range metrics {
246247
resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric))

pkg/api/cloudwatch/metrics.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/aws/aws-sdk-go/aws/awsutil"
1212
"github.com/aws/aws-sdk-go/aws/session"
1313
"github.com/aws/aws-sdk-go/service/cloudwatch"
14+
"github.com/grafana/grafana/pkg/metrics"
1415
"github.com/grafana/grafana/pkg/middleware"
1516
"github.com/grafana/grafana/pkg/util"
1617
)
@@ -261,6 +262,7 @@ func getAllMetrics(cwData *datasourceInfo) (cloudwatch.ListMetricsOutput, error)
261262
var resp cloudwatch.ListMetricsOutput
262263
err := svc.ListMetricsPages(params,
263264
func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
265+
metrics.M_Aws_CloudWatch_ListMetrics.Inc(1)
264266
metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
265267
for _, metric := range metrics {
266268
resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric))

pkg/metrics/metrics.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ var (
4747
M_Alerting_Notification_Sent_PagerDuty Counter
4848
M_Alerting_Notification_Sent_Victorops Counter
4949
M_Alerting_Notification_Sent_OpsGenie Counter
50+
M_Aws_CloudWatch_GetMetricStatistics Counter
51+
M_Aws_CloudWatch_ListMetrics Counter
5052

5153
// Timers
5254
M_DataSource_ProxyReq_Timer Timer
@@ -113,6 +115,9 @@ func initMetricVars(settings *MetricSettings) {
113115
M_Alerting_Notification_Sent_Victorops = RegCounter("alerting.notifications_sent", "type", "victorops")
114116
M_Alerting_Notification_Sent_OpsGenie = RegCounter("alerting.notifications_sent", "type", "opsgenie")
115117

118+
M_Aws_CloudWatch_GetMetricStatistics = RegCounter("aws.cloudwatch.get_metric_statistics")
119+
M_Aws_CloudWatch_ListMetrics = RegCounter("aws.cloudwatch.list_metrics")
120+
116121
// Timers
117122
M_DataSource_ProxyReq_Timer = RegTimer("api.dataproxy.request.all")
118123
M_Alerting_Execution_Time = RegTimer("alerting.execution_time")

pkg/models/notifications.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var ErrInvalidEmailCode = errors.New("Invalid or expired email code")
77
type SendEmailCommand struct {
88
To []string
99
Template string
10+
Subject string
1011
Data map[string]interface{}
1112
Info string
1213
EmbededFiles []string

pkg/services/alerting/notifiers/email.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
5757

5858
cmd := &m.SendEmailCommandSync{
5959
SendEmailCommand: m.SendEmailCommand{
60+
Subject: evalContext.GetNotificationTitle(),
6061
Data: map[string]interface{}{
6162
"Title": evalContext.GetNotificationTitle(),
6263
"State": evalContext.Rule.State,

pkg/services/notifications/mailer.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) {
111111

112112
var buffer bytes.Buffer
113113
var err error
114-
var subjectText interface{}
115114

116115
data := cmd.Data
117116
if data == nil {
@@ -124,28 +123,34 @@ func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) {
124123
return nil, err
125124
}
126125

127-
subjectData := data["Subject"].(map[string]interface{})
128-
subjectText, hasSubject := subjectData["value"]
126+
subject := cmd.Subject
127+
if cmd.Subject == "" {
128+
var subjectText interface{}
129+
subjectData := data["Subject"].(map[string]interface{})
130+
subjectText, hasSubject := subjectData["value"]
129131

130-
if !hasSubject {
131-
return nil, errors.New(fmt.Sprintf("Missing subject in Template %s", cmd.Template))
132-
}
132+
if !hasSubject {
133+
return nil, errors.New(fmt.Sprintf("Missing subject in Template %s", cmd.Template))
134+
}
133135

134-
subjectTmpl, err := template.New("subject").Parse(subjectText.(string))
135-
if err != nil {
136-
return nil, err
137-
}
136+
subjectTmpl, err := template.New("subject").Parse(subjectText.(string))
137+
if err != nil {
138+
return nil, err
139+
}
138140

139-
var subjectBuffer bytes.Buffer
140-
err = subjectTmpl.ExecuteTemplate(&subjectBuffer, "subject", data)
141-
if err != nil {
142-
return nil, err
141+
var subjectBuffer bytes.Buffer
142+
err = subjectTmpl.ExecuteTemplate(&subjectBuffer, "subject", data)
143+
if err != nil {
144+
return nil, err
145+
}
146+
147+
subject = subjectBuffer.String()
143148
}
144149

145150
return &Message{
146151
To: cmd.To,
147152
From: setting.Smtp.FromAddress,
148-
Subject: subjectBuffer.String(),
153+
Subject: subject,
149154
Body: buffer.String(),
150155
EmbededFiles: cmd.EmbededFiles,
151156
}, nil

pkg/services/notifications/notifications.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func sendEmailCommandHandlerSync(ctx context.Context, cmd *m.SendEmailCommandSyn
8080
Template: cmd.Template,
8181
To: cmd.To,
8282
EmbededFiles: cmd.EmbededFiles,
83+
Subject: cmd.Subject,
8384
})
8485

8586
if err != nil {

pkg/tsdb/mqe/types.go

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -42,63 +42,54 @@ func (q *Query) Build(availableSeries []string) ([]QueryToSend, error) {
4242
where := q.buildWhereClause()
4343
functions := q.buildFunctionList()
4444

45-
for _, v := range q.Metrics {
46-
if !containsWildcardPattern.Match([]byte(v.Metric)) {
47-
alias := ""
48-
if v.Alias != "" {
49-
alias = fmt.Sprintf(" {%s}", v.Alias)
50-
}
45+
for _, metric := range q.Metrics {
46+
alias := ""
47+
if metric.Alias != "" {
48+
alias = fmt.Sprintf(" {%s}", metric.Alias)
49+
}
5150

52-
rawQuery := fmt.Sprintf(
53-
"`%s`%s%s %s from %v to %v",
54-
v.Metric,
55-
functions,
56-
alias,
57-
where,
58-
q.TimeRange.GetFromAsMsEpoch(),
59-
q.TimeRange.GetToAsMsEpoch())
51+
if !containsWildcardPattern.Match([]byte(metric.Metric)) {
52+
rawQuery := q.renderQuerystring(metric.Metric, functions, alias, where, q.TimeRange)
6053
queriesToSend = append(queriesToSend, QueryToSend{
6154
RawQuery: rawQuery,
6255
QueryRef: q,
6356
})
64-
continue
65-
}
57+
} else {
58+
m := strings.Replace(metric.Metric, "*", ".*", -1)
59+
mp, err := regexp.Compile(m)
6660

67-
m := strings.Replace(v.Metric, "*", ".*", -1)
68-
mp, err := regexp.Compile(m)
69-
70-
if err != nil {
71-
log.Error2("failed to compile regex for ", "metric", m)
72-
continue
73-
}
61+
if err != nil {
62+
log.Error2("failed to compile regex for ", "metric", m)
63+
continue
64+
}
7465

75-
//TODO: this lookup should be cached
76-
for _, a := range availableSeries {
77-
if mp.Match([]byte(a)) {
78-
alias := ""
79-
if v.Alias != "" {
80-
alias = fmt.Sprintf(" {%s}", v.Alias)
66+
//TODO: this lookup should be cached
67+
for _, wildcardMatch := range availableSeries {
68+
if mp.Match([]byte(wildcardMatch)) {
69+
rawQuery := q.renderQuerystring(wildcardMatch, functions, alias, where, q.TimeRange)
70+
queriesToSend = append(queriesToSend, QueryToSend{
71+
RawQuery: rawQuery,
72+
QueryRef: q,
73+
})
8174
}
82-
83-
rawQuery := fmt.Sprintf(
84-
"`%s`%s%s %s from %v to %v",
85-
a,
86-
functions,
87-
alias,
88-
where,
89-
q.TimeRange.GetFromAsMsEpoch(),
90-
q.TimeRange.GetToAsMsEpoch())
91-
92-
queriesToSend = append(queriesToSend, QueryToSend{
93-
RawQuery: rawQuery,
94-
QueryRef: q,
95-
})
9675
}
9776
}
9877
}
78+
9979
return queriesToSend, nil
10080
}
10181

82+
func (q *Query) renderQuerystring(path, functions, alias, where string, timerange *tsdb.TimeRange) string {
83+
return fmt.Sprintf(
84+
"`%s`%s%s %s from %v to %v",
85+
path,
86+
functions,
87+
alias,
88+
where,
89+
q.TimeRange.GetFromAsMsEpoch(),
90+
q.TimeRange.GetToAsMsEpoch())
91+
}
92+
10293
func (q *Query) buildFunctionList() string {
10394
functions := ""
10495
for _, v := range q.FunctionList {

public/app/features/alerting/alert_def.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ var conditionTypes = [
2020
{text: 'Query', value: 'query'},
2121
];
2222

23+
var alertStateSortScore = {
24+
alerting: 1,
25+
no_data: 2,
26+
pending: 3,
27+
ok: 4,
28+
paused: 5,
29+
};
30+
2331
var evalFunctions = [
2432
{text: 'IS ABOVE', value: 'gt'},
2533
{text: 'IS BELOW', value: 'lt'},
@@ -129,4 +137,5 @@ export default {
129137
reducerTypes: reducerTypes,
130138
createReducerPart: createReducerPart,
131139
joinEvalMatches: joinEvalMatches,
140+
alertStateSortScore: alertStateSortScore,
132141
};

public/app/plugins/panel/alertlist/editor.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ <h5 class="section-heading">Options</h5>
1111
<span class="gf-form-label width-8">Max items</span>
1212
<input type="text" class="gf-form-input max-width-15" ng-model="ctrl.panel.limit" ng-change="ctrl.onRender()" />
1313
</div>
14+
<div class="gf-form" ng-show="ctrl.panel.show === 'current'">
15+
<span class="gf-form-label width-8">Sort order</span>
16+
<div class="gf-form-select-wrapper max-width-15">
17+
<select class="gf-form-input" ng-model="ctrl.panel.sortOrder" ng-options="f.value as f.text for f in ctrl.sortOrderOptions" ng-change="ctrl.onRender()"></select>
18+
</div>
19+
</div>
1420
<gf-form-switch class="gf-form" label="Alerts from this dashboard" label-class="width-18" checked="ctrl.panel.onlyAlertsOnDashboard" on-change="ctrl.updateStateFilter()"></gf-form-switch>
1521
</div>
1622
<div class="section gf-form-group">

public/app/plugins/panel/alertlist/module.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ class AlertListPanel extends PanelCtrl {
1717
{text: 'Recent state changes', value: 'changes'}
1818
];
1919

20+
sortOrderOptions = [
21+
{text: 'Alphabetical (asc)', value: 1},
22+
{text: 'Alphabetical (desc)', value: 2},
23+
{text: 'Importance', value: 3},
24+
];
25+
2026
contentHeight: string;
2127
stateFilter: any = {};
2228
currentAlerts: any = [];
@@ -26,10 +32,10 @@ class AlertListPanel extends PanelCtrl {
2632
show: 'current',
2733
limit: 10,
2834
stateFilter: [],
29-
onlyAlertsOnDashboard: false
35+
onlyAlertsOnDashboard: false,
36+
sortOrder: 1
3037
};
3138

32-
3339
/** @ngInject */
3440
constructor($scope, $injector, private $location, private backendSrv, private timeSrv, private templateSrv) {
3541
super($scope, $injector);
@@ -44,6 +50,19 @@ class AlertListPanel extends PanelCtrl {
4450
}
4551
}
4652

53+
sortResult(alerts) {
54+
if (this.panel.sortOrder === 3) {
55+
return _.sortBy(alerts, a => { return alertDef.alertStateSortScore[a.state]; });
56+
}
57+
58+
var result = _.sortBy(alerts, a => { return a.name.toLowerCase();});
59+
if (this.panel.sortOrder === 2) {
60+
result.reverse();
61+
}
62+
63+
return result;
64+
}
65+
4766
updateStateFilter() {
4867
var result = [];
4968

@@ -104,11 +123,11 @@ class AlertListPanel extends PanelCtrl {
104123

105124
this.backendSrv.get(`/api/alerts`, params)
106125
.then(res => {
107-
this.currentAlerts = _.map(res, al => {
126+
this.currentAlerts = this.sortResult(_.map(res, al => {
108127
al.stateModel = alertDef.getStateDisplayModel(al.state);
109128
al.newStateDateAgo = moment(al.newStateDate).fromNow().replace(" ago", "");
110129
return al;
111-
});
130+
}));
112131
});
113132
}
114133

0 commit comments

Comments
 (0)