[settings/indexPattern/fields] add "field filters" tab

This commit is contained in:
spalger 2016-04-04 23:07:31 -07:00 committed by Stéphane Campinas
parent fa5b22d9ab
commit 7819fa984d
10 changed files with 216 additions and 51 deletions

View file

@ -38,16 +38,21 @@
<br />
<ul class="nav nav-tabs">
<li class="kbn-settings-tab" ng-class="{ active: state.tab === fieldType.index }" ng-repeat="fieldType in fieldTypes">
<a ng-click="changeTab(fieldType)">
{{ fieldType.title }}
<small>({{ fieldType.count }})</small>
<li class="kbn-settings-tab" ng-repeat="tab in tabs" ng-class="{ active: state.tab === tab.index }">
<a ng-click="changeTab(tab)">
{{ tab.title }}
<small ng-if="tab.count">({{ tab.count(indexPattern) }})</small>
</a>
</li>
</ul>
<indexed-fields ng-show="state.tab == 'indexedFields'" class="fields indexed-fields"></indexed-fields>
<scripted-fields ng-show="state.tab == 'scriptedFields'" class="fields scripted-fields"></scripted-fields>
<settings-indices-field-filters
ng-show="state.tab == 'fieldFilters'"
index-pattern="indexPattern"
class="fields field-filters">
</settings-indices-field-filters>
</div>
</kbn-settings-indices>

View file

@ -6,10 +6,11 @@ import uiModules from 'ui/modules';
import Notifier from 'ui/notify/notifier';
import RefreshKibanaIndexProvider from './_refresh_kibana_index';
import FieldTypesProvider from './_field_types';
import FieldTabsProvider from './_field_tabs';
import editTemplate from './_edit.html';
import './_indexed_fields';
import './_scripted_fields';
import './field_filters/field_filters';
import './_index_header';
uiRoutes
@ -35,22 +36,18 @@ uiModules.get('apps/settings')
docTitle.change($scope.indexPattern.id);
const otherIds = _.without($route.current.locals.indexPatternIds, $scope.indexPattern.id);
const fieldTypes = Private(FieldTypesProvider);
$scope.$watch('indexPattern.fields', function () {
$scope.fieldTypes = fieldTypes($scope.indexPattern);
});
$scope.tabs = Private(FieldTabsProvider);
$scope.changeTab = function (obj) {
$state.tab = obj.index;
$state.save();
};
$scope.$watch('state.tab', function (tab) {
if (!tab) $scope.changeTab($scope.fieldTypes[0]);
if (!tab) $scope.changeTab($scope.tabs[0]);
});
$scope.$watchCollection('indexPattern.fields', function () {
$scope.conflictFields = _.filter($scope.indexPattern.fields, {type: 'conflict'});
$scope.$watchCollection('indexPattern.fields', function (fields) {
$scope.conflictFields = _.filter(fields, { type: 'conflict' });
});
$scope.refreshFields = function () {

View file

@ -0,0 +1,25 @@
import { filter, size } from 'lodash';
export default function GetFieldTypes() {
const scriptedFieldCount = fields => size(filter(fields, 'scripted'));
return [
{
title: 'fields',
index: 'indexedFields',
count({ fields }) {
return size(fields) - scriptedFieldCount(fields);
},
},
{
title: 'scripted fields',
index: 'scriptedFields',
count({ fields }) {
return scriptedFieldCount(fields);
},
},
{
title: 'field filters',
index: 'fieldFilters'
},
];
};

View file

@ -1,31 +0,0 @@
import _ from 'lodash';
export default function GetFieldTypes() {
return function (indexPattern) {
const fieldCount = _.countBy(indexPattern.fields, function (field) {
return (field.scripted) ? 'scripted' : 'indexed';
});
_.defaults(fieldCount, {
indexed: 0,
scripted: 0
});
return [
{
title: 'fields',
index: 'indexedFields',
count: fieldCount.indexed
},
{
title: 'scripted fields',
index: 'scriptedFields',
count: fieldCount.scripted
},
{
title: 'Retrieved Fields',
index: 'sourceFiltering'
}
];
};
};

View file

@ -34,12 +34,7 @@ uiModules.get('apps/settings')
function refreshRows() {
// clear and destroy row scopes
_.invoke(rowScopes.splice(0), '$destroy');
const metaFields = config.get('metaFields');
const sourceFiltering = $scope.indexPattern.getSourceFiltering();
const fields = filter($scope.indexPattern.getNonScriptedFields(), $scope.fieldFilter);
_.find($scope.fieldTypes, {index: 'indexedFields'}).count = fields.length; // Update the tab count
$scope.rows = fields.map(function (field) {
const childScope = _.assign($scope.$new(), { field: field });
rowScopes.push(childScope);

View file

@ -37,8 +37,6 @@ uiModules.get('apps/settings')
rowScopes.length = 0;
const fields = filter($scope.indexPattern.getScriptedFields(), $scope.fieldFilter);
_.find($scope.fieldTypes, {index: 'scriptedFields'}).count = fields.length; // Update the tab count
$scope.rows = fields.map(function (field) {
const rowScope = $scope.$new();
rowScope.field = field;

View file

@ -0,0 +1,70 @@
<h3>Field Filters</h3>
<p>
Field filters can be used to exclude one or more fields when fetching the document source. This happens in apps like Discover for the doc table. Each row is built using the source of a single document, and if you have documents with large or unimportant fields you may benefit from filtering those out at this lowere level.
</p>
<div ng-class="{ saving: fieldFilters.saving }" class="field-filters-container">
<form
ng-repeat="filter in fieldFilters.all()"
ng-submit="fieldFilters.save(filter)"
class="field-filter"
name="form">
<div class="value">
<span ng-if="fieldFilters.editting !== filter">{{ filter.value }}</span>
<input
ng-model="filter.value"
ng-if="fieldFilters.editting === filter"
placeholder="field name filter, accepts wildcards (i.e. `user:*`)"
type="text"
required
class="form-control">
</div>
<div class="controls">
<button
aria-label="Edit field filter"
ng-if="fieldFilters.editting !== filter"
ng-click="fieldFilters.editting = filter"
type="button"
class="btn btn-default">
<i aria-hidden="true" class="fa fa-pencil"></i>
</button>
<button
aria-label="Save field filter"
ng-if="fieldFilters.editting === filter"
type="submit"
class="btn btn-primary">
<i aria-hidden="true" class="fa fa-save"></i>
</button>
<button
aria-label="Delete field filter"
ng-click="fieldFilters.delete(filter)"
type="button"
class="btn btn-danger">
<i aria-hidden="true" class="fa fa-trash"></i>
</button>
</div>
</form>
<form name="form" ng-submit="fieldFilters.create()">
<div class="input-group">
<input
ng-model="fieldFilters.newValue"
placeholder="field name filter, accepts wildcards (i.e. `user:*`)"
type="text"
class="form-control">
<div class="input-group-btn" role="group" aria-label="Field Filter Editor Controls">
<button
type="submit"
class="btn btn-primary"
ng-disabled="!fieldFilters.newValue">
Add
</button>
</div>
</div>
</form>
</div>

View file

@ -0,0 +1,61 @@
import { without } from 'lodash';
import uiModules from 'ui/modules';
import Notifier from 'ui/notify/notifier';
import template from './field_filters.html';
import './field_filters.less';
const notify = new Notifier();
uiModules.get('kibana')
.directive('settingsIndicesFieldFilters', function () {
return {
restrict: 'E',
scope: {
indexPattern: '='
},
template,
controllerAs: 'fieldFilters',
controller: class FieldFiltersController {
constructor($scope) {
if (!$scope.indexPattern) {
throw new Error('index pattern is required');
}
this.$scope = $scope;
this.saving = false;
this.editting = null;
this.newValue = null;
}
all() {
return this.$scope.indexPattern.fieldFilters || [];
}
delete(filter) {
if (this.editting === filter) {
this.editting = null;
}
this.$scope.indexPattern.fieldFilters = without(this.all(), filter);
return this.save();
}
create() {
const value = this.newValue;
this.newValue = null;
this.$scope.indexPattern.fieldFilters = [...this.all(), { value }];
return this.save();
}
save() {
this.saving = true;
this.$scope.indexPattern.save()
.then(() => this.editting = null)
.catch(notify.error)
.finally(() => this.saving = false);
}
}
};
});

View file

@ -0,0 +1,29 @@
@import (reference) "~ui/styles/variables";
settings-indices-field-filters {
.field-filters-container {
margin-top: 15px;
&.saving {
pointer-events: none;
opacity: .4;
transition: opacity 0.75s;
}
.field-filter {
display: flex;
align-items: center;
margin: 10px 0;
> .value {
flex: 1 1 auto;
padding-right: 5px;
font-family: @font-family-monospace;
:not(input) {
padding-left: 15px;
}
}
}
}
}

View file

@ -36,6 +36,7 @@ export default function IndexPatternFactory(Private, timefilter, Notifier, confi
notExpandable: 'boolean',
intervalName: 'string',
fields: 'json',
fieldFilters: 'json',
fieldFormatMap: {
type: 'string',
_serialize: function (map) {
@ -64,6 +65,9 @@ export default function IndexPatternFactory(Private, timefilter, Notifier, confi
let self = this;
setId(id);
if (!self.fieldFilters) {
self.fieldFilters = [];
}
let docSource = new DocSource();
@ -150,6 +154,18 @@ export default function IndexPatternFactory(Private, timefilter, Notifier, confi
}
};
// Get the source filtering configuration for that index.
// Fields which name appears in the given columns array will not be excluded.
self.getSourceFiltering = function (columns) {
return {
exclude: _(self.getNonScriptedFields())
.filter((field) => field.exclude && !_.contains(columns, field.name))
.map((field) => field.name)
.concat(self.fieldFilters.map(filter => filter.value))
.value()
};
};
self.addScriptedField = function (name, script, type, lang) {
type = type || 'string';