Merge branch 'master' into adjust_interval_variable_with_min_step

This commit is contained in:
Alin Sinpalean
2017-10-04 15:30:38 +02:00
238 changed files with 10240 additions and 18230 deletions

View File

@@ -1,2 +0,0 @@
declare var test: any;
export default test;

View File

@@ -1,106 +0,0 @@
define([
'lodash',
],
function (_) {
'use strict';
function CloudWatchAnnotationQuery(datasource, annotation, $q, templateSrv) {
this.datasource = datasource;
this.annotation = annotation;
this.$q = $q;
this.templateSrv = templateSrv;
}
CloudWatchAnnotationQuery.prototype.process = function(from, to) {
var self = this;
var usePrefixMatch = this.annotation.prefixMatching;
var region = this.templateSrv.replace(this.annotation.region);
var namespace = this.templateSrv.replace(this.annotation.namespace);
var metricName = this.templateSrv.replace(this.annotation.metricName);
var dimensions = this.datasource.convertDimensionFormat(this.annotation.dimensions);
var statistics = _.map(this.annotation.statistics, function(s) { return self.templateSrv.replace(s); });
var defaultPeriod = usePrefixMatch ? '' : '300';
var period = this.annotation.period || defaultPeriod;
period = parseInt(period, 10);
var actionPrefix = this.annotation.actionPrefix || '';
var alarmNamePrefix = this.annotation.alarmNamePrefix || '';
var d = this.$q.defer();
var allQueryPromise;
if (usePrefixMatch) {
allQueryPromise = [
this.datasource.performDescribeAlarms(region, actionPrefix, alarmNamePrefix, [], '').then(function(alarms) {
alarms.MetricAlarms = self.filterAlarms(alarms, namespace, metricName, dimensions, statistics, period);
return alarms;
})
];
} else {
if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return this.$q.when([]); }
allQueryPromise = _.map(statistics, function(statistic) {
return self.datasource.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
});
}
this.$q.all(allQueryPromise).then(function(alarms) {
var eventList = [];
var start = self.datasource.convertToCloudWatchTime(from, false);
var end = self.datasource.convertToCloudWatchTime(to, true);
_.chain(alarms)
.map('MetricAlarms')
.flatten()
.each(function(alarm) {
if (!alarm) {
d.resolve(eventList);
return;
}
self.datasource.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
_.each(history.AlarmHistoryItems, function(h) {
var event = {
annotation: self.annotation,
time: Date.parse(h.Timestamp),
title: h.AlarmName,
tags: [h.HistoryItemType],
text: h.HistorySummary
};
eventList.push(event);
});
d.resolve(eventList);
});
})
.value();
});
return d.promise;
};
CloudWatchAnnotationQuery.prototype.filterAlarms = function(alarms, namespace, metricName, dimensions, statistics, period) {
return _.filter(alarms.MetricAlarms, function(alarm) {
if (!_.isEmpty(namespace) && alarm.Namespace !== namespace) {
return false;
}
if (!_.isEmpty(metricName) && alarm.MetricName !== metricName) {
return false;
}
var sd = function(d) {
return d.Name;
};
var isSameDimensions = JSON.stringify(_.sortBy(alarm.Dimensions, sd)) === JSON.stringify(_.sortBy(dimensions, sd));
if (!_.isEmpty(dimensions) && !isSameDimensions) {
return false;
}
if (!_.isEmpty(statistics) && !_.includes(statistics, alarm.Statistic)) {
return false;
}
if (!_.isNaN(period) && alarm.Period !== period) {
return false;
}
return true;
});
};
return CloudWatchAnnotationQuery;
});

View File

@@ -1,3 +1,3 @@
declare var CloudWatchDatasource: any;
export {CloudWatchDatasource};
export default CloudWatchDatasource;

View File

@@ -5,18 +5,18 @@ define([
'app/core/utils/datemath',
'app/core/utils/kbn',
'app/features/templating/variable',
'./annotation_query',
],
function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnotationQuery) {
function (angular, _, moment, dateMath, kbn, templatingVariable) {
'use strict';
/** @ngInject */
function CloudWatchDatasource(instanceSettings, $q, backendSrv, templateSrv) {
function CloudWatchDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) {
this.type = 'cloudwatch';
this.name = instanceSettings.name;
this.supportMetrics = true;
this.proxyUrl = instanceSettings.url;
this.defaultRegion = instanceSettings.jsonData.defaultRegion;
this.instanceSettings = instanceSettings;
this.standardStatistics = [
'Average',
'Maximum',
@@ -27,31 +27,30 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
var self = this;
this.query = function(options) {
var start = self.convertToCloudWatchTime(options.range.from, false);
var end = self.convertToCloudWatchTime(options.range.to, true);
var queries = [];
options = angular.copy(options);
options.targets = this.expandTemplateVariable(options.targets, options.scopedVars, templateSrv);
_.each(options.targets, function(target) {
if (target.hide || !target.namespace || !target.metricName || _.isEmpty(target.statistics)) {
return;
}
var query = {};
query.region = templateSrv.replace(target.region, options.scopedVars);
query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
query.statistics = target.statistics;
var queries = _.filter(options.targets, function (item) {
return item.hide !== true &&
!!item.region &&
!!item.namespace &&
!!item.metricName &&
!_.isEmpty(item.statistics);
}).map(function (item) {
item.region = templateSrv.replace(item.region, options.scopedVars);
item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
item.period = self.getPeriod(item, options);
var now = Math.round(Date.now() / 1000);
var period = this.getPeriod(target, query, options, start, end, now);
target.period = period;
query.period = period;
queries.push(query);
}.bind(this));
return _.extend({
refId: item.refId,
intervalMs: options.intervalMs,
maxDataPoints: options.maxDataPoints,
datasourceId: self.instanceSettings.id,
type: 'timeSeriesQuery',
}, item);
});
// No valid targets, return the empty result to save a round trip.
if (_.isEmpty(queries)) {
@@ -60,23 +59,20 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
return d.promise;
}
var allQueryPromise = _.map(queries, function(query) {
return this.performTimeSeriesQuery(query, start, end);
}.bind(this));
var request = {
from: options.range.from.valueOf().toString(),
to: options.range.to.valueOf().toString(),
queries: queries
};
return $q.all(allQueryPromise).then(function(allResponse) {
var result = [];
_.each(allResponse, function(response, index) {
var metrics = transformMetricData(response, options.targets[index], options.scopedVars);
result = result.concat(metrics);
});
return {data: result};
});
return this.performTimeSeriesQuery(request);
};
this.getPeriod = function(target, query, options, start, end, now) {
this.getPeriod = function(target, options, now) {
var start = this.convertToCloudWatchTime(options.range.from, false);
var end = this.convertToCloudWatchTime(options.range.to, true);
now = Math.round((now || Date.now()) / 1000);
var period;
var range = end - start;
@@ -85,7 +81,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
var periodUnit = 60;
if (!target.period) {
if (now - start <= (daySec * 15)) { // until 15 days ago
if (query.namespace === 'AWS/EC2') {
if (target.namespace === 'AWS/EC2') {
periodUnit = period = 300;
} else {
periodUnit = period = 60;
@@ -114,85 +110,93 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
return period;
};
this.performTimeSeriesQuery = function(query, start, end) {
var statistics = _.filter(query.statistics, function(s) { return _.includes(self.standardStatistics, s); });
var extendedStatistics = _.reject(query.statistics, function(s) { return _.includes(self.standardStatistics, s); });
return this.awsRequest({
region: query.region,
action: 'GetMetricStatistics',
parameters: {
namespace: query.namespace,
metricName: query.metricName,
dimensions: query.dimensions,
statistics: statistics,
extendedStatistics: extendedStatistics,
startTime: start,
endTime: end,
period: query.period
this.performTimeSeriesQuery = function(request) {
return backendSrv.post('/api/tsdb/query', request).then(function (res) {
var data = [];
if (res.results) {
_.forEach(res.results, function (queryRes) {
_.forEach(queryRes.series, function (series) {
data.push({target: series.name, datapoints: series.points});
});
});
}
return {data: data};
});
};
this.getRegions = function() {
return this.awsRequest({action: '__GetRegions'});
function transformSuggestDataFromTable(suggestData) {
return _.map(suggestData.results['metricFindQuery'].tables[0].rows, function (v) {
return {
text: v[0],
value: v[1]
};
});
}
this.doMetricQueryRequest = function (subtype, parameters) {
var range = timeSrv.timeRange();
return backendSrv.post('/api/tsdb/query', {
from: range.from.valueOf().toString(),
to: range.to.valueOf().toString(),
queries: [
_.extend({
refId: 'metricFindQuery',
intervalMs: 1, // dummy
maxDataPoints: 1, // dummy
datasourceId: this.instanceSettings.id,
type: 'metricFindQuery',
subtype: subtype
}, parameters)
]
}).then(function (r) { return transformSuggestDataFromTable(r); });
};
this.getRegions = function () {
return this.doMetricQueryRequest('regions', null);
};
this.getNamespaces = function() {
return this.awsRequest({action: '__GetNamespaces'});
return this.doMetricQueryRequest('namespaces', null);
};
this.getMetrics = function(namespace, region) {
return this.awsRequest({
action: '__GetMetrics',
region: region,
parameters: {
namespace: templateSrv.replace(namespace)
}
this.getMetrics = function (namespace, region) {
return this.doMetricQueryRequest('metrics', {
region: templateSrv.replace(region),
namespace: templateSrv.replace(namespace)
});
};
this.getDimensionKeys = function(namespace, region) {
return this.awsRequest({
action: '__GetDimensions',
region: region,
parameters: {
namespace: templateSrv.replace(namespace)
}
return this.doMetricQueryRequest('dimension_keys', {
region: templateSrv.replace(region),
namespace: templateSrv.replace(namespace)
});
};
this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
var request = {
return this.doMetricQueryRequest('dimension_values', {
region: templateSrv.replace(region),
action: 'ListMetrics',
parameters: {
namespace: templateSrv.replace(namespace),
metricName: templateSrv.replace(metricName),
dimensions: this.convertDimensionFormat(filterDimensions, {}),
}
};
return this.awsRequest(request).then(function(result) {
return _.chain(result.Metrics)
.map('Dimensions')
.flatten()
.filter(function(dimension) {
return dimension !== null && dimension.Name === dimensionKey;
})
.map('Value')
.uniq()
.sortBy()
.map(function(value) {
return {value: value, text: value};
}).value();
namespace: templateSrv.replace(namespace),
metricName: templateSrv.replace(metricName),
dimensionKey: templateSrv.replace(dimensionKey),
dimensions: this.convertDimensionFormat(filterDimensions, {}),
});
};
this.performEC2DescribeInstances = function(region, filters, instanceIds) {
return this.awsRequest({
region: region,
action: 'DescribeInstances',
parameters: { filters: filters, instanceIds: instanceIds }
this.getEbsVolumeIds = function(region, instanceId) {
return this.doMetricQueryRequest('ebs_volume_ids', {
region: templateSrv.replace(region),
instanceId: templateSrv.replace(instanceId)
});
};
this.getEc2InstanceAttribute = function(region, attributeName, filters) {
return this.doMetricQueryRequest('ec2_instance_attribute', {
region: templateSrv.replace(region),
attributeName: templateSrv.replace(attributeName),
filters: filters
});
};
@@ -201,12 +205,6 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
var namespace;
var metricName;
var transformSuggestData = function(suggestData) {
return _.map(suggestData, function(v) {
return { text: v };
});
};
var regionQuery = query.match(/^regions\(\)/);
if (regionQuery) {
return this.getRegions();
@@ -219,114 +217,98 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
var metricNameQuery = query.match(/^metrics\(([^\)]+?)(,\s?([^,]+?))?\)/);
if (metricNameQuery) {
return this.getMetrics(templateSrv.replace(metricNameQuery[1]), templateSrv.replace(metricNameQuery[3]));
namespace = metricNameQuery[1];
region = metricNameQuery[3];
return this.getMetrics(namespace, region);
}
var dimensionKeysQuery = query.match(/^dimension_keys\(([^\)]+?)(,\s?([^,]+?))?\)/);
if (dimensionKeysQuery) {
return this.getDimensionKeys(templateSrv.replace(dimensionKeysQuery[1]), templateSrv.replace(dimensionKeysQuery[3]));
namespace = dimensionKeysQuery[1];
region = dimensionKeysQuery[3];
return this.getDimensionKeys(namespace, region);
}
var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/);
if (dimensionValuesQuery) {
region = templateSrv.replace(dimensionValuesQuery[1]);
namespace = templateSrv.replace(dimensionValuesQuery[2]);
metricName = templateSrv.replace(dimensionValuesQuery[3]);
var dimensionKey = templateSrv.replace(dimensionValuesQuery[4]);
region = dimensionValuesQuery[1];
namespace = dimensionValuesQuery[2];
metricName = dimensionValuesQuery[3];
var dimensionKey = dimensionValuesQuery[4];
return this.getDimensionValues(region, namespace, metricName, dimensionKey, {});
}
var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
if (ebsVolumeIdsQuery) {
region = templateSrv.replace(ebsVolumeIdsQuery[1]);
var instanceId = templateSrv.replace(ebsVolumeIdsQuery[2]);
var instanceIds = [
instanceId
];
return this.performEC2DescribeInstances(region, [], instanceIds).then(function(result) {
var volumeIds = _.map(result.Reservations[0].Instances[0].BlockDeviceMappings, function(mapping) {
return mapping.Ebs.VolumeId;
});
return transformSuggestData(volumeIds);
});
region = ebsVolumeIdsQuery[1];
var instanceId = ebsVolumeIdsQuery[2];
return this.getEbsVolumeIds(region, instanceId);
}
var ec2InstanceAttributeQuery = query.match(/^ec2_instance_attribute\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/);
if (ec2InstanceAttributeQuery) {
region = templateSrv.replace(ec2InstanceAttributeQuery[1]);
region = ec2InstanceAttributeQuery[1];
var targetAttributeName = ec2InstanceAttributeQuery[2];
var filterJson = JSON.parse(templateSrv.replace(ec2InstanceAttributeQuery[3]));
var filters = _.map(filterJson, function(values, name) {
return {
Name: name,
Values: values
};
});
var targetAttributeName = templateSrv.replace(ec2InstanceAttributeQuery[2]);
return this.performEC2DescribeInstances(region, filters, null).then(function(result) {
var attributes = _.chain(result.Reservations)
.map(function(reservations) {
return _.map(reservations.Instances, function(instance) {
var tags = {};
_.each(instance.Tags, function(tag) {
tags[tag.Key] = tag.Value;
});
instance.Tags = tags;
return instance;
});
})
.map(function(instances) {
return _.map(instances, targetAttributeName);
})
.flatten().uniq().sortBy().value();
return transformSuggestData(attributes);
});
return this.getEc2InstanceAttribute(region, targetAttributeName, filterJson);
}
return $q.when([]);
};
this.performDescribeAlarms = function(region, actionPrefix, alarmNamePrefix, alarmNames, stateValue) {
return this.awsRequest({
region: region,
action: 'DescribeAlarms',
parameters: { actionPrefix: actionPrefix, alarmNamePrefix: alarmNamePrefix, alarmNames: alarmNames, stateValue: stateValue }
this.annotationQuery = function (options) {
var annotation = options.annotation;
var statistics = _.map(annotation.statistics, function (s) { return templateSrv.replace(s); });
var defaultPeriod = annotation.prefixMatching ? '' : '300';
var period = annotation.period || defaultPeriod;
period = parseInt(period, 10);
var parameters = {
prefixMatching: annotation.prefixMatching,
region: templateSrv.replace(annotation.region),
namespace: templateSrv.replace(annotation.namespace),
metricName: templateSrv.replace(annotation.metricName),
dimensions: this.convertDimensionFormat(annotation.dimensions, {}),
statistics: statistics,
period: period,
actionPrefix: annotation.actionPrefix || '',
alarmNamePrefix: annotation.alarmNamePrefix || ''
};
return backendSrv.post('/api/tsdb/query', {
from: options.range.from.valueOf().toString(),
to: options.range.to.valueOf().toString(),
queries: [
_.extend({
refId: 'annotationQuery',
intervalMs: 1, // dummy
maxDataPoints: 1, // dummy
datasourceId: this.instanceSettings.id,
type: 'annotationQuery'
}, parameters)
]
}).then(function (r) {
return _.map(r.results['annotationQuery'].tables[0].rows, function (v) {
return {
annotation: annotation,
time: Date.parse(v[0]),
title: v[1],
tags: [v[2]],
text: v[3]
};
});
});
};
this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
var s = _.includes(self.standardStatistics, statistic) ? statistic : '';
var es = _.includes(self.standardStatistics, statistic) ? '' : statistic;
return this.awsRequest({
region: region,
action: 'DescribeAlarmsForMetric',
parameters: {
namespace: namespace,
metricName: metricName,
dimensions: dimensions,
statistic: s,
extendedStatistic: es,
period: period
}
this.targetContainsTemplate = function(target) {
return templateSrv.variableExists(target.region) ||
templateSrv.variableExists(target.namespace) ||
templateSrv.variableExists(target.metricName) ||
_.find(target.dimensions, function(v, k) {
return templateSrv.variableExists(k) || templateSrv.variableExists(v);
});
};
this.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) {
return this.awsRequest({
region: region,
action: 'DescribeAlarmHistory',
parameters: { alarmName: alarmName, startDate: startDate, endDate: endDate }
});
};
this.annotationQuery = function(options) {
var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
return annotationQuery.process(options.range.from, options.range.to);
};
this.testDatasource = function() {
/* use billing metrics for test */
var region = this.defaultRegion;
@@ -355,62 +337,6 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
return this.defaultRegion;
};
function transformMetricData(md, options, scopedVars) {
var aliasRegex = /\{\{(.+?)\}\}/g;
var aliasPattern = options.alias || '{{metric}}_{{stat}}';
var aliasData = {
region: templateSrv.replace(options.region, scopedVars),
namespace: templateSrv.replace(options.namespace, scopedVars),
metric: templateSrv.replace(options.metricName, scopedVars),
};
var aliasDimensions = {};
_.each(_.keys(options.dimensions), function(origKey) {
var key = templateSrv.replace(origKey, scopedVars);
var value = templateSrv.replace(options.dimensions[origKey], scopedVars);
aliasDimensions[key] = value;
});
_.extend(aliasData, aliasDimensions);
var periodMs = options.period * 1000;
return _.map(options.statistics, function(stat) {
var extended = !_.includes(self.standardStatistics, stat);
var dps = [];
var lastTimestamp = null;
_.chain(md.Datapoints)
.sortBy(function(dp) {
return dp.Timestamp;
})
.each(function(dp) {
var timestamp = new Date(dp.Timestamp).getTime();
while (lastTimestamp && (timestamp - lastTimestamp) > periodMs) {
dps.push([null, lastTimestamp + periodMs]);
lastTimestamp = lastTimestamp + periodMs;
}
lastTimestamp = timestamp;
if (!extended) {
dps.push([dp[stat], timestamp]);
} else {
dps.push([dp.ExtendedStatistics[stat], timestamp]);
}
})
.value();
aliasData.stat = stat;
var seriesName = aliasPattern.replace(aliasRegex, function(match, g1) {
if (aliasData[g1]) {
return aliasData[g1];
}
return g1;
});
return {target: seriesName, datapoints: dps};
});
}
this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
/* if the all checkbox is marked we should add all values to the targets */
var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
@@ -461,17 +387,14 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
};
this.convertDimensionFormat = function(dimensions, scopedVars) {
return _.map(dimensions, function(value, key) {
return {
Name: templateSrv.replace(key, scopedVars),
Value: templateSrv.replace(value, scopedVars)
};
var convertedDimensions = {};
_.each(dimensions, function (value, key) {
convertedDimensions[templateSrv.replace(key, scopedVars)] = templateSrv.replace(value, scopedVars);
});
return convertedDimensions;
};
}
return {
CloudWatchDatasource: CloudWatchDatasource
};
return CloudWatchDatasource;
});

View File

@@ -1,6 +1,6 @@
import './query_parameter_ctrl';
import {CloudWatchDatasource} from './datasource';
import CloudWatchDatasource from './datasource';
import {CloudWatchQueryCtrl} from './query_ctrl';
import {CloudWatchConfigCtrl} from './config_ctrl';

View File

@@ -3,18 +3,16 @@
<div class="editor-row" style="padding: 2rem 0">
<div class="section">
<h5>Prefix matching</h5>
<div class="editor-option">
<editor-checkbox text="Enable" model="ctrl.annotation.prefixMatching"></editor-checkbox>
</div>
<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
<label class="small">Action</label>
<input type="text" class="input-small" ng-model='ctrl.annotation.actionPrefix'></input>
</div>
<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
<label class="small">Alarm Name</label>
<input type="text" class="input-small" ng-model='ctrl.annotation.alarmNamePrefix'></input>
<div class="gf-form-inline">
<gf-form-switch class="gf-form" label="Enable" checked="ctrl.annotation.prefixMatching" switch-class="max-width-6"></gf-form-switch>
<div class="gf-form" ng-if="ctrl.annotation.prefixMatching">
<span class="gf-form-label">Action</span>
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.actionPrefix'></input>
</div>
<div class="gf-form" ng-if="ctrl.annotation.prefixMatching">
<span class="gf-form-label">Alarm Name</span>
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.alarmNamePrefix'></input>
</div>
</div>
</div>
</div>

View File

@@ -4,6 +4,7 @@
"id": "cloudwatch",
"metrics": true,
"alerting": true,
"annotations": true,
"info": {

View File

@@ -1,81 +0,0 @@
import "../datasource";
import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
import moment from 'moment';
import helpers from 'test/specs/helpers';
import {CloudWatchDatasource} from "../datasource";
import CloudWatchAnnotationQuery from '../annotation_query';
describe('CloudWatchAnnotationQuery', function() {
var ctx = new helpers.ServiceTestContext();
var instanceSettings = {
jsonData: {defaultRegion: 'us-east-1', access: 'proxy'},
};
beforeEach(angularMocks.module('grafana.core'));
beforeEach(angularMocks.module('grafana.services'));
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
ctx.$q = $q;
ctx.$httpBackend = $httpBackend;
ctx.$rootScope = $rootScope;
ctx.ds = $injector.instantiate(CloudWatchDatasource, {instanceSettings: instanceSettings});
}));
describe('When performing annotationQuery', function() {
var parameter = {
annotation: {
region: 'us-east-1',
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensions: {
InstanceId: 'i-12345678'
},
statistics: ['Average'],
period: 300
},
range: {
from: moment(1443438674760),
to: moment(1443460274760)
}
};
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});
case 'DescribeAlarmHistory':
return ctx.$q.when({data: historyResponse});
}
};
});
it('should return annotation list', function(done) {
var annotationQuery = new CloudWatchAnnotationQuery(ctx.ds, parameter.annotation, ctx.$q, ctx.templateSrv);
annotationQuery.process(parameter.range.from, parameter.range.to).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();
});
});
});

View File

@@ -1,8 +1,7 @@
import "../datasource";
import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
import helpers from 'test/specs/helpers';
import {CloudWatchDatasource} from "../datasource";
import CloudWatchDatasource from "../datasource";
describe('CloudWatchDatasource', function() {
var ctx = new helpers.ServiceTestContext();
@@ -28,6 +27,7 @@ describe('CloudWatchDatasource', function() {
var query = {
range: { from: 'now-1h', to: 'now' },
rangeRaw: { from: 1483228800, to: 1483232400 },
targets: [
{
region: 'us-east-1',
@@ -43,37 +43,41 @@ describe('CloudWatchDatasource', function() {
};
var response = {
Datapoints: [
{
Average: 1,
Timestamp: 'Wed Dec 31 1969 16:00:00 GMT-0800 (PST)'
},
{
Average: 2,
Timestamp: 'Wed Dec 31 1969 16:05:00 GMT-0800 (PST)'
},
{
Average: 5,
Timestamp: 'Wed Dec 31 1969 16:15:00 GMT-0800 (PST)'
timings: [null],
results: {
A: {
error: '',
refId: 'A',
series: [
{
name: 'CPUUtilization_Average',
points: [
[1, 1483228800000],
[2, 1483229100000],
[5, 1483229700000],
],
tags: {
InstanceId: 'i-12345678'
}
}
]
}
],
Label: 'CPUUtilization'
}
};
beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(params) {
ctx.backendSrv.post = function(path, params) {
requestParams = params;
return ctx.$q.when({data: response});
return ctx.$q.when(response);
};
});
it('should generate the correct query', function(done) {
ctx.ds.query(query).then(function() {
var params = requestParams.data.parameters;
var params = requestParams.queries[0];
expect(params.namespace).to.be(query.targets[0].namespace);
expect(params.metricName).to.be(query.targets[0].metricName);
expect(params.dimensions[0].Name).to.be(Object.keys(query.targets[0].dimensions)[0]);
expect(params.dimensions[0].Value).to.be(query.targets[0].dimensions[Object.keys(query.targets[0].dimensions)[0]]);
expect(params.dimensions['InstanceId']).to.be('i-12345678');
expect(params.statistics).to.eql(query.targets[0].statistics);
expect(params.period).to.be(query.targets[0].period);
done();
@@ -88,6 +92,7 @@ describe('CloudWatchDatasource', function() {
var query = {
range: { from: 'now-1h', to: 'now' },
rangeRaw: { from: 1483228800, to: 1483232400 },
targets: [
{
region: 'us-east-1',
@@ -103,7 +108,7 @@ describe('CloudWatchDatasource', function() {
};
ctx.ds.query(query).then(function() {
var params = requestParams.data.parameters;
var params = requestParams.queries[0];
expect(params.period).to.be(600);
done();
});
@@ -112,16 +117,8 @@ describe('CloudWatchDatasource', function() {
it('should return series list', function(done) {
ctx.ds.query(query).then(function(result) {
expect(result.data[0].target).to.be('CPUUtilization_Average');
expect(result.data[0].datapoints[0][0]).to.be(response.Datapoints[0]['Average']);
done();
});
ctx.$rootScope.$apply();
});
it('should return null for missing data point', function(done) {
ctx.ds.query(query).then(function(result) {
expect(result.data[0].datapoints[2][0]).to.be(null);
expect(result.data[0].target).to.be(response.results.A.series[0].name);
expect(result.data[0].datapoints[0][0]).to.be(response.results.A.series[0].points[0][0]);
done();
});
ctx.$rootScope.$apply();
@@ -173,6 +170,7 @@ describe('CloudWatchDatasource', function() {
var query = {
range: { from: 'now-1h', to: 'now' },
rangeRaw: { from: 1483228800, to: 1483232400 },
targets: [
{
region: 'us-east-1',
@@ -189,40 +187,40 @@ describe('CloudWatchDatasource', function() {
};
var response = {
Datapoints: [
{
ExtendedStatistics: {
'p90.00': 1
},
Timestamp: 'Wed Dec 31 1969 16:00:00 GMT-0800 (PST)'
},
{
ExtendedStatistics: {
'p90.00': 2
},
Timestamp: 'Wed Dec 31 1969 16:05:00 GMT-0800 (PST)'
},
{
ExtendedStatistics: {
'p90.00': 5
},
Timestamp: 'Wed Dec 31 1969 16:15:00 GMT-0800 (PST)'
timings: [null],
results: {
A: {
error: '',
refId: 'A',
series: [
{
name: 'TargetResponseTime_p90.00',
points: [
[1, 1483228800000],
[2, 1483229100000],
[5, 1483229700000],
],
tags: {
LoadBalancer: 'lb',
TargetGroup: 'tg'
}
}
]
}
],
Label: 'TargetResponseTime'
}
};
beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(params) {
ctx.backendSrv.post = function(path, params) {
requestParams = params;
return ctx.$q.when({data: response});
return ctx.$q.when(response);
};
});
it('should return series list', function(done) {
ctx.ds.query(query).then(function(result) {
expect(result.data[0].target).to.be('TargetResponseTime_p90.00');
expect(result.data[0].datapoints[0][0]).to.be(response.Datapoints[0].ExtendedStatistics['p90.00']);
expect(result.data[0].target).to.be(response.results.A.series[0].name);
expect(result.data[0].datapoints[0][0]).to.be(response.results.A.series[0].points[0][0]);
done();
});
ctx.$rootScope.$apply();
@@ -237,7 +235,11 @@ describe('CloudWatchDatasource', function() {
setupCallback();
ctx.backendSrv.datasourceRequest = args => {
scenario.request = args;
return ctx.$q.when({data: scenario.requestResponse });
return ctx.$q.when({ data: scenario.requestResponse });
};
ctx.backendSrv.post = (path, args) => {
scenario.request = args;
return ctx.$q.when(scenario.requestResponse);
};
ctx.ds.metricFindQuery(query).then(args => {
scenario.result = args;
@@ -252,135 +254,178 @@ describe('CloudWatchDatasource', function() {
describeMetricFindQuery('regions()', scenario => {
scenario.setup(() => {
scenario.requestResponse = [{text: 'us-east-1'}];
scenario.requestResponse = {
results: {
metricFindQuery: {
tables: [
{ rows: [['us-east-1', 'us-east-1']] }
]
}
}
};
});
it('should call __GetRegions and return result', () => {
expect(scenario.result[0].text).to.contain('us-east-1');
expect(scenario.request.data.action).to.be('__GetRegions');
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
expect(scenario.request.queries[0].subtype).to.be('regions');
});
});
describeMetricFindQuery('namespaces()', scenario => {
scenario.setup(() => {
scenario.requestResponse = [{text: 'AWS/EC2'}];
scenario.requestResponse = {
results: {
metricFindQuery: {
tables: [
{ rows: [['AWS/EC2', 'AWS/EC2']] }
]
}
}
};
});
it('should call __GetNamespaces and return result', () => {
expect(scenario.result[0].text).to.contain('AWS/EC2');
expect(scenario.request.data.action).to.be('__GetNamespaces');
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
expect(scenario.request.queries[0].subtype).to.be('namespaces');
});
});
describeMetricFindQuery('metrics(AWS/EC2)', scenario => {
scenario.setup(() => {
scenario.requestResponse = [{text: 'CPUUtilization'}];
scenario.requestResponse = {
results: {
metricFindQuery: {
tables: [
{ rows: [['CPUUtilization', 'CPUUtilization']] }
]
}
}
};
});
it('should call __GetMetrics and return result', () => {
expect(scenario.result[0].text).to.be('CPUUtilization');
expect(scenario.request.data.action).to.be('__GetMetrics');
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
expect(scenario.request.queries[0].subtype).to.be('metrics');
});
});
describeMetricFindQuery('dimension_keys(AWS/EC2)', scenario => {
scenario.setup(() => {
scenario.requestResponse = [{text: 'InstanceId'}];
scenario.requestResponse = {
results: {
metricFindQuery: {
tables: [
{ rows: [['InstanceId', 'InstanceId']] }
]
}
}
};
});
it('should call __GetDimensions and return result', () => {
expect(scenario.result[0].text).to.be('InstanceId');
expect(scenario.request.data.action).to.be('__GetDimensions');
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
expect(scenario.request.queries[0].subtype).to.be('dimension_keys');
});
});
describeMetricFindQuery('dimension_values(us-east-1,AWS/EC2,CPUUtilization,InstanceId)', scenario => {
scenario.setup(() => {
scenario.requestResponse = {
Metrics: [
{
Namespace: 'AWS/EC2',
MetricName: 'CPUUtilization',
Dimensions: [
{
Name: 'InstanceId',
Value: 'i-12345678'
}
results: {
metricFindQuery: {
tables: [
{ rows: [['i-12345678', 'i-12345678']] }
]
}
]
}
};
});
it('should call __ListMetrics and return result', () => {
expect(scenario.result[0].text).to.be('i-12345678');
expect(scenario.request.data.action).to.be('ListMetrics');
expect(scenario.result[0].text).to.contain('i-12345678');
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
expect(scenario.request.queries[0].subtype).to.be('dimension_values');
});
});
it('should caclculate the correct period', function () {
var hourSec = 60 * 60;
var daySec = hourSec * 24;
var start = 1483196400;
var start = 1483196400 * 1000;
var testData: any[] = [
[{ period: 60 }, { namespace: 'AWS/EC2' }, {}, start, start + 3600, (hourSec * 3), 60],
[{ period: null }, { namespace: 'AWS/EC2' }, {}, start, start + 3600, (hourSec * 3), 300],
[{ period: 60 }, { namespace: 'AWS/ELB' }, {}, start, start + 3600, (hourSec * 3), 60],
[{ period: null }, { namespace: 'AWS/ELB' }, {}, start, start + 3600, (hourSec * 3), 60],
[{ period: 1 }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 1440 - 1, (hourSec * 3 - 1), 1],
[{ period: 1 }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3 - 1), 60],
[{ period: 60 }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3), 60],
[{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3 - 1), 60],
[{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3), 60],
[{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (daySec * 15), 60],
[{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (daySec * 63), 300],
[{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (daySec * 455), 3600]
[
{ period: 60, namespace: 'AWS/EC2' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3), 60
],
[
{ period: null, namespace: 'AWS/EC2' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3), 300
],
[
{ period: 60, namespace: 'AWS/ELB' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3), 60
],
[
{ period: null, namespace: 'AWS/ELB' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3), 60
],
[
{ period: 1, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + (1440 - 1) * 1000) } },
(hourSec * 3 - 1), 1
],
[
{ period: 1, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3 - 1), 60
],
[
{ period: 60, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3), 60
],
[
{ period: null, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3 - 1), 60
],
[
{ period: null, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(hourSec * 3), 60
],
[
{ period: null, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(daySec * 15), 60
],
[
{ period: null, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(daySec * 63), 300
],
[
{ period: null, namespace: 'CustomMetricsNamespace' },
{ range: { from: new Date(start), to: new Date(start + 3600 * 1000) } },
(daySec * 455), 3600
]
];
for (let t of testData) {
let target = t[0];
let query = t[1];
let options = t[2];
let start = t[3];
let end = t[4];
let now = start + t[5];
let expected = t[6];
let actual = ctx.ds.getPeriod(target, query, options, start, end, now);
let options = t[1];
let now = new Date(options.range.from.valueOf() + t[2] * 1000);
let expected = t[3];
let actual = ctx.ds.getPeriod(target, options, now);
expect(actual).to.be(expected);
}
});
describeMetricFindQuery('ec2_instance_attribute(us-east-1, Tags.Name, { "tag:team": [ "sysops" ] })', scenario => {
scenario.setup(() => {
scenario.requestResponse = {
Reservations: [
{
Instances: [
{
Tags: [
{ Key: 'InstanceId', Value: 'i-123456' },
{ Key: 'Name', Value: 'Sysops Dev Server' },
{ Key: 'env', Value: 'dev' },
{ Key: 'team', Value: 'sysops' }
]
},
{
Tags: [
{ Key: 'InstanceId', Value: 'i-789012' },
{ Key: 'Name', Value: 'Sysops Staging Server' },
{ Key: 'env', Value: 'staging' },
{ Key: 'team', Value: 'sysops' }
]
}
]
}
]
};
});
it('should return the "Name" tag for each instance', function() {
expect(scenario.result[0].text).to.be('Sysops Dev Server');
expect(scenario.result[1].text).to.be('Sysops Staging Server');
});
});
});

View File

@@ -1,3 +0,0 @@
declare var ElasticDatasource: any;
export {ElasticDatasource};

View File

@@ -1,372 +0,0 @@
define([
'angular',
'lodash',
'moment',
'app/core/utils/kbn',
'./query_builder',
'./index_pattern',
'./elastic_response',
'./query_ctrl',
],
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) {
'use strict';
ElasticResponse = ElasticResponse.ElasticResponse;
/** @ngInject */
function ElasticDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) {
this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials;
this.url = instanceSettings.url;
this.name = instanceSettings.name;
this.index = instanceSettings.index;
this.timeField = instanceSettings.jsonData.timeField;
this.esVersion = instanceSettings.jsonData.esVersion;
this.indexPattern = new IndexPattern(instanceSettings.index, instanceSettings.jsonData.interval);
this.interval = instanceSettings.jsonData.timeInterval;
this.queryBuilder = new ElasticQueryBuilder({
timeField: this.timeField,
esVersion: this.esVersion,
});
this._request = function(method, url, data) {
var options = {
url: this.url + "/" + url,
method: method,
data: data
};
if (this.basicAuth || this.withCredentials) {
options.withCredentials = true;
}
if (this.basicAuth) {
options.headers = {
"Authorization": this.basicAuth
};
}
return backendSrv.datasourceRequest(options);
};
this._get = function(url) {
var range = timeSrv.timeRange();
var index_list = this.indexPattern.getIndexList(range.from.valueOf(), range.to.valueOf());
if (_.isArray(index_list) && index_list.length) {
return this._request('GET', index_list[0] + url).then(function(results) {
results.data.$$config = results.config;
return results.data;
});
} else {
return this._request('GET', this.indexPattern.getIndexForToday() + url).then(function(results) {
results.data.$$config = results.config;
return results.data;
});
}
};
this._post = function(url, data) {
return this._request('POST', url, data).then(function(results) {
results.data.$$config = results.config;
return results.data;
});
};
this.annotationQuery = function(options) {
var annotation = options.annotation;
var timeField = annotation.timeField || '@timestamp';
var queryString = annotation.query || '*';
var tagsField = annotation.tagsField || 'tags';
var titleField = annotation.titleField || 'desc';
var textField = annotation.textField || null;
var range = {};
range[timeField]= {
from: options.range.from.valueOf(),
to: options.range.to.valueOf(),
format: "epoch_millis",
};
var queryInterpolated = templateSrv.replace(queryString, {}, 'lucene');
var query = {
"bool": {
"filter": [
{ "range": range },
{
"query_string": {
"query": queryInterpolated
}
}
]
}
};
var data = {
"query" : query,
"size": 10000
};
// fields field not supported on ES 5.x
if (this.esVersion < 5) {
data["fields"] = [timeField, "_source"];
}
var header = {search_type: "query_then_fetch", "ignore_unavailable": true};
// old elastic annotations had index specified on them
if (annotation.index) {
header.index = annotation.index;
} else {
header.index = this.indexPattern.getIndexList(options.range.from, options.range.to);
}
var payload = angular.toJson(header) + '\n' + angular.toJson(data) + '\n';
return this._post('_msearch', payload).then(function(res) {
var list = [];
var hits = res.responses[0].hits.hits;
var getFieldFromSource = function(source, fieldName) {
if (!fieldName) { return; }
var fieldNames = fieldName.split('.');
var fieldValue = source;
for (var i = 0; i < fieldNames.length; i++) {
fieldValue = fieldValue[fieldNames[i]];
if (!fieldValue) {
console.log('could not find field in annotation: ', fieldName);
return '';
}
}
if (_.isArray(fieldValue)) {
fieldValue = fieldValue.join(', ');
}
return fieldValue;
};
for (var i = 0; i < hits.length; i++) {
var source = hits[i]._source;
var time = source[timeField];
if (typeof hits[i].fields !== 'undefined') {
var fields = hits[i].fields;
if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
time = fields[timeField];
}
}
var event = {
annotation: annotation,
time: moment.utc(time).valueOf(),
title: getFieldFromSource(source, titleField),
tags: getFieldFromSource(source, tagsField),
text: getFieldFromSource(source, textField)
};
list.push(event);
}
return list;
});
};
this.testDatasource = function() {
timeSrv.setTime({ from: 'now-1m', to: 'now' }, true);
// validate that the index exist and has date field
return this.getFields({type: 'date'}).then(function(dateFields) {
var timeField = _.find(dateFields, {text: this.timeField});
if (!timeField) {
return { status: "error", message: "No date field named " + this.timeField + ' found' };
}
return { status: "success", message: "Index OK. Time field name OK." };
}.bind(this), function(err) {
console.log(err);
if (err.data && err.data.error) {
var message = angular.toJson(err.data.error);
if (err.data.error.reason) {
message = err.data.error.reason;
}
return { status: "error", message: message };
} else {
return { status: "error", message: err.status };
}
});
};
this.getQueryHeader = function(searchType, timeFrom, timeTo) {
var header = {search_type: searchType, "ignore_unavailable": true};
header.index = this.indexPattern.getIndexList(timeFrom, timeTo);
return angular.toJson(header);
};
this.query = function(options) {
var payload = "";
var target;
var sentTargets = [];
// add global adhoc filters to timeFilter
var adhocFilters = templateSrv.getAdhocFilters(this.name);
for (var i = 0; i < options.targets.length; i++) {
target = options.targets[i];
if (target.hide) {continue;}
var queryString = templateSrv.replace(target.query || '*', options.scopedVars, 'lucene');
var queryObj = this.queryBuilder.build(target, adhocFilters, queryString);
var esQuery = angular.toJson(queryObj);
var searchType = (queryObj.size === 0 && this.esVersion < 5) ? 'count' : 'query_then_fetch';
var header = this.getQueryHeader(searchType, options.range.from, options.range.to);
payload += header + '\n';
payload += esQuery + '\n';
sentTargets.push(target);
}
if (sentTargets.length === 0) {
return $q.when([]);
}
payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf());
payload = payload.replace(/\$timeTo/g, options.range.to.valueOf());
payload = templateSrv.replace(payload, options.scopedVars);
return this._post('_msearch', payload).then(function(res) {
return new ElasticResponse(sentTargets, res).getTimeSeries();
});
};
this.getFields = function(query) {
return this._get('/_mapping').then(function(result) {
var typeMap = {
'float': 'number',
'double': 'number',
'integer': 'number',
'long': 'number',
'date': 'date',
'string': 'string',
'text': 'string',
'scaled_float': 'number',
'nested': 'nested'
};
function shouldAddField(obj, key, query) {
if (key[0] === '_') {
return false;
}
if (!query.type) {
return true;
}
// equal query type filter, or via typemap translation
return query.type === obj.type || query.type === typeMap[obj.type];
}
// Store subfield names: [system, process, cpu, total] -> system.process.cpu.total
var fieldNameParts = [];
var fields = {};
function getFieldsRecursively(obj) {
for (var key in obj) {
var subObj = obj[key];
// Check mapping field for nested fields
if (_.isObject(subObj.properties)) {
fieldNameParts.push(key);
getFieldsRecursively(subObj.properties);
}
if (_.isObject(subObj.fields)) {
fieldNameParts.push(key);
getFieldsRecursively(subObj.fields);
}
if (_.isString(subObj.type)) {
var fieldName = fieldNameParts.concat(key).join('.');
// Hide meta-fields and check field type
if (shouldAddField(subObj, key, query)) {
fields[fieldName] = {
text: fieldName,
type: subObj.type
};
}
}
}
fieldNameParts.pop();
}
for (var indexName in result) {
var index = result[indexName];
if (index && index.mappings) {
var mappings = index.mappings;
for (var typeName in mappings) {
var properties = mappings[typeName].properties;
getFieldsRecursively(properties);
}
}
}
// transform to array
return _.map(fields, function(value) {
return value;
});
});
};
this.getTerms = function(queryDef) {
var range = timeSrv.timeRange();
var searchType = this.esVersion >= 5 ? 'query_then_fetch' : 'count' ;
var header = this.getQueryHeader(searchType, range.from, range.to);
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
esQuery = esQuery.replace(/\$timeFrom/g, range.from.valueOf());
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
esQuery = header + '\n' + esQuery + '\n';
return this._post('_msearch?search_type=' + searchType, esQuery).then(function(res) {
if (!res.responses[0].aggregations) {
return [];
}
var buckets = res.responses[0].aggregations["1"].buckets;
return _.map(buckets, function(bucket) {
return {
text: bucket.key_as_string || bucket.key,
value: bucket.key
};
});
});
};
this.metricFindQuery = function(query) {
query = angular.fromJson(query);
if (!query) {
return $q.when([]);
}
if (query.find === 'fields') {
query.field = templateSrv.replace(query.field, {}, 'lucene');
return this.getFields(query);
}
if (query.find === 'terms') {
query.query = templateSrv.replace(query.query || '*', {}, 'lucene');
return this.getTerms(query);
}
};
this.getTagKeys = function() {
return this.getFields({});
};
this.getTagValues = function(options) {
return this.getTerms({field: options.key, query: '*'});
};
}
return {
ElasticDatasource: ElasticDatasource
};
});

View File

@@ -0,0 +1,376 @@
///<reference path="../../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
import moment from 'moment';
import {ElasticQueryBuilder} from './query_builder';
import {IndexPattern} from './index_pattern';
import {ElasticResponse} from './elastic_response';
export class ElasticDatasource {
basicAuth: string;
withCredentials: boolean;
url: string;
name: string;
index: string;
timeField: string;
esVersion: number;
interval: string;
queryBuilder: ElasticQueryBuilder;
indexPattern: IndexPattern;
/** @ngInject */
constructor(instanceSettings, private $q, private backendSrv, private templateSrv, private timeSrv) {
this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials;
this.url = instanceSettings.url;
this.name = instanceSettings.name;
this.index = instanceSettings.index;
this.timeField = instanceSettings.jsonData.timeField;
this.esVersion = instanceSettings.jsonData.esVersion;
this.indexPattern = new IndexPattern(instanceSettings.index, instanceSettings.jsonData.interval);
this.interval = instanceSettings.jsonData.timeInterval;
this.queryBuilder = new ElasticQueryBuilder({
timeField: this.timeField,
esVersion: this.esVersion,
});
}
private request(method, url, data?) {
var options: any = {
url: this.url + "/" + url,
method: method,
data: data
};
if (this.basicAuth || this.withCredentials) {
options.withCredentials = true;
}
if (this.basicAuth) {
options.headers = {
"Authorization": this.basicAuth
};
}
return this.backendSrv.datasourceRequest(options);
}
private get(url) {
var range = this.timeSrv.timeRange();
var index_list = this.indexPattern.getIndexList(range.from.valueOf(), range.to.valueOf());
if (_.isArray(index_list) && index_list.length) {
return this.request('GET', index_list[0] + url).then(function(results) {
results.data.$$config = results.config;
return results.data;
});
} else {
return this.request('GET', this.indexPattern.getIndexForToday() + url).then(function(results) {
results.data.$$config = results.config;
return results.data;
});
}
}
private post(url, data) {
return this.request('POST', url, data).then(function(results) {
results.data.$$config = results.config;
return results.data;
});
}
annotationQuery(options) {
var annotation = options.annotation;
var timeField = annotation.timeField || '@timestamp';
var queryString = annotation.query || '*';
var tagsField = annotation.tagsField || 'tags';
var titleField = annotation.titleField || 'desc';
var textField = annotation.textField || null;
var range = {};
range[timeField]= {
from: options.range.from.valueOf(),
to: options.range.to.valueOf(),
format: "epoch_millis",
};
var queryInterpolated = this.templateSrv.replace(queryString, {}, 'lucene');
var query = {
"bool": {
"filter": [
{ "range": range },
{
"query_string": {
"query": queryInterpolated
}
}
]
}
};
var data = {
"query" : query,
"size": 10000
};
// fields field not supported on ES 5.x
if (this.esVersion < 5) {
data["fields"] = [timeField, "_source"];
}
var header: any = {search_type: "query_then_fetch", "ignore_unavailable": true};
// old elastic annotations had index specified on them
if (annotation.index) {
header.index = annotation.index;
} else {
header.index = this.indexPattern.getIndexList(options.range.from, options.range.to);
}
var payload = angular.toJson(header) + '\n' + angular.toJson(data) + '\n';
return this.post('_msearch', payload).then(res => {
var list = [];
var hits = res.responses[0].hits.hits;
var getFieldFromSource = function(source, fieldName) {
if (!fieldName) { return; }
var fieldNames = fieldName.split('.');
var fieldValue = source;
for (var i = 0; i < fieldNames.length; i++) {
fieldValue = fieldValue[fieldNames[i]];
if (!fieldValue) {
console.log('could not find field in annotation: ', fieldName);
return '';
}
}
if (_.isArray(fieldValue)) {
fieldValue = fieldValue.join(', ');
}
return fieldValue;
};
for (var i = 0; i < hits.length; i++) {
var source = hits[i]._source;
var time = source[timeField];
if (typeof hits[i].fields !== 'undefined') {
var fields = hits[i].fields;
if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
time = fields[timeField];
}
}
var event = {
annotation: annotation,
time: moment.utc(time).valueOf(),
title: getFieldFromSource(source, titleField),
tags: getFieldFromSource(source, tagsField),
text: getFieldFromSource(source, textField)
};
list.push(event);
}
return list;
});
};
testDatasource() {
this.timeSrv.setTime({ from: 'now-1m', to: 'now' }, true);
// validate that the index exist and has date field
return this.getFields({type: 'date'}).then(function(dateFields) {
var timeField = _.find(dateFields, {text: this.timeField});
if (!timeField) {
return { status: "error", message: "No date field named " + this.timeField + ' found' };
}
return { status: "success", message: "Index OK. Time field name OK." };
}.bind(this), function(err) {
console.log(err);
if (err.data && err.data.error) {
var message = angular.toJson(err.data.error);
if (err.data.error.reason) {
message = err.data.error.reason;
}
return { status: "error", message: message };
} else {
return { status: "error", message: err.status };
}
});
}
getQueryHeader(searchType, timeFrom, timeTo) {
return angular.toJson({
search_type: searchType,
"ignore_unavailable": true,
index: this.indexPattern.getIndexList(timeFrom, timeTo),
});
}
query(options) {
var payload = "";
var target;
var sentTargets = [];
// add global adhoc filters to timeFilter
var adhocFilters = this.templateSrv.getAdhocFilters(this.name);
for (var i = 0; i < options.targets.length; i++) {
target = options.targets[i];
if (target.hide) {continue;}
var queryString = this.templateSrv.replace(target.query || '*', options.scopedVars, 'lucene');
var queryObj = this.queryBuilder.build(target, adhocFilters, queryString);
var esQuery = angular.toJson(queryObj);
var searchType = (queryObj.size === 0 && this.esVersion < 5) ? 'count' : 'query_then_fetch';
var header = this.getQueryHeader(searchType, options.range.from, options.range.to);
payload += header + '\n';
payload += esQuery + '\n';
sentTargets.push(target);
}
if (sentTargets.length === 0) {
return this.$q.when([]);
}
payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf());
payload = payload.replace(/\$timeTo/g, options.range.to.valueOf());
payload = this.templateSrv.replace(payload, options.scopedVars);
return this.post('_msearch', payload).then(function(res) {
return new ElasticResponse(sentTargets, res).getTimeSeries();
});
};
getFields(query) {
return this.get('/_mapping').then(function(result) {
var typeMap = {
'float': 'number',
'double': 'number',
'integer': 'number',
'long': 'number',
'date': 'date',
'string': 'string',
'text': 'string',
'scaled_float': 'number',
'nested': 'nested'
};
function shouldAddField(obj, key, query) {
if (key[0] === '_') {
return false;
}
if (!query.type) {
return true;
}
// equal query type filter, or via typemap translation
return query.type === obj.type || query.type === typeMap[obj.type];
}
// Store subfield names: [system, process, cpu, total] -> system.process.cpu.total
var fieldNameParts = [];
var fields = {};
function getFieldsRecursively(obj) {
for (var key in obj) {
var subObj = obj[key];
// Check mapping field for nested fields
if (_.isObject(subObj.properties)) {
fieldNameParts.push(key);
getFieldsRecursively(subObj.properties);
}
if (_.isObject(subObj.fields)) {
fieldNameParts.push(key);
getFieldsRecursively(subObj.fields);
}
if (_.isString(subObj.type)) {
var fieldName = fieldNameParts.concat(key).join('.');
// Hide meta-fields and check field type
if (shouldAddField(subObj, key, query)) {
fields[fieldName] = {
text: fieldName,
type: subObj.type
};
}
}
}
fieldNameParts.pop();
}
for (var indexName in result) {
var index = result[indexName];
if (index && index.mappings) {
var mappings = index.mappings;
for (var typeName in mappings) {
var properties = mappings[typeName].properties;
getFieldsRecursively(properties);
}
}
}
// transform to array
return _.map(fields, function(value) {
return value;
});
});
}
getTerms(queryDef) {
var range = this.timeSrv.timeRange();
var searchType = this.esVersion >= 5 ? 'query_then_fetch' : 'count' ;
var header = this.getQueryHeader(searchType, range.from, range.to);
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
esQuery = esQuery.replace(/\$timeFrom/g, range.from.valueOf());
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
esQuery = header + '\n' + esQuery + '\n';
return this.post('_msearch?search_type=' + searchType, esQuery).then(function(res) {
if (!res.responses[0].aggregations) {
return [];
}
var buckets = res.responses[0].aggregations["1"].buckets;
return _.map(buckets, function(bucket) {
return {
text: bucket.key_as_string || bucket.key,
value: bucket.key
};
});
});
}
metricFindQuery(query) {
query = angular.fromJson(query);
if (!query) {
return this.$q.when([]);
}
if (query.find === 'fields') {
query.field = this.templateSrv.replace(query.field, {}, 'lucene');
return this.getFields(query);
}
if (query.find === 'terms') {
query.query = this.templateSrv.replace(query.query || '*', {}, 'lucene');
return this.getTerms(query);
}
}
getTagKeys() {
return this.getFields({});
}
getTagValues(options) {
return this.getTerms({field: options.key, query: '*'});
}
}

View File

@@ -1,130 +1,55 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import queryDef from "./query_def";
import * as queryDef from "./query_def";
import TableModel from 'app/core/table_model';
export function ElasticResponse(targets, response) {
this.targets = targets;
this.response = response;
}
export class ElasticResponse {
ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) {
var metric, y, i, newSeries, bucket, value;
constructor(private targets, private response) {
this.targets = targets;
this.response = response;
}
for (y = 0; y < target.metrics.length; y++) {
metric = target.metrics[y];
if (metric.hide) {
continue;
}
processMetrics(esAgg, target, seriesList, props) {
var metric, y, i, newSeries, bucket, value;
switch (metric.type) {
case 'count': {
newSeries = { datapoints: [], metric: 'count', props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
value = bucket.doc_count;
newSeries.datapoints.push([value, bucket.key]);
}
seriesList.push(newSeries);
break;
for (y = 0; y < target.metrics.length; y++) {
metric = target.metrics[y];
if (metric.hide) {
continue;
}
case 'percentiles': {
if (esAgg.buckets.length === 0) {
switch (metric.type) {
case 'count': {
newSeries = { datapoints: [], metric: 'count', props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
value = bucket.doc_count;
newSeries.datapoints.push([value, bucket.key]);
}
seriesList.push(newSeries);
break;
}
var firstBucket = esAgg.buckets[0];
var percentiles = firstBucket[metric.id].values;
for (var percentileName in percentiles) {
newSeries = {datapoints: [], metric: 'p' + percentileName, props: props, field: metric.field};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
var values = bucket[metric.id].values;
newSeries.datapoints.push([values[percentileName], bucket.key]);
}
seriesList.push(newSeries);
}
break;
}
case 'extended_stats': {
for (var statName in metric.meta) {
if (!metric.meta[statName]) {
continue;
case 'percentiles': {
if (esAgg.buckets.length === 0) {
break;
}
newSeries = {datapoints: [], metric: statName, props: props, field: metric.field};
var firstBucket = esAgg.buckets[0];
var percentiles = firstBucket[metric.id].values;
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
var stats = bucket[metric.id];
for (var percentileName in percentiles) {
newSeries = {datapoints: [], metric: 'p' + percentileName, props: props, field: metric.field};
// add stats that are in nested obj to top level obj
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
newSeries.datapoints.push([stats[statName], bucket.key]);
}
seriesList.push(newSeries);
}
break;
}
default: {
newSeries = { datapoints: [], metric: metric.type, field: metric.field, props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
value = bucket[metric.id];
if (value !== undefined) {
if (value.normalized_value) {
newSeries.datapoints.push([value.normalized_value, bucket.key]);
} else {
newSeries.datapoints.push([value.value, bucket.key]);
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
var values = bucket[metric.id].values;
newSeries.datapoints.push([values[percentileName], bucket.key]);
}
seriesList.push(newSeries);
}
}
seriesList.push(newSeries);
break;
}
}
}
};
ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, target, table, props) {
// add columns
if (table.columns.length === 0) {
for (let propKey of _.keys(props)) {
table.addColumn({text: propKey, filterable: true});
}
table.addColumn({text: aggDef.field, filterable: true});
}
// helper func to add values to value array
let addMetricValue = (values, metricName, value) => {
table.addColumn({text: metricName});
values.push(value);
};
for (let bucket of esAgg.buckets) {
let values = [];
for (let propValues of _.values(props)) {
values.push(propValues);
}
// add bucket key (value)
values.push(bucket.key);
for (let metric of target.metrics) {
switch (metric.type) {
case "count": {
addMetricValue(values, this._getMetricName(metric.type), bucket.doc_count);
break;
}
case 'extended_stats': {
@@ -133,228 +58,304 @@ ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, targe
continue;
}
var stats = bucket[metric.id];
// add stats that are in nested obj to top level obj
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
newSeries = {datapoints: [], metric: statName, props: props, field: metric.field};
addMetricValue(values, this._getMetricName(statName), stats[statName]);
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
var stats = bucket[metric.id];
// add stats that are in nested obj to top level obj
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
newSeries.datapoints.push([stats[statName], bucket.key]);
}
seriesList.push(newSeries);
}
break;
}
default: {
let metricName = this._getMetricName(metric.type);
let otherMetrics = _.filter(target.metrics, {type: metric.type});
default: {
newSeries = { datapoints: [], metric: metric.type, field: metric.field, props: props};
for (i = 0; i < esAgg.buckets.length; i++) {
bucket = esAgg.buckets[i];
value = bucket[metric.id];
if (value !== undefined) {
if (value.normalized_value) {
newSeries.datapoints.push([value.normalized_value, bucket.key]);
} else {
newSeries.datapoints.push([value.value, bucket.key]);
}
}
// if more of the same metric type include field field name in property
if (otherMetrics.length > 1) {
metricName += ' ' + metric.field;
}
addMetricValue(values, metricName, bucket[metric.id].value);
seriesList.push(newSeries);
break;
}
}
}
table.rows.push(values);
}
};
// This is quite complex
// neeed to recurise down the nested buckets to build series
ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, table, props, depth) {
var bucket, aggDef, esAgg, aggId;
var maxDepth = target.bucketAggs.length-1;
for (aggId in aggs) {
aggDef = _.find(target.bucketAggs, {id: aggId});
esAgg = aggs[aggId];
if (!aggDef) {
continue;
}
if (depth === maxDepth) {
if (aggDef.type === 'date_histogram') {
this.processMetrics(esAgg, target, seriesList, props);
} else {
this.processAggregationDocs(esAgg, aggDef, target, table, props);
}
} else {
for (var nameIndex in esAgg.buckets) {
bucket = esAgg.buckets[nameIndex];
props = _.clone(props);
if (bucket.key !== void 0) {
props[aggDef.field] = bucket.key;
} else {
props["filter"] = nameIndex;
}
if (bucket.key_as_string) {
props[aggDef.field] = bucket.key_as_string;
}
this.processBuckets(bucket, target, seriesList, table, props, depth+1);
processAggregationDocs(esAgg, aggDef, target, table, props) {
// add columns
if (table.columns.length === 0) {
for (let propKey of _.keys(props)) {
table.addColumn({text: propKey, filterable: true});
}
table.addColumn({text: aggDef.field, filterable: true});
}
}
};
ElasticResponse.prototype._getMetricName = function(metric) {
var metricDef = _.find(queryDef.metricAggTypes, {value: metric});
if (!metricDef) {
metricDef = _.find(queryDef.extendedStats, {value: metric});
}
return metricDef ? metricDef.text : metric;
};
ElasticResponse.prototype._getSeriesName = function(series, target, metricTypeCount) {
var metricName = this._getMetricName(series.metric);
if (target.alias) {
var regex = /\{\{([\s\S]+?)\}\}/g;
return target.alias.replace(regex, function(match, g1, g2) {
var group = g1 || g2;
if (group.indexOf('term ') === 0) { return series.props[group.substring(5)]; }
if (series.props[group] !== void 0) { return series.props[group]; }
if (group === 'metric') { return metricName; }
if (group === 'field') { return series.field; }
return match;
});
}
if (series.field && queryDef.isPipelineAgg(series.metric)) {
var appliedAgg = _.find(target.metrics, { id: series.field });
if (appliedAgg) {
metricName += ' ' + queryDef.describeMetric(appliedAgg);
} else {
metricName = 'Unset';
}
} else if (series.field) {
metricName += ' ' + series.field;
}
var propKeys = _.keys(series.props);
if (propKeys.length === 0) {
return metricName;
}
var name = '';
for (var propName in series.props) {
name += series.props[propName] + ' ';
}
if (metricTypeCount === 1) {
return name.trim();
}
return name.trim() + ' ' + metricName;
};
ElasticResponse.prototype.nameSeries = function(seriesList, target) {
var metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length;
var fieldNameCount = _.uniq(_.map(seriesList, 'field')).length;
for (var i = 0; i < seriesList.length; i++) {
var series = seriesList[i];
series.target = this._getSeriesName(series, target, metricTypeCount, fieldNameCount);
}
};
ElasticResponse.prototype.processHits = function(hits, seriesList) {
var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total, filterable: true};
var propName, hit, doc, i;
for (i = 0; i < hits.hits.length; i++) {
hit = hits.hits[i];
doc = {
_id: hit._id,
_type: hit._type,
_index: hit._index
// helper func to add values to value array
let addMetricValue = (values, metricName, value) => {
table.addColumn({text: metricName});
values.push(value);
};
if (hit._source) {
for (propName in hit._source) {
doc[propName] = hit._source[propName];
}
}
for (let bucket of esAgg.buckets) {
let values = [];
for (propName in hit.fields) {
doc[propName] = hit.fields[propName];
}
series.datapoints.push(doc);
}
seriesList.push(series);
};
ElasticResponse.prototype.trimDatapoints = function(aggregations, target) {
var histogram = _.find(target.bucketAggs, { type: 'date_histogram'});
var shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges;
if (shouldDropFirstAndLast) {
var trim = histogram.settings.trimEdges;
for (var prop in aggregations) {
var points = aggregations[prop];
if (points.datapoints.length > trim * 2) {
points.datapoints = points.datapoints.slice(trim, points.datapoints.length - trim);
}
}
}
};
ElasticResponse.prototype.getErrorFromElasticResponse = function(response, err) {
var result: any = {};
result.data = JSON.stringify(err, null, 4);
if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
result.message = err.root_cause[0].reason;
} else {
result.message = err.reason || 'Unkown elatic error response';
}
if (response.$$config) {
result.config = response.$$config;
}
return result;
};
ElasticResponse.prototype.getTimeSeries = function() {
var seriesList = [];
for (var i = 0; i < this.response.responses.length; i++) {
var response = this.response.responses[i];
if (response.error) {
throw this.getErrorFromElasticResponse(this.response, response.error);
}
if (response.hits && response.hits.hits.length > 0) {
this.processHits(response.hits, seriesList);
}
if (response.aggregations) {
var aggregations = response.aggregations;
var target = this.targets[i];
var tmpSeriesList = [];
var table = new TableModel();
this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0);
this.trimDatapoints(tmpSeriesList, target);
this.nameSeries(tmpSeriesList, target);
for (var y = 0; y < tmpSeriesList.length; y++) {
seriesList.push(tmpSeriesList[y]);
for (let propValues of _.values(props)) {
values.push(propValues);
}
if (table.rows.length > 0) {
seriesList.push(table);
// add bucket key (value)
values.push(bucket.key);
for (let metric of target.metrics) {
switch (metric.type) {
case "count": {
addMetricValue(values, this.getMetricName(metric.type), bucket.doc_count);
break;
}
case 'extended_stats': {
for (var statName in metric.meta) {
if (!metric.meta[statName]) {
continue;
}
var stats = bucket[metric.id];
// add stats that are in nested obj to top level obj
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
addMetricValue(values, this.getMetricName(statName), stats[statName]);
}
break;
}
default: {
let metricName = this.getMetricName(metric.type);
let otherMetrics = _.filter(target.metrics, {type: metric.type});
// if more of the same metric type include field field name in property
if (otherMetrics.length > 1) {
metricName += ' ' + metric.field;
}
addMetricValue(values, metricName, bucket[metric.id].value);
break;
}
}
}
table.rows.push(values);
}
}
// This is quite complex
// neeed to recurise down the nested buckets to build series
processBuckets(aggs, target, seriesList, table, props, depth) {
var bucket, aggDef, esAgg, aggId;
var maxDepth = target.bucketAggs.length-1;
for (aggId in aggs) {
aggDef = _.find(target.bucketAggs, {id: aggId});
esAgg = aggs[aggId];
if (!aggDef) {
continue;
}
if (depth === maxDepth) {
if (aggDef.type === 'date_histogram') {
this.processMetrics(esAgg, target, seriesList, props);
} else {
this.processAggregationDocs(esAgg, aggDef, target, table, props);
}
} else {
for (var nameIndex in esAgg.buckets) {
bucket = esAgg.buckets[nameIndex];
props = _.clone(props);
if (bucket.key !== void 0) {
props[aggDef.field] = bucket.key;
} else {
props["filter"] = nameIndex;
}
if (bucket.key_as_string) {
props[aggDef.field] = bucket.key_as_string;
}
this.processBuckets(bucket, target, seriesList, table, props, depth+1);
}
}
}
}
return { data: seriesList };
};
private getMetricName(metric) {
var metricDef = _.find(queryDef.metricAggTypes, {value: metric});
if (!metricDef) {
metricDef = _.find(queryDef.extendedStats, {value: metric});
}
return metricDef ? metricDef.text : metric;
}
private getSeriesName(series, target, metricTypeCount) {
var metricName = this.getMetricName(series.metric);
if (target.alias) {
var regex = /\{\{([\s\S]+?)\}\}/g;
return target.alias.replace(regex, function(match, g1, g2) {
var group = g1 || g2;
if (group.indexOf('term ') === 0) { return series.props[group.substring(5)]; }
if (series.props[group] !== void 0) { return series.props[group]; }
if (group === 'metric') { return metricName; }
if (group === 'field') { return series.field; }
return match;
});
}
if (series.field && queryDef.isPipelineAgg(series.metric)) {
var appliedAgg = _.find(target.metrics, { id: series.field });
if (appliedAgg) {
metricName += ' ' + queryDef.describeMetric(appliedAgg);
} else {
metricName = 'Unset';
}
} else if (series.field) {
metricName += ' ' + series.field;
}
var propKeys = _.keys(series.props);
if (propKeys.length === 0) {
return metricName;
}
var name = '';
for (var propName in series.props) {
name += series.props[propName] + ' ';
}
if (metricTypeCount === 1) {
return name.trim();
}
return name.trim() + ' ' + metricName;
}
nameSeries(seriesList, target) {
var metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length;
for (var i = 0; i < seriesList.length; i++) {
var series = seriesList[i];
series.target = this.getSeriesName(series, target, metricTypeCount);
}
}
processHits(hits, seriesList) {
var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total, filterable: true};
var propName, hit, doc, i;
for (i = 0; i < hits.hits.length; i++) {
hit = hits.hits[i];
doc = {
_id: hit._id,
_type: hit._type,
_index: hit._index
};
if (hit._source) {
for (propName in hit._source) {
doc[propName] = hit._source[propName];
}
}
for (propName in hit.fields) {
doc[propName] = hit.fields[propName];
}
series.datapoints.push(doc);
}
seriesList.push(series);
}
trimDatapoints(aggregations, target) {
var histogram = _.find(target.bucketAggs, { type: 'date_histogram'});
var shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges;
if (shouldDropFirstAndLast) {
var trim = histogram.settings.trimEdges;
for (var prop in aggregations) {
var points = aggregations[prop];
if (points.datapoints.length > trim * 2) {
points.datapoints = points.datapoints.slice(trim, points.datapoints.length - trim);
}
}
}
}
getErrorFromElasticResponse(response, err) {
var result: any = {};
result.data = JSON.stringify(err, null, 4);
if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
result.message = err.root_cause[0].reason;
} else {
result.message = err.reason || 'Unkown elatic error response';
}
if (response.$$config) {
result.config = response.$$config;
}
return result;
}
getTimeSeries() {
var seriesList = [];
for (var i = 0; i < this.response.responses.length; i++) {
var response = this.response.responses[i];
if (response.error) {
throw this.getErrorFromElasticResponse(this.response, response.error);
}
if (response.hits && response.hits.hits.length > 0) {
this.processHits(response.hits, seriesList);
}
if (response.aggregations) {
var aggregations = response.aggregations;
var target = this.targets[i];
var tmpSeriesList = [];
var table = new TableModel();
this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0);
this.trimDatapoints(tmpSeriesList, target);
this.nameSeries(tmpSeriesList, target);
for (var y = 0; y < tmpSeriesList.length; y++) {
seriesList.push(tmpSeriesList[y]);
}
if (table.rows.length > 0) {
seriesList.push(table);
}
}
}
return { data: seriesList };
}
}

View File

@@ -1,2 +0,0 @@
declare var test: any;
export default test;

View File

@@ -1,48 +0,0 @@
define([
'lodash',
'moment',
],
function (_, moment) {
'use strict';
function IndexPattern(pattern, interval) {
this.pattern = pattern;
this.interval = interval;
}
IndexPattern.intervalMap = {
"Hourly": { startOf: 'hour', amount: 'hours'},
"Daily": { startOf: 'day', amount: 'days'},
"Weekly": { startOf: 'isoWeek', amount: 'weeks'},
"Monthly": { startOf: 'month', amount: 'months'},
"Yearly": { startOf: 'year', amount: 'years'},
};
IndexPattern.prototype.getIndexForToday = function() {
if (this.interval) {
return moment.utc().format(this.pattern);
} else {
return this.pattern;
}
};
IndexPattern.prototype.getIndexList = function(from, to) {
if (!this.interval) {
return this.pattern;
}
var intervalInfo = IndexPattern.intervalMap[this.interval];
var start = moment(from).utc().startOf(intervalInfo.startOf);
var end = moment(to).utc().startOf(intervalInfo.startOf).valueOf();
var indexList = [];
while (start <= end) {
indexList.push(start.format(this.pattern));
start.add(1, intervalInfo.amount);
}
return indexList;
};
return IndexPattern;
});

View File

@@ -0,0 +1,41 @@
import moment from 'moment';
const intervalMap = {
"Hourly": { startOf: 'hour', amount: 'hours'},
"Daily": { startOf: 'day', amount: 'days'},
"Weekly": { startOf: 'isoWeek', amount: 'weeks'},
"Monthly": { startOf: 'month', amount: 'months'},
"Yearly": { startOf: 'year', amount: 'years'},
};
export class IndexPattern {
constructor(private pattern, private interval: string | null) { }
getIndexForToday() {
if (this.interval) {
return moment.utc().format(this.pattern);
} else {
return this.pattern;
}
};
getIndexList(from, to) {
if (!this.interval) {
return this.pattern;
}
var intervalInfo = intervalMap[this.interval];
var start = moment(from).utc().startOf(intervalInfo.startOf);
var endEpoch = moment(to).utc().startOf(intervalInfo.startOf).valueOf();
var indexList = [];
while (start.valueOf() <= endEpoch) {
indexList.push(start.format(this.pattern));
start.add(1, intervalInfo.amount);
}
return indexList;
}
}

View File

@@ -1,2 +0,0 @@
declare var test: any;
export default test;

View File

@@ -1,15 +1,15 @@
define([
'./query_def',
],
function (queryDef) {
'use strict';
import * as queryDef from './query_def';
function ElasticQueryBuilder(options) {
export class ElasticQueryBuilder {
timeField: string;
esVersion: number;
constructor(options) {
this.timeField = options.timeField;
this.esVersion = options.esVersion;
}
ElasticQueryBuilder.prototype.getRangeFilter = function() {
getRangeFilter() {
var filter = {};
filter[this.timeField] = {
gte: "$timeFrom",
@@ -18,9 +18,9 @@ function (queryDef) {
};
return filter;
};
}
ElasticQueryBuilder.prototype.buildTermsAgg = function(aggDef, queryNode, target) {
buildTermsAgg(aggDef, queryNode, target) {
var metricRef, metric, y;
queryNode.terms = { "field": aggDef.field };
@@ -57,10 +57,10 @@ function (queryDef) {
}
return queryNode;
};
}
ElasticQueryBuilder.prototype.getDateHistogramAgg = function(aggDef) {
var esAgg = {};
getDateHistogramAgg(aggDef) {
var esAgg: any = {};
var settings = aggDef.settings || {};
esAgg.interval = settings.interval;
esAgg.field = this.timeField;
@@ -77,10 +77,10 @@ function (queryDef) {
}
return esAgg;
};
}
ElasticQueryBuilder.prototype.getHistogramAgg = function(aggDef) {
var esAgg = {};
getHistogramAgg(aggDef) {
var esAgg: any = {};
var settings = aggDef.settings || {};
esAgg.interval = settings.interval;
esAgg.field = aggDef.field;
@@ -90,9 +90,9 @@ function (queryDef) {
esAgg.missing = settings.missing;
}
return esAgg;
};
}
ElasticQueryBuilder.prototype.getFiltersAgg = function(aggDef) {
getFiltersAgg(aggDef) {
var filterObj = {};
for (var i = 0; i < aggDef.settings.filters.length; i++) {
var query = aggDef.settings.filters[i].query;
@@ -107,9 +107,9 @@ function (queryDef) {
}
return filterObj;
};
}
ElasticQueryBuilder.prototype.documentQuery = function(query, size) {
documentQuery(query, size) {
query.size = size;
query.sort = {};
query.sort[this.timeField] = {order: 'desc', unmapped_type: 'boolean'};
@@ -126,9 +126,9 @@ function (queryDef) {
query.docvalue_fields = [this.timeField];
}
return query;
};
}
ElasticQueryBuilder.prototype.addAdhocFilters = function(query, adhocFilters) {
addAdhocFilters(query, adhocFilters) {
if (!adhocFilters) {
return;
}
@@ -142,7 +142,7 @@ function (queryDef) {
queryCondition = {};
queryCondition[filter.key] = {query: filter.value};
switch(filter.operator){
switch (filter.operator){
case "=":
if (!query.query.bool.must) { query.query.bool.must = []; }
query.query.bool.must.push({match_phrase: queryCondition});
@@ -169,7 +169,7 @@ function (queryDef) {
}
};
ElasticQueryBuilder.prototype.build = function(target, adhocFilters, queryString) {
build(target, adhocFilters?, queryString?) {
// make sure query has defaults;
target.metrics = target.metrics || [{ type: 'count', id: '1' }];
target.dsType = 'elasticsearch';
@@ -213,7 +213,7 @@ function (queryDef) {
var aggDef = target.bucketAggs[i];
var esAgg = {};
switch(aggDef.type) {
switch (aggDef.type) {
case 'date_histogram': {
esAgg["date_histogram"] = this.getDateHistogramAgg(aggDef);
break;
@@ -273,10 +273,10 @@ function (queryDef) {
}
return query;
};
}
ElasticQueryBuilder.prototype.getTermsQuery = function(queryDef) {
var query = {
getTermsQuery(queryDef) {
var query: any = {
"size": 0,
"query": {
"bool": {
@@ -311,7 +311,5 @@ function (queryDef) {
}
};
return query;
};
return ElasticQueryBuilder;
});
}
}

View File

@@ -5,7 +5,7 @@ import './metric_agg';
import angular from 'angular';
import _ from 'lodash';
import queryDef from './query_def';
import * as queryDef from './query_def';
import {QueryCtrl} from 'app/plugins/sdk';
export class ElasticQueryCtrl extends QueryCtrl {

View File

@@ -1,2 +0,0 @@
declare var test: any;
export default test;

View File

@@ -1,199 +0,0 @@
define([
'lodash'
],
function (_) {
'use strict';
return {
metricAggTypes: [
{text: "Count", value: 'count', requiresField: false},
{text: "Average", value: 'avg', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Sum", value: 'sum', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Max", value: 'max', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Min", value: 'min', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Extended Stats", value: 'extended_stats', requiresField: true, supportsMissing: true, supportsInlineScript: true},
{text: "Percentiles", value: 'percentiles', requiresField: true, supportsMissing: true, supportsInlineScript: true},
{text: "Unique Count", value: "cardinality", requiresField: true, supportsMissing: true},
{text: "Moving Average", value: 'moving_avg', requiresField: false, isPipelineAgg: true, minVersion: 2},
{text: "Derivative", value: 'derivative', requiresField: false, isPipelineAgg: true, minVersion: 2 },
{text: "Raw Document", value: "raw_document", requiresField: false}
],
bucketAggTypes: [
{text: "Terms", value: 'terms', requiresField: true},
{text: "Filters", value: 'filters' },
{text: "Geo Hash Grid", value: 'geohash_grid', requiresField: true},
{text: "Date Histogram", value: 'date_histogram', requiresField: true},
{text: "Histogram", value: 'histogram', requiresField: true},
],
orderByOptions: [
{text: "Doc Count", value: '_count' },
{text: "Term value", value: '_term' },
],
orderOptions: [
{text: "Top", value: 'desc' },
{text: "Bottom", value: 'asc' },
],
sizeOptions: [
{text: "No limit", value: '0' },
{text: "1", value: '1' },
{text: "2", value: '2' },
{text: "3", value: '3' },
{text: "5", value: '5' },
{text: "10", value: '10' },
{text: "15", value: '15' },
{text: "20", value: '20' },
],
extendedStats: [
{text: 'Avg', value: 'avg'},
{text: 'Min', value: 'min'},
{text: 'Max', value: 'max'},
{text: 'Sum', value: 'sum'},
{text: 'Count', value: 'count'},
{text: 'Std Dev', value: 'std_deviation'},
{text: 'Std Dev Upper', value: 'std_deviation_bounds_upper'},
{text: 'Std Dev Lower', value: 'std_deviation_bounds_lower'},
],
intervalOptions: [
{text: 'auto', value: 'auto'},
{text: '10s', value: '10s'},
{text: '1m', value: '1m'},
{text: '5m', value: '5m'},
{text: '10m', value: '10m'},
{text: '20m', value: '20m'},
{text: '1h', value: '1h'},
{text: '1d', value: '1d'},
],
movingAvgModelOptions: [
{text: 'Simple', value: 'simple'},
{text: 'Linear', value: 'linear'},
{text: 'Exponentially Weighted', value: 'ewma'},
{text: 'Holt Linear', value: 'holt'},
{text: 'Holt Winters', value: 'holt_winters'},
],
pipelineOptions: {
'moving_avg' : [
{text: 'window', default: 5},
{text: 'model', default: 'simple'},
{text: 'predict', default: undefined},
{text: 'minimize', default: false},
],
'derivative': [
{text: 'unit', default: undefined},
]
},
movingAvgModelSettings: {
'simple' : [],
'linear' : [],
'ewma' : [
{text: "Alpha", value: "alpha", default: undefined}],
'holt' : [
{text: "Alpha", value: "alpha", default: undefined},
{text: "Beta", value: "beta", default: undefined},
],
'holt_winters' : [
{text: "Alpha", value: "alpha", default: undefined},
{text: "Beta", value: "beta", default: undefined},
{text: "Gamma", value: "gamma", default: undefined},
{text: "Period", value: "period", default: undefined},
{text: "Pad", value: "pad", default: undefined, isCheckbox: true},
],
},
getMetricAggTypes: function(esVersion) {
return _.filter(this.metricAggTypes, function(f) {
if (f.minVersion) {
return f.minVersion <= esVersion;
} else {
return true;
}
});
},
getPipelineOptions: function(metric) {
if (!this.isPipelineAgg(metric.type)) {
return [];
}
return this.pipelineOptions[metric.type];
},
isPipelineAgg: function(metricType) {
if (metricType) {
var po = this.pipelineOptions[metricType];
return po !== null && po !== undefined;
}
return false;
},
getPipelineAggOptions: function(targets) {
var self = this;
var result = [];
_.each(targets.metrics, function(metric) {
if (!self.isPipelineAgg(metric.type)) {
result.push({text: self.describeMetric(metric), value: metric.id });
}
});
return result;
},
getMovingAvgSettings: function(model, filtered) {
var filteredResult = [];
if (filtered) {
_.each(this.movingAvgModelSettings[model], function(setting) {
if (!(setting.isCheckbox)) {
filteredResult.push(setting);
}
});
return filteredResult;
}
return this.movingAvgModelSettings[model];
},
getOrderByOptions: function(target) {
var self = this;
var metricRefs = [];
_.each(target.metrics, function(metric) {
if (metric.type !== 'count') {
metricRefs.push({text: self.describeMetric(metric), value: metric.id});
}
});
return this.orderByOptions.concat(metricRefs);
},
describeOrder: function(order) {
var def = _.find(this.orderOptions, {value: order});
return def.text;
},
describeMetric: function(metric) {
var def = _.find(this.metricAggTypes, {value: metric.type});
return def.text + ' ' + metric.field;
},
describeOrderBy: function(orderBy, target) {
var def = _.find(this.orderByOptions, {value: orderBy});
if (def) {
return def.text;
}
var metric = _.find(target.metrics, {id: orderBy});
if (metric) {
return this.describeMetric(metric);
} else {
return "metric not found";
}
},
};
});

View File

@@ -0,0 +1,191 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
export const metricAggTypes = [
{text: "Count", value: 'count', requiresField: false},
{text: "Average", value: 'avg', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Sum", value: 'sum', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Max", value: 'max', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Min", value: 'min', requiresField: true, supportsInlineScript: true, supportsMissing: true},
{text: "Extended Stats", value: 'extended_stats', requiresField: true, supportsMissing: true, supportsInlineScript: true},
{text: "Percentiles", value: 'percentiles', requiresField: true, supportsMissing: true, supportsInlineScript: true},
{text: "Unique Count", value: "cardinality", requiresField: true, supportsMissing: true},
{text: "Moving Average", value: 'moving_avg', requiresField: false, isPipelineAgg: true, minVersion: 2},
{text: "Derivative", value: 'derivative', requiresField: false, isPipelineAgg: true, minVersion: 2 },
{text: "Raw Document", value: "raw_document", requiresField: false}
];
export const bucketAggTypes = [
{text: "Terms", value: 'terms', requiresField: true},
{text: "Filters", value: 'filters' },
{text: "Geo Hash Grid", value: 'geohash_grid', requiresField: true},
{text: "Date Histogram", value: 'date_histogram', requiresField: true},
{text: "Histogram", value: 'histogram', requiresField: true},
];
export const orderByOptions = [
{text: "Doc Count", value: '_count' },
{text: "Term value", value: '_term' },
];
export const orderOptions = [
{text: "Top", value: 'desc' },
{text: "Bottom", value: 'asc' },
];
export const sizeOptions = [
{text: "No limit", value: '0' },
{text: "1", value: '1' },
{text: "2", value: '2' },
{text: "3", value: '3' },
{text: "5", value: '5' },
{text: "10", value: '10' },
{text: "15", value: '15' },
{text: "20", value: '20' },
];
export const extendedStats = [
{text: 'Avg', value: 'avg'},
{text: 'Min', value: 'min'},
{text: 'Max', value: 'max'},
{text: 'Sum', value: 'sum'},
{text: 'Count', value: 'count'},
{text: 'Std Dev', value: 'std_deviation'},
{text: 'Std Dev Upper', value: 'std_deviation_bounds_upper'},
{text: 'Std Dev Lower', value: 'std_deviation_bounds_lower'},
];
export const intervalOptions = [
{text: 'auto', value: 'auto'},
{text: '10s', value: '10s'},
{text: '1m', value: '1m'},
{text: '5m', value: '5m'},
{text: '10m', value: '10m'},
{text: '20m', value: '20m'},
{text: '1h', value: '1h'},
{text: '1d', value: '1d'},
];
export const movingAvgModelOptions = [
{text: 'Simple', value: 'simple'},
{text: 'Linear', value: 'linear'},
{text: 'Exponentially Weighted', value: 'ewma'},
{text: 'Holt Linear', value: 'holt'},
{text: 'Holt Winters', value: 'holt_winters'},
];
export const pipelineOptions = {
'moving_avg' : [
{text: 'window', default: 5},
{text: 'model', default: 'simple'},
{text: 'predict', default: undefined},
{text: 'minimize', default: false},
],
'derivative': [
{text: 'unit', default: undefined},
]
};
export const movingAvgModelSettings = {
'simple' : [],
'linear' : [],
'ewma' : [
{text: "Alpha", value: "alpha", default: undefined}],
'holt' : [
{text: "Alpha", value: "alpha", default: undefined},
{text: "Beta", value: "beta", default: undefined},
],
'holt_winters' : [
{text: "Alpha", value: "alpha", default: undefined},
{text: "Beta", value: "beta", default: undefined},
{text: "Gamma", value: "gamma", default: undefined},
{text: "Period", value: "period", default: undefined},
{text: "Pad", value: "pad", default: undefined, isCheckbox: true},
],
};
export function getMetricAggTypes(esVersion) {
return _.filter(metricAggTypes, function(f) {
if (f.minVersion) {
return f.minVersion <= esVersion;
} else {
return true;
}
});
}
export function getPipelineOptions(metric) {
if (!isPipelineAgg(metric.type)) {
return [];
}
return pipelineOptions[metric.type];
}
export function isPipelineAgg(metricType) {
if (metricType) {
var po = pipelineOptions[metricType];
return po !== null && po !== undefined;
}
return false;
}
export function getPipelineAggOptions(targets) {
var result = [];
_.each(targets.metrics, function(metric) {
if (!isPipelineAgg(metric.type)) {
result.push({text: describeMetric(metric), value: metric.id });
}
});
return result;
}
export function getMovingAvgSettings(model, filtered) {
var filteredResult = [];
if (filtered) {
_.each(movingAvgModelSettings[model], function(setting) {
if (!(setting.isCheckbox)) {
filteredResult.push(setting);
}
});
return filteredResult;
}
return movingAvgModelSettings[model];
}
export function getOrderByOptions(target) {
var metricRefs = [];
_.each(target.metrics, function(metric) {
if (metric.type !== 'count') {
metricRefs.push({text: describeMetric(metric), value: metric.id});
}
});
return orderByOptions.concat(metricRefs);
}
export function describeOrder(order) {
var def = _.find(orderOptions, {value: order});
return def.text;
}
export function describeMetric(metric) {
var def = _.find(metricAggTypes, {value: metric.type});
return def.text + ' ' + metric.field;
}
export function describeOrderBy(orderBy, target) {
var def = _.find(orderByOptions, {value: orderBy});
if (def) {
return def.text;
}
var metric = _.find(target.metrics, {id: orderBy});
if (metric) {
return describeMetric(metric);
} else {
return "metric not found";
}
};

View File

@@ -2,7 +2,7 @@
import {describe, it, expect} from 'test/lib/common';
import moment from 'moment';
import IndexPattern from '../index_pattern';
import {IndexPattern} from '../index_pattern';
describe('IndexPattern', function() {
@@ -19,7 +19,7 @@ describe('IndexPattern', function() {
describe('no interval', function() {
it('should return correct index', function() {
var pattern = new IndexPattern('my-metrics');
var pattern = new IndexPattern('my-metrics', null);
var from = new Date(2015, 4, 30, 1, 2, 3);
var to = new Date(2015, 5, 1, 12, 5 , 6);
expect(pattern.getIndexList(from, to)).to.eql('my-metrics');

View File

@@ -1,6 +1,6 @@
import {describe, beforeEach, it, expect} from 'test/lib/common';
import ElasticQueryBuilder from '../query_builder';
import {ElasticQueryBuilder} from '../query_builder';
describe('ElasticQueryBuilder', function() {
var builder;

View File

@@ -1,7 +1,7 @@
import {describe, it, expect} from 'test/lib/common';
import queryDef from '../query_def';
import * as queryDef from '../query_def';
describe('ElasticQueryDef', function() {

View File

@@ -1,5 +1,3 @@
///<reference path="../../../headers/common.d.ts" />
import './add_graphite_func';
import './func_editor';

View File

@@ -9,7 +9,7 @@ describe('graphiteDatasource', function() {
beforeEach(angularMocks.module('grafana.core'));
beforeEach(angularMocks.module('grafana.services'));
beforeEach(ctx.providePhase(['backendSrv']));
beforeEach(ctx.providePhase(['backendSrv', 'templateSrv']));
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
ctx.$q = $q;
ctx.$httpBackend = $httpBackend;

View File

@@ -1,5 +1,4 @@
import '../query_ctrl';
import 'app/core/services/segment_srv';
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';

View File

@@ -1,106 +0,0 @@
// jshint ignore: start
// jscs: disable
ace.define("ace/mode/sql_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var SqlHighlightRules = function() {
var keywords = (
"select|insert|update|delete|from|where|and|or|group|by|order|limit|offset|having|as|case|" +
"when|else|end|type|left|right|join|on|outer|desc|asc|union|create|table|primary|key|if|" +
"foreign|not|references|default|null|inner|cross|natural|database|drop|grant"
);
var builtinConstants = (
"true|false"
);
var builtinFunctions = (
"avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|format|" +
"coalesce|ifnull|isnull|nvl"
);
var dataTypes = (
"int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|" +
"money|real|number|integer"
);
var keywordMapper = this.createKeywordMapper({
"support.function": builtinFunctions,
"keyword": keywords,
"constant.language": builtinConstants,
"storage.type": dataTypes
}, "identifier", true);
this.$rules = {
"start" : [ {
token : "comment",
regex : "--.*$"
}, {
token : "comment",
start : "/\\*",
end : "\\*/"
}, {
token : "string", // " string
regex : '".*?"'
}, {
token : "string", // ' string
regex : "'.*?'"
}, {
token : "string", // ` string (apache drill)
regex : "`.*?`"
}, {
token : "constant.numeric", // float
regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
}, {
token : keywordMapper,
regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
}, {
token : "keyword.operator",
regex : "\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="
}, {
token : "paren.lparen",
regex : "[\\(]"
}, {
token : "paren.rparen",
regex : "[\\)]"
}, {
token : "text",
regex : "\\s+"
} ]
};
this.normalizeRules();
};
oop.inherits(SqlHighlightRules, TextHighlightRules);
exports.SqlHighlightRules = SqlHighlightRules;
});
ace.define("ace/mode/sql",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sql_highlight_rules"], function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var SqlHighlightRules = require("./sql_highlight_rules").SqlHighlightRules;
var Mode = function() {
this.HighlightRules = SqlHighlightRules;
this.$behaviour = this.$defaultBehaviour;
};
oop.inherits(Mode, TextMode);
(function() {
this.lineCommentStart = "--";
this.$id = "ace/mode/sql";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@@ -1,3 +1,3 @@
declare var OpenTsDatasource: any;
export {OpenTsDatasource};
export default OpenTsDatasource;

View File

@@ -472,7 +472,5 @@ function (angular, _, dateMath) {
}
}
return {
OpenTsDatasource: OpenTsDatasource
};
return OpenTsDatasource;
});

View File

@@ -1,4 +1,4 @@
import {OpenTsDatasource} from './datasource';
import OpenTsDatasource from './datasource';
import {OpenTsQueryCtrl} from './query_ctrl';
import {OpenTsConfigCtrl} from './config_ctrl';

View File

@@ -3,8 +3,5 @@
<span class="gf-form-label width-13">OpenTSDB metrics query</span>
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.target' placeholder="events.eventname"></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-13">Show Global Annotations?</span>
<editor-checkbox text="" model="ctrl.annotation.isGlobal"></editor-checkbox>
</div>
<gf-form-switch class="gf-form" label="Show Global Annotations?" checked="ctrl.annotation.isGlobal" switch-class="max-width-6" label-class="width-13"></gf-form-switch>
</div>

View File

@@ -1,6 +1,6 @@
import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
import helpers from 'test/specs/helpers';
import {OpenTsDatasource} from "../datasource";
import OpenTsDatasource from "../datasource";
describe('opentsdb', function() {
var ctx = new helpers.ServiceTestContext();

View File

@@ -1,10 +1,9 @@
///<reference path="../../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
import {QueryCtrl} from 'app/plugins/sdk';
import {PromCompleter} from './completer';
import './mode-prometheus';
import './snippets/prometheus';
class PrometheusQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
@@ -44,12 +43,6 @@ class PrometheusQueryCtrl extends QueryCtrl {
getCompleter(query) {
return new PromCompleter(this.datasource);
// console.log('getquery);
// return this.datasource.performSuggestQuery(query).then(res => {
// return res.map(item => {
// return {word: item, type: 'metric'};
// });
// });
}
getDefaultFormat() {