ditched the new 'retrieved fields' tab and added checkbox to exclude a field in the field control

This commit is contained in:
Stéphane Campinas 2016-01-13 14:44:38 +00:00
parent e7204bef63
commit b228c66c01
12 changed files with 44 additions and 279 deletions

View file

@ -1,60 +0,0 @@
var expect = require('expect.js');
define(function (require) {
var isRetrieved = require('src/plugins/kibana/public/settings/sections/indices/retrieved_field');
describe('Settings', function () {
describe('Indices', function () {
describe('isRetrieved(sourceFiltering, name)', function () {
it('should be a function', function () {
expect(isRetrieved).to.be.a(Function);
});
it('should retrieve john', function () {
var sourceFiltering = {
include: 'john'
};
expect(isRetrieved(sourceFiltering, 'john')).to.be(true);
});
it('should not retrieve connor', function () {
var sourceFiltering = {
exclude: 'connor'
};
expect(isRetrieved(sourceFiltering, 'connor')).to.be(false);
});
it('should retrieve connor', function () {
var sourceFiltering = {
exclude: '*.connor'
};
expect(isRetrieved(sourceFiltering, 'connor')).to.be(true);
expect(isRetrieved(sourceFiltering, 'john.connor')).to.be(false);
});
it('should not retrieve neither john nor connor', function () {
var sourceFiltering = {
exclude: [ 'john', 'connor' ]
};
expect(isRetrieved(sourceFiltering, 'connor')).to.be(false);
expect(isRetrieved(sourceFiltering, 'john')).to.be(false);
expect(isRetrieved(sourceFiltering, 'toto')).to.be(true);
});
it('should not retrieve john.*.connor', function () {
var sourceFiltering = {
exclude: 'john.*.connor'
};
expect(isRetrieved(sourceFiltering, 'john.j.connor')).to.be(false);
expect(isRetrieved(sourceFiltering, 'john.t.connor')).to.be(false);
expect(isRetrieved(sourceFiltering, 'john.j.watterson')).to.be(true);
});
});
});
});
});

View file

@ -41,14 +41,13 @@
<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 ng-if="fieldType.count !== undefined">({{ fieldType.count }})</small>
<small>({{ fieldType.count }})</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>
<source-filtering ng-show="state.tab == 'sourceFiltering'" class="fields source-filtering"></source-filtering>
</div>
</kbn-settings-indices>

View file

@ -1,5 +1,4 @@
import _ from 'lodash';
import isRetrieved from 'plugins/kibana/settings/sections/indices/retrieved_field';
import 'ui/paginated_table';
import nameHtml from 'plugins/kibana/settings/sections/indices/_field_name.html';
import typeHtml from 'plugins/kibana/settings/sections/indices/_field_type.html';
@ -44,7 +43,6 @@ uiModules.get('apps/settings')
const childScope = _.assign($scope.$new(), { field: field });
rowScopes.push(childScope);
<<<<<<< HEAD
return [
{
markup: nameHtml,
@ -66,7 +64,8 @@ uiModules.get('apps/settings')
value: field.indexed
},
{
markup: isRetrieved(sourceFiltering, field.displayName) ? yesTemplate : noTemplate
markup: field.exclude ? noTemplate : yesTemplate,
value: field.exclude
},
{
markup: controlsHtml,

View file

@ -1,114 +0,0 @@
<h3>Retrieved fields
<span class="pull-right text-info hintbox-label" ng-click="showHelp = !showHelp">
<h4><i class="fa fa-info"></i> Retrieved Fields Help</h4>
</span>
</h3>
<div class="hintbox" ng-if="showHelp">
<h4 class="hintbox-heading">
<i class="fa fa-question-circle text-info"></i> Retrieved Fields Help
</h4>
<p>
All fields are by default retrieved and are available inside the "_source" object of each hit. Thanks to the
<a target="_window" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html">
source filtering API
<i aria-hidden="true" class="fa-link fa"></i>
</a>
of Elasticsearch, you can choose which fields are actually retrieved. The value needs to be a JSON object.
</p>
<br/>
<strong>Examples</strong>
<p style="color: #b4bcc2;">Exclusion of a single field</p>
<div
readonly
ui-ace="{
advanced: {
highlightActiveLine: false
},
useWrapMode: true,
rendererOptions: {
showPrintMargin: false,
maxLines: 4294967296
},
mode: 'json'
}">{
"exclude": "user"
}</div>
<p style="color: #b4bcc2;">Exclusion with a path pattern</p>
<div
readonly
ui-ace="{
advanced: {
highlightActiveLine: false
},
useWrapMode: true,
rendererOptions: {
showPrintMargin: false,
maxLines: 4294967296
},
mode: 'json'
}">{
"exclude": [
"obj1.*",
"*.value"
]
}</div>
<p style="color: #b4bcc2;">Complete control with exclusions and inclusion</p>
<div
readonly
ui-ace="{
advanced: {
highlightActiveLine: false
},
useWrapMode: true,
rendererOptions: {
showPrintMargin: false,
maxLines: 4294967296
},
mode: 'json'
}">{
"exclude": "obj1.*",
"include": "obj2.*.val"
}</div>
</div>
<p>
By default, all fields are retrieved to populate results table.
Sometimes however some fields can be very large and seriously affect performance. This can be often the case when you have materialized one to many relationships, e.g., an entity having many nested entities. Those fields are still useful for searching or analytics but not in a result table.<br/><br/>
Use this setting to decide what to include or exclude in the data retrieved from the Elasticsearch index.<br/>
If empty, all fields are retrieved.
</p>
<form ng-submit="save()" name="form">
<div
id="json-ace"
ng-model="sourceFiltering"
ui-ace="{
useWrapMode: true,
advanced: {
highlightActiveLine: false
},
rendererOptions: {
showPrintMargin: false,
maxLines: 4294967296
},
mode: 'json'
}">{{ sourceFiltering | json }}</div>
<div class="form-group">
<button
ng-disabled="form.$invalid"
type="submit"
aria-label="Submit"
class="btn btn-success">
Submit
</button>
</div>
</form>

View file

@ -1,44 +0,0 @@
define(function (require) {
require('ui/modules').get('apps/settings')
.directive('sourceFiltering', function (Notifier, $window) {
var notify = new Notifier();
return {
restrict: 'E',
template: require('plugins/kibana/settings/sections/indices/_source_filtering.html'),
link: function ($scope) {
$scope.showHelp = false;
$scope.sourceFiltering = JSON.stringify($scope.indexPattern.getSourceFiltering(), null, ' ');
$scope.save = function () {
try {
var sourceFiltering;
if ($scope.sourceFiltering) {
sourceFiltering = JSON.parse($scope.sourceFiltering);
if (sourceFiltering.constructor !== Object) {
throw 'You must enter a JSON object with exclude/include field(s)';
}
for (var att in sourceFiltering) {
if (sourceFiltering.hasOwnProperty(att) && att !== 'exclude' && att !== 'include') {
throw 'The JSON object should have only either an exclude or an include field';
}
}
$scope.indexPattern.setSourceFiltering(sourceFiltering);
notify.info('Updated the set of retrieved fields');
} else if ($scope.indexPattern.getSourceFiltering()) {
var confirmIfEmpty = 'The following configuration will be deleted:\n\n' +
JSON.stringify($scope.indexPattern.getSourceFiltering(), null, ' ');
if ($window.confirm(confirmIfEmpty)) {
$scope.indexPattern.setSourceFiltering(undefined);
notify.info('All fields are now retrieved');
} else {
$scope.sourceFiltering = JSON.stringify($scope.indexPattern.getSourceFiltering(), null, ' ');
}
}
} catch (e) {
notify.error(e);
}
};
}
};
});
});

View file

@ -1,50 +0,0 @@
define(function (require) {
/**
* Returns true if the path matches the given pattern
*/
function matchPath(pathPattern, path) {
var pattern = pathPattern.split('.');
var pathArr = path.split('.');
if (pattern.length !== pathArr.length) {
return false;
}
for (var i = 0; i < pattern.length; i++) {
if (pattern[i] !== '*' && pattern[i] !== pathArr[i]) {
return false;
}
}
return true;
}
function process(val, name) {
if (val.constructor === Array) {
for (var i = 0; i < val.length; i++) {
if (matchPath(val[i], name)) {
return true;
}
}
return false;
} else {
return matchPath(val, name);
}
}
/**
* Returns true if the field named "name" should be retrieved as part of
* the _source object for each hit.
*/
return function isRetrieved(sourceFiltering, name) {
if (sourceFiltering === undefined) {
return true;
}
if (sourceFiltering.include) {
var inc = sourceFiltering.include;
return process(inc, name);
} else if (sourceFiltering.exclude) {
var exc = sourceFiltering.exclude;
return !process(exc, name);
}
return false;
};
});

View file

@ -17,6 +17,7 @@ module.exports = function initDefaultFieldProps(fields) {
analyzed: true,
doc_values: false,
scripted: false,
exclude: false,
count: 0
});
@ -27,6 +28,7 @@ module.exports = function initDefaultFieldProps(fields) {
analyzed: false,
doc_values: true,
scripted: false,
exclude: false,
count: 0
});
}
@ -36,6 +38,7 @@ module.exports = function initDefaultFieldProps(fields) {
analyzed: false,
doc_values: true,
scripted: false,
exclude: false,
count: 0
});
}

View file

@ -80,7 +80,7 @@ uiModules.get('kibana')
$scope.searchSource.size(config.get('discover:sampleSize'));
$scope.searchSource.sort(getSort($scope.sorting, $scope.indexPattern));
const sourceFiltering = $scope.indexPattern.getSourceFiltering();
const sourceFiltering = $scope.indexPattern.getSourceFiltering($scope.columns);
if (sourceFiltering) {
$scope.searchSource.source(sourceFiltering);
}

View file

@ -83,6 +83,32 @@
</div>
</div>
<div class="form-group">
<span class="pull-right text-info hintbox-label" ng-click="editor.showExcludeHelp = !editor.showExcludeHelp">
<i class="fa fa-info"></i> Help
</span>
<label><input type="checkbox" ng-model="editor.field.exclude"> Exclude from _source</label>
<div class="hintbox" ng-if="editor.showExcludeHelp">
<h4 class="hintbox-heading">
<i class="fa fa-info text-info"></i> Field Exclusion
</h4>
<p>
If checked, this field will not be retrieved as part of the <b>_source</b> object. This is thanks to the
<a target="_window" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html">
source filtering API
<i aria-hidden="true" class="fa-link fa"></i>
</a>
of Elasticsearch.
</p>
<p>
If this field is explicitly selected in your saved search, then it will not be excluded.
</p>
</div>
</div>
<div ng-if="editor.field.scripted">
<div class="form-group">
<label>Script</label>

View file

@ -46,6 +46,7 @@ export default function FieldObjectProvider(Private, shortDotsFilter, $rootScope
obj.fact('name');
obj.fact('type');
obj.writ('count', spec.count || 0);
obj.writ('exclude', spec.exclude);
// scripted objs
obj.fact('scripted', scripted);

View file

@ -1,4 +1,3 @@
<<<<<<< HEAD
import _ from 'lodash';
import errors from 'ui/errors';
import angular from 'angular';
@ -36,7 +35,6 @@ export default function IndexPatternFactory(Private, timefilter, Notifier, confi
timeFieldName: 'string',
notExpandable: 'boolean',
intervalName: 'string',
sourceFiltering: 'json',
fields: 'json',
fieldFormatMap: {
type: 'string',
@ -127,9 +125,15 @@ export default function IndexPatternFactory(Private, timefilter, Notifier, confi
self.save();
};
// Get the source filtering configuration for that index
self.getSourceFiltering = function () {
return self.sourceFiltering;
// 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)
.value()
};
};
function initFields(fields) {

View file

@ -51,6 +51,7 @@ import {
'format',
'analyzed',
'indexed',
'retrieved',
'controls'
];