mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'elasticsearch-filtering'
This commit is contained in:
commit
781dd25c82
@ -3,9 +3,11 @@ export default class TableModel {
|
|||||||
columns: any[];
|
columns: any[];
|
||||||
rows: any[];
|
rows: any[];
|
||||||
type: string;
|
type: string;
|
||||||
|
columnMap: any;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.columns = [];
|
this.columns = [];
|
||||||
|
this.columnMap = {};
|
||||||
this.rows = [];
|
this.rows = [];
|
||||||
this.type = 'table';
|
this.type = 'table';
|
||||||
}
|
}
|
||||||
@ -36,4 +38,11 @@ export default class TableModel {
|
|||||||
this.columns[options.col].desc = false;
|
this.columns[options.col].desc = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addColumn(col) {
|
||||||
|
if (!this.columnMap[col.text]) {
|
||||||
|
this.columns.push(col);
|
||||||
|
this.columnMap[col.text] = col;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,10 @@ export class AdHocFiltersCtrl {
|
|||||||
removeTagFilterSegment: any;
|
removeTagFilterSegment: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private uiSegmentSrv, private datasourceSrv, private $q, private templateSrv, private $rootScope) {
|
constructor(private uiSegmentSrv, private datasourceSrv, private $q, private variableSrv, private $scope, private $rootScope) {
|
||||||
this.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove filter --'});
|
this.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove filter --'});
|
||||||
this.buildSegmentModel();
|
this.buildSegmentModel();
|
||||||
|
this.$rootScope.onAppEvent('template-variable-value-updated', this.buildSegmentModel.bind(this), $scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSegmentModel() {
|
buildSegmentModel() {
|
||||||
@ -141,8 +142,7 @@ export class AdHocFiltersCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.variable.setFilters(filters);
|
this.variable.setFilters(filters);
|
||||||
this.$rootScope.$emit('template-variable-value-updated');
|
this.variableSrv.variableUpdated(this.variable, true);
|
||||||
this.$rootScope.$broadcast('refresh');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,10 +22,7 @@ export class SubmenuCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
variableUpdated(variable) {
|
variableUpdated(variable) {
|
||||||
this.variableSrv.variableUpdated(variable).then(() => {
|
this.variableSrv.variableUpdated(variable, true);
|
||||||
this.$rootScope.$emit('template-variable-value-updated');
|
|
||||||
this.$rootScope.$broadcast('refresh');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openEditView(editview) {
|
openEditView(editview) {
|
||||||
|
@ -55,9 +55,8 @@ export class VariableEditorCtrl {
|
|||||||
|
|
||||||
$scope.add = function() {
|
$scope.add = function() {
|
||||||
if ($scope.isValid()) {
|
if ($scope.isValid()) {
|
||||||
$scope.variables.push($scope.current);
|
variableSrv.addVariable($scope.current);
|
||||||
$scope.update();
|
$scope.update();
|
||||||
$scope.dashboard.updateSubmenuVisibility();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,9 +113,8 @@ export class VariableEditorCtrl {
|
|||||||
$scope.duplicate = function(variable) {
|
$scope.duplicate = function(variable) {
|
||||||
var clone = _.cloneDeep(variable.getSaveModel());
|
var clone = _.cloneDeep(variable.getSaveModel());
|
||||||
$scope.current = variableSrv.createVariableFromModel(clone);
|
$scope.current = variableSrv.createVariableFromModel(clone);
|
||||||
$scope.variables.push($scope.current);
|
|
||||||
$scope.current.name = 'copy_of_'+variable.name;
|
$scope.current.name = 'copy_of_'+variable.name;
|
||||||
$scope.dashboard.updateSubmenuVisibility();
|
$scope.variableSrv.addVariable($scope.current);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.update = function() {
|
$scope.update = function() {
|
||||||
@ -150,9 +148,7 @@ export class VariableEditorCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeVariable = function(variable) {
|
$scope.removeVariable = function(variable) {
|
||||||
var index = _.indexOf($scope.variables, variable);
|
variableSrv.removeVariable(variable);
|
||||||
$scope.variables.splice(index, 1);
|
|
||||||
$scope.dashboard.updateSubmenuVisibility();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ describe('VariableSrv', function() {
|
|||||||
ctx.variableSrv.init({
|
ctx.variableSrv.init({
|
||||||
templating: {list: []},
|
templating: {list: []},
|
||||||
events: new Emitter(),
|
events: new Emitter(),
|
||||||
|
updateSubmenuVisibility: sinon.stub(),
|
||||||
});
|
});
|
||||||
ctx.$rootScope.$digest();
|
ctx.$rootScope.$digest();
|
||||||
}));
|
}));
|
||||||
@ -41,7 +42,9 @@ describe('VariableSrv', function() {
|
|||||||
ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources);
|
ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources);
|
||||||
|
|
||||||
|
|
||||||
scenario.variable = ctx.variableSrv.addVariable(scenario.variableModel);
|
scenario.variable = ctx.variableSrv.createVariableFromModel(scenario.variableModel);
|
||||||
|
ctx.variableSrv.addVariable(scenario.variable);
|
||||||
|
|
||||||
ctx.variableSrv.updateOptions(scenario.variable);
|
ctx.variableSrv.updateOptions(scenario.variable);
|
||||||
ctx.$rootScope.$digest();
|
ctx.$rootScope.$digest();
|
||||||
});
|
});
|
||||||
|
@ -90,17 +90,24 @@ export class VariableSrv {
|
|||||||
return variable;
|
return variable;
|
||||||
}
|
}
|
||||||
|
|
||||||
addVariable(model) {
|
addVariable(variable) {
|
||||||
var variable = this.createVariableFromModel(model);
|
|
||||||
this.variables.push(variable);
|
this.variables.push(variable);
|
||||||
return variable;
|
this.templateSrv.updateTemplateData();
|
||||||
|
this.dashboard.updateSubmenuVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeVariable(variable) {
|
||||||
|
var index = _.indexOf(this.variables, variable);
|
||||||
|
this.variables.splice(index, 1);
|
||||||
|
this.templateSrv.updateTemplateData();
|
||||||
|
this.dashboard.updateSubmenuVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOptions(variable) {
|
updateOptions(variable) {
|
||||||
return variable.updateOptions();
|
return variable.updateOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
variableUpdated(variable) {
|
variableUpdated(variable, emitChangeEvents?) {
|
||||||
// if there is a variable lock ignore cascading update because we are in a boot up scenario
|
// if there is a variable lock ignore cascading update because we are in a boot up scenario
|
||||||
if (variable.initLock) {
|
if (variable.initLock) {
|
||||||
return this.$q.when();
|
return this.$q.when();
|
||||||
@ -117,7 +124,12 @@ export class VariableSrv {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.$q.all(promises);
|
return this.$q.all(promises).then(() => {
|
||||||
|
if (emitChangeEvents) {
|
||||||
|
this.$rootScope.$emit('template-variable-value-updated');
|
||||||
|
this.$rootScope.$broadcast('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOptionsForCurrentValue(variable) {
|
selectOptionsForCurrentValue(variable) {
|
||||||
@ -218,6 +230,28 @@ export class VariableSrv {
|
|||||||
// update url
|
// update url
|
||||||
this.$location.search(params);
|
this.$location.search(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAdhocFilter(options) {
|
||||||
|
var variable = _.find(this.variables, {type: 'adhoc', datasource: options.datasource});
|
||||||
|
if (!variable) {
|
||||||
|
variable = this.createVariableFromModel({name: 'Filters', type: 'adhoc', datasource: options.datasource});
|
||||||
|
this.addVariable(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
let filters = variable.filters;
|
||||||
|
let filter = _.find(filters, {key: options.key, value: options.value});
|
||||||
|
|
||||||
|
if (!filter) {
|
||||||
|
filter = {key: options.key, value: options.value};
|
||||||
|
filters.push(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.operator = options.operator;
|
||||||
|
|
||||||
|
variable.setFilters(filters);
|
||||||
|
this.variableUpdated(variable, true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.service('variableSrv', VariableSrv);
|
coreModule.service('variableSrv', VariableSrv);
|
||||||
|
@ -11,6 +11,8 @@ define([
|
|||||||
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) {
|
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
ElasticResponse = ElasticResponse.ElasticResponse;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ElasticDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) {
|
function ElasticDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) {
|
||||||
this.basicAuth = instanceSettings.basicAuth;
|
this.basicAuth = instanceSettings.basicAuth;
|
||||||
@ -270,10 +272,17 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
|||||||
var subObj = obj[key];
|
var subObj = obj[key];
|
||||||
|
|
||||||
// Check mapping field for nested fields
|
// Check mapping field for nested fields
|
||||||
if (subObj.hasOwnProperty('properties')) {
|
if (_.isObject(subObj.properties)) {
|
||||||
fieldNameParts.push(key);
|
fieldNameParts.push(key);
|
||||||
getFieldsRecursively(subObj.properties);
|
getFieldsRecursively(subObj.properties);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (_.isObject(subObj.fields)) {
|
||||||
|
fieldNameParts.push(key);
|
||||||
|
getFieldsRecursively(subObj.fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isString(subObj.type)) {
|
||||||
var fieldName = fieldNameParts.concat(key).join('.');
|
var fieldName = fieldNameParts.concat(key).join('.');
|
||||||
|
|
||||||
// Hide meta-fields and check field type
|
// Hide meta-fields and check field type
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
declare var test: any;
|
|
||||||
export default test;
|
|
@ -1,350 +0,0 @@
|
|||||||
define([
|
|
||||||
"lodash",
|
|
||||||
"./query_def"
|
|
||||||
],
|
|
||||||
function (_, queryDef) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function ElasticResponse(targets, response) {
|
|
||||||
this.targets = targets;
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) {
|
|
||||||
var metric, y, i, newSeries, bucket, value;
|
|
||||||
|
|
||||||
for (y = 0; y < target.metrics.length; y++) {
|
|
||||||
metric = target.metrics[y];
|
|
||||||
if (metric.hide) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
case 'percentiles': {
|
|
||||||
if (esAgg.buckets.length === 0) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
newSeries = {datapoints: [], metric: statName, props: props, field: metric.field};
|
|
||||||
|
|
||||||
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: {
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
seriesList.push(newSeries);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, target, docs, props) {
|
|
||||||
var metric, y, i, bucket, metricName, doc;
|
|
||||||
|
|
||||||
for (i = 0; i < esAgg.buckets.length; i++) {
|
|
||||||
bucket = esAgg.buckets[i];
|
|
||||||
doc = _.defaults({}, props);
|
|
||||||
doc[aggDef.field] = bucket.key;
|
|
||||||
|
|
||||||
for (y = 0; y < target.metrics.length; y++) {
|
|
||||||
metric = target.metrics[y];
|
|
||||||
|
|
||||||
switch(metric.type) {
|
|
||||||
case "count": {
|
|
||||||
metricName = this._getMetricName(metric.type);
|
|
||||||
doc[metricName] = 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;
|
|
||||||
|
|
||||||
metricName = this._getMetricName(statName);
|
|
||||||
doc[metricName] = stats[statName];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
metricName = this._getMetricName(metric.type);
|
|
||||||
var 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
doc[metricName] = bucket[metric.id].value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
docs.push(doc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is quite complex
|
|
||||||
// neeed to recurise down the nested buckets to build series
|
|
||||||
ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, docs, 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, docs, 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, docs, props, depth+1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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};
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = {};
|
|
||||||
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 docs = [];
|
|
||||||
|
|
||||||
this.processBuckets(aggregations, target, tmpSeriesList, docs, {}, 0);
|
|
||||||
this.trimDatapoints(tmpSeriesList, target);
|
|
||||||
this.nameSeries(tmpSeriesList, target);
|
|
||||||
|
|
||||||
for (var y = 0; y < tmpSeriesList.length; y++) {
|
|
||||||
seriesList.push(tmpSeriesList[y]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seriesList.length === 0 && docs.length > 0) {
|
|
||||||
seriesList.push({target: 'docs', type: 'docs', datapoints: docs});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { data: seriesList };
|
|
||||||
};
|
|
||||||
|
|
||||||
return ElasticResponse;
|
|
||||||
});
|
|
360
public/app/plugins/datasource/elasticsearch/elastic_response.ts
Normal file
360
public/app/plugins/datasource/elasticsearch/elastic_response.ts
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import queryDef from "./query_def";
|
||||||
|
import TableModel from 'app/core/table_model';
|
||||||
|
|
||||||
|
export function ElasticResponse(targets, response) {
|
||||||
|
this.targets = targets;
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) {
|
||||||
|
var metric, y, i, newSeries, bucket, value;
|
||||||
|
|
||||||
|
for (y = 0; y < target.metrics.length; y++) {
|
||||||
|
metric = target.metrics[y];
|
||||||
|
if (metric.hide) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
case 'percentiles': {
|
||||||
|
if (esAgg.buckets.length === 0) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSeries = {datapoints: [], metric: statName, props: props, field: metric.field};
|
||||||
|
|
||||||
|
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: {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
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': {
|
||||||
|
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
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table.rows.length > 0) {
|
||||||
|
seriesList.push(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: seriesList };
|
||||||
|
};
|
||||||
|
|
@ -129,7 +129,10 @@ describe('ElasticDatasource', function() {
|
|||||||
'@timestamp': {type: 'date'},
|
'@timestamp': {type: 'date'},
|
||||||
beat: {
|
beat: {
|
||||||
properties: {
|
properties: {
|
||||||
name: {type: 'string'},
|
name: {
|
||||||
|
fields: {raw: {type: 'keyword'}},
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
hostname: {type: 'string'},
|
hostname: {type: 'string'},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -169,6 +172,7 @@ describe('ElasticDatasource', function() {
|
|||||||
var fields = _.map(fieldObjects, 'text');
|
var fields = _.map(fieldObjects, 'text');
|
||||||
expect(fields).to.eql([
|
expect(fields).to.eql([
|
||||||
'@timestamp',
|
'@timestamp',
|
||||||
|
'beat.name.raw',
|
||||||
'beat.name',
|
'beat.name',
|
||||||
'beat.hostname',
|
'beat.hostname',
|
||||||
'system.cpu.system',
|
'system.cpu.system',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import {describe, beforeEach, it, expect} from 'test/lib/common';
|
import {describe, beforeEach, it, expect} from 'test/lib/common';
|
||||||
import ElasticResponse from '../elastic_response';
|
import {ElasticResponse} from '../elastic_response';
|
||||||
|
|
||||||
describe('ElasticResponse', function() {
|
describe('ElasticResponse', function() {
|
||||||
var targets;
|
var targets;
|
||||||
@ -387,10 +387,9 @@ describe('ElasticResponse', function() {
|
|||||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return docs with byte and count', function() {
|
it('should return table with byte and count', function() {
|
||||||
expect(result.data[0].datapoints.length).to.be(3);
|
expect(result.data[0].rows.length).to.be(3);
|
||||||
expect(result.data[0].datapoints[0].Count).to.be(1);
|
expect(result.data[0].columns).to.eql([{text: 'bytes', filterable: true}, {text: 'Count'}]);
|
||||||
expect(result.data[0].datapoints[0].bytes).to.be(1000);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -530,14 +529,14 @@ describe('ElasticResponse', function() {
|
|||||||
|
|
||||||
it('should return table', function() {
|
it('should return table', function() {
|
||||||
expect(result.data.length).to.be(1);
|
expect(result.data.length).to.be(1);
|
||||||
expect(result.data[0].type).to.be('docs');
|
expect(result.data[0].type).to.be('table');
|
||||||
expect(result.data[0].datapoints.length).to.be(2);
|
expect(result.data[0].rows.length).to.be(2);
|
||||||
expect(result.data[0].datapoints[0].host).to.be("server-1");
|
expect(result.data[0].rows[0][0]).to.be("server-1");
|
||||||
expect(result.data[0].datapoints[0].Average).to.be(1000);
|
expect(result.data[0].rows[0][1]).to.be(1000);
|
||||||
expect(result.data[0].datapoints[0].Count).to.be(369);
|
expect(result.data[0].rows[0][2]).to.be(369);
|
||||||
|
|
||||||
expect(result.data[0].datapoints[1].host).to.be("server-2");
|
expect(result.data[0].rows[1][0]).to.be("server-2");
|
||||||
expect(result.data[0].datapoints[1].Average).to.be(2000);
|
expect(result.data[0].rows[1][1]).to.be(2000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -573,10 +572,9 @@ describe('ElasticResponse', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should include field in metric name', function() {
|
it('should include field in metric name', function() {
|
||||||
expect(result.data[0].type).to.be('docs');
|
expect(result.data[0].type).to.be('table');
|
||||||
expect(result.data[0].datapoints[0].Average).to.be(undefined);
|
expect(result.data[0].rows[0][1]).to.be(1000);
|
||||||
expect(result.data[0].datapoints[0]['Average test']).to.be(1000);
|
expect(result.data[0].rows[0][2]).to.be(3000);
|
||||||
expect(result.data[0].datapoints[0]['Average test2']).to.be(3000);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,9 +17,17 @@
|
|||||||
<span>{{column.text}}</span>
|
<span>{{column.text}}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form" ng-show="editor.canSetColumns">
|
||||||
<metric-segment segment="editor.addColumnSegment" get-options="editor.getColumnOptions()" on-change="editor.addColumn()"></metric-segment>
|
<metric-segment segment="editor.addColumnSegment" get-options="editor.getColumnOptions()" on-change="editor.addColumn()"></metric-segment>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="gf-form" ng-hide="editor.canSetColumns">
|
||||||
|
<label class="gf-form-label">
|
||||||
|
Auto
|
||||||
|
<info-popover mode="right-normal" ng-if="editor.columnsHelpMessage">
|
||||||
|
{{editor.columnsHelpMessage}}
|
||||||
|
</info-popover>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ export class TablePanelEditorCtrl {
|
|||||||
fontSizes: any;
|
fontSizes: any;
|
||||||
addColumnSegment: any;
|
addColumnSegment: any;
|
||||||
getColumnNames: any;
|
getColumnNames: any;
|
||||||
|
canSetColumns: boolean;
|
||||||
|
columnsHelpMessage: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, private $q, private uiSegmentSrv) {
|
constructor($scope, private $q, private uiSegmentSrv) {
|
||||||
@ -24,8 +26,27 @@ export class TablePanelEditorCtrl {
|
|||||||
this.panel = this.panelCtrl.panel;
|
this.panel = this.panelCtrl.panel;
|
||||||
this.transformers = transformers;
|
this.transformers = transformers;
|
||||||
this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
|
this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
|
||||||
|
|
||||||
this.addColumnSegment = uiSegmentSrv.newPlusButton();
|
this.addColumnSegment = uiSegmentSrv.newPlusButton();
|
||||||
|
this.updateTransformHints();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTransformHints() {
|
||||||
|
this.canSetColumns = false;
|
||||||
|
this.columnsHelpMessage = '';
|
||||||
|
|
||||||
|
switch (this.panel.transform) {
|
||||||
|
case "timeseries_aggregations": {
|
||||||
|
this.canSetColumns = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "json": {
|
||||||
|
this.canSetColumns = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "table": {
|
||||||
|
this.columnsHelpMessage = "Columns and their order are determined by the data query";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumnOptions() {
|
getColumnOptions() {
|
||||||
@ -57,6 +78,7 @@ export class TablePanelEditorCtrl {
|
|||||||
this.panel.columns.push({text: 'Avg', value: 'avg'});
|
this.panel.columns.push({text: 'Avg', value: 'avg'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateTransformHints();
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +50,9 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize) {
|
constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize, private variableSrv) {
|
||||||
super($scope, $injector);
|
super($scope, $injector);
|
||||||
|
|
||||||
this.pageIndex = 0;
|
this.pageIndex = 0;
|
||||||
|
|
||||||
if (this.panel.styles === void 0) {
|
if (this.panel.styles === void 0) {
|
||||||
@ -223,10 +224,24 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
|||||||
selector: '[data-link-tooltip]'
|
selector: '[data-link-tooltip]'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function addFilterClicked(e) {
|
||||||
|
let filterData = $(e.currentTarget).data();
|
||||||
|
var options = {
|
||||||
|
datasource: panel.datasource,
|
||||||
|
key: data.columns[filterData.column].text,
|
||||||
|
value: data.rows[filterData.row][filterData.column],
|
||||||
|
operator: filterData.operator,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrl.variableSrv.setAdhocFilter(options);
|
||||||
|
}
|
||||||
|
|
||||||
elem.on('click', '.table-panel-page-link', switchPage);
|
elem.on('click', '.table-panel-page-link', switchPage);
|
||||||
|
elem.on('click', '.table-panel-filter-link', addFilterClicked);
|
||||||
|
|
||||||
var unbindDestroy = scope.$on('$destroy', function() {
|
var unbindDestroy = scope.$on('$destroy', function() {
|
||||||
elem.off('click', '.table-panel-page-link');
|
elem.off('click', '.table-panel-page-link');
|
||||||
|
elem.off('click', '.table-panel-filter-link');
|
||||||
unbindDestroy();
|
unbindDestroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -140,9 +140,12 @@ export class TableRenderer {
|
|||||||
|
|
||||||
renderCell(columnIndex, rowIndex, value, addWidthHack = false) {
|
renderCell(columnIndex, rowIndex, value, addWidthHack = false) {
|
||||||
value = this.formatColumnValue(columnIndex, value);
|
value = this.formatColumnValue(columnIndex, value);
|
||||||
|
|
||||||
|
var column = this.table.columns[columnIndex];
|
||||||
var style = '';
|
var style = '';
|
||||||
var cellClasses = [];
|
var cellClasses = [];
|
||||||
var cellClass = '';
|
var cellClass = '';
|
||||||
|
|
||||||
if (this.colorState.cell) {
|
if (this.colorState.cell) {
|
||||||
style = ' style="background-color:' + this.colorState.cell + ';color: white"';
|
style = ' style="background-color:' + this.colorState.cell + ';color: white"';
|
||||||
this.colorState.cell = null;
|
this.colorState.cell = null;
|
||||||
@ -161,26 +164,25 @@ export class TableRenderer {
|
|||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
style = ' style="display:none;"';
|
style = ' style="display:none;"';
|
||||||
this.table.columns[columnIndex].hidden = true;
|
column.hidden = true;
|
||||||
} else {
|
} else {
|
||||||
this.table.columns[columnIndex].hidden = false;
|
column.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var columnStyle = this.table.columns[columnIndex].style;
|
if (column.style && column.style.preserveFormat) {
|
||||||
if (columnStyle && columnStyle.preserveFormat) {
|
|
||||||
cellClasses.push("table-panel-cell-pre");
|
cellClasses.push("table-panel-cell-pre");
|
||||||
}
|
}
|
||||||
|
|
||||||
var columnHtml = value + widthHack;
|
var columnHtml = widthHack + value;
|
||||||
|
|
||||||
if (columnStyle && columnStyle.link) {
|
if (column.style && column.style.link) {
|
||||||
// Render cell as link
|
// Render cell as link
|
||||||
var scopedVars = this.renderRowVariables(rowIndex);
|
var scopedVars = this.renderRowVariables(rowIndex);
|
||||||
scopedVars['__cell'] = { value: value };
|
scopedVars['__cell'] = { value: value };
|
||||||
|
|
||||||
var cellLink = this.templateSrv.replace(columnStyle.linkUrl, scopedVars);
|
var cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars);
|
||||||
var cellLinkTooltip = this.templateSrv.replace(columnStyle.linkTooltip, scopedVars);
|
var cellLinkTooltip = this.templateSrv.replace(column.style.linkTooltip, scopedVars);
|
||||||
var cellTarget = columnStyle.linkTargetBlank ? '_blank' : '';
|
var cellTarget = column.style.linkTargetBlank ? '_blank' : '';
|
||||||
|
|
||||||
cellClasses.push("table-panel-cell-link");
|
cellClasses.push("table-panel-cell-link");
|
||||||
columnHtml = `
|
columnHtml = `
|
||||||
@ -190,6 +192,19 @@ export class TableRenderer {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (column.filterable) {
|
||||||
|
cellClasses.push("table-panel-cell-filterable");
|
||||||
|
columnHtml += `
|
||||||
|
<a class="table-panel-filter-link" data-link-tooltip data-original-title="Filter out value" data-placement="bottom"
|
||||||
|
data-row="${rowIndex}" data-column="${columnIndex}" data-operator="!=">
|
||||||
|
<i class="fa fa-search-minus"></i>
|
||||||
|
</a>
|
||||||
|
<a class="table-panel-filter-link" data-link-tooltip data-original-title="Filter for value" data-placement="bottom"
|
||||||
|
data-row="${rowIndex}" data-column="${columnIndex}" data-operator="=">
|
||||||
|
<i class="fa fa-search-plus"></i>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
if (cellClasses.length) {
|
if (cellClasses.length) {
|
||||||
cellClass = ' class="' + cellClasses.join(' ') + '"';
|
cellClass = ' class="' + cellClasses.join(' ') + '"';
|
||||||
}
|
}
|
||||||
|
@ -185,8 +185,16 @@ transformers['json'] = {
|
|||||||
},
|
},
|
||||||
transform: function(data, panel, model) {
|
transform: function(data, panel, model) {
|
||||||
var i, y, z;
|
var i, y, z;
|
||||||
for (i = 0; i < panel.columns.length; i++) {
|
|
||||||
model.columns.push({text: panel.columns[i].text});
|
for (let column of panel.columns) {
|
||||||
|
var tableCol: any = {text: column.text};
|
||||||
|
|
||||||
|
// if filterable data then set columns to filterable
|
||||||
|
if (data.length > 0 && data[0].filterable) {
|
||||||
|
tableCol.filterable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.columns.push(tableCol);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.columns.length === 0) {
|
if (model.columns.length === 0) {
|
||||||
|
@ -91,9 +91,23 @@
|
|||||||
&.cell-highlighted:hover {
|
&.cell-highlighted:hover {
|
||||||
background-color: $tight-form-func-bg;
|
background-color: $tight-form-func-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.table-panel-filter-link {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-panel-filter-link {
|
||||||
|
visibility: hidden;
|
||||||
|
color: $text-color-weak;
|
||||||
|
float: right;
|
||||||
|
display: block;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.table-panel-header-bg {
|
.table-panel-header-bg {
|
||||||
background: $grafanaListAccent;
|
background: $grafanaListAccent;
|
||||||
border-top: 2px solid $body-bg;
|
border-top: 2px solid $body-bg;
|
||||||
|
Loading…
Reference in New Issue
Block a user