mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[settings/indexPattern/fields] add "field filters" tab
This commit is contained in:
parent
fa5b22d9ab
commit
7819fa984d
10 changed files with 216 additions and 51 deletions
|
@ -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>
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
];
|
||||
};
|
|
@ -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'
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue