De-angularize field-name directive (#40744) (#41106)

This commit is contained in:
Matthias Wilhelm 2019-07-15 15:32:28 +02:00 committed by GitHub
parent df954c74d6
commit 9ed0b2fa76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 355 additions and 199 deletions

View file

@ -101,13 +101,15 @@ describe('docViews', function () {
it('should have the field name in the first column', function () {
_.each(_.keys(flattened), function (field) {
expect($elem.find('td[title="' + field + '"]').length).to.be(1);
expect($elem.find('[data-test-subj="tableDocViewRow-' + field + '"]').length).to.be(1);
});
});
it('should have the a value for each field', function () {
_.each(_.keys(flattened), function (field) {
const cellValue = $elem.find('td[title="' + field + '"]').siblings().find('.kbnDocViewer__value').text();
const cellValue = $elem
.find('[data-test-subj="tableDocViewRow-' + field + '"]')
.find('.kbnDocViewer__value').text();
// This sucks, but testing the filter chain is too hairy ATM
expect(cellValue.length).to.be.greaterThan(0);
@ -117,48 +119,48 @@ describe('docViews', function () {
describe('filtering', function () {
it('should apply a filter when clicking filterable fields', function () {
const cell = $elem.find('td[title="bytes"]').prev();
const row = $elem.find('[data-test-subj="tableDocViewRow-bytes"]');
cell.find('.fa-search-plus').first().click();
row.find('.fa-search-plus').first().click();
expect($scope.filter.calledOnce).to.be(true);
cell.find('.fa-search-minus').first().click();
row.find('.fa-search-minus').first().click();
expect($scope.filter.calledTwice).to.be(true);
cell.find('.fa-asterisk').first().click();
row.find('.fa-asterisk').first().click();
expect($scope.filter.calledThrice).to.be(true);
});
it('should NOT apply a filter when clicking non-filterable fields', function () {
const cell = $elem.find('td[title="area"]').prev();
const row = $elem.find('[data-test-subj="tableDocViewRow-area"]');
cell.find('.fa-search-plus').first().click();
row.find('.fa-search-plus').first().click();
expect($scope.filter.calledOnce).to.be(false);
cell.find('.fa-search-minus').first().click();
row.find('.fa-search-minus').first().click();
expect($scope.filter.calledTwice).to.be(false);
cell.find('.fa-asterisk').first().click();
row.find('.fa-asterisk').first().click();
expect($scope.filter.calledOnce).to.be(true);
});
});
describe('warnings', function () {
it('displays a warning about field name starting with underscore', function () {
const cells = $elem.find('td[title="_underscore"]').siblings();
expect(cells.find('.kbnDocViewer__underscore').length).to.be(1);
expect(cells.find('.kbnDocViewer__noMapping').length).to.be(0);
expect(cells.find('.kbnDocViewer__objectArray').length).to.be(0);
const row = $elem.find('[data-test-subj="tableDocViewRow-_underscore"]');
expect(row.find('.kbnDocViewer__underscore').length).to.be(1);
expect(row.find('.kbnDocViewer__noMapping').length).to.be(0);
expect(row.find('.kbnDocViewer__objectArray').length).to.be(0);
});
it('displays a warning about missing mappings', function () {
const cells = $elem.find('td[title="noMapping"]').siblings();
expect(cells.find('.kbnDocViewer__underscore').length).to.be(0);
expect(cells.find('.kbnDocViewer__noMapping').length).to.be(1);
expect(cells.find('.kbnDocViewer__objectArray').length).to.be(0);
const row = $elem.find('[data-test-subj="tableDocViewRow-noMapping"]');
expect(row.find('.kbnDocViewer__underscore').length).to.be(0);
expect(row.find('.kbnDocViewer__noMapping').length).to.be(1);
expect(row.find('.kbnDocViewer__objectArray').length).to.be(0);
});
it('displays a warning about objects in arrays', function () {
const cells = $elem.find('td[title="objectArray"]').siblings();
expect(cells.find('.kbnDocViewer__underscore').length).to.be(0);
expect(cells.find('.kbnDocViewer__noMapping').length).to.be(0);
expect(cells.find('.kbnDocViewer__objectArray').length).to.be(1);
const row = $elem.find('[data-test-subj="tableDocViewRow-objectArray"]');
expect(row.find('.kbnDocViewer__underscore').length).to.be(0);
expect(row.find('.kbnDocViewer__noMapping').length).to.be(0);
expect(row.find('.kbnDocViewer__objectArray').length).to.be(1);
});
});
});

View file

@ -167,9 +167,8 @@ discover-app {
color: $euiColorDarkShade;
}
// SASSTODO: replace the margin-right value with a variables
.dscField__icon {
margin-right: 5px;
margin-right: $euiSizeS;
text-align: center;
display: inline-block;
width: $euiSizeM;

View file

@ -8,10 +8,11 @@
kbn-accessible-click
class="sidebar-item-title dscSidebarItem"
>
<field-name
class="dscField dscSidebarItem__label"
field="field"
></field-name>
<div class="dscField dscSidebarItem__label">
<field-name
field="field"
></field-name>
</div>
<button
ng-if="field.name !== '_source'"

View file

@ -16,131 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
import $ from 'jquery';
import { i18n } from '@kbn/i18n';
import { template } from 'lodash';
import { shortenDottedString } from '../../../core_plugins/kibana/common/utils/shorten_dotted_string';
import booleanFieldNameIcon from './field_name_icons/boolean_field_name_icon.html';
import conflictFieldNameIcon from './field_name_icons/conflict_field_name_icon.html';
import dateFieldNameIcon from './field_name_icons/date_field_name_icon.html';
import geoPointFieldNameIcon from './field_name_icons/geo_point_field_name_icon.html';
import ipFieldNameIcon from './field_name_icons/ip_field_name_icon.html';
import murmur3FieldNameIcon from './field_name_icons/murmur3_field_name_icon.html';
import numberFieldNameIcon from './field_name_icons/number_field_name_icon.html';
import sourceFieldNameIcon from './field_name_icons/source_field_name_icon.html';
import stringFieldNameIcon from './field_name_icons/string_field_name_icon.html';
import unknownFieldNameIcon from './field_name_icons/unknown_field_name_icon.html';
import { FieldName } from './field_name/field_name';
import { uiModules } from '../modules';
import { wrapInI18nContext } from 'ui/i18n';
const module = uiModules.get('kibana');
const compiledBooleanFieldNameIcon = template(booleanFieldNameIcon);
const compiledConflictFieldNameIcon = template(conflictFieldNameIcon);
const compiledDateFieldNameIcon = template(dateFieldNameIcon);
const compiledGeoPointFieldNameIcon = template(geoPointFieldNameIcon);
const compiledIpFieldNameIcon = template(ipFieldNameIcon);
const compiledMurmur3FieldNameIcon = template(murmur3FieldNameIcon);
const compiledNumberFieldNameIcon = template(numberFieldNameIcon);
const compiledSourceFieldNameIcon = template(sourceFieldNameIcon);
const compiledStringFieldNameIcon = template(stringFieldNameIcon);
const compiledUnknownFieldNameIcon = template(unknownFieldNameIcon);
module.directive('fieldName', function ($rootScope, config) {
return {
restrict: 'AE',
scope: {
'field': '=',
'fieldName': '=',
'fieldType': '='
},
link: function ($scope, $el) {
const typeToIconMap = {
boolean: compiledBooleanFieldNameIcon({
booleanFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.booleanAriaLabel', {
defaultMessage: 'Boolean field'
}),
}),
conflict: compiledConflictFieldNameIcon({
conflictingFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.conflictFieldAriaLabel', {
defaultMessage: 'Conflicting field'
}),
}),
date: compiledDateFieldNameIcon({
dateFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.dateFieldAriaLabel', {
defaultMessage: 'Date field'
}),
}),
geo_point: compiledGeoPointFieldNameIcon({
geoPointFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.geoPointFieldAriaLabel', {
defaultMessage: 'Date field'
}),
}),
ip: compiledIpFieldNameIcon({
ipAddressFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.ipAddressFieldAriaLabel', {
defaultMessage: 'IP address field'
}),
}),
murmur3: compiledMurmur3FieldNameIcon({
murmur3FieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.murmur3FieldAriaLabel', {
defaultMessage: 'Murmur3 field'
}),
}),
number: compiledNumberFieldNameIcon({
numberFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.numberFieldAriaLabel', {
defaultMessage: 'Number field'
}),
}),
source: compiledSourceFieldNameIcon({
sourceFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.sourceFieldAriaLabel', {
defaultMessage: 'Source field'
}),
}),
string: compiledStringFieldNameIcon({
stringFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.stringFieldAriaLabel', {
defaultMessage: 'String field'
}),
}),
};
function typeIcon(fieldType) {
if (typeToIconMap.hasOwnProperty(fieldType)) {
return typeToIconMap[fieldType];
}
return compiledUnknownFieldNameIcon({
unknownFieldAriaLabel: i18n.translate('common.ui.directives.fieldNameIcons.unknownFieldAriaLabel', {
defaultMessage: 'Unknown field'
}),
});
}
$rootScope.$watchMulti.call($scope, [
'field',
'fieldName',
'fieldType',
'field.rowCount'
], function () {
const type = $scope.field ? $scope.field.type : $scope.fieldType;
const name = $scope.field ? $scope.field.name : $scope.fieldName;
const results = $scope.field ? !$scope.field.rowCount && !$scope.field.scripted : false;
const scripted = $scope.field ? $scope.field.scripted : false;
const isShortDots = config.get('shortDots:enable');
const displayName = isShortDots ? shortenDottedString(name) : name;
$el
.attr('title', name)
.toggleClass('dscField--noResults', results)
.toggleClass('scripted', scripted)
.prepend(typeIcon(type))
.append($('<span>')
.text(displayName)
.addClass('dscFieldName')
);
});
module.directive('fieldName', function (config, reactDirective) {
return reactDirective(
wrapInI18nContext(FieldName),
[
['field', { watchDepth: 'collection' }],
['fieldName', { watchDepth: 'reference' }],
['fieldType', { watchDepth: 'reference' }],
],
{ restrict: 'AE' },
{
useShortDots: config.get('shortDots:enable'),
}
};
);
});

View file

@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FieldName renders a geo field, useShortDots is set to true 1`] = `
<span
class="dscField--noResults"
title="test.test.test"
>
<span
aria-label="Geo Point"
class="dscField__icon kuiIcon fa-globe"
/>
<span
class="dscFieldName"
>
t.t.test
</span>
</span>
`;
exports[`FieldName renders a number field by providing a field record, useShortDots is set to false 1`] = `
<span
class=""
title="test.test.test"
>
<span
aria-label="Number field"
class="dscField__icon"
>
<strong
aria-hidden="true"
>
#
</strong>
</span>
<span
class="dscFieldName"
>
test.test.test
</span>
</span>
`;
exports[`FieldName renders a string field by providing fieldType and fieldName 1`] = `
<span
class=""
title="test"
>
<span
aria-label="String field"
class="dscField__icon"
>
<strong
aria-hidden="true"
>
t
</strong>
</span>
<span
class="dscFieldName"
>
test
</span>
</span>
`;

View file

@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { render } from 'enzyme';
import { FieldName } from './field_name';
// Note that it currently provides just 2 basic tests, there should be more, but
// the components involved will soon change
test('FieldName renders a string field by providing fieldType and fieldName', () => {
const component = render(<FieldName fieldType="string" fieldName="test" />);
expect(component).toMatchSnapshot();
});
test('FieldName renders a number field by providing a field record, useShortDots is set to false', () => {
const field = {
type: 'number',
name: 'test.test.test',
rowCount: 100,
scripted: false,
};
const component = render(<FieldName field={field} />);
expect(component).toMatchSnapshot();
});
test('FieldName renders a geo field, useShortDots is set to true', () => {
const field = {
type: 'geo_point',
name: 'test.test.test',
rowCount: 0,
scripted: false,
};
const component = render(<FieldName field={field} useShortDots={true} />);
expect(component).toMatchSnapshot();
});

View file

@ -0,0 +1,57 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import classNames from 'classnames';
// @ts-ignore
import { shortenDottedString } from '../../../../core_plugins/kibana/common/utils/shorten_dotted_string';
import { FieldNameIcon } from './field_name_icon';
// property field is provided at discover's field chooser
// properties fieldType and fieldName are provided in kbn_doc_view
// this should be changed when both components are deangularized
interface Props {
field?: {
type: string;
name: string;
rowCount: number;
scripted: boolean;
};
fieldName?: string;
fieldType?: string;
useShortDots?: boolean;
}
export function FieldName({ field, fieldName, fieldType, useShortDots }: Props) {
const type = field ? String(field.type) : String(fieldType);
const name = field ? String(field.name) : String(fieldName);
const displayName = useShortDots ? shortenDottedString(name) : name;
const className = classNames({
'dscField--noResults': field ? !field.rowCount && !field.scripted : false,
// this is currently not styled, should display an icon
scripted: field ? field.scripted : false,
});
return (
<span className={className} title={name}>
<FieldNameIcon type={type} />
<span className="dscFieldName">{displayName}</span>
</span>
);
}

View file

@ -0,0 +1,140 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
interface Props {
type: string;
}
export function FieldNameIcon({ type }: Props) {
switch (type) {
case 'boolean':
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.booleanAriaLabel', {
defaultMessage: 'Boolean field',
})}
className="dscField__icon kuiIcon fa-adjust"
></span>
);
case 'conflict':
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.conflictFieldAriaLabel', {
defaultMessage: 'Conflicting field',
})}
className="dscField__icon kuiIcon fa-warning"
></span>
);
case 'date':
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.dateFieldAriaLabel', {
defaultMessage: 'Date field',
})}
className="dscField__icon kuiIcon fa-clock-o"
></span>
);
case 'geo_point':
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.geoPointFieldAriaLabel', {
defaultMessage: 'Geo Point',
})}
className="dscField__icon kuiIcon fa-globe"
></span>
);
case 'ip':
return (
<span
aria-label={i18n.translate(
'common.ui.directives.fieldNameIcons.ipAddressFieldAriaLabel',
{
defaultMessage: 'IP address field',
}
)}
className="dscField__icon kuiIcon fa-laptop"
></span>
);
case 'murmur3':
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.murmur3FieldAriaLabel', {
defaultMessage: 'Murmur3 field',
})}
className="dscField__icon"
>
<strong aria-hidden="true">h</strong>
</span>
);
case 'number':
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.numberFieldAriaLabel', {
defaultMessage: 'Number field',
})}
className="dscField__icon"
>
<strong aria-hidden="true">#</strong>
</span>
);
case 'source':
// Note that this type is currently not provided, type for _source is undefined
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.sourceFieldAriaLabel', {
defaultMessage: 'Source field',
})}
className="dscField__icon kuiIcon fa-file-text-o"
></span>
);
case 'string':
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.stringFieldAriaLabel', {
defaultMessage: 'String field',
})}
className="dscField__icon"
>
<strong aria-hidden="true">t</strong>
</span>
);
default:
return (
<span
aria-label={i18n.translate('common.ui.directives.fieldNameIcons.unknownFieldAriaLabel', {
defaultMessage: 'Unknown field',
})}
className="dscField__icon"
>
<strong aria-hidden="true">?</strong>
</span>
);
}
}

View file

@ -1,4 +0,0 @@
<span
aria-label="<%= booleanFieldAriaLabel %>"
class="dscField__icon kuiIcon fa-adjust"
></span>

View file

@ -1,4 +0,0 @@
<span
aria-label="<%= conflictingFieldAriaLabel %>"
class="dscField__icon kuiIcon fa-warning"
></span>

View file

@ -1,4 +0,0 @@
<span
aria-label="<%= dateFieldAriaLabel %>"
class="dscField__icon kuiIcon fa-clock-o"
></span>

View file

@ -1,4 +0,0 @@
<span
aria-label="<%= geoPointFieldAriaLabel %>"
class="dscField__icon kuiIcon fa-globe"
></span>

View file

@ -1,4 +0,0 @@
<span
aria-label="<%= ipAddressFieldAriaLabel %>"
class="dscField__icon kuiIcon fa-laptop"
></span>

View file

@ -1,6 +0,0 @@
<span
aria-label="<%= murmur3FieldAriaLabel %>"
class="dscField__icon"
>
<strong aria-hidden="true">h</strong>
</span>

View file

@ -1,6 +0,0 @@
<span
aria-label="<%= numberFieldAriaLabel %>"
class="dscField__icon"
>
<strong aria-hidden="true">#</strong>
</span>

View file

@ -1,4 +0,0 @@
<span
aria-label="<%= sourceFieldAriaLabel %>"
class="dscField__icon kuiIcon fa-file-text-o"
></span>

View file

@ -1,6 +0,0 @@
<span
aria-label="<%= stringFieldAriaLabel %>"
class="dscField__icon"
>
<strong aria-hidden="true">t</strong>
</span>

View file

@ -1,6 +0,0 @@
<span
aria-label="<%= unknownFieldAriaLabel %>"
class="dscField__icon"
>
<strong aria-hidden="true">?</strong>
</span>