mirror of
https://github.com/grafana/grafana.git
synced 2025-01-08 23:23:45 -06:00
feat: add ad hoc filters directly from table panel cells, kibana 3 style, #8052
This commit is contained in:
parent
c17b5d1306
commit
a5d5f3d82f
@ -10,9 +10,10 @@ export class AdHocFiltersCtrl {
|
||||
removeTagFilterSegment: any;
|
||||
|
||||
/** @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.buildSegmentModel();
|
||||
this.$rootScope.onAppEvent('template-variable-value-updated', this.buildSegmentModel.bind(this), $scope);
|
||||
}
|
||||
|
||||
buildSegmentModel() {
|
||||
@ -141,8 +142,7 @@ export class AdHocFiltersCtrl {
|
||||
}
|
||||
|
||||
this.variable.setFilters(filters);
|
||||
this.$rootScope.$emit('template-variable-value-updated');
|
||||
this.$rootScope.$broadcast('refresh');
|
||||
this.variableSrv.variableUpdated(this.variable, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,7 @@ export class SubmenuCtrl {
|
||||
}
|
||||
|
||||
variableUpdated(variable) {
|
||||
this.variableSrv.variableUpdated(variable).then(() => {
|
||||
this.$rootScope.$emit('template-variable-value-updated');
|
||||
this.$rootScope.$broadcast('refresh');
|
||||
});
|
||||
this.variableSrv.variableUpdated(variable, true);
|
||||
}
|
||||
|
||||
openEditView(editview) {
|
||||
|
@ -55,9 +55,8 @@ export class VariableEditorCtrl {
|
||||
|
||||
$scope.add = function() {
|
||||
if ($scope.isValid()) {
|
||||
$scope.variables.push($scope.current);
|
||||
variableSrv.addVariable($scope.current);
|
||||
$scope.update();
|
||||
$scope.dashboard.updateSubmenuVisibility();
|
||||
}
|
||||
};
|
||||
|
||||
@ -114,9 +113,8 @@ export class VariableEditorCtrl {
|
||||
$scope.duplicate = function(variable) {
|
||||
var clone = _.cloneDeep(variable.getSaveModel());
|
||||
$scope.current = variableSrv.createVariableFromModel(clone);
|
||||
$scope.variables.push($scope.current);
|
||||
$scope.current.name = 'copy_of_'+variable.name;
|
||||
$scope.dashboard.updateSubmenuVisibility();
|
||||
$scope.variableSrv.addVariable($scope.current);
|
||||
};
|
||||
|
||||
$scope.update = function() {
|
||||
@ -150,9 +148,7 @@ export class VariableEditorCtrl {
|
||||
};
|
||||
|
||||
$scope.removeVariable = function(variable) {
|
||||
var index = _.indexOf($scope.variables, variable);
|
||||
$scope.variables.splice(index, 1);
|
||||
$scope.dashboard.updateSubmenuVisibility();
|
||||
variableSrv.removeVariable(variable);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ describe('VariableSrv', function() {
|
||||
ctx.variableSrv.init({
|
||||
templating: {list: []},
|
||||
events: new Emitter(),
|
||||
updateSubmenuVisibility: sinon.stub(),
|
||||
});
|
||||
ctx.$rootScope.$digest();
|
||||
}));
|
||||
@ -41,7 +42,9 @@ describe('VariableSrv', function() {
|
||||
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.$rootScope.$digest();
|
||||
});
|
||||
|
@ -90,17 +90,24 @@ export class VariableSrv {
|
||||
return variable;
|
||||
}
|
||||
|
||||
addVariable(model) {
|
||||
var variable = this.createVariableFromModel(model);
|
||||
addVariable(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) {
|
||||
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 (variable.initLock) {
|
||||
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) {
|
||||
@ -218,6 +230,28 @@ export class VariableSrv {
|
||||
// update url
|
||||
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);
|
||||
|
@ -100,9 +100,9 @@ ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, targe
|
||||
// add columns
|
||||
if (table.columns.length === 0) {
|
||||
for (let propKey of _.keys(props)) {
|
||||
table.addColumn({text: propKey});
|
||||
table.addColumn({text: propKey, filterable: true});
|
||||
}
|
||||
table.addColumn({text: aggDef.field});
|
||||
table.addColumn({text: aggDef.field, filterable: true});
|
||||
}
|
||||
|
||||
// helper func to add values to value array
|
||||
@ -265,7 +265,7 @@ ElasticResponse.prototype.nameSeries = function(seriesList, target) {
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
for (i = 0; i < hits.hits.length; i++) {
|
||||
|
@ -389,7 +389,7 @@ describe('ElasticResponse', function() {
|
||||
|
||||
it('should return table with byte and count', function() {
|
||||
expect(result.data[0].rows.length).to.be(3);
|
||||
expect(result.data[0].columns).to.eql([{text: 'bytes'}, {text: 'Count'}]);
|
||||
expect(result.data[0].columns).to.eql([{text: 'bytes', filterable: true}, {text: 'Count'}]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -50,8 +50,9 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize) {
|
||||
constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize, private variableSrv) {
|
||||
super($scope, $injector);
|
||||
|
||||
this.pageIndex = 0;
|
||||
|
||||
if (this.panel.styles === void 0) {
|
||||
@ -223,10 +224,25 @@ class TablePanelCtrl extends MetricsPanelCtrl {
|
||||
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);
|
||||
console.log('clicked', options);
|
||||
}
|
||||
|
||||
elem.on('click', '.table-panel-page-link', switchPage);
|
||||
elem.on('click', '.table-panel-filter-link', addFilterClicked);
|
||||
|
||||
var unbindDestroy = scope.$on('$destroy', function() {
|
||||
elem.off('click', '.table-panel-page-link');
|
||||
elem.off('click', '.table-panel-filter-link');
|
||||
unbindDestroy();
|
||||
});
|
||||
|
||||
|
@ -140,9 +140,12 @@ export class TableRenderer {
|
||||
|
||||
renderCell(columnIndex, rowIndex, value, addWidthHack = false) {
|
||||
value = this.formatColumnValue(columnIndex, value);
|
||||
|
||||
var column = this.table.columns[columnIndex];
|
||||
var style = '';
|
||||
var cellClasses = [];
|
||||
var cellClass = '';
|
||||
|
||||
if (this.colorState.cell) {
|
||||
style = ' style="background-color:' + this.colorState.cell + ';color: white"';
|
||||
this.colorState.cell = null;
|
||||
@ -161,26 +164,25 @@ export class TableRenderer {
|
||||
|
||||
if (value === undefined) {
|
||||
style = ' style="display:none;"';
|
||||
this.table.columns[columnIndex].hidden = true;
|
||||
column.hidden = true;
|
||||
} else {
|
||||
this.table.columns[columnIndex].hidden = false;
|
||||
column.hidden = false;
|
||||
}
|
||||
|
||||
var columnStyle = this.table.columns[columnIndex].style;
|
||||
if (columnStyle && columnStyle.preserveFormat) {
|
||||
if (column.style && column.style.preserveFormat) {
|
||||
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
|
||||
var scopedVars = this.renderRowVariables(rowIndex);
|
||||
scopedVars['__cell'] = { value: value };
|
||||
|
||||
var cellLink = this.templateSrv.replace(columnStyle.linkUrl, scopedVars);
|
||||
var cellLinkTooltip = this.templateSrv.replace(columnStyle.linkTooltip, scopedVars);
|
||||
var cellTarget = columnStyle.linkTargetBlank ? '_blank' : '';
|
||||
var cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars);
|
||||
var cellLinkTooltip = this.templateSrv.replace(column.style.linkTooltip, scopedVars);
|
||||
var cellTarget = column.style.linkTargetBlank ? '_blank' : '';
|
||||
|
||||
cellClasses.push("table-panel-cell-link");
|
||||
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) {
|
||||
cellClass = ' class="' + cellClasses.join(' ') + '"';
|
||||
}
|
||||
|
@ -185,8 +185,16 @@ transformers['json'] = {
|
||||
},
|
||||
transform: function(data, panel, model) {
|
||||
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) {
|
||||
|
@ -91,9 +91,23 @@
|
||||
&.cell-highlighted:hover {
|
||||
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 {
|
||||
background: $grafanaListAccent;
|
||||
border-top: 2px solid $body-bg;
|
||||
|
Loading…
Reference in New Issue
Block a user