Commit 781dd25c by Torkel Ödegaard

Merge branch 'elasticsearch-filtering'

parents 1507c02e f979e958
...@@ -3,9 +3,11 @@ export default class TableModel { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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() { ...@@ -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() { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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([ ...@@ -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 ...@@ -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
......
define([ ///<reference path="../../../headers/common.d.ts" />
"lodash",
"./query_def" import _ from 'lodash';
], import queryDef from "./query_def";
function (_, queryDef) { import TableModel from 'app/core/table_model';
'use strict';
export function ElasticResponse(targets, response) {
function ElasticResponse(targets, response) {
this.targets = targets; this.targets = targets;
this.response = response; this.response = response;
} }
ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) { ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) {
var metric, y, i, newSeries, bucket, value; var metric, y, i, newSeries, bucket, value;
for (y = 0; y < target.metrics.length; y++) { for (y = 0; y < target.metrics.length; y++) {
...@@ -19,7 +18,7 @@ function (_, queryDef) { ...@@ -19,7 +18,7 @@ function (_, queryDef) {
continue; continue;
} }
switch(metric.type) { switch (metric.type) {
case 'count': { case 'count': {
newSeries = { datapoints: [], metric: 'count', props: props}; newSeries = { datapoints: [], metric: 'count', props: props};
for (i = 0; i < esAgg.buckets.length; i++) { for (i = 0; i < esAgg.buckets.length; i++) {
...@@ -95,23 +94,37 @@ function (_, queryDef) { ...@@ -95,23 +94,37 @@ function (_, queryDef) {
} }
} }
} }
};
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);
}; };
ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, target, docs, props) { for (let bucket of esAgg.buckets) {
var metric, y, i, bucket, metricName, doc; let values = [];
for (i = 0; i < esAgg.buckets.length; i++) { for (let propValues of _.values(props)) {
bucket = esAgg.buckets[i]; values.push(propValues);
doc = _.defaults({}, props); }
doc[aggDef.field] = bucket.key;
for (y = 0; y < target.metrics.length; y++) { // add bucket key (value)
metric = target.metrics[y]; values.push(bucket.key);
switch(metric.type) { for (let metric of target.metrics) {
switch (metric.type) {
case "count": { case "count": {
metricName = this._getMetricName(metric.type); addMetricValue(values, this._getMetricName(metric.type), bucket.doc_count);
doc[metricName] = bucket.doc_count;
break; break;
} }
case 'extended_stats': { case 'extended_stats': {
...@@ -125,33 +138,32 @@ function (_, queryDef) { ...@@ -125,33 +138,32 @@ function (_, queryDef) {
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper; stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower; stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
metricName = this._getMetricName(statName); addMetricValue(values, this._getMetricName(statName), stats[statName]);
doc[metricName] = stats[statName];
} }
break; break;
} }
default: { default: {
metricName = this._getMetricName(metric.type); let metricName = this._getMetricName(metric.type);
var otherMetrics = _.filter(target.metrics, {type: metric.type}); let otherMetrics = _.filter(target.metrics, {type: metric.type});
// if more of the same metric type include field field name in property // if more of the same metric type include field field name in property
if (otherMetrics.length > 1) { if (otherMetrics.length > 1) {
metricName += ' ' + metric.field; metricName += ' ' + metric.field;
} }
doc[metricName] = bucket[metric.id].value; addMetricValue(values, metricName, bucket[metric.id].value);
break; break;
} }
} }
} }
docs.push(doc); table.rows.push(values);
} }
}; };
// This is quite complex // This is quite complex
// neeed to recurise down the nested buckets to build series // neeed to recurise down the nested buckets to build series
ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, docs, props, depth) { ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, table, props, depth) {
var bucket, aggDef, esAgg, aggId; var bucket, aggDef, esAgg, aggId;
var maxDepth = target.bucketAggs.length-1; var maxDepth = target.bucketAggs.length-1;
...@@ -167,7 +179,7 @@ function (_, queryDef) { ...@@ -167,7 +179,7 @@ function (_, queryDef) {
if (aggDef.type === 'date_histogram') { if (aggDef.type === 'date_histogram') {
this.processMetrics(esAgg, target, seriesList, props); this.processMetrics(esAgg, target, seriesList, props);
} else { } else {
this.processAggregationDocs(esAgg, aggDef, target, docs, props); this.processAggregationDocs(esAgg, aggDef, target, table, props);
} }
} else { } else {
for (var nameIndex in esAgg.buckets) { for (var nameIndex in esAgg.buckets) {
...@@ -181,22 +193,22 @@ function (_, queryDef) { ...@@ -181,22 +193,22 @@ function (_, queryDef) {
if (bucket.key_as_string) { if (bucket.key_as_string) {
props[aggDef.field] = bucket.key_as_string; props[aggDef.field] = bucket.key_as_string;
} }
this.processBuckets(bucket, target, seriesList, docs, props, depth+1); this.processBuckets(bucket, target, seriesList, table, props, depth+1);
} }
} }
} }
}; };
ElasticResponse.prototype._getMetricName = function(metric) { ElasticResponse.prototype._getMetricName = function(metric) {
var metricDef = _.find(queryDef.metricAggTypes, {value: metric}); var metricDef = _.find(queryDef.metricAggTypes, {value: metric});
if (!metricDef) { if (!metricDef) {
metricDef = _.find(queryDef.extendedStats, {value: metric}); metricDef = _.find(queryDef.extendedStats, {value: metric});
} }
return metricDef ? metricDef.text : metric; return metricDef ? metricDef.text : metric;
}; };
ElasticResponse.prototype._getSeriesName = function(series, target, metricTypeCount) { ElasticResponse.prototype._getSeriesName = function(series, target, metricTypeCount) {
var metricName = this._getMetricName(series.metric); var metricName = this._getMetricName(series.metric);
if (target.alias) { if (target.alias) {
...@@ -240,9 +252,9 @@ function (_, queryDef) { ...@@ -240,9 +252,9 @@ function (_, queryDef) {
} }
return name.trim() + ' ' + metricName; return name.trim() + ' ' + metricName;
}; };
ElasticResponse.prototype.nameSeries = function(seriesList, target) { ElasticResponse.prototype.nameSeries = function(seriesList, target) {
var metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length; var metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length;
var fieldNameCount = _.uniq(_.map(seriesList, 'field')).length; var fieldNameCount = _.uniq(_.map(seriesList, 'field')).length;
...@@ -250,10 +262,10 @@ function (_, queryDef) { ...@@ -250,10 +262,10 @@ function (_, queryDef) {
var series = seriesList[i]; var series = seriesList[i];
series.target = this._getSeriesName(series, target, metricTypeCount, fieldNameCount); series.target = this._getSeriesName(series, target, metricTypeCount, fieldNameCount);
} }
}; };
ElasticResponse.prototype.processHits = function(hits, seriesList) { ElasticResponse.prototype.processHits = function(hits, seriesList) {
var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total}; var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total, filterable: true};
var propName, hit, doc, i; var propName, hit, doc, i;
for (i = 0; i < hits.hits.length; i++) { for (i = 0; i < hits.hits.length; i++) {
...@@ -277,25 +289,25 @@ function (_, queryDef) { ...@@ -277,25 +289,25 @@ function (_, queryDef) {
} }
seriesList.push(series); seriesList.push(series);
}; };
ElasticResponse.prototype.trimDatapoints = function(aggregations, target) { ElasticResponse.prototype.trimDatapoints = function(aggregations, target) {
var histogram = _.find(target.bucketAggs, { type: 'date_histogram'}); var histogram = _.find(target.bucketAggs, { type: 'date_histogram'});
var shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges; var shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges;
if (shouldDropFirstAndLast) { if (shouldDropFirstAndLast) {
var trim = histogram.settings.trimEdges; var trim = histogram.settings.trimEdges;
for(var prop in aggregations) { for (var prop in aggregations) {
var points = aggregations[prop]; var points = aggregations[prop];
if (points.datapoints.length > trim * 2) { if (points.datapoints.length > trim * 2) {
points.datapoints = points.datapoints.slice(trim, points.datapoints.length - trim); points.datapoints = points.datapoints.slice(trim, points.datapoints.length - trim);
} }
} }
} }
}; };
ElasticResponse.prototype.getErrorFromElasticResponse = function(response, err) { ElasticResponse.prototype.getErrorFromElasticResponse = function(response, err) {
var result = {}; var result: any = {};
result.data = JSON.stringify(err, null, 4); result.data = JSON.stringify(err, null, 4);
if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) { if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
result.message = err.root_cause[0].reason; result.message = err.root_cause[0].reason;
...@@ -308,9 +320,9 @@ function (_, queryDef) { ...@@ -308,9 +320,9 @@ function (_, queryDef) {
} }
return result; return result;
}; };
ElasticResponse.prototype.getTimeSeries = function() { ElasticResponse.prototype.getTimeSeries = function() {
var seriesList = []; var seriesList = [];
for (var i = 0; i < this.response.responses.length; i++) { for (var i = 0; i < this.response.responses.length; i++) {
...@@ -327,9 +339,9 @@ function (_, queryDef) { ...@@ -327,9 +339,9 @@ function (_, queryDef) {
var aggregations = response.aggregations; var aggregations = response.aggregations;
var target = this.targets[i]; var target = this.targets[i];
var tmpSeriesList = []; var tmpSeriesList = [];
var docs = []; var table = new TableModel();
this.processBuckets(aggregations, target, tmpSeriesList, docs, {}, 0); this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0);
this.trimDatapoints(tmpSeriesList, target); this.trimDatapoints(tmpSeriesList, target);
this.nameSeries(tmpSeriesList, target); this.nameSeries(tmpSeriesList, target);
...@@ -337,14 +349,12 @@ function (_, queryDef) { ...@@ -337,14 +349,12 @@ function (_, queryDef) {
seriesList.push(tmpSeriesList[y]); seriesList.push(tmpSeriesList[y]);
} }
if (seriesList.length === 0 && docs.length > 0) { if (table.rows.length > 0) {
seriesList.push({target: 'docs', type: 'docs', datapoints: docs}); seriesList.push(table);
} }
} }
} }
return { data: seriesList }; return { data: seriesList };
}; };
return ElasticResponse;
});
...@@ -129,7 +129,10 @@ describe('ElasticDatasource', function() { ...@@ -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() { ...@@ -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',
......
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() { ...@@ -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() { ...@@ -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() { ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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'] = { ...@@ -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 @@ ...@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment