From 6beb9be42cc0109f95e2ed56761f38d0b48d910c Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Mon, 26 Oct 2015 15:25:43 +0900 Subject: [PATCH 1/9] add CloudWatch Alarm API support --- pkg/api/cloudwatch/cloudwatch.go | 80 +++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index d5abe4958ea..7fdb722e72a 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -31,13 +31,15 @@ type cwRequest struct { func init() { actionHandlers = map[string]actionHandler{ - "GetMetricStatistics": handleGetMetricStatistics, - "ListMetrics": handleListMetrics, - "DescribeInstances": handleDescribeInstances, - "__GetRegions": handleGetRegions, - "__GetNamespaces": handleGetNamespaces, - "__GetMetrics": handleGetMetrics, - "__GetDimensions": handleGetDimensions, + "GetMetricStatistics": handleGetMetricStatistics, + "ListMetrics": handleListMetrics, + "DescribeAlarmsForMetric": handleDescribeAlarmsForMetric, + "DescribeAlarmHistory": handleDescribeAlarmHistory, + "DescribeInstances": handleDescribeInstances, + "__GetRegions": handleGetRegions, + "__GetNamespaces": handleGetNamespaces, + "__GetMetrics": handleGetMetrics, + "__GetDimensions": handleGetDimensions, } } @@ -137,6 +139,70 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { c.JSON(200, resp) } +func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { + svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)}) + reqParam := &struct { + Parameters struct { + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` + Dimensions []*cloudwatch.Dimension `json:"dimensions"` + Statistic string `json:"statistic"` + Period int64 `json:"period"` + } `json:"parameters"` + }{} + json.Unmarshal(req.Body, reqParam) + + params := &cloudwatch.DescribeAlarmsForMetricInput{ + Namespace: aws.String(reqParam.Parameters.Namespace), + MetricName: aws.String(reqParam.Parameters.MetricName), + Period: aws.Int64(reqParam.Parameters.Period), + } + if len(reqParam.Parameters.Dimensions) != 0 { + params.Dimensions = reqParam.Parameters.Dimensions + } + if reqParam.Parameters.Statistic != "" { + params.Statistic = aws.String(reqParam.Parameters.Statistic) + } + + resp, err := svc.DescribeAlarmsForMetric(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + c.JSON(200, resp) +} + +func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) { + svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)}) + reqParam := &struct { + Parameters struct { + AlarmName string `json:"alarmName"` + HistoryItemType string `json:"historyItemType"` + StartDate int64 `json:"startDate"` + EndDate int64 `json:"endDate"` + } `json:"parameters"` + }{} + json.Unmarshal(req.Body, reqParam) + + params := &cloudwatch.DescribeAlarmHistoryInput{ + AlarmName: aws.String(reqParam.Parameters.AlarmName), + StartDate: aws.Time(time.Unix(reqParam.Parameters.StartDate, 0)), + EndDate: aws.Time(time.Unix(reqParam.Parameters.EndDate, 0)), + } + if reqParam.Parameters.HistoryItemType != "" { + params.HistoryItemType = aws.String(reqParam.Parameters.HistoryItemType) + } + + resp, err := svc.DescribeAlarmHistory(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + c.JSON(200, resp) +} + func handleDescribeInstances(req *cwRequest, c *middleware.Context) { sess := session.New() creds := credentials.NewChainCredentials( From 1626982a3a2460bf9da94dfb32e87e17adb4b386 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Mon, 26 Oct 2015 15:47:34 +0900 Subject: [PATCH 2/9] add CloudWatch Annotation --- .../datasource/cloudwatch/datasource.js | 68 +++++++++++++++++++ .../datasource/cloudwatch/directives.js | 4 ++ .../partials/annotations.editor.html | 38 +++++++++++ .../plugins/datasource/cloudwatch/plugin.json | 3 +- .../cloudwatch/specs/datasource_specs.ts | 47 +++++++++++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index e01bdce6096..aeee1e59bf8 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -209,6 +209,74 @@ function (angular, _) { return $q.when([]); }; + CloudWatchDatasource.prototype.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) { + return this.awsRequest({ + region: region, + action: 'DescribeAlarmsForMetric', + parameters: { namespace: namespace, metricName: metricName, dimensions: dimensions, statistic: statistic, period: period } + }); + }; + + CloudWatchDatasource.prototype.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) { + return this.awsRequest({ + region: region, + action: 'DescribeAlarmHistory', + parameters: { alarmName: alarmName, startDate: startDate, endDate: endDate } + }); + }; + + CloudWatchDatasource.prototype.annotationQuery = function(annotation, range) { + var region = templateSrv.replace(annotation.region); + var namespace = templateSrv.replace(annotation.namespace); + var metricName = templateSrv.replace(annotation.metricName); + var dimensionPart = templateSrv.replace(annotation.dimensions); + var statistic = templateSrv.replace(annotation.statistic) || ''; + var period = annotation.period || '300'; + + if (!region || !namespace || !metricName) { return $q.when([]); } + + var dimensions = {}; + if (!_.isEmpty(dimensionPart)) { + _.each(dimensionPart.split(','), function(v) { + var t = v.split('='); + if (t.length !== 2) { + throw new Error('Invalid query format'); + } + dimensions[t[0]] = t[1]; + }); + dimensions = convertDimensionFormat(dimensions); + } + period = parseInt(period, 10); + + var d = $q.defer(); + var self = this; + this.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period).then(function(alarms) { + var eventList = []; + + var start = convertToCloudWatchTime(range.from); + var end = convertToCloudWatchTime(range.to); + _.each(alarms.MetricAlarms, function(alarm) { + self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) { + _.each(history.AlarmHistoryItems, function(h) { + var event = { + annotation: annotation, + time: Date.parse(h.Timestamp), + title: h.AlarmName, + tags: [h.HistoryItemType], + text: h.HistorySummary + }; + + eventList.push(event); + }); + + d.resolve(eventList); + }); + }); + }); + + return d.promise; + }; + CloudWatchDatasource.prototype.testDatasource = function() { /* use billing metrics for test */ var region = this.defaultRegion; diff --git a/public/app/plugins/datasource/cloudwatch/directives.js b/public/app/plugins/datasource/cloudwatch/directives.js index c758aa34e5c..22a98e036e4 100644 --- a/public/app/plugins/datasource/cloudwatch/directives.js +++ b/public/app/plugins/datasource/cloudwatch/directives.js @@ -10,4 +10,8 @@ function (angular) { return {controller: 'CloudWatchQueryCtrl', templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.editor.html'}; }); + module.directive('annotationsQueryEditorCloudwatch', function() { + return {templateUrl: 'app/plugins/datasource/cloudwatch/partials/annotations.editor.html'}; + }); + }); diff --git a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html new file mode 100644 index 00000000000..1db6eb704b3 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html @@ -0,0 +1,38 @@ +
+
+
Metric name
+
+ +
+
+
+ +
+
+
Field mappings
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
diff --git a/public/app/plugins/datasource/cloudwatch/plugin.json b/public/app/plugins/datasource/cloudwatch/plugin.json index 1fa46416ca6..f1cf0e5512c 100644 --- a/public/app/plugins/datasource/cloudwatch/plugin.json +++ b/public/app/plugins/datasource/cloudwatch/plugin.json @@ -12,5 +12,6 @@ "query": "app/plugins/datasource/cloudwatch/partials/query.editor.html" }, - "metrics": true + "metrics": true, + "annotations": true } diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index f3b105c5da5..fa7f23c46b8 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -2,6 +2,7 @@ /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; +import moment = require('moment'); declare var helpers: any; @@ -189,4 +190,50 @@ describe('CloudWatchDatasource', function() { }); }); + describe('When performing annotationQuery', function() { + var annotation = { + region: 'us-east-1', + namespace: 'AWS/EC2', + metricName: 'CPUUtilization', + dimensions: 'InstanceId=i-12345678' + }; + var alarmResponse = { + MetricAlarms: [ + { + AlarmName: 'test_alarm_name' + } + ] + }; + var historyResponse = { + AlarmHistoryItems: [ + { + Timestamp: '2015-01-01T00:00:00.000Z', + HistoryItemType: 'StateUpdate', + AlarmName: 'test_alarm_name', + HistoryData: '{}', + HistorySummary: 'test_history_summary' + } + ] + }; + beforeEach(function() { + ctx.backendSrv.datasourceRequest = function(params) { + switch (params.data.action) { + case 'DescribeAlarmsForMetric': + return ctx.$q.when({data: alarmResponse}); + break; + case 'DescribeAlarmHistory': + return ctx.$q.when({data: historyResponse}); + break; + } + }; + }); + it('should return annotation list', function(done) { + ctx.ds.annotationQuery(annotation, {from: moment(1443438674760), to: moment(1443460274760)}).then(function(result) { + expect(result[0].title).to.be('test_alarm_name'); + expect(result[0].text).to.be('test_history_summary'); + done(); + }); + ctx.$rootScope.$apply(); + }); + }); }); From 7d16307db46b5e10aab2138c9dd3b968bb0598ff Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 28 Oct 2015 12:14:06 +0900 Subject: [PATCH 3/9] parse string date format --- .../plugins/datasource/cloudwatch/datasource.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index aeee1e59bf8..646a3d84bd0 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -2,10 +2,11 @@ define([ 'angular', 'lodash', 'moment', + 'app/core/utils/datemath', './query_ctrl', './directives', ], -function (angular, _) { +function (angular, _, moment, dateMath) { 'use strict'; var module = angular.module('grafana.services'); @@ -21,8 +22,8 @@ function (angular, _) { } CloudWatchDatasource.prototype.query = function(options) { - var start = convertToCloudWatchTime(options.range.from); - var end = convertToCloudWatchTime(options.range.to); + var start = convertToCloudWatchTime(options.range.from, false); + var end = convertToCloudWatchTime(options.range.to, true); var queries = []; options = angular.copy(options); @@ -253,8 +254,8 @@ function (angular, _) { this.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period).then(function(alarms) { var eventList = []; - var start = convertToCloudWatchTime(range.from); - var end = convertToCloudWatchTime(range.to); + var start = convertToCloudWatchTime(range.from, false); + var end = convertToCloudWatchTime(range.to, true); _.each(alarms.MetricAlarms, function(alarm) { self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) { _.each(history.AlarmHistoryItems, function(h) { @@ -344,7 +345,10 @@ function (angular, _) { }); } - function convertToCloudWatchTime(date) { + function convertToCloudWatchTime(date, roundUp) { + if (_.isString(date)) { + date = dateMath.parse(date, roundUp); + } return Math.round(date.valueOf() / 1000); } From 79c85569270a44ab27fe744c0408b8393081835d Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 28 Oct 2015 22:40:45 +0900 Subject: [PATCH 4/9] style change annotation editor --- .../partials/annotations.editor.html | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html index 1db6eb704b3..ff545b7bf01 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html @@ -1,30 +1,33 @@
-
Metric name
- + + +
+ +
+ + +
+ +
+ +
-
Field mappings
-
- - -
- -
- - -
-
- +
+
+
+
+
@@ -32,7 +35,7 @@
- +
From e22e20fa9ddca87ecc08d91c1f60a0409eadbfc1 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Fri, 13 Nov 2015 22:09:00 +0900 Subject: [PATCH 5/9] fix cloudwatch annotation, reflect API change --- .../datasource/cloudwatch/datasource.js | 7 ++++--- .../cloudwatch/specs/datasource_specs.ts | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 646a3d84bd0..2b152131bc2 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -226,7 +226,8 @@ function (angular, _, moment, dateMath) { }); }; - CloudWatchDatasource.prototype.annotationQuery = function(annotation, range) { + CloudWatchDatasource.prototype.annotationQuery = function(options) { + var annotation = options.annotation; var region = templateSrv.replace(annotation.region); var namespace = templateSrv.replace(annotation.namespace); var metricName = templateSrv.replace(annotation.metricName); @@ -254,8 +255,8 @@ function (angular, _, moment, dateMath) { this.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period).then(function(alarms) { var eventList = []; - var start = convertToCloudWatchTime(range.from, false); - var end = convertToCloudWatchTime(range.to, true); + var start = convertToCloudWatchTime(options.range.from, false); + var end = convertToCloudWatchTime(options.range.to, true); _.each(alarms.MetricAlarms, function(alarm) { self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) { _.each(history.AlarmHistoryItems, function(h) { diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index fa7f23c46b8..eaacff9c0b8 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -191,11 +191,17 @@ describe('CloudWatchDatasource', function() { }); describe('When performing annotationQuery', function() { - var annotation = { - region: 'us-east-1', - namespace: 'AWS/EC2', - metricName: 'CPUUtilization', - dimensions: 'InstanceId=i-12345678' + var parameter = { + annotation: { + region: 'us-east-1', + namespace: 'AWS/EC2', + metricName: 'CPUUtilization', + dimensions: 'InstanceId=i-12345678' + }, + range: { + from: moment(1443438674760), + to: moment(1443460274760) + } }; var alarmResponse = { MetricAlarms: [ @@ -228,7 +234,7 @@ describe('CloudWatchDatasource', function() { }; }); it('should return annotation list', function(done) { - ctx.ds.annotationQuery(annotation, {from: moment(1443438674760), to: moment(1443460274760)}).then(function(result) { + ctx.ds.annotationQuery(parameter).then(function(result) { expect(result[0].title).to.be('test_alarm_name'); expect(result[0].text).to.be('test_history_summary'); done(); From e631940d9e1837d7f4ecf8188e8223b13990931a Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Thu, 19 Nov 2015 19:47:06 +0900 Subject: [PATCH 6/9] pkg/api/cloudwatch: fix api client construction against aws-sdk-go v0.10.2 --- pkg/api/cloudwatch/cloudwatch.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 7fdb722e72a..19f4aed3ba3 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -140,7 +140,20 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { } func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { - svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)}) + creds := credentials.NewChainCredentials( + []credentials.Provider{ + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, + &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + }) + + cfg := &aws.Config{ + Region: aws.String(req.Region), + Credentials: creds, + } + + svc := cloudwatch.New(session.New(cfg), cfg) + reqParam := &struct { Parameters struct { Namespace string `json:"namespace"` @@ -174,7 +187,20 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { } func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) { - svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)}) + creds := credentials.NewChainCredentials( + []credentials.Provider{ + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, + &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + }) + + cfg := &aws.Config{ + Region: aws.String(req.Region), + Credentials: creds, + } + + svc := cloudwatch.New(session.New(cfg), cfg) + reqParam := &struct { Parameters struct { AlarmName string `json:"alarmName"` From 6fa5e681aad62e20525eea95b44335fdeee0b1c3 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Thu, 26 Nov 2015 16:20:14 +0900 Subject: [PATCH 7/9] fix EC2RoleProvider parameter --- pkg/api/cloudwatch/cloudwatch.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 19f4aed3ba3..0995c88da4b 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -140,11 +140,12 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { } func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { + sess := session.New() creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, - &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) cfg := &aws.Config{ @@ -187,11 +188,12 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { } func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) { + sess := session.New() creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, - &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) cfg := &aws.Config{ From d302c82c82a6588b3dece3b97b2b0d808a8dc4e5 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Sun, 29 Nov 2015 22:25:36 +0900 Subject: [PATCH 8/9] share cloudwatch query editor --- .../datasource/cloudwatch/datasource.js | 35 ++-- .../datasource/cloudwatch/directives.js | 14 ++ .../partials/annotations.editor.html | 42 +--- .../cloudwatch/partials/query.editor.html | 47 +---- .../cloudwatch/partials/query.parameter.html | 53 +++++ .../datasource/cloudwatch/query_ctrl.js | 165 +-------------- .../cloudwatch/query_parameter_ctrl.js | 192 ++++++++++++++++++ 7 files changed, 279 insertions(+), 269 deletions(-) create mode 100644 public/app/plugins/datasource/cloudwatch/partials/query.parameter.html create mode 100644 public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 2b152131bc2..f606b6e3dc8 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -231,33 +231,32 @@ function (angular, _, moment, dateMath) { var region = templateSrv.replace(annotation.region); var namespace = templateSrv.replace(annotation.namespace); var metricName = templateSrv.replace(annotation.metricName); - var dimensionPart = templateSrv.replace(annotation.dimensions); - var statistic = templateSrv.replace(annotation.statistic) || ''; + var dimensions = convertDimensionFormat(annotation.dimensions); + var statistics = _.map(annotation.statistics, function(s) { return templateSrv.replace(s); }); var period = annotation.period || '300'; - - if (!region || !namespace || !metricName) { return $q.when([]); } - - var dimensions = {}; - if (!_.isEmpty(dimensionPart)) { - _.each(dimensionPart.split(','), function(v) { - var t = v.split('='); - if (t.length !== 2) { - throw new Error('Invalid query format'); - } - dimensions[t[0]] = t[1]; - }); - dimensions = convertDimensionFormat(dimensions); - } period = parseInt(period, 10); + if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return $q.when([]); } + var d = $q.defer(); var self = this; - this.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period).then(function(alarms) { + var allQueryPromise = _.map(statistics, function(statistic) { + return self.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period); + }); + $q.all(allQueryPromise).then(function(alarms) { var eventList = []; var start = convertToCloudWatchTime(options.range.from, false); var end = convertToCloudWatchTime(options.range.to, true); - _.each(alarms.MetricAlarms, function(alarm) { + _.chain(alarms) + .pluck('MetricAlarms') + .flatten() + .each(function(alarm) { + if (!alarm) { + d.resolve(eventList); + return; + } + self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) { _.each(history.AlarmHistoryItems, function(h) { var event = { diff --git a/public/app/plugins/datasource/cloudwatch/directives.js b/public/app/plugins/datasource/cloudwatch/directives.js index 22a98e036e4..a08d4fc8d9a 100644 --- a/public/app/plugins/datasource/cloudwatch/directives.js +++ b/public/app/plugins/datasource/cloudwatch/directives.js @@ -1,5 +1,6 @@ define([ 'angular', + './query_parameter_ctrl', ], function (angular) { 'use strict'; @@ -14,4 +15,17 @@ function (angular) { return {templateUrl: 'app/plugins/datasource/cloudwatch/partials/annotations.editor.html'}; }); + module.directive('cloudwatchQueryParameter', function() { + return { + templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.parameter.html', + controller: 'CloudWatchQueryParameterCtrl', + restrict: 'E', + scope: { + target: "=", + datasourceName: "@", + onChange: "&", + } + }; + }); + }); diff --git a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html index ff545b7bf01..dfecb80032d 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html @@ -1,41 +1 @@ -
-
-
- - -
- -
- - -
- -
- - -
-
-
- -
-
-
- - -
-
-
- -
-
-
- - -
- -
- - -
-
-
+ diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html index 0984bc521e2..cc2dfa3a601 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html @@ -33,61 +33,16 @@ - -
-
- -
- - -
-
- -
-
+ diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html b/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html new file mode 100644 index 00000000000..6e2dde5bf48 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html @@ -0,0 +1,53 @@ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ diff --git a/public/app/plugins/datasource/cloudwatch/query_ctrl.js b/public/app/plugins/datasource/cloudwatch/query_ctrl.js index d0f6fe5b52a..e24b73cd068 100644 --- a/public/app/plugins/datasource/cloudwatch/query_ctrl.js +++ b/public/app/plugins/datasource/cloudwatch/query_ctrl.js @@ -7,173 +7,10 @@ function (angular, _) { var module = angular.module('grafana.controllers'); - module.controller('CloudWatchQueryCtrl', function($scope, templateSrv, uiSegmentSrv, $q) { + module.controller('CloudWatchQueryCtrl', function($scope) { $scope.init = function() { - var target = $scope.target; - target.namespace = target.namespace || ''; - target.metricName = target.metricName || ''; - target.statistics = target.statistics || ['Average']; - target.dimensions = target.dimensions || {}; - target.period = target.period || ''; - target.region = target.region || $scope.datasource.getDefaultRegion(); - $scope.aliasSyntax = '{{metric}} {{stat}} {{namespace}} {{region}} {{}}'; - - $scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region'); - $scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace'); - $scope.metricSegment = uiSegmentSrv.getSegmentForValue($scope.target.metricName, 'select metric'); - - $scope.dimSegments = _.reduce($scope.target.dimensions, function(memo, value, key) { - memo.push(uiSegmentSrv.newKey(key)); - memo.push(uiSegmentSrv.newOperator("=")); - memo.push(uiSegmentSrv.newKeyValue(value)); - return memo; - }, []); - - $scope.statSegments = _.map($scope.target.statistics, function(stat) { - return uiSegmentSrv.getSegmentForValue(stat); - }); - - $scope.ensurePlusButton($scope.statSegments); - $scope.ensurePlusButton($scope.dimSegments); - $scope.removeDimSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove dimension --'}); - $scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'}); - }; - - $scope.getStatSegments = function() { - return $q.when([ - angular.copy($scope.removeStatSegment), - uiSegmentSrv.getSegmentForValue('Average'), - uiSegmentSrv.getSegmentForValue('Maximum'), - uiSegmentSrv.getSegmentForValue('Minimum'), - uiSegmentSrv.getSegmentForValue('Sum'), - uiSegmentSrv.getSegmentForValue('SampleCount'), - ]); - }; - - $scope.statSegmentChanged = function(segment, index) { - if (segment.value === $scope.removeStatSegment.value) { - $scope.statSegments.splice(index, 1); - } else { - segment.type = 'value'; - } - - $scope.target.statistics = _.reduce($scope.statSegments, function(memo, seg) { - if (!seg.fake) { memo.push(seg.value); } return memo; - }, []); - - $scope.ensurePlusButton($scope.statSegments); - $scope.get_data(); - }; - - $scope.ensurePlusButton = function(segments) { - var count = segments.length; - var lastSegment = segments[Math.max(count-1, 0)]; - - if (!lastSegment || lastSegment.type !== 'plus-button') { - segments.push(uiSegmentSrv.newPlusButton()); - } - }; - - $scope.getDimSegments = function(segment, $index) { - if (segment.type === 'operator') { return $q.when([]); } - - var target = $scope.target; - var query = $q.when([]); - - if (segment.type === 'key' || segment.type === 'plus-button') { - query = $scope.datasource.getDimensionKeys($scope.target.namespace); - } else if (segment.type === 'value') { - var dimensionKey = $scope.dimSegments[$index-2].value; - query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, dimensionKey, {}); - } - - return query.then($scope.transformToSegments(true)).then(function(results) { - if (segment.type === 'key') { - results.splice(0, 0, angular.copy($scope.removeDimSegment)); - } - return results; - }); - }; - - $scope.dimSegmentChanged = function(segment, index) { - $scope.dimSegments[index] = segment; - - if (segment.value === $scope.removeDimSegment.value) { - $scope.dimSegments.splice(index, 3); - } - else if (segment.type === 'plus-button') { - $scope.dimSegments.push(uiSegmentSrv.newOperator('=')); - $scope.dimSegments.push(uiSegmentSrv.newFake('select dimension value', 'value', 'query-segment-value')); - segment.type = 'key'; - segment.cssClass = 'query-segment-key'; - } - - $scope.syncDimSegmentsWithModel(); - $scope.ensurePlusButton($scope.dimSegments); - $scope.get_data(); - }; - - $scope.syncDimSegmentsWithModel = function() { - var dims = {}; - var length = $scope.dimSegments.length; - - for (var i = 0; i < length - 2; i += 3) { - var keySegment = $scope.dimSegments[i]; - var valueSegment = $scope.dimSegments[i + 2]; - if (!valueSegment.fake) { - dims[keySegment.value] = valueSegment.value; - } - } - - $scope.target.dimensions = dims; - }; - - $scope.getRegions = function() { - return $scope.datasource.metricFindQuery('regions()') - .then($scope.transformToSegments(true)); - }; - - $scope.getNamespaces = function() { - return $scope.datasource.metricFindQuery('namespaces()') - .then($scope.transformToSegments(true)); - }; - - $scope.getMetrics = function() { - return $scope.datasource.metricFindQuery('metrics(' + $scope.target.namespace + ')') - .then($scope.transformToSegments(true)); - }; - - $scope.regionChanged = function() { - $scope.target.region = $scope.regionSegment.value; - $scope.get_data(); - }; - - $scope.namespaceChanged = function() { - $scope.target.namespace = $scope.namespaceSegment.value; - $scope.get_data(); - }; - - $scope.metricChanged = function() { - $scope.target.metricName = $scope.metricSegment.value; - $scope.get_data(); - }; - - $scope.transformToSegments = function(addTemplateVars) { - return function(results) { - var segments = _.map(results, function(segment) { - return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); - }); - - if (addTemplateVars) { - _.each(templateSrv.variables, function(variable) { - segments.unshift(uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true })); - }); - } - - return segments; - }; }; $scope.refreshMetricData = function() { diff --git a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js new file mode 100644 index 00000000000..0ed23aff205 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js @@ -0,0 +1,192 @@ +define([ + 'angular', + 'lodash', +], +function (angular, _) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('CloudWatchQueryParameterCtrl', function($scope, templateSrv, uiSegmentSrv, datasourceSrv, $q) { + + $scope.init = function() { + var target = $scope.target; + target.namespace = target.namespace || ''; + target.metricName = target.metricName || ''; + target.statistics = target.statistics || ['Average']; + target.dimensions = target.dimensions || {}; + target.period = target.period || ''; + target.region = target.region || ''; + + $scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region'); + $scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace'); + $scope.metricSegment = uiSegmentSrv.getSegmentForValue($scope.target.metricName, 'select metric'); + + $scope.dimSegments = _.reduce($scope.target.dimensions, function(memo, value, key) { + memo.push(uiSegmentSrv.newKey(key)); + memo.push(uiSegmentSrv.newOperator("=")); + memo.push(uiSegmentSrv.newKeyValue(value)); + return memo; + }, []); + + $scope.statSegments = _.map($scope.target.statistics, function(stat) { + return uiSegmentSrv.getSegmentForValue(stat); + }); + + $scope.ensurePlusButton($scope.statSegments); + $scope.ensurePlusButton($scope.dimSegments); + $scope.removeDimSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove dimension --'}); + $scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'}); + + datasourceSrv.get($scope.datasourceName).then(function(datasource) { + $scope.datasource = datasource; + if (_.isEmpty($scope.target.region)) { + $scope.target.region = $scope.datasource.getDefaultRegion(); + } + }); + + if (!$scope.onChange) { + $scope.onChange = function() {}; + } + }; + + $scope.getStatSegments = function() { + return $q.when([ + angular.copy($scope.removeStatSegment), + uiSegmentSrv.getSegmentForValue('Average'), + uiSegmentSrv.getSegmentForValue('Maximum'), + uiSegmentSrv.getSegmentForValue('Minimum'), + uiSegmentSrv.getSegmentForValue('Sum'), + uiSegmentSrv.getSegmentForValue('SampleCount'), + ]); + }; + + $scope.statSegmentChanged = function(segment, index) { + if (segment.value === $scope.removeStatSegment.value) { + $scope.statSegments.splice(index, 1); + } else { + segment.type = 'value'; + } + + $scope.target.statistics = _.reduce($scope.statSegments, function(memo, seg) { + if (!seg.fake) { memo.push(seg.value); } return memo; + }, []); + + $scope.ensurePlusButton($scope.statSegments); + $scope.onChange(); + }; + + $scope.ensurePlusButton = function(segments) { + var count = segments.length; + var lastSegment = segments[Math.max(count-1, 0)]; + + if (!lastSegment || lastSegment.type !== 'plus-button') { + segments.push(uiSegmentSrv.newPlusButton()); + } + }; + + $scope.getDimSegments = function(segment, $index) { + if (segment.type === 'operator') { return $q.when([]); } + + var target = $scope.target; + var query = $q.when([]); + + if (segment.type === 'key' || segment.type === 'plus-button') { + query = $scope.datasource.getDimensionKeys($scope.target.namespace); + } else if (segment.type === 'value') { + var dimensionKey = $scope.dimSegments[$index-2].value; + query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, dimensionKey, {}); + } + + return query.then($scope.transformToSegments(true)).then(function(results) { + if (segment.type === 'key') { + results.splice(0, 0, angular.copy($scope.removeDimSegment)); + } + return results; + }); + }; + + $scope.dimSegmentChanged = function(segment, index) { + $scope.dimSegments[index] = segment; + + if (segment.value === $scope.removeDimSegment.value) { + $scope.dimSegments.splice(index, 3); + } + else if (segment.type === 'plus-button') { + $scope.dimSegments.push(uiSegmentSrv.newOperator('=')); + $scope.dimSegments.push(uiSegmentSrv.newFake('select dimension value', 'value', 'query-segment-value')); + segment.type = 'key'; + segment.cssClass = 'query-segment-key'; + } + + $scope.syncDimSegmentsWithModel(); + $scope.ensurePlusButton($scope.dimSegments); + $scope.onChange(); + }; + + $scope.syncDimSegmentsWithModel = function() { + var dims = {}; + var length = $scope.dimSegments.length; + + for (var i = 0; i < length - 2; i += 3) { + var keySegment = $scope.dimSegments[i]; + var valueSegment = $scope.dimSegments[i + 2]; + if (!valueSegment.fake) { + dims[keySegment.value] = valueSegment.value; + } + } + + $scope.target.dimensions = dims; + }; + + $scope.getRegions = function() { + return $scope.datasource.metricFindQuery('regions()') + .then($scope.transformToSegments(true)); + }; + + $scope.getNamespaces = function() { + return $scope.datasource.metricFindQuery('namespaces()') + .then($scope.transformToSegments(true)); + }; + + $scope.getMetrics = function() { + return $scope.datasource.metricFindQuery('metrics(' + $scope.target.namespace + ')') + .then($scope.transformToSegments(true)); + }; + + $scope.regionChanged = function() { + $scope.target.region = $scope.regionSegment.value; + $scope.onChange(); + }; + + $scope.namespaceChanged = function() { + $scope.target.namespace = $scope.namespaceSegment.value; + $scope.onChange(); + }; + + $scope.metricChanged = function() { + $scope.target.metricName = $scope.metricSegment.value; + $scope.onChange(); + }; + + $scope.transformToSegments = function(addTemplateVars) { + return function(results) { + var segments = _.map(results, function(segment) { + return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); + }); + + if (addTemplateVars) { + _.each(templateSrv.variables, function(variable) { + segments.unshift(uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true })); + }); + } + + return segments; + }; + }; + + $scope.init(); + + }); + +}); From 31de9faaf27fddd880b7e75d2d217761a9310d41 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Mon, 30 Nov 2015 01:32:29 +0900 Subject: [PATCH 9/9] fix test --- .../plugins/datasource/cloudwatch/specs/datasource_specs.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index eaacff9c0b8..58c6603d37c 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -196,7 +196,11 @@ describe('CloudWatchDatasource', function() { region: 'us-east-1', namespace: 'AWS/EC2', metricName: 'CPUUtilization', - dimensions: 'InstanceId=i-12345678' + dimensions: { + InstanceId: 'i-12345678' + }, + statistics: ['Average'], + period: 300 }, range: { from: moment(1443438674760),