Indexed fields type filter (#10708)

* Add a dropdown to filter indexed fields by type

Added tests and test subjects for index pattern filters

Use UI Framework components

Change index field filter to use kuiFieldGroup, move filters below tabs

* Dynamically load indexed type filter based on existing field types

* Iterate over fields once to get types / languages

* Only check scripted flag for checking scripted lang fields, all other
fields should have their type added to type filter
This commit is contained in:
Richard Hoffman 2017-03-22 12:42:35 -07:00 committed by Jim Unger
parent 1914d159a5
commit b4c18e4aa4
9 changed files with 246 additions and 17 deletions

View file

@ -80,17 +80,6 @@
</div>
</div>
<!-- Search box -->
<form role="form" class="kuiVerticalRhythm">
<input
aria-label="Filter"
ng-model="fieldFilter"
class="kuiTextInput fullWidth"
type="text"
placeholder="Filter"
/>
</form>
<!-- Tabs -->
<div class="kuiTabs kuiVerticalRhythm">
<button
@ -108,6 +97,52 @@
</button>
</div>
<!-- Field Filters -->
<form role="form" class="kuiFieldGroup kuiVerticalRhythm">
<div class="kuiFieldGroupSection kuiFieldGroupSection--wide">
<div class="kuiSearchInput">
<div class="kuiSearchInput__icon kuiIcon fa-search"></div>
<input
class="kuiSearchInput__input"
type="text"
aria-label="Filter"
ng-model="fieldFilter"
placeholder="Filter"
>
</div>
</div>
<div
class="kuiFieldGroupSection"
ng-if="state.tab == 'indexedFields' && indexedFieldTypes.length > 0"
>
<select
data-test-subj="indexedFieldTypeFilterDropdown"
class="kuiSelect"
ng-model="indexedFieldTypeFilter"
ng-change="changeFilter('indexedFieldTypeFilter', indexedFieldTypeFilter)"
ng-options="o for o in indexedFieldTypes"
>
<option value="">All field types</option>
</select>
</div>
<div
class="kuiFieldGroupSection"
ng-if="state.tab == 'scriptedFields' && scriptedFieldLanguages.length > 0"
>
<select
data-test-subj="scriptedFieldLanguageFilterDropdown"
class="kuiSelect"
ng-model="scriptedFieldLanguageFilter"
ng-change="changeFilter('scriptedFieldLanguageFilter', scriptedFieldLanguageFilter)"
ng-options="o for o in scriptedFieldLanguages"
>
<option value="">All languages</option>
</select>
</div>
</form>
<!-- Tab content -->
<indexed-fields
ng-show="state.tab == 'indexedFields'"

View file

@ -52,8 +52,28 @@ uiModules.get('apps/management')
$scope.$watch('indexPattern.fields', function () {
$scope.editSections = Private(IndicesEditSectionsProvider)($scope.indexPattern);
$scope.refreshFilters();
});
$scope.refreshFilters = function () {
const indexedFieldTypes = [];
const scriptedFieldLanguages = [];
$scope.indexPattern.fields.forEach(field => {
if (field.scripted) {
scriptedFieldLanguages.push(field.lang);
} else {
indexedFieldTypes.push(field.type);
}
});
$scope.indexedFieldTypes = _.unique(indexedFieldTypes);
$scope.scriptedFieldLanguages = _.unique(scriptedFieldLanguages);
};
$scope.changeFilter = function (filter, val) {
$scope[filter] = val || ''; // null causes filter to check for null explicitly
};
$scope.changeTab = function (obj) {
$state.tab = obj.index;
$state.save();

View file

@ -32,12 +32,15 @@ uiModules.get('apps/management')
{ title: 'controls', sortable: false }
];
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter'], refreshRows);
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'indexedFieldTypeFilter'], refreshRows);
function refreshRows() {
// clear and destroy row scopes
_.invoke(rowScopes.splice(0), '$destroy');
const fields = filter($scope.indexPattern.getNonScriptedFields(), { name: $scope.fieldFilter });
const fields = filter($scope.indexPattern.getNonScriptedFields(), {
name: $scope.fieldFilter,
type: $scope.indexedFieldTypeFilter
});
const sourceFilters = $scope.indexPattern.sourceFilters && $scope.indexPattern.sourceFilters.map(f => f.value) || [];
const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters);
_.find($scope.editSections, { index: 'indexedFields' }).count = fields.length; // Update the tab count
@ -60,7 +63,10 @@ uiModules.get('apps/management')
{
markup: typeHtml,
scope: childScope,
value: field.type
value: field.type,
attr: {
'data-test-subj': 'indexedFieldType'
}
},
_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']),
{

View file

@ -30,13 +30,16 @@ uiModules.get('apps/management')
{ title: 'controls', sortable: false }
];
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter'], refreshRows);
$scope.$watchMulti(['[]indexPattern.fields', 'fieldFilter', 'scriptedFieldLanguageFilter'], refreshRows);
function refreshRows() {
_.invoke(rowScopes, '$destroy');
rowScopes.length = 0;
const fields = filter($scope.indexPattern.getScriptedFields(), { name: $scope.fieldFilter });
const fields = filter($scope.indexPattern.getScriptedFields(), {
name: $scope.fieldFilter,
lang: $scope.scriptedFieldLanguageFilter
});
_.find($scope.editSections, { index: 'scriptedFields' }).count = fields.length; // Update the tab count
$scope.rows = fields.map(function (field) {
@ -46,7 +49,12 @@ uiModules.get('apps/management')
return [
_.escape(field.name),
_.escape(field.lang),
{
markup: field.lang,
attr: {
'data-test-subj': 'scriptedFieldLang'
}
},
_.escape(field.script),
_.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']),
{

View file

@ -209,6 +209,7 @@ kbn-management-objects-view {
kbn-management-indices {
.fields {
display: block;
table {
.table-striped()
}

View file

@ -0,0 +1,59 @@
import expect from 'expect.js';
import {
bdd,
defaultTimeout,
scenarioManager,
esClient
} from '../../../support';
import PageObjects from '../../../support/page_objects';
bdd.describe('index pattern filter', function describeIndexTests() {
bdd.before(function () {
// delete .kibana index and then wait for Kibana to re-create it
return esClient.deleteAndUpdateConfigDoc()
.then(function () {
return PageObjects.settings.navigateTo();
})
.then(function () {
return PageObjects.settings.clickKibanaIndicies();
});
});
bdd.beforeEach(function () {
return PageObjects.settings.createIndexPattern();
});
bdd.afterEach(function () {
return PageObjects.settings.removeIndexPattern();
});
bdd.it('should filter indexed fields', async function () {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndicies();
const fieldTypesBefore = await PageObjects.settings.getFieldTypes();
await PageObjects.settings.setFieldTypeFilter('string');
await PageObjects.common.try(async function() {
const fieldTypes = await PageObjects.settings.getFieldTypes();
expect(fieldTypes.length).to.be.above(0);
for (const fieldType of fieldTypes) {
expect(fieldType).to.be('string');
}
});
await PageObjects.settings.setFieldTypeFilter('number');
await PageObjects.common.try(async function() {
const fieldTypes = await PageObjects.settings.getFieldTypes();
expect(fieldTypes.length).to.be.above(0);
for (const fieldType of fieldTypes) {
expect(fieldType).to.be('number');
}
});
});
});

View file

@ -0,0 +1,70 @@
import expect from 'expect.js';
import {
bdd,
scenarioManager,
esClient
} from '../../../support';
import PageObjects from '../../../support/page_objects';
bdd.describe('filter scripted fields', function describeIndexTests() {
bdd.beforeEach(async function () {
await PageObjects.remote.setWindowSize(1200,800);
// delete .kibana index and then wait for Kibana to re-create it
await esClient.deleteAndUpdateConfigDoc({ 'dateFormat:tz':'UTC' });
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndicies();
await PageObjects.settings.createIndexPattern();
await esClient.updateConfigDoc({ 'dateFormat:tz':'UTC' });
});
const scriptedExpressionFieldName = 'ram_expr1';
const scriptedPainlessFieldName = 'ram_pain1';
bdd.it('should filter scripted fields', async function () {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndicies();
await PageObjects.settings.clickScriptedFieldsTab();
const scriptedFieldLangsBefore = await PageObjects.settings.getScriptedFieldLangs();
await PageObjects.common.debug('add scripted field');
await PageObjects.settings
.addScriptedField(scriptedExpressionFieldName,
'expression', 'number', null, '1', 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)'
);
await PageObjects.settings
.addScriptedField(scriptedPainlessFieldName,
'painless', 'number', null, '1', 'doc[\'machine.ram\'].value / (1024 * 1024 * 1024)'
);
// confirm two additional scripted fields were created
await PageObjects.common.try(async function() {
const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs();
expect(scriptedFieldLangs.length).to.be(scriptedFieldLangsBefore.length + 2);
});
await PageObjects.settings.setScriptedFieldLanguageFilter('painless');
await PageObjects.common.try(async function() {
const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs();
expect(scriptedFieldLangs.length).to.be.above(0);
for (const lang of scriptedFieldLangs) {
expect(lang).to.be('painless');
}
});
await PageObjects.settings.setScriptedFieldLanguageFilter('expression');
await PageObjects.common.try(async function() {
const scriptedFieldLangs = await PageObjects.settings.getScriptedFieldLangs();
expect(scriptedFieldLangs.length).to.be.above(0);
for (const lang of scriptedFieldLangs) {
expect(lang).to.be('expression');
}
});
});
});

View file

@ -22,4 +22,6 @@ bdd.describe('settings app', function () {
require('./_index_pattern_popularity');
require('./_kibana_settings');
require('./_scripted_fields');
require('./_index_pattern_filter');
require('./_scripted_fields_filter');
});

View file

@ -202,6 +202,34 @@ export default class SettingsPage {
});
}
async getFieldTypes() {
const fieldNameCells = await PageObjects.common.findAllTestSubjects('editIndexPattern indexedFieldType');
return await mapAsync(fieldNameCells, async cell => {
return (await cell.getVisibleText()).trim();
});
}
async getScriptedFieldLangs() {
const fieldNameCells = await PageObjects.common.findAllTestSubjects('editIndexPattern scriptedFieldLang');
return await mapAsync(fieldNameCells, async cell => {
return (await cell.getVisibleText()).trim();
});
}
async setFieldTypeFilter(type) {
await this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]')
.click();
}
async setScriptedFieldLanguageFilter(language) {
await this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + language + '"]')
.click();
}
async goToPage(pageNum) {
await this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('ul.pagination-other-pages-list.pagination-sm.ng-scope li.ng-scope:nth-child(' +