diff --git a/CHANGELOG.md b/CHANGELOG.md index db342a40bb9..edbbde5386e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,13 @@ * **LDAP**: Basic Auth now supports LDAP username and password, [#6940](https://github.com/grafana/grafana/pull/6940), thx [@utkarshcmu](https://github.com/utkarshcmu) * **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) * **Alerting**: Adds OK as no data option. [#6866](https://github.com/grafana/grafana/issues/6866) +* **Alert list**: Order alerts based on state. [#6676](https://github.com/grafana/grafana/issues/6676) ### Bugfixes * **API**: HTTP API for deleting org returning incorrect message for a non-existing org [#6679](https://github.com/grafana/grafana/issues/6679) * **Dashboard**: Posting empty dashboard result in corrupted dashboard [#5443](https://github.com/grafana/grafana/issues/5443) * **Logging**: Fixed logging level confing issue [#6978](https://github.com/grafana/grafana/issues/6978) +* **Notifications**: Remove html escaping the email subject. [#6905](https://github.com/grafana/grafana/issues/6905) # 4.0.2 (2016-12-08) diff --git a/docs/sources/datasources/cloudwatch.md b/docs/sources/datasources/cloudwatch.md index 4a25a2b475b..b4ad0b3288e 100644 --- a/docs/sources/datasources/cloudwatch.md +++ b/docs/sources/datasources/cloudwatch.md @@ -71,7 +71,7 @@ Name | Description ------- | -------- `regions()` | Returns a list of regions AWS provides their service. `namespaces()` | Returns a list of namespaces CloudWatch support. -`metrics(namespace)` | Returns a list of metrics in the namespace. +`metrics(namespace, [region])` | Returns a list of metrics in the namespace. (specify region for custom metrics) `dimension_keys(namespace)` | Returns a list of dimension keys in the namespace. `dimension_values(region, namespace, metric, dimension_key)` | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`. `ebs_volume_ids(region, instance_id)` | Returns a list of volume id matching the specified `region`, `instance_id`. diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 06c4b171d22..0fb7b09f3cb 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -18,6 +18,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/sts" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" ) @@ -194,14 +195,12 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { json.Unmarshal(req.Body, reqParam) params := &cloudwatch.GetMetricStatisticsInput{ - Namespace: aws.String(reqParam.Parameters.Namespace), - MetricName: aws.String(reqParam.Parameters.MetricName), - Dimensions: reqParam.Parameters.Dimensions, - Statistics: reqParam.Parameters.Statistics, - ExtendedStatistics: reqParam.Parameters.ExtendedStatistics, - StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)), - EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)), - Period: aws.Int64(reqParam.Parameters.Period), + Namespace: aws.String(reqParam.Parameters.Namespace), + MetricName: aws.String(reqParam.Parameters.MetricName), + Dimensions: reqParam.Parameters.Dimensions, + StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)), + EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)), + Period: aws.Int64(reqParam.Parameters.Period), } if len(reqParam.Parameters.Statistics) != 0 { params.Statistics = reqParam.Parameters.Statistics @@ -215,6 +214,7 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { c.JsonApiErr(500, "Unable to call AWS API", err) return } + metrics.M_Aws_CloudWatch_GetMetricStatistics.Inc(1) c.JSON(200, resp) } @@ -241,6 +241,7 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { var resp cloudwatch.ListMetricsOutput err := svc.ListMetricsPages(params, func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool { + metrics.M_Aws_CloudWatch_ListMetrics.Inc(1) metrics, _ := awsutil.ValuesAtPath(page, "Metrics") for _, metric := range metrics { resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric)) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 1880c6375b0..9a39a29a760 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/util" ) @@ -261,6 +262,7 @@ func getAllMetrics(cwData *datasourceInfo) (cloudwatch.ListMetricsOutput, error) var resp cloudwatch.ListMetricsOutput err := svc.ListMetricsPages(params, func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool { + metrics.M_Aws_CloudWatch_ListMetrics.Inc(1) metrics, _ := awsutil.ValuesAtPath(page, "Metrics") for _, metric := range metrics { resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric)) diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index f408aecba96..0d9b6b7bf9d 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -47,6 +47,8 @@ var ( M_Alerting_Notification_Sent_PagerDuty Counter M_Alerting_Notification_Sent_Victorops Counter M_Alerting_Notification_Sent_OpsGenie Counter + M_Aws_CloudWatch_GetMetricStatistics Counter + M_Aws_CloudWatch_ListMetrics Counter // Timers M_DataSource_ProxyReq_Timer Timer @@ -113,6 +115,9 @@ func initMetricVars(settings *MetricSettings) { M_Alerting_Notification_Sent_Victorops = RegCounter("alerting.notifications_sent", "type", "victorops") M_Alerting_Notification_Sent_OpsGenie = RegCounter("alerting.notifications_sent", "type", "opsgenie") + M_Aws_CloudWatch_GetMetricStatistics = RegCounter("aws.cloudwatch.get_metric_statistics") + M_Aws_CloudWatch_ListMetrics = RegCounter("aws.cloudwatch.list_metrics") + // Timers M_DataSource_ProxyReq_Timer = RegTimer("api.dataproxy.request.all") M_Alerting_Execution_Time = RegTimer("alerting.execution_time") diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go index c43024da691..42ebda9ea44 100644 --- a/pkg/models/notifications.go +++ b/pkg/models/notifications.go @@ -7,6 +7,7 @@ var ErrInvalidEmailCode = errors.New("Invalid or expired email code") type SendEmailCommand struct { To []string Template string + Subject string Data map[string]interface{} Info string EmbededFiles []string diff --git a/pkg/services/alerting/notifiers/email.go b/pkg/services/alerting/notifiers/email.go index bb01ba8b6e1..8cd4273e6be 100644 --- a/pkg/services/alerting/notifiers/email.go +++ b/pkg/services/alerting/notifiers/email.go @@ -57,6 +57,7 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { cmd := &m.SendEmailCommandSync{ SendEmailCommand: m.SendEmailCommand{ + Subject: evalContext.GetNotificationTitle(), Data: map[string]interface{}{ "Title": evalContext.GetNotificationTitle(), "State": evalContext.Rule.State, diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index c2d2cd5beca..97bf75fdcb9 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -111,7 +111,6 @@ func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) { var buffer bytes.Buffer var err error - var subjectText interface{} data := cmd.Data if data == nil { @@ -124,28 +123,34 @@ func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) { return nil, err } - subjectData := data["Subject"].(map[string]interface{}) - subjectText, hasSubject := subjectData["value"] + subject := cmd.Subject + if cmd.Subject == "" { + var subjectText interface{} + subjectData := data["Subject"].(map[string]interface{}) + subjectText, hasSubject := subjectData["value"] - if !hasSubject { - return nil, errors.New(fmt.Sprintf("Missing subject in Template %s", cmd.Template)) - } + if !hasSubject { + return nil, errors.New(fmt.Sprintf("Missing subject in Template %s", cmd.Template)) + } - subjectTmpl, err := template.New("subject").Parse(subjectText.(string)) - if err != nil { - return nil, err - } + subjectTmpl, err := template.New("subject").Parse(subjectText.(string)) + if err != nil { + return nil, err + } - var subjectBuffer bytes.Buffer - err = subjectTmpl.ExecuteTemplate(&subjectBuffer, "subject", data) - if err != nil { - return nil, err + var subjectBuffer bytes.Buffer + err = subjectTmpl.ExecuteTemplate(&subjectBuffer, "subject", data) + if err != nil { + return nil, err + } + + subject = subjectBuffer.String() } return &Message{ To: cmd.To, From: setting.Smtp.FromAddress, - Subject: subjectBuffer.String(), + Subject: subject, Body: buffer.String(), EmbededFiles: cmd.EmbededFiles, }, nil diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go index 85bda1643d1..c1ed9ac9e74 100644 --- a/pkg/services/notifications/notifications.go +++ b/pkg/services/notifications/notifications.go @@ -80,6 +80,7 @@ func sendEmailCommandHandlerSync(ctx context.Context, cmd *m.SendEmailCommandSyn Template: cmd.Template, To: cmd.To, EmbededFiles: cmd.EmbededFiles, + Subject: cmd.Subject, }) if err != nil { diff --git a/pkg/tsdb/mqe/types.go b/pkg/tsdb/mqe/types.go index 4365114c147..4fa2c1d4e7f 100644 --- a/pkg/tsdb/mqe/types.go +++ b/pkg/tsdb/mqe/types.go @@ -42,63 +42,54 @@ func (q *Query) Build(availableSeries []string) ([]QueryToSend, error) { where := q.buildWhereClause() functions := q.buildFunctionList() - for _, v := range q.Metrics { - if !containsWildcardPattern.Match([]byte(v.Metric)) { - alias := "" - if v.Alias != "" { - alias = fmt.Sprintf(" {%s}", v.Alias) - } + for _, metric := range q.Metrics { + alias := "" + if metric.Alias != "" { + alias = fmt.Sprintf(" {%s}", metric.Alias) + } - rawQuery := fmt.Sprintf( - "`%s`%s%s %s from %v to %v", - v.Metric, - functions, - alias, - where, - q.TimeRange.GetFromAsMsEpoch(), - q.TimeRange.GetToAsMsEpoch()) + if !containsWildcardPattern.Match([]byte(metric.Metric)) { + rawQuery := q.renderQuerystring(metric.Metric, functions, alias, where, q.TimeRange) queriesToSend = append(queriesToSend, QueryToSend{ RawQuery: rawQuery, QueryRef: q, }) - continue - } + } else { + m := strings.Replace(metric.Metric, "*", ".*", -1) + mp, err := regexp.Compile(m) - m := strings.Replace(v.Metric, "*", ".*", -1) - mp, err := regexp.Compile(m) + if err != nil { + log.Error2("failed to compile regex for ", "metric", m) + continue + } - if err != nil { - log.Error2("failed to compile regex for ", "metric", m) - continue - } - - //TODO: this lookup should be cached - for _, a := range availableSeries { - if mp.Match([]byte(a)) { - alias := "" - if v.Alias != "" { - alias = fmt.Sprintf(" {%s}", v.Alias) + //TODO: this lookup should be cached + for _, wildcardMatch := range availableSeries { + if mp.Match([]byte(wildcardMatch)) { + rawQuery := q.renderQuerystring(wildcardMatch, functions, alias, where, q.TimeRange) + queriesToSend = append(queriesToSend, QueryToSend{ + RawQuery: rawQuery, + QueryRef: q, + }) } - - rawQuery := fmt.Sprintf( - "`%s`%s%s %s from %v to %v", - a, - functions, - alias, - where, - q.TimeRange.GetFromAsMsEpoch(), - q.TimeRange.GetToAsMsEpoch()) - - queriesToSend = append(queriesToSend, QueryToSend{ - RawQuery: rawQuery, - QueryRef: q, - }) } } } + return queriesToSend, nil } +func (q *Query) renderQuerystring(path, functions, alias, where string, timerange *tsdb.TimeRange) string { + return fmt.Sprintf( + "`%s`%s%s %s from %v to %v", + path, + functions, + alias, + where, + q.TimeRange.GetFromAsMsEpoch(), + q.TimeRange.GetToAsMsEpoch()) +} + func (q *Query) buildFunctionList() string { functions := "" for _, v := range q.FunctionList { diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/alert_def.ts index fd08823c4a9..ce6ae23be5b 100644 --- a/public/app/features/alerting/alert_def.ts +++ b/public/app/features/alerting/alert_def.ts @@ -20,6 +20,14 @@ var conditionTypes = [ {text: 'Query', value: 'query'}, ]; +var alertStateSortScore = { + alerting: 1, + no_data: 2, + pending: 3, + ok: 4, + paused: 5, +}; + var evalFunctions = [ {text: 'IS ABOVE', value: 'gt'}, {text: 'IS BELOW', value: 'lt'}, @@ -129,4 +137,5 @@ export default { reducerTypes: reducerTypes, createReducerPart: createReducerPart, joinEvalMatches: joinEvalMatches, + alertStateSortScore: alertStateSortScore, }; diff --git a/public/app/plugins/panel/alertlist/editor.html b/public/app/plugins/panel/alertlist/editor.html index 344af7408fc..b8cee3b3317 100644 --- a/public/app/plugins/panel/alertlist/editor.html +++ b/public/app/plugins/panel/alertlist/editor.html @@ -11,6 +11,12 @@ Max items +