diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index e42a2deedf2..3af191a7af7 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -1,11 +1,10 @@ package api import ( - "errors" - "fmt" "strconv" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" @@ -45,7 +44,8 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro meta, exists := plugins.DataSources[ds.Type] if !exists { - return nil, errors.New(fmt.Sprintf("Could not find plugin definition for data source: %v", ds.Type)) + log.Error(3, "Could not find plugin definition for data source: %v", ds.Type) + continue } dsMap["meta"] = meta diff --git a/src/app/plugins/datasource/kairosdb/datasource.js b/src/app/plugins/datasource/kairosdb/datasource.js new file mode 100644 index 00000000000..e055c118956 --- /dev/null +++ b/src/app/plugins/datasource/kairosdb/datasource.js @@ -0,0 +1,420 @@ +define([ + 'angular', + 'lodash', + 'kbn', + './queryCtrl', +], +function (angular, _, kbn) { + 'use strict'; + + var module = angular.module('grafana.services'); + var tagList = null; + + module.factory('KairosDBDatasource', function($q, $http) { + + function KairosDBDatasource(datasource) { + this.type = datasource.type; + this.editorSrc = 'plugins/datasources/kairosdb/kairosdb.editor.html'; + this.url = datasource.url; + this.name = datasource.name; + this.supportMetrics = true; + this.grafanaDB = datasource.grafanaDB; + } + + // Called once per panel (graph) + KairosDBDatasource.prototype.query = function(options) { + var start = options.range.from; + var end = options.range.to; + + var queries = _.compact(_.map(options.targets, _.partial(convertTargetToQuery, options))); + var plotParams = _.compact(_.map(options.targets, function(target){ + var alias = target.alias; + if (typeof target.alias == 'undefined' || target.alias == "") + alias = target.metric; + return !target.hide + ? {alias: alias, + exouter: target.exOuter} + : null; + })); + var handleKairosDBQueryResponseAlias = _.partial(handleKairosDBQueryResponse, plotParams); + // No valid targets, return the empty result to save a round trip. + if (_.isEmpty(queries)) { + var d = $q.defer(); + d.resolve({ data: [] }); + return d.promise; + } + return this.performTimeSeriesQuery(queries, start, end).then(handleKairosDBQueryResponseAlias,handleQueryError); + }; + + /////////////////////////////////////////////////////////////////////// + /// Query methods + /////////////////////////////////////////////////////////////////////// + + KairosDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) { + var reqBody = { + metrics: queries + }; + reqBody.cache_time=0; + convertToKairosTime(start,reqBody,'start'); + convertToKairosTime(end,reqBody,'end'); + var options = { + method: 'POST', + url: '/api/v1/datapoints/query', + data: reqBody + }; + + options.url = this.url + options.url; + return $http(options); + }; + + /** + * Gets the list of metrics + * @returns {*|Promise} + */ + KairosDBDatasource.prototype.performMetricSuggestQuery = function() { + var options = { + url : this.url + '/api/v1/metricnames', + method : 'GET' + }; + return $http(options).then(function(results) { + if (!results.data) { + return []; + } + return results.data.results; + }); + + }; + + KairosDBDatasource.prototype.performTagSuggestQuery = function(metricname,range,type,keyValue) { + if(tagList && (metricname === tagList.metricName) && (range.from === tagList.range.from) && + (range.to === tagList.range.to)) { + return getTagListFromResponse(tagList.results,type,keyValue); + } + tagList = { + metricName:metricname, + range:range + }; + var body = { + metrics : [{name : metricname}] + }; + convertToKairosTime(range.from,body,'start'); + convertToKairosTime(range.to,body,'end'); + var options = { + url : this.url + '/api/v1/datapoints/query/tags', + method : 'POST', + data : body + }; + return $http(options).then(function(results) { + tagList.results = results; + return getTagListFromResponse(results,type,keyValue); + }); + + }; + + ///////////////////////////////////////////////////////////////////////// + /// Formatting methods + //////////////////////////////////////////////////////////////////////// + + function getTagListFromResponse(results,type,keyValue) { + if (!results.data) { + return []; + } + if(type==="key") { + return _.keys(results.data.queries[0].results[0].tags); + } + else if(type==="value" && _.has(results.data.queries[0].results[0].tags,keyValue)) { + return results.data.queries[0].results[0].tags[keyValue]; + } + return []; + } + + /** + * Requires a verion of KairosDB with every CORS defects fixed + * @param results + * @returns {*} + */ + function handleQueryError(results) { + if(results.data.errors && !_.isEmpty(results.data.errors)) { + var errors = { + message: results.data.errors[0] + }; + return $q.reject(errors); + } + else{ + return $q.reject(results); + } + } + + function handleKairosDBQueryResponse(plotParams, results) { + var output = []; + var index = 0; + _.each(results.data.queries, function (series) { + var sample_size = series.sample_size; + console.log("sample_size:" + sample_size + " samples"); + + _.each(series.results, function (result) { + + //var target = result.name; + var target = plotParams[index].alias; + var details = " ( "; + _.each(result.group_by,function(element) { + if(element.name==="tag") { + _.each(element.group,function(value, key) { + details+= key+"="+value+" "; + }); + } + else if(element.name==="value") { + details+= 'value_group='+element.group.group_number+" "; + } + else if(element.name==="time") { + details+= 'time_group='+element.group.group_number+" "; + } + }); + details+= ") "; + if (details != " ( ) ") + target += details; + var datapoints = []; + + for (var i = 0; i < result.values.length; i++) { + var t = Math.floor(result.values[i][0]); + var v = result.values[i][1]; + datapoints[i] = [v, t]; + } + if (plotParams[index].exouter) + datapoints = PeakFilter(datapoints, 10); + output.push({ target: target, datapoints: datapoints }); + }); + index ++; + }); + var output2 = { data: _.flatten(output) }; + + return output2; + } + + function convertTargetToQuery(options,target) { + if (!target.metric || target.hide) { + return null; + } + + var query = { + name: target.metric + }; + + query.aggregators = []; + if(target.downsampling!=='(NONE)') { + query.aggregators.push({ + name: target.downsampling, + align_sampling: true, + align_start_time: true, + sampling: KairosDBDatasource.prototype.convertToKairosInterval(target.sampling || options.interval) + }); + } + if(target.horizontalAggregators) { + _.each(target.horizontalAggregators,function(chosenAggregator) { + var returnedAggregator = { + name:chosenAggregator.name + }; + if(chosenAggregator.sampling_rate) { + returnedAggregator.sampling = KairosDBDatasource.prototype.convertToKairosInterval(chosenAggregator.sampling_rate); + returnedAggregator.align_sampling = true; + returnedAggregator.align_start_time=true; + } + if(chosenAggregator.unit) { + returnedAggregator.unit = chosenAggregator.unit+'s'; + } + if(chosenAggregator.factor && chosenAggregator.name==='div') { + returnedAggregator.divisor = chosenAggregator.factor; + } + else if(chosenAggregator.factor && chosenAggregator.name==='scale') { + returnedAggregator.factor = chosenAggregator.factor; + } + if(chosenAggregator.percentile) { + returnedAggregator.percentile = chosenAggregator.percentile; + } + query.aggregators.push(returnedAggregator); + }); + } + if(_.isEmpty(query.aggregators)) { + delete query.aggregators; + } + + if(target.tags) { + query.tags = angular.copy(target.tags); + } + + if(target.groupByTags || target.nonTagGroupBys) { + query.group_by = []; + if(target.groupByTags) {query.group_by.push({name: "tag", tags: angular.copy(target.groupByTags)});} + if(target.nonTagGroupBys) { + _.each(target.nonTagGroupBys,function(rawGroupBy) { + var formattedGroupBy = angular.copy(rawGroupBy); + if(formattedGroupBy.name==='time') { + formattedGroupBy.range_size=KairosDBDatasource.prototype.convertToKairosInterval(formattedGroupBy.range_size); + } + query.group_by.push(formattedGroupBy); + }); + } + } + return query; + } + + /////////////////////////////////////////////////////////////////////// + /// Time conversion functions specifics to KairosDB + ////////////////////////////////////////////////////////////////////// + + KairosDBDatasource.prototype.convertToKairosInterval = function(intervalString) { + var interval_regex = /(\d+(?:\.\d+)?)([Mwdhmsy])/; + var interval_regex_ms = /(\d+(?:\.\d+)?)(ms)/; + var matches = intervalString.match(interval_regex_ms); + if(!matches) { + matches = intervalString.match(interval_regex); + } + if (!matches) { + throw new Error('Invalid interval string, expecting a number followed by one of "y M w d h m s ms"'); + } + + var value = matches[1]; + var unit = matches[2]; + if (value%1!==0) { + if(unit==='ms') {throw new Error('Invalid interval value, cannot be smaller than the millisecond');} + value = Math.round(kbn.intervals_in_seconds[unit]*value*1000); + unit = 'ms'; + + } + switch(unit) { + case 'ms': + unit = 'milliseconds'; + break; + case 's': + unit = 'seconds'; + break; + case 'm': + unit = 'minutes'; + break; + case 'h': + unit = 'hours'; + break; + case 'd': + unit = 'days'; + break; + case 'w': + unit = 'weeks'; + break; + case 'M': + unit = 'months'; + break; + case 'y': + unit = 'years'; + break; + default: + console.log("Unknown interval ", intervalString); + break; + } + + return { + "value": value, + "unit": unit + }; + + }; + + function convertToKairosTime(date, response_obj, start_stop_name) { + var name; + if (_.isString(date)) { + if (date === 'now') { + return; + } + else if (date.indexOf('now-') >= 0) { + + name = start_stop_name + "_relative"; + + date = date.substring(4); + var re_date = /(\d+)\s*(\D+)/; + var result = re_date.exec(date); + if (result) { + var value = result[1]; + var unit = result[2]; + switch(unit) { + case 'ms': + unit = 'milliseconds'; + break; + case 's': + unit = 'seconds'; + break; + case 'm': + unit = 'minutes'; + break; + case 'h': + unit = 'hours'; + break; + case 'd': + unit = 'days'; + break; + case 'w': + unit = 'weeks'; + break; + case 'M': + unit = 'months'; + break; + case 'y': + unit = 'years'; + break; + default: + console.log("Unknown date ", date); + break; + } + response_obj[name] = { + "value": value, + "unit": unit + }; + return; + } + console.log("Unparseable date", date); + return; + } + date = kbn.parseDate(date); + } + + if(_.isDate(date)) { + name = start_stop_name + "_absolute"; + response_obj[name] = date.getTime(); + return; + } + + console.log("Date is neither string nor date"); + } + + function PeakFilter(dataIn, limit) { + var datapoints = dataIn; + var arrLength = datapoints.length; + if (arrLength <= 3) + return datapoints; + var LastIndx = arrLength - 1; + + // Check first point + var prvDelta = Math.abs((datapoints[1][0] - datapoints[0][0]) / datapoints[0][0]); + var nxtDelta = Math.abs((datapoints[1][0] - datapoints[2][0]) / datapoints[2][0]); + if (prvDelta >= limit && nxtDelta < limit) + datapoints[0][0] = datapoints[1][0]; + + // Check last point + prvDelta = Math.abs((datapoints[LastIndx - 1][0] - datapoints[LastIndx - 2][0]) / datapoints[LastIndx - 2][0]); + nxtDelta = Math.abs((datapoints[LastIndx - 1][0] - datapoints[LastIndx][0]) / datapoints[LastIndx][0]); + if (prvDelta >= limit && nxtDelta < limit) + datapoints[LastIndx][0] = datapoints[LastIndx - 1][0]; + + for (var i = 1; i < arrLength - 1; i++){ + prvDelta = Math.abs((datapoints[i][0] - datapoints[i - 1][0]) / datapoints[i - 1][0]); + nxtDelta = Math.abs((datapoints[i][0] - datapoints[i + 1][0]) / datapoints[i + 1][0]); + if (prvDelta >= limit && nxtDelta >= limit) + datapoints[i][0] = (datapoints[i-1][0] + datapoints[i+1][0]) / 2; + } + + return datapoints; + } + + //////////////////////////////////////////////////////////////////////// + return KairosDBDatasource; + }); + +}); diff --git a/src/app/plugins/datasource/kairosdb/partials/config.html b/src/app/plugins/datasource/kairosdb/partials/config.html new file mode 100644 index 00000000000..384edeaeafe --- /dev/null +++ b/src/app/plugins/datasource/kairosdb/partials/config.html @@ -0,0 +1 @@ +
diff --git a/src/app/plugins/datasource/kairosdb/partials/query.editor.html b/src/app/plugins/datasource/kairosdb/partials/query.editor.html new file mode 100644 index 00000000000..8a794e7fdf2 --- /dev/null +++ b/src/app/plugins/datasource/kairosdb/partials/query.editor.html @@ -0,0 +1,384 @@ +
+
+ +
+ + +
    +
  • + {{targetLetters[$index]}} +
  • +
  • + + + +
  • +
  • + +
  • +
  • + + + + +
  • + +
  • +  Peak filter + +
  • +
+ +
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+
diff --git a/src/app/plugins/datasource/kairosdb/plugin.json b/src/app/plugins/datasource/kairosdb/plugin.json new file mode 100644 index 00000000000..bdbf27c5fa8 --- /dev/null +++ b/src/app/plugins/datasource/kairosdb/plugin.json @@ -0,0 +1,17 @@ +{ + "pluginType": "datasource", + "name": "KairosDB", + + "type": "kairosdb", + "serviceName": "KairosDBDatasource", + + "module": "plugins/datasource/kairosdb/datasource", + + "partials": { + "config": "app/plugins/datasource/kairosdb/partials/config.html", + "query": "app/plugins/datasource/kairosdb/partials/query.editor.html" + }, + + "metrics": true, + "annotations": false +} diff --git a/src/app/plugins/datasource/kairosdb/queryCtrl.js b/src/app/plugins/datasource/kairosdb/queryCtrl.js new file mode 100644 index 00000000000..fef1e4d39f4 --- /dev/null +++ b/src/app/plugins/datasource/kairosdb/queryCtrl.js @@ -0,0 +1,379 @@ +define([ + 'angular', + 'lodash' +], +function (angular, _) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + var metricList = null; + var targetLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O']; + + module.controller('KairosDBTargetCtrl', function($scope) { + + $scope.init = function() { + $scope.metric = { + list: ["Loading..."], + value: "Loading..." + }; + $scope.panel.stack = false; + if (!$scope.panel.downsampling) { + $scope.panel.downsampling = 'avg'; + } + if (!$scope.target.downsampling) { + $scope.target.downsampling = $scope.panel.downsampling; + $scope.target.sampling = $scope.panel.sampling; + } + $scope.targetLetters = targetLetters; + $scope.updateMetricList(); + $scope.target.errors = validateTarget($scope.target); + }; + + $scope.targetBlur = function() { + $scope.target.metric = $scope.metric.value; + $scope.target.errors = validateTarget($scope.target); + if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) { + $scope.oldTarget = angular.copy($scope.target); + $scope.get_data(); + } + }; + $scope.panelBlur = function() { + _.each($scope.panel.targets, function(target) { + target.downsampling = $scope.panel.downsampling; + target.sampling = $scope.panel.sampling; + }); + $scope.get_data(); + }; + + $scope.duplicate = function() { + var clone = angular.copy($scope.target); + $scope.panel.targets.push(clone); + }; + $scope.moveMetricQuery = function(fromIndex, toIndex) { + _.move($scope.panel.targets, fromIndex, toIndex); + }; + + ////////////////////////////// + // SUGGESTION QUERIES + ////////////////////////////// + + $scope.updateMetricList = function() { + $scope.metricListLoading = true; + metricList = []; + $scope.datasource.performMetricSuggestQuery().then(function(series) { + metricList = series; + $scope.metric.list = series; + if ($scope.target.metric) + $scope.metric.value = $scope.target.metric; + else + $scope.metric.value = ""; + $scope.metricListLoading = false; + return metricList; + }); + }; + + $scope.suggestTagKeys = function(query, callback) { + $scope.updateTimeRange(); + callback($scope.datasource + .performTagSuggestQuery($scope.target.metric,$scope.rangeUnparsed, 'key','')); + + }; + + $scope.suggestTagValues = function(query, callback) { + callback($scope.datasource + .performTagSuggestQuery($scope.target.metric,$scope.rangeUnparsed, 'value',$scope.target.currentTagKey)); + }; + + ////////////////////////////// + // FILTER by TAG + ////////////////////////////// + + $scope.addFilterTag = function() { + if (!$scope.addFilterTagMode) { + $scope.addFilterTagMode = true; + $scope.validateFilterTag(); + return; + } + + if (!$scope.target.tags) { + $scope.target.tags = {}; + } + + $scope.validateFilterTag(); + if (!$scope.target.errors.tags) { + if(!_.has($scope.target.tags,$scope.target.currentTagKey)) { + $scope.target.tags[$scope.target.currentTagKey] = []; + } + $scope.target.tags[$scope.target.currentTagKey].push($scope.target.currentTagValue); + $scope.target.currentTagKey = ''; + $scope.target.currentTagValue = ''; + $scope.targetBlur(); + } + + $scope.addFilterTagMode = false; + }; + + $scope.removeFilterTag = function(key) { + delete $scope.target.tags[key]; + if(_.size($scope.target.tags)===0) { + $scope.target.tags = null; + } + $scope.targetBlur(); + }; + + $scope.validateFilterTag = function() { + $scope.target.errors.tags = null; + if(!$scope.target.currentTagKey || !$scope.target.currentTagValue) { + $scope.target.errors.tags = "You must specify a tag name and value."; + } + }; + + ////////////////////////////// + // GROUP BY + ////////////////////////////// + + $scope.addGroupBy = function() { + if (!$scope.addGroupByMode) { + $scope.addGroupByMode = true; + $scope.target.currentGroupByType = 'tag'; + $scope.isTagGroupBy = true; + $scope.validateGroupBy(); + return; + } + $scope.validateGroupBy(); + // nb: if error is found, means that user clicked on cross : cancels input + if (_.isEmpty($scope.target.errors.groupBy)) { + if($scope.isTagGroupBy) { + if (!$scope.target.groupByTags) { + $scope.target.groupByTags = []; + } + console.log($scope.target.groupBy.tagKey); + if (!_.contains($scope.target.groupByTags, $scope.target.groupBy.tagKey)) { + $scope.target.groupByTags.push($scope.target.groupBy.tagKey); + $scope.targetBlur(); + } + $scope.target.groupBy.tagKey = ''; + } + else { + if (!$scope.target.nonTagGroupBys) { + $scope.target.nonTagGroupBys = []; + } + var groupBy = { + name: $scope.target.currentGroupByType + }; + if($scope.isValueGroupBy) {groupBy.range_size = $scope.target.groupBy.valueRange;} + else if($scope.isTimeGroupBy) { + groupBy.range_size = $scope.target.groupBy.timeInterval; + groupBy.group_count = $scope.target.groupBy.groupCount; + } + $scope.target.nonTagGroupBys.push(groupBy); + } + $scope.targetBlur(); + } + $scope.isTagGroupBy = false; + $scope.isValueGroupBy = false; + $scope.isTimeGroupBy = false; + $scope.addGroupByMode = false; + }; + + $scope.removeGroupByTag = function(index) { + $scope.target.groupByTags.splice(index, 1); + if(_.size($scope.target.groupByTags)===0) { + $scope.target.groupByTags = null; + } + $scope.targetBlur(); + }; + + $scope.removeNonTagGroupBy = function(index) { + $scope.target.nonTagGroupBys.splice(index, 1); + if(_.size($scope.target.nonTagGroupBys)===0) { + $scope.target.nonTagGroupBys = null; + } + $scope.targetBlur(); + }; + + $scope.changeGroupByInput = function() { + $scope.isTagGroupBy = $scope.target.currentGroupByType==='tag'; + $scope.isValueGroupBy = $scope.target.currentGroupByType==='value'; + $scope.isTimeGroupBy = $scope.target.currentGroupByType==='time'; + $scope.validateGroupBy(); + }; + + $scope.validateGroupBy = function() { + delete $scope.target.errors.groupBy; + var errors = {}; + $scope.isGroupByValid = true; + if($scope.isTagGroupBy) { + if(!$scope.target.groupBy.tagKey) { + $scope.isGroupByValid = false; + errors.tagKey = 'You must supply a tag name'; + } + } + if($scope.isValueGroupBy) { + if(!$scope.target.groupBy.valueRange || !isInt($scope.target.groupBy.valueRange)) { + errors.valueRange = "Range must be an integer"; + $scope.isGroupByValid = false; + } + } + if($scope.isTimeGroupBy) { + try { + $scope.datasource.convertToKairosInterval($scope.target.groupBy.timeInterval); + } catch(err) { + errors.timeInterval = err.message; + $scope.isGroupByValid = false; + } + if(!$scope.target.groupBy.groupCount || !isInt($scope.target.groupBy.groupCount)) { + errors.groupCount = "Group count must be an integer"; + $scope.isGroupByValid = false; + } + } + + if(!_.isEmpty(errors)) { + $scope.target.errors.groupBy = errors; + } + }; + + function isInt(n) { + return parseInt(n) % 1 === 0; + } + + ////////////////////////////// + // HORIZONTAL AGGREGATION + ////////////////////////////// + + $scope.addHorizontalAggregator = function() { + if (!$scope.addHorizontalAggregatorMode) { + $scope.addHorizontalAggregatorMode = true; + $scope.target.currentHorizontalAggregatorName = 'avg'; + $scope.hasSamplingRate = true; + $scope.validateHorizontalAggregator(); + return; + } + + $scope.validateHorizontalAggregator(); + // nb: if error is found, means that user clicked on cross : cancels input + if(_.isEmpty($scope.target.errors.horAggregator)) { + if (!$scope.target.horizontalAggregators) { + $scope.target.horizontalAggregators = []; + } + var aggregator = { + name:$scope.target.currentHorizontalAggregatorName + }; + if($scope.hasSamplingRate) {aggregator.sampling_rate = $scope.target.horAggregator.samplingRate;} + if($scope.hasUnit) {aggregator.unit = $scope.target.horAggregator.unit;} + if($scope.hasFactor) {aggregator.factor = $scope.target.horAggregator.factor;} + if($scope.hasPercentile) {aggregator.percentile = $scope.target.horAggregator.percentile;} + $scope.target.horizontalAggregators.push(aggregator); + $scope.targetBlur(); + } + + $scope.addHorizontalAggregatorMode = false; + $scope.hasSamplingRate = false; + $scope.hasUnit = false; + $scope.hasFactor = false; + $scope.hasPercentile = false; + + }; + + $scope.removeHorizontalAggregator = function(index) { + $scope.target.horizontalAggregators.splice(index, 1); + if(_.size($scope.target.horizontalAggregators)===0) { + $scope.target.horizontalAggregators = null; + } + + $scope.targetBlur(); + }; + + $scope.changeHorAggregationInput = function() { + $scope.hasSamplingRate = _.contains(['avg','dev','max','min','sum','least_squares','count','percentile'], + $scope.target.currentHorizontalAggregatorName); + $scope.hasUnit = _.contains(['sampler','rate'], $scope.target.currentHorizontalAggregatorName); + $scope.hasFactor = _.contains(['div','scale'], $scope.target.currentHorizontalAggregatorName); + $scope.hasPercentile = 'percentile'===$scope.target.currentHorizontalAggregatorName; + $scope.validateHorizontalAggregator(); + }; + + $scope.validateHorizontalAggregator = function() { + delete $scope.target.errors.horAggregator; + var errors = {}; + $scope.isAggregatorValid = true; + if($scope.hasSamplingRate) { + try { + $scope.datasource.convertToKairosInterval($scope.target.horAggregator.samplingRate); + } catch(err) { + errors.samplingRate = err.message; + $scope.isAggregatorValid = false; + } + } + if($scope.hasFactor) { + if(!$scope.target.horAggregator.factor) { + errors.factor = 'You must supply a numeric value for this aggregator'; + $scope.isAggregatorValid = false; + } + else if(parseInt($scope.target.horAggregator.factor)===0 && $scope.target.currentHorizontalAggregatorName==='div') { + errors.factor = 'Cannot divide by 0'; + $scope.isAggregatorValid = false; + } + } + if($scope.hasPercentile) { + if(!$scope.target.horAggregator.percentile || + $scope.target.horAggregator.percentile<=0 || + $scope.target.horAggregator.percentile>1) { + errors.percentile = 'Percentile must be between 0 and 1'; + $scope.isAggregatorValid = false; + } + } + + if(!_.isEmpty(errors)) { + $scope.target.errors.horAggregator = errors; + } + }; + + $scope.alert = function(message) { + alert(message); + }; + + ////////////////////////////// + // VALIDATION + ////////////////////////////// + + function MetricListToObject(MetricList) { + var result = {}; + var Metric; + var MetricArray = []; + var MetricCnt = 0; + for (var i =0;i < MetricList.length; i++) { + Metric = MetricList[i]; + MetricArray = Metric.split('.'); + if(!result.hasOwnProperty(MetricArray[0])) { + result[MetricArray[0]] = {}; + } + if(!result[MetricArray[0]].hasOwnProperty(MetricArray[1])) { + result[MetricArray[0]][MetricArray[1]] = []; + } + result[MetricArray[0]][MetricArray[1]].push(MetricArray[2]); + } + return result; + } + + function validateTarget(target) { + var errs = {}; + + if (!target.metric) { + errs.metric = "You must supply a metric name."; + } + + try { + if (target.sampling) { + $scope.datasource.convertToKairosInterval(target.sampling); + } + } catch(err) { + errs.sampling = err.message; + } + + return errs; + } + + }); + +});