mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge pull request #229 from simianhacker/feature/filter-objects
Filter Bar Feature
This commit is contained in:
commit
b7637368b6
16 changed files with 254 additions and 19 deletions
|
@ -77,7 +77,8 @@ define(function (require) {
|
|||
query: initialQuery || '',
|
||||
columns: ['_source'],
|
||||
index: config.get('defaultIndex'),
|
||||
interval: 'auto'
|
||||
interval: 'auto',
|
||||
filters: _.cloneDeep($scope.searchSource.get('filter'))
|
||||
};
|
||||
|
||||
var metaFields = config.get('metaFields');
|
||||
|
@ -93,7 +94,7 @@ define(function (require) {
|
|||
'year'
|
||||
];
|
||||
|
||||
var $state = $scope.state = new appStateFactory.create(stateDefaults);
|
||||
var $state = $scope.state = appStateFactory.create(stateDefaults);
|
||||
|
||||
if (!_.contains(indexPatternList, $state.index)) {
|
||||
var reason = 'The index specified in the URL is not a configured pattern. ';
|
||||
|
@ -161,6 +162,10 @@ define(function (require) {
|
|||
if (!angular.equals(sort, currentSort)) $scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watch('state.filters', function (filters) {
|
||||
$scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watch('opts.timefield', function (timefield) {
|
||||
timefilter.enabled(!!timefield);
|
||||
});
|
||||
|
@ -199,6 +204,8 @@ define(function (require) {
|
|||
if (!init.complete) return;
|
||||
|
||||
$scope.updateTime();
|
||||
if (_.isEmpty($state.columns)) refreshColumns();
|
||||
$state.save();
|
||||
$scope.updateDataSource()
|
||||
.then(setupVisualization)
|
||||
.then(function () {
|
||||
|
@ -340,7 +347,6 @@ define(function (require) {
|
|||
|
||||
$scope.updateDataSource = function () {
|
||||
var chartOptions;
|
||||
|
||||
$scope.searchSource
|
||||
.size($scope.opts.sampleSize)
|
||||
.sort(function () {
|
||||
|
@ -354,7 +360,8 @@ define(function (require) {
|
|||
}
|
||||
return sort;
|
||||
})
|
||||
.query(!$state.query ? null : $state.query);
|
||||
.query(!$state.query ? null : $state.query)
|
||||
.set('filter', $state.filters || []);
|
||||
|
||||
// get the current indexPattern
|
||||
var indexPattern = $scope.searchSource.get('index');
|
||||
|
@ -432,20 +439,26 @@ define(function (require) {
|
|||
// TODO: On array fields, negating does not negate the combination, rather all terms
|
||||
$scope.filterQuery = function (field, value, operation) {
|
||||
value = _.isArray(value) ? value : [value];
|
||||
operation = operation || '+';
|
||||
|
||||
var indexPattern = $scope.searchSource.get('index');
|
||||
indexPattern.popularizeField(field, 1);
|
||||
|
||||
_.each(value, function (clause) {
|
||||
var filter = field + ':"' + addSlashes(clause) + '"';
|
||||
var regex = '[\\+-]' + regexEscape(filter) + '\\s*';
|
||||
// Grap the filters from the searchSource and ensure it's an array
|
||||
var filters = _.flatten([$state.filters], true);
|
||||
|
||||
$state.query = $state.query.replace(new RegExp(regex), '') +
|
||||
' ' + operation + filter;
|
||||
_.each(value, function (clause) {
|
||||
var previous = _.find(filters, function (item) {
|
||||
return item && item.query.match[field] === clause;
|
||||
});
|
||||
if (!previous) {
|
||||
var filter = { query: { match: {} } };
|
||||
filter.negate = operation === '-';
|
||||
filter.query.match[field] = clause;
|
||||
filters.push(filter);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.fetch();
|
||||
$state.filters = filters;
|
||||
};
|
||||
|
||||
$scope.toggleField = function (name) {
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
<config config-template="configTemplate" config-object="opts" config-close="configClose" config-submit="fetch"></config>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<filter-bar state="state"></filter-bar>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="discover-hits"><strong>{{hits || 0}}</strong> hits</div>
|
||||
|
||||
|
|
|
@ -19,6 +19,19 @@ define(function (require) {
|
|||
var serviceObj = registry.get($routeParams.service);
|
||||
var service = $injector.get(serviceObj.service);
|
||||
|
||||
/**
|
||||
* Creates a field definition and pushes it to the memo stack. This function
|
||||
* is designed to be used in conjunction with _.reduce(). If the
|
||||
* values is plain object it will recurse through all the keys till it hits
|
||||
* a string, number or an array.
|
||||
*
|
||||
* @param {array} memo The stack of fields
|
||||
* @param {mixed} value The value of the field
|
||||
* @param {stirng} key The key of the field
|
||||
* @param {object} collection This is a reference the collection being reduced
|
||||
* @param {array} parents The parent keys to the field
|
||||
* @returns {array}
|
||||
*/
|
||||
var createField = function (memo, val, key, collection, parents) {
|
||||
if (_.isArray(parents)) {
|
||||
parents.push(key);
|
||||
|
@ -92,6 +105,11 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes an object and sets the notification
|
||||
* @param {type} name description
|
||||
* @returns {type} description
|
||||
*/
|
||||
$scope.delete = function () {
|
||||
$scope.obj.delete().then(function (resp) {
|
||||
$location.path('/settings/objects').search({ _a: rison.encode({
|
||||
|
@ -132,4 +150,4 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -63,7 +63,7 @@ define(function (require) {
|
|||
$scope.fields = _.sortBy(indexPattern.fields, 'name');
|
||||
$scope.fields.byName = indexPattern.fieldsByName;
|
||||
|
||||
var $state = $scope.state = new appStateFactory.create(vis.getState());
|
||||
var $state = $scope.state = appStateFactory.create(vis.getState());
|
||||
|
||||
if ($state.query) {
|
||||
vis.searchSource.set('query', $state.query);
|
||||
|
|
|
@ -247,6 +247,29 @@ define(function (require) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a filter that can be reversed for filters with negate set
|
||||
* @param {boolean} reverse This will reverse the filter. If true then
|
||||
* anything where negate is set will come
|
||||
* through otherwise it will filter out
|
||||
* @returns {function}
|
||||
*/
|
||||
var filterNegate = function (reverse) {
|
||||
return function (filter) {
|
||||
if (_.isUndefined(filter.negate)) return !reverse;
|
||||
return filter.negate === reverse;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean out any invalid attributes from the filters
|
||||
* @param {object} filter The filter to clean
|
||||
* @returns {object}
|
||||
*/
|
||||
var cleanFilter = function (filter) {
|
||||
return _.omit(filter, ['negate', 'disabled']);
|
||||
};
|
||||
|
||||
// switch to filtered query if there are filters
|
||||
if (flatState.filters) {
|
||||
if (flatState.filters.length) {
|
||||
|
@ -255,7 +278,8 @@ define(function (require) {
|
|||
query: flatState.body.query,
|
||||
filter: {
|
||||
bool: {
|
||||
must: flatState.filters
|
||||
must: _(flatState.filters).filter(filterNegate(false)).map(cleanFilter).value(),
|
||||
must_not: _(flatState.filters).filter(filterNegate(true)).map(cleanFilter).value()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,7 +157,16 @@ define(function (require) {
|
|||
switch (key) {
|
||||
case 'filter':
|
||||
// user a shallow flatten to detect if val is an array, and pull the values out if it is
|
||||
state.filters = _.flatten([ state.filters || [], val ], true);
|
||||
state.filters = _([ state.filters || [], val ])
|
||||
.flatten(true)
|
||||
// Yo Dawg! I heard you needed to filter out your filters
|
||||
.filter(function (filter) {
|
||||
if (!filter) return false;
|
||||
// return true for anything that is either empty or false
|
||||
// return false for anything that is explicitly set to true
|
||||
return !filter.disabled;
|
||||
})
|
||||
.value();
|
||||
return;
|
||||
case 'index':
|
||||
case 'type':
|
||||
|
|
30
src/kibana/components/filter_bar/filter_bar.css
Normal file
30
src/kibana/components/filter_bar/filter_bar.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
filter-bar .bar {
|
||||
padding: 6px 6px 4px 6px;
|
||||
background: #dde4e6;
|
||||
}
|
||||
filter-bar .bar .title {
|
||||
display: inline;
|
||||
color: #748287;
|
||||
margin: 0 6px;
|
||||
}
|
||||
filter-bar .bar .filter {
|
||||
font-size: 12px;
|
||||
border-radius: 12px;
|
||||
display: inline-block;
|
||||
background-color: #83949C;
|
||||
padding: 4px 8px;
|
||||
color: #fff;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
filter-bar .bar .filter .value {
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.4);
|
||||
padding-right: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
filter-bar .bar .filter.negate {
|
||||
background-color: #D18282;
|
||||
}
|
||||
filter-bar .bar .filter a {
|
||||
color: #FFF;
|
||||
}
|
9
src/kibana/components/filter_bar/filter_bar.html
Normal file
9
src/kibana/components/filter_bar/filter_bar.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div class="bar" ng-show="filters.length">
|
||||
<!-- <div class="title">Filters</div> -->
|
||||
<div class="filter" ng-class="{ negate: filter.negate }" ng-repeat="filter in filters">
|
||||
<span class="key">{{ filter.key }}:</span>
|
||||
<span class="value">"{{ filter.value }}"</span>
|
||||
<a class="fa" tooltip="Toggle" tooltip-placement="top" ng-class="{ 'fa-eye-slash': filter.disabled, 'fa-eye': !filter.disabled }" ng-click="toggleFilter(filter)"><a>
|
||||
<a class="fa fa-times" tooltip="Remove" tooltip-placement="top" ng-click="removeFilter(filter)"><a>
|
||||
</div>
|
||||
</div>
|
88
src/kibana/components/filter_bar/filter_bar.js
Normal file
88
src/kibana/components/filter_bar/filter_bar.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
define(function (require) {
|
||||
'use strict';
|
||||
var _ = require('lodash');
|
||||
var module = require('modules').get('kibana');
|
||||
var template = require('text!components/filter_bar/filter_bar.html');
|
||||
|
||||
module.directive('filterBar', function (courier) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: template,
|
||||
scope: {
|
||||
state: '='
|
||||
},
|
||||
link: function ($scope, $el, attrs) {
|
||||
|
||||
/**
|
||||
* Map the filter into an object with the key and value exposed so it's
|
||||
* easier to work with in the template
|
||||
* @param {object} fitler The filter the map
|
||||
* @returns {object}
|
||||
*/
|
||||
var mapFilter = function (filter) {
|
||||
var key = _.keys(filter.query.match)[0];
|
||||
return {
|
||||
key: key,
|
||||
value: filter.query.match[key],
|
||||
disabled: !!(filter.disabled),
|
||||
negate: !!(filter.negate),
|
||||
filter: filter
|
||||
};
|
||||
};
|
||||
|
||||
$scope.$watch('state.filters', function (filters) {
|
||||
// Get the filters from the searchSource
|
||||
$scope.filters = _(filters)
|
||||
.filter(function (filter) {
|
||||
return filter;
|
||||
})
|
||||
.flatten(true)
|
||||
.map(mapFilter)
|
||||
.value();
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Remap the filter from the intermediary back to it's original.
|
||||
* @param {object} filter The original filter
|
||||
* @returns {object}
|
||||
*/
|
||||
var remapFilters = function (filter) {
|
||||
return filter.filter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the filter between enabled/disabled.
|
||||
* @param {object} filter The filter to toggle
|
||||
* @returns {void}
|
||||
*/
|
||||
$scope.toggleFilter = function (filter) {
|
||||
// Toggle the disabled flag
|
||||
var disabled = !filter.disabled;
|
||||
filter.disabled = disabled;
|
||||
filter.filter.disabled = disabled;
|
||||
|
||||
// Save the filters back to the searchSource
|
||||
$scope.state.filters = _.map($scope.filters, remapFilters);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the filter from the searchSource
|
||||
* @param {object} filter The filter to remove
|
||||
* @returns {void}
|
||||
*/
|
||||
$scope.removeFilter = function (invalidFilter) {
|
||||
// Remove the filter from the the scope $filters and map it back
|
||||
// to the original format to save in searchSource
|
||||
$scope.state.filters = _($scope.filters)
|
||||
.filter(function (filter) {
|
||||
return filter.filter !== invalidFilter.filter;
|
||||
})
|
||||
.map(remapFilters)
|
||||
.value();
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
35
src/kibana/components/filter_bar/filter_bar.less
Normal file
35
src/kibana/components/filter_bar/filter_bar.less
Normal file
|
@ -0,0 +1,35 @@
|
|||
filter-bar .bar {
|
||||
padding: 6px 6px 4px 6px;
|
||||
background: #dde4e6;
|
||||
|
||||
.title {
|
||||
display: inline;
|
||||
color: #748287;
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
.filter {
|
||||
font-size: 12px;
|
||||
border-radius: 12px;
|
||||
display: inline-block;
|
||||
background-color: #83949C;
|
||||
padding: 4px 8px;
|
||||
color: #fff;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.value {
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.4);
|
||||
padding-right: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&.negate {
|
||||
background-color: #D18282;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ define(function (require) {
|
|||
var getAppStash = function (search) {
|
||||
var appStash = search._a && rison.decode(search._a);
|
||||
if (app.current) {
|
||||
// Apply the defaults to appStash
|
||||
appStash = _.defaults(appStash || {}, app.defaults);
|
||||
}
|
||||
return appStash;
|
||||
|
@ -137,4 +138,4 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ define(function (require) {
|
|||
AppState.Super.call(this, '_a', defaults);
|
||||
}
|
||||
|
||||
|
||||
return AppState;
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ define(function (require) {
|
|||
require('components/courier/courier');
|
||||
require('components/notify/notify');
|
||||
require('components/state_management/app_state_factory');
|
||||
require('components/filter_bar/filter_bar');
|
||||
require('directives/info');
|
||||
require('directives/spinner');
|
||||
require('directives/paginate');
|
||||
|
|
|
@ -347,3 +347,4 @@ input[type="checkbox"],
|
|||
}
|
||||
}
|
||||
|
||||
@import '../components/filter_bar/filter_bar.less';
|
||||
|
|
|
@ -9,7 +9,8 @@ module.exports = {
|
|||
'<%= src %>/kibana/apps/settings/styles/main.less',
|
||||
'<%= src %>/kibana/apps/visualize/styles/main.less',
|
||||
'<%= src %>/kibana/apps/visualize/styles/visualization.less',
|
||||
'<%= src %>/kibana/styles/main.less'
|
||||
'<%= src %>/kibana/styles/main.less',
|
||||
'<%= src %>/kibana/components/**/*.less'
|
||||
],
|
||||
expand: true,
|
||||
ext: '.css',
|
||||
|
@ -18,4 +19,4 @@ module.exports = {
|
|||
paths: [bc + '/lesshat/build/']
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,7 +8,8 @@ module.exports = function (grunt) {
|
|||
},
|
||||
less: {
|
||||
files: [
|
||||
'<%= app %>/**/styles/**/*.less'
|
||||
'<%= app %>/**/styles/**/*.less',
|
||||
'<%= app %>/**/components/**/*.less'
|
||||
],
|
||||
tasks: ['less']
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue