[7.x] Index pattern management UI -> TypeScript and New Platform Ready (indexed_fields_table) (#63364) (#63659)

* Index pattern management UI -> TypeScript and New Platform Ready (indexed_fields_table) (#63364)

* MIgrated indexed_fields_table to typescript.

* Updated docs

* Fixed comments

* Fixed types

* Fixed types.

* Fixed types

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Uladzislau Lasitsa 2020-04-17 14:53:00 +03:00 committed by GitHub
parent bc04e709ad
commit 65086bb796
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 456 additions and 306 deletions

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) &gt; [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md)
## IndexPatternField.indexPattern property
<b>Signature:</b>
```typescript
indexPattern?: IndexPattern;
```

View file

@ -27,6 +27,7 @@ export declare class Field implements IFieldType
| [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | <code>string[]</code> | | | [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | <code>string[]</code> | |
| [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | <code>boolean</code> | | | [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | <code>boolean</code> | |
| [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) | | <code>any</code> | | | [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) | | <code>any</code> | |
| [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) | | <code>IndexPattern</code> | |
| [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | <code>string</code> | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | <code>string</code> | |
| [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | <code>string</code> | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | <code>string</code> | |
| [routes](./kibana-plugin-plugins-data-public.indexpatternfield.routes.md) | | <code>Record&lt;string, string&gt;</code> | | | [routes](./kibana-plugin-plugins-data-public.indexpatternfield.routes.md) | | <code>Record&lt;string, string&gt;</code> | |

View file

@ -16,10 +16,11 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = `
"excluded": false, "excluded": false,
"format": undefined, "format": undefined,
"indexPattern": undefined, "indexPattern": undefined,
"info": undefined, "info": Array [],
"name": "Elastic", "name": "Elastic",
"routes": undefined, "routes": undefined,
"searchable": true, "searchable": true,
"type": "name",
}, },
] ]
} }
@ -43,7 +44,7 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = `
"excluded": false, "excluded": false,
"format": undefined, "format": undefined,
"indexPattern": undefined, "indexPattern": undefined,
"info": undefined, "info": Array [],
"name": "timestamp", "name": "timestamp",
"routes": undefined, "routes": undefined,
"type": "date", "type": "date",
@ -70,17 +71,18 @@ exports[`IndexedFieldsTable should render normally 1`] = `
"excluded": false, "excluded": false,
"format": undefined, "format": undefined,
"indexPattern": undefined, "indexPattern": undefined,
"info": undefined, "info": Array [],
"name": "Elastic", "name": "Elastic",
"routes": undefined, "routes": undefined,
"searchable": true, "searchable": true,
"type": "name",
}, },
Object { Object {
"displayName": "timestamp", "displayName": "timestamp",
"excluded": false, "excluded": false,
"format": undefined, "format": undefined,
"indexPattern": undefined, "indexPattern": undefined,
"info": undefined, "info": Array [],
"name": "timestamp", "name": "timestamp",
"routes": undefined, "routes": undefined,
"type": "date", "type": "date",
@ -90,7 +92,7 @@ exports[`IndexedFieldsTable should render normally 1`] = `
"excluded": false, "excluded": false,
"format": undefined, "format": undefined,
"indexPattern": undefined, "indexPattern": undefined,
"info": undefined, "info": Array [],
"name": "conflictingField", "name": "conflictingField",
"routes": undefined, "routes": undefined,
"type": "conflict", "type": "conflict",

View file

@ -98,19 +98,26 @@ exports[`Table should render normally 1`] = `
Array [ Array [
Object { Object {
"displayName": "Elastic", "displayName": "Elastic",
"info": Object {}, "excluded": false,
"format": "",
"info": Array [],
"name": "Elastic", "name": "Elastic",
"searchable": true, "searchable": true,
"type": "name",
}, },
Object { Object {
"displayName": "timestamp", "displayName": "timestamp",
"info": Object {}, "excluded": false,
"format": "YYYY-MM-DD",
"info": Array [],
"name": "timestamp", "name": "timestamp",
"type": "date", "type": "date",
}, },
Object { Object {
"displayName": "conflictingField", "displayName": "conflictingField",
"info": Object {}, "excluded": false,
"format": "",
"info": Array [],
"name": "conflictingField", "name": "conflictingField",
"type": "conflict", "type": "conflict",
}, },

View file

@ -1,231 +0,0 @@
/*
* 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, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { EuiIcon, EuiInMemoryTable, EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export class Table extends PureComponent {
static propTypes = {
indexPattern: PropTypes.object.isRequired,
items: PropTypes.array.isRequired,
editField: PropTypes.func.isRequired,
};
renderBooleanTemplate(value, label) {
return value ? <EuiIcon type="dot" color="secondary" aria-label={label} /> : <span />;
}
renderFieldName(name, field) {
const { indexPattern } = this.props;
const infoLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.additionalInfoAriaLabel',
{ defaultMessage: 'Additional field information' }
);
const timeLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.primaryTimeAriaLabel',
{ defaultMessage: 'Primary time field' }
);
const timeContent = i18n.translate(
'kbn.management.editIndexPattern.fields.table.primaryTimeTooltip',
{ defaultMessage: 'This field represents the time that events occurred.' }
);
return (
<span>
{name}
{field.info && field.info.length ? (
<span>
&nbsp;
<EuiIconTip
type="questionInCircle"
color="primary"
aria-label={infoLabel}
content={field.info.map((info, i) => (
<div key={i}>{info}</div>
))}
/>
</span>
) : null}
{indexPattern.timeFieldName === name ? (
<span>
&nbsp;
<EuiIconTip type="clock" color="primary" aria-label={timeLabel} content={timeContent} />
</span>
) : null}
</span>
);
}
renderFieldType(type, isConflict) {
const label = i18n.translate('kbn.management.editIndexPattern.fields.table.multiTypeAria', {
defaultMessage: 'Multiple type field',
});
const content = i18n.translate(
'kbn.management.editIndexPattern.fields.table.multiTypeTooltip',
{
defaultMessage:
'The type of this field changes across indices. It is unavailable for many analysis functions.',
}
);
return (
<span>
{type}
{isConflict ? (
<span>
&nbsp;
<EuiIconTip type="alert" color="warning" aria-label={label} content={content} />
</span>
) : (
''
)}
</span>
);
}
render() {
const { items, editField } = this.props;
const pagination = {
initialPageSize: 10,
pageSizeOptions: [5, 10, 25, 50],
};
const columns = [
{
field: 'displayName',
name: i18n.translate('kbn.management.editIndexPattern.fields.table.nameHeader', {
defaultMessage: 'Name',
}),
dataType: 'string',
sortable: true,
render: (value, field) => {
return this.renderFieldName(value, field);
},
width: '38%',
'data-test-subj': 'indexedFieldName',
},
{
field: 'type',
name: i18n.translate('kbn.management.editIndexPattern.fields.table.typeHeader', {
defaultMessage: 'Type',
}),
dataType: 'string',
sortable: true,
render: value => {
return this.renderFieldType(value, value === 'conflict');
},
'data-test-subj': 'indexedFieldType',
},
{
field: 'format',
name: i18n.translate('kbn.management.editIndexPattern.fields.table.formatHeader', {
defaultMessage: 'Format',
}),
dataType: 'string',
sortable: true,
},
{
field: 'searchable',
name: i18n.translate('kbn.management.editIndexPattern.fields.table.searchableHeader', {
defaultMessage: 'Searchable',
}),
description: i18n.translate(
'kbn.management.editIndexPattern.fields.table.searchableDescription',
{ defaultMessage: 'These fields can be used in the filter bar' }
),
dataType: 'boolean',
sortable: true,
render: value =>
this.renderBooleanTemplate(
value,
i18n.translate('kbn.management.editIndexPattern.fields.table.isSearchableAria', {
defaultMessage: 'Is searchable',
})
),
},
{
field: 'aggregatable',
name: i18n.translate('kbn.management.editIndexPattern.fields.table.aggregatableLabel', {
defaultMessage: 'Aggregatable',
}),
description: i18n.translate(
'kbn.management.editIndexPattern.fields.table.aggregatableDescription',
{ defaultMessage: 'These fields can be used in visualization aggregations' }
),
dataType: 'boolean',
sortable: true,
render: value =>
this.renderBooleanTemplate(
value,
i18n.translate('kbn.management.editIndexPattern.fields.table.isAggregatableAria', {
defaultMessage: 'Is aggregatable',
})
),
},
{
field: 'excluded',
name: i18n.translate('kbn.management.editIndexPattern.fields.table.excludedLabel', {
defaultMessage: 'Excluded',
}),
description: i18n.translate(
'kbn.management.editIndexPattern.fields.table.excludedDescription',
{ defaultMessage: 'Fields that are excluded from _source when it is fetched' }
),
dataType: 'boolean',
sortable: true,
render: value =>
this.renderBooleanTemplate(
value,
i18n.translate('kbn.management.editIndexPattern.fields.table.isExcludedAria', {
defaultMessage: 'Is excluded',
})
),
},
{
name: '',
actions: [
{
name: i18n.translate('kbn.management.editIndexPattern.fields.table.editLabel', {
defaultMessage: 'Edit',
}),
description: i18n.translate(
'kbn.management.editIndexPattern.fields.table.editDescription',
{ defaultMessage: 'Edit' }
),
icon: 'pencil',
onClick: editField,
type: 'icon',
'data-test-subj': 'editFieldFormat',
},
],
width: '40px',
},
];
return (
<EuiInMemoryTable items={items} columns={columns} pagination={pagination} sorting={true} />
);
}
}

View file

@ -19,31 +19,53 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public';
import { IndexedFieldItem } from '../../types';
import { Table } from '../table'; import { Table } from './table';
const indexPattern = { const indexPattern = {
timeFieldName: 'timestamp', timeFieldName: 'timestamp',
}; } as IIndexPattern;
const items = [ const items: IndexedFieldItem[] = [
{ name: 'Elastic', displayName: 'Elastic', searchable: true, info: {} }, {
{ name: 'timestamp', displayName: 'timestamp', type: 'date', info: {} }, name: 'Elastic',
{ name: 'conflictingField', displayName: 'conflictingField', type: 'conflict', info: {} }, displayName: 'Elastic',
searchable: true,
info: [],
type: 'name',
excluded: false,
format: '',
},
{
name: 'timestamp',
displayName: 'timestamp',
type: 'date',
info: [],
excluded: false,
format: 'YYYY-MM-DD',
},
{
name: 'conflictingField',
displayName: 'conflictingField',
type: 'conflict',
info: [],
excluded: false,
format: '',
},
]; ];
describe('Table', () => { describe('Table', () => {
it('should render normally', async () => { test('should render normally', () => {
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={() => {}} /> <Table indexPattern={indexPattern} items={items} editField={() => {}} />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
}); });
it('should render normal field name', async () => { test('should render normal field name', () => {
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={() => {}} /> <Table indexPattern={indexPattern} items={items} editField={() => {}} />
); );
@ -51,8 +73,8 @@ describe('Table', () => {
expect(tableCell).toMatchSnapshot(); expect(tableCell).toMatchSnapshot();
}); });
it('should render timestamp field name', async () => { test('should render timestamp field name', () => {
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={() => {}} /> <Table indexPattern={indexPattern} items={items} editField={() => {}} />
); );
@ -60,8 +82,8 @@ describe('Table', () => {
expect(tableCell).toMatchSnapshot(); expect(tableCell).toMatchSnapshot();
}); });
it('should render the boolean template (true)', async () => { test('should render the boolean template (true)', () => {
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={() => {}} /> <Table indexPattern={indexPattern} items={items} editField={() => {}} />
); );
@ -69,8 +91,8 @@ describe('Table', () => {
expect(tableCell).toMatchSnapshot(); expect(tableCell).toMatchSnapshot();
}); });
it('should render the boolean template (false)', async () => { test('should render the boolean template (false)', () => {
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={() => {}} /> <Table indexPattern={indexPattern} items={items} editField={() => {}} />
); );
@ -78,8 +100,8 @@ describe('Table', () => {
expect(tableCell).toMatchSnapshot(); expect(tableCell).toMatchSnapshot();
}); });
it('should render normal type', async () => { test('should render normal type', () => {
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={() => {}} /> <Table indexPattern={indexPattern} items={items} editField={() => {}} />
); );
@ -87,8 +109,8 @@ describe('Table', () => {
expect(tableCell).toMatchSnapshot(); expect(tableCell).toMatchSnapshot();
}); });
it('should render conflicting type', async () => { test('should render conflicting type', () => {
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={() => {}} /> <Table indexPattern={indexPattern} items={items} editField={() => {}} />
); );
@ -96,10 +118,10 @@ describe('Table', () => {
expect(tableCell).toMatchSnapshot(); expect(tableCell).toMatchSnapshot();
}); });
it('should allow edits', () => { test('should allow edits', () => {
const editField = jest.fn(); const editField = jest.fn();
const component = shallowWithI18nProvider( const component = shallow(
<Table indexPattern={indexPattern} items={items} editField={editField} /> <Table indexPattern={indexPattern} items={items} editField={editField} />
); );

View file

@ -0,0 +1,281 @@
/*
* 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, { PureComponent } from 'react';
import { EuiIcon, EuiInMemoryTable, EuiIconTip, EuiBasicTableColumn } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { IIndexPattern } from '../../../../../../../../../../../plugins/data/public';
import { IndexedFieldItem } from '../../types';
// localized labels
const additionalInfoAriaLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.additionalInfoAriaLabel',
{ defaultMessage: 'Additional field information' }
);
const primaryTimeAriaLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.primaryTimeAriaLabel',
{ defaultMessage: 'Primary time field' }
);
const primaryTimeTooltip = i18n.translate(
'kbn.management.editIndexPattern.fields.table.primaryTimeTooltip',
{ defaultMessage: 'This field represents the time that events occurred.' }
);
const multiTypeAriaLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.multiTypeAria',
{
defaultMessage: 'Multiple type field',
}
);
const multiTypeTooltip = i18n.translate(
'kbn.management.editIndexPattern.fields.table.multiTypeTooltip',
{
defaultMessage:
'The type of this field changes across indices. It is unavailable for many analysis functions.',
}
);
const nameHeader = i18n.translate('kbn.management.editIndexPattern.fields.table.nameHeader', {
defaultMessage: 'Name',
});
const typeHeader = i18n.translate('kbn.management.editIndexPattern.fields.table.typeHeader', {
defaultMessage: 'Type',
});
const formatHeader = i18n.translate('kbn.management.editIndexPattern.fields.table.formatHeader', {
defaultMessage: 'Format',
});
const searchableHeader = i18n.translate(
'kbn.management.editIndexPattern.fields.table.searchableHeader',
{
defaultMessage: 'Searchable',
}
);
const searchableDescription = i18n.translate(
'kbn.management.editIndexPattern.fields.table.searchableDescription',
{ defaultMessage: 'These fields can be used in the filter bar' }
);
const isSearchableAriaLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.isSearchableAria',
{
defaultMessage: 'Is searchable',
}
);
const aggregatableLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.aggregatableLabel',
{
defaultMessage: 'Aggregatable',
}
);
const aggregatableDescription = i18n.translate(
'kbn.management.editIndexPattern.fields.table.aggregatableDescription',
{ defaultMessage: 'These fields can be used in visualization aggregations' }
);
const isAggregatableAriaLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.isAggregatableAria',
{
defaultMessage: 'Is aggregatable',
}
);
const excludedLabel = i18n.translate('kbn.management.editIndexPattern.fields.table.excludedLabel', {
defaultMessage: 'Excluded',
});
const excludedDescription = i18n.translate(
'kbn.management.editIndexPattern.fields.table.excludedDescription',
{ defaultMessage: 'Fields that are excluded from _source when it is fetched' }
);
const isExcludedAriaLabel = i18n.translate(
'kbn.management.editIndexPattern.fields.table.isExcludedAria',
{
defaultMessage: 'Is excluded',
}
);
const editLabel = i18n.translate('kbn.management.editIndexPattern.fields.table.editLabel', {
defaultMessage: 'Edit',
});
const editDescription = i18n.translate(
'kbn.management.editIndexPattern.fields.table.editDescription',
{ defaultMessage: 'Edit' }
);
interface IndexedFieldProps {
indexPattern: IIndexPattern;
items: IndexedFieldItem[];
editField: (field: IndexedFieldItem) => void;
}
export class Table extends PureComponent<IndexedFieldProps> {
renderBooleanTemplate(value: string, arialLabel: string) {
return value ? <EuiIcon type="dot" color="secondary" aria-label={arialLabel} /> : <span />;
}
renderFieldName(name: string, field: IndexedFieldItem) {
const { indexPattern } = this.props;
return (
<span>
{name}
{field.info && field.info.length ? (
<span>
&nbsp;
<EuiIconTip
type="questionInCircle"
color="primary"
aria-label={additionalInfoAriaLabel}
content={field.info.map((info: string, i: number) => (
<div key={i}>{info}</div>
))}
/>
</span>
) : null}
{indexPattern.timeFieldName === name ? (
<span>
&nbsp;
<EuiIconTip
type="clock"
color="primary"
aria-label={primaryTimeAriaLabel}
content={primaryTimeTooltip}
/>
</span>
) : null}
</span>
);
}
renderFieldType(type: string, isConflict: boolean) {
return (
<span>
{type}
{isConflict ? (
<span>
&nbsp;
<EuiIconTip
type="alert"
color="warning"
aria-label={multiTypeAriaLabel}
content={multiTypeTooltip}
/>
</span>
) : (
''
)}
</span>
);
}
render() {
const { items, editField } = this.props;
const pagination = {
initialPageSize: 10,
pageSizeOptions: [5, 10, 25, 50],
};
const columns: Array<EuiBasicTableColumn<IndexedFieldItem>> = [
{
field: 'displayName',
name: nameHeader,
dataType: 'string',
sortable: true,
render: (value: string, field: IndexedFieldItem) => {
return this.renderFieldName(value, field);
},
width: '38%',
'data-test-subj': 'indexedFieldName',
},
{
field: 'type',
name: typeHeader,
dataType: 'string',
sortable: true,
render: (value: string) => {
return this.renderFieldType(value, value === 'conflict');
},
'data-test-subj': 'indexedFieldType',
},
{
field: 'format',
name: formatHeader,
dataType: 'string',
sortable: true,
},
{
field: 'searchable',
name: searchableHeader,
description: searchableDescription,
dataType: 'boolean',
sortable: true,
render: (value: string) => this.renderBooleanTemplate(value, isSearchableAriaLabel),
},
{
field: 'aggregatable',
name: aggregatableLabel,
description: aggregatableDescription,
dataType: 'boolean',
sortable: true,
render: (value: string) => this.renderBooleanTemplate(value, isAggregatableAriaLabel),
},
{
field: 'excluded',
name: excludedLabel,
description: excludedDescription,
dataType: 'boolean',
sortable: true,
render: (value: string) => this.renderBooleanTemplate(value, isExcludedAriaLabel),
},
{
name: '',
actions: [
{
name: editLabel,
description: editDescription,
icon: 'pencil',
onClick: editField,
type: 'icon',
'data-test-subj': 'editFieldFormat',
},
],
width: '40px',
},
];
return (
<EuiInMemoryTable items={items} columns={columns} pagination={pagination} sorting={true} />
);
}
}

View file

@ -19,8 +19,8 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { IndexPatternField, IIndexPattern } from '../../../../../../../../../plugins/data/public';
import { IndexedFieldsTable } from '../indexed_fields_table'; import { IndexedFieldsTable } from './indexed_fields_table';
jest.mock('@elastic/eui', () => ({ jest.mock('@elastic/eui', () => ({
EuiFlexGroup: 'eui-flex-group', EuiFlexGroup: 'eui-flex-group',
@ -29,7 +29,7 @@ jest.mock('@elastic/eui', () => ({
EuiInMemoryTable: 'eui-in-memory-table', EuiInMemoryTable: 'eui-in-memory-table',
})); }));
jest.mock('../components/table', () => ({ jest.mock('./components/table', () => ({
// Note: this seems to fix React complaining about non lowercase attributes // Note: this seems to fix React complaining about non lowercase attributes
Table: () => { Table: () => {
return 'table'; return 'table';
@ -37,27 +37,37 @@ jest.mock('../components/table', () => ({
})); }));
const helpers = { const helpers = {
redirectToRoute: () => {}, redirectToRoute: (obj: any, route: string) => {},
getFieldInfo: () => [],
}; };
const fields = [ const fields = [
{ name: 'Elastic', displayName: 'Elastic', searchable: true }, {
name: 'Elastic',
displayName: 'Elastic',
searchable: true,
type: 'name',
},
{ name: 'timestamp', displayName: 'timestamp', type: 'date' }, { name: 'timestamp', displayName: 'timestamp', type: 'date' },
{ name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' }, { name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' },
]; ] as IndexPatternField[];
const indexPattern = { const indexPattern = ({
getNonScriptedFields: () => fields, getNonScriptedFields: () => fields,
}; } as unknown) as IIndexPattern;
describe('IndexedFieldsTable', () => { describe('IndexedFieldsTable', () => {
it('should render normally', async () => { test('should render normally', async () => {
const component = shallow( const component = shallow(
<IndexedFieldsTable <IndexedFieldsTable
fields={fields} fields={fields}
indexPattern={indexPattern} indexPattern={indexPattern}
helpers={helpers} helpers={helpers}
fieldWildcardMatcher={() => {}} fieldWildcardMatcher={() => {
return () => false;
}}
indexedFieldTypeFilter=""
fieldFilter=""
/> />
); );
@ -67,13 +77,17 @@ describe('IndexedFieldsTable', () => {
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
}); });
it('should filter based on the query bar', async () => { test('should filter based on the query bar', async () => {
const component = shallow( const component = shallow(
<IndexedFieldsTable <IndexedFieldsTable
fields={fields} fields={fields}
indexPattern={indexPattern} indexPattern={indexPattern}
helpers={helpers} helpers={helpers}
fieldWildcardMatcher={() => {}} fieldWildcardMatcher={() => {
return () => false;
}}
indexedFieldTypeFilter=""
fieldFilter=""
/> />
); );
@ -84,13 +98,17 @@ describe('IndexedFieldsTable', () => {
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
}); });
it('should filter based on the type filter', async () => { test('should filter based on the type filter', async () => {
const component = shallow( const component = shallow(
<IndexedFieldsTable <IndexedFieldsTable
fields={fields} fields={fields}
indexPattern={indexPattern} indexPattern={indexPattern}
helpers={helpers} helpers={helpers}
fieldWildcardMatcher={() => {}} fieldWildcardMatcher={() => {
return () => false;
}}
indexedFieldTypeFilter=""
fieldFilter=""
/> />
); );

View file

@ -18,26 +18,33 @@
*/ */
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { IndexPatternField, IIndexPattern } from '../../../../../../../../../plugins/data/public';
import { Table } from './components/table'; import { Table } from './components/table';
import { getFieldFormat } from './lib'; import { getFieldFormat } from './lib';
import { IndexedFieldItem } from './types';
export class IndexedFieldsTable extends Component { interface IndexedFieldsTableProps {
static propTypes = { fields: IndexPatternField[];
fields: PropTypes.array.isRequired, indexPattern: IIndexPattern;
indexPattern: PropTypes.object.isRequired, fieldFilter?: string;
fieldFilter: PropTypes.string, indexedFieldTypeFilter?: string;
indexedFieldTypeFilter: PropTypes.string, helpers: {
helpers: PropTypes.shape({ redirectToRoute: (obj: any, route: string) => void;
redirectToRoute: PropTypes.func.isRequired, getFieldInfo: (indexPattern: IIndexPattern, field: string) => string[];
getFieldInfo: PropTypes.func,
}),
fieldWildcardMatcher: PropTypes.func.isRequired,
}; };
fieldWildcardMatcher: (filters: any[]) => (val: any) => boolean;
}
constructor(props) { interface IndexedFieldsTableState {
fields: IndexedFieldItem[];
}
export class IndexedFieldsTable extends Component<
IndexedFieldsTableProps,
IndexedFieldsTableState
> {
constructor(props: IndexedFieldsTableProps) {
super(props); super(props);
this.state = { this.state = {
@ -45,7 +52,7 @@ export class IndexedFieldsTable extends Component {
}; };
} }
UNSAFE_componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps: IndexedFieldsTableProps) {
if (nextProps.fields !== this.props.fields) { if (nextProps.fields !== this.props.fields) {
this.setState({ this.setState({
fields: this.mapFields(nextProps.fields), fields: this.mapFields(nextProps.fields),
@ -53,10 +60,11 @@ export class IndexedFieldsTable extends Component {
} }
} }
mapFields(fields) { mapFields(fields: IndexPatternField[]): IndexedFieldItem[] {
const { indexPattern, fieldWildcardMatcher, helpers } = this.props; const { indexPattern, fieldWildcardMatcher, helpers } = this.props;
const sourceFilters = const sourceFilters =
indexPattern.sourceFilters && indexPattern.sourceFilters.map(f => f.value); indexPattern.sourceFilters &&
indexPattern.sourceFilters.map((f: Record<string, any>) => f.value);
const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters || []); const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters || []);
return ( return (
@ -77,9 +85,10 @@ export class IndexedFieldsTable extends Component {
} }
getFilteredFields = createSelector( getFilteredFields = createSelector(
state => state.fields, (state: IndexedFieldsTableState) => state.fields,
(state, props) => props.fieldFilter, (state: IndexedFieldsTableState, props: IndexedFieldsTableProps) => props.fieldFilter,
(state, props) => props.indexedFieldTypeFilter, (state: IndexedFieldsTableState, props: IndexedFieldsTableProps) =>
props.indexedFieldTypeFilter,
(fields, fieldFilter, indexedFieldTypeFilter) => { (fields, fieldFilter, indexedFieldTypeFilter) => {
if (fieldFilter) { if (fieldFilter) {
const normalizedFieldFilter = fieldFilter.toLowerCase(); const normalizedFieldFilter = fieldFilter.toLowerCase();

View file

@ -17,9 +17,10 @@
* under the License. * under the License.
*/ */
import { getFieldFormat } from '../get_field_format'; import { IIndexPattern } from '../../../../../../../../../../plugins/data/public';
import { getFieldFormat } from './get_field_format';
const indexPattern = { const indexPattern = ({
fieldFormatMap: { fieldFormatMap: {
Elastic: { Elastic: {
type: { type: {
@ -27,26 +28,26 @@ const indexPattern = {
}, },
}, },
}, },
}; } as unknown) as IIndexPattern;
describe('getFieldFormat', () => { describe('getFieldFormat', () => {
it('should handle no arguments', () => { test('should handle no arguments', () => {
expect(getFieldFormat()).toEqual(''); expect(getFieldFormat()).toEqual('');
}); });
it('should handle no field name', () => { test('should handle no field name', () => {
expect(getFieldFormat(indexPattern)).toEqual(''); expect(getFieldFormat(indexPattern)).toEqual('');
}); });
it('should handle empty name', () => { test('should handle empty name', () => {
expect(getFieldFormat(indexPattern, '')).toEqual(''); expect(getFieldFormat(indexPattern, '')).toEqual('');
}); });
it('should handle undefined field name', () => { test('should handle undefined field name', () => {
expect(getFieldFormat(indexPattern, 'none')).toEqual(undefined); expect(getFieldFormat(indexPattern, 'none')).toEqual(undefined);
}); });
it('should retrieve field format', () => { test('should retrieve field format', () => {
expect(getFieldFormat(indexPattern, 'Elastic')).toEqual('string'); expect(getFieldFormat(indexPattern, 'Elastic')).toEqual('string');
}); });
}); });

View file

@ -18,8 +18,9 @@
*/ */
import { get } from 'lodash'; import { get } from 'lodash';
import { IIndexPattern } from '../../../../../../../../../../plugins/data/public';
export function getFieldFormat(indexPattern, fieldName) { export function getFieldFormat(indexPattern?: IIndexPattern, fieldName?: string): string {
return indexPattern && fieldName return indexPattern && fieldName
? get(indexPattern, ['fieldFormatMap', fieldName, 'type', 'title']) ? get(indexPattern, ['fieldFormatMap', fieldName, 'type', 'title'])
: ''; : '';

View file

@ -0,0 +1,25 @@
/*
* 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 { IFieldType } from '../../../../../../../../../plugins/data/public';
export interface IndexedFieldItem extends IFieldType {
info: string[];
excluded: boolean;
}

View file

@ -44,6 +44,7 @@ export class Field implements IFieldType {
scripted?: boolean; scripted?: boolean;
subType?: IFieldSubType; subType?: IFieldSubType;
displayName?: string; displayName?: string;
indexPattern?: IndexPattern;
format: any; format: any;
routes: Record<string, string> = { routes: Record<string, string> = {
edit: '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}', edit: '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}',

View file

@ -953,6 +953,8 @@ export class IndexPatternField implements IFieldType {
// (undocumented) // (undocumented)
format: any; format: any;
// (undocumented) // (undocumented)
indexPattern?: IndexPattern;
// (undocumented)
lang?: string; lang?: string;
// (undocumented) // (undocumented)
name: string; name: string;