[Discover] Hide multifields from doc table (#109242) (#110064)

* [Discover] Hide multifields from doc table

* Fix failing type check

* Fix eslint

* Fix faulty logic

* Fix linting error

* Add memoization to the function

* Move getFieldsToShow a bit higher up

* Extracting getFieldsToShow logic higher up

* Fix linting error / table logic

* Move fieldsToShow to doc_table_wrapper

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Maja Grubic 2021-08-25 18:38:31 +02:00 committed by GitHub
parent a0fd8e2089
commit aa7680e3c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 210 additions and 30 deletions

View file

@ -35,6 +35,7 @@ export interface TableRowProps {
hideTimeColumn: boolean;
filterManager: FilterManager;
addBasePath: (path: string) => string;
fieldsToShow: string[];
}
export const TableRow = ({
@ -43,6 +44,7 @@ export const TableRow = ({
row,
indexPattern,
useNewFieldsApi,
fieldsToShow,
hideTimeColumn,
onAddColumn,
onRemoveColumn,
@ -125,7 +127,7 @@ export const TableRow = ({
}
if (columns.length === 0 && useNewFieldsApi) {
const formatted = formatRow(row, indexPattern);
const formatted = formatRow(row, indexPattern, fieldsToShow);
rowCells.push(
<TableCell

View file

@ -14,12 +14,14 @@ import { FORMATS_UI_SETTINGS } from '../../../../../../../field_formats/common';
import {
DOC_HIDE_TIME_COLUMN_SETTING,
SAMPLE_SIZE_SETTING,
SHOW_MULTIFIELDS,
SORT_DEFAULT_ORDER_SETTING,
} from '../../../../../../common';
import { getServices, IndexPattern } from '../../../../../kibana_services';
import { getServices, IndexPattern, IndexPatternField } from '../../../../../kibana_services';
import { SortOrder } from './components/table_header/helpers';
import { DocTableRow, TableRow } from './components/table_row';
import { DocViewFilterFn } from '../../../../doc_views/doc_views_types';
import { getFieldsToShow } from '../../../../helpers/get_fields_to_show';
export interface DocTableProps {
/**
@ -120,6 +122,7 @@ export const DocTableWrapper = ({
hideTimeColumn,
isShortDots,
sampleSize,
showMultiFields,
filterManager,
addBasePath,
] = useMemo(() => {
@ -129,6 +132,7 @@ export const DocTableWrapper = ({
services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false),
services.uiSettings.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE),
services.uiSettings.get(SAMPLE_SIZE_SETTING, 500),
services.uiSettings.get(SHOW_MULTIFIELDS, false),
services.filterManager,
services.addBasePath,
];
@ -149,6 +153,16 @@ export const DocTableWrapper = ({
bottomMarker!.blur();
}, [setMinimumVisibleRows, rows]);
const fieldsToShow = useMemo(
() =>
getFieldsToShow(
indexPattern.fields.map((field: IndexPatternField) => field.name),
indexPattern,
showMultiFields
),
[indexPattern, showMultiFields]
);
const renderHeader = useCallback(
() => (
<TableHeader
@ -193,6 +207,7 @@ export const DocTableWrapper = ({
onRemoveColumn={onRemoveColumn}
filterManager={filterManager}
addBasePath={addBasePath}
fieldsToShow={fieldsToShow}
/>
));
},
@ -206,6 +221,7 @@ export const DocTableWrapper = ({
onRemoveColumn,
filterManager,
addBasePath,
fieldsToShow,
]
);

View file

@ -45,6 +45,8 @@ describe('Row formatter', () => {
const indexPattern = createIndexPattern();
const fieldsToShow = indexPattern.fields.getAll().map((fld) => fld.name);
// Realistic response with alphabetical insertion order
const formatHitReturnValue = {
also: 'with \\&quot;quotes\\&quot; or &#39;single qoutes&#39;',
@ -69,7 +71,7 @@ describe('Row formatter', () => {
});
it('formats document properly', () => {
expect(formatRow(hit, indexPattern)).toMatchInlineSnapshot(`
expect(formatRow(hit, indexPattern, fieldsToShow)).toMatchInlineSnapshot(`
<TemplateComponent
defPairs={
Array [
@ -113,7 +115,7 @@ describe('Row formatter', () => {
get: () => 1,
},
} as unknown) as DiscoverServices);
expect(formatRow(hit, indexPattern)).toMatchInlineSnapshot(`
expect(formatRow(hit, indexPattern, [])).toMatchInlineSnapshot(`
<TemplateComponent
defPairs={
Array [
@ -128,7 +130,8 @@ describe('Row formatter', () => {
});
it('formats document with highlighted fields first', () => {
expect(formatRow({ ...hit, highlight: { number: '42' } }, indexPattern)).toMatchInlineSnapshot(`
expect(formatRow({ ...hit, highlight: { number: '42' } }, indexPattern, fieldsToShow))
.toMatchInlineSnapshot(`
<TemplateComponent
defPairs={
Array [

View file

@ -29,8 +29,12 @@ const TemplateComponent = ({ defPairs }: Props) => {
);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const formatRow = (hit: Record<string, any>, indexPattern: IndexPattern) => {
export const formatRow = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hit: Record<string, any>,
indexPattern: IndexPattern,
fieldsToShow: string[]
) => {
const highlights = hit?.highlight ?? {};
// Keys are sorted in the hits object
const formatted = indexPattern.formatHit(hit);
@ -40,7 +44,13 @@ export const formatRow = (hit: Record<string, any>, indexPattern: IndexPattern)
Object.entries(formatted).forEach(([key, val]) => {
const displayKey = fields.getByName ? fields.getByName(key)?.displayName : undefined;
const pairs = highlights[key] ? highlightPairs : sourcePairs;
pairs.push([displayKey ? displayKey : key, val]);
if (displayKey) {
if (fieldsToShow.includes(displayKey)) {
pairs.push([displayKey, val]);
}
} else {
pairs.push([key, val]);
}
});
const maxEntries = getServices().uiSettings.get(MAX_DOC_FIELDS_DISPLAYED);
return <TemplateComponent defPairs={[...highlightPairs, ...sourcePairs].slice(0, maxEntries)} />;

View file

@ -37,9 +37,10 @@ import { defaultPageSize, gridStyle, pageSizeArr, toolbarVisibility } from './co
import { DiscoverServices } from '../../../build_services';
import { getDisplayedColumns } from '../../helpers/columns';
import { KibanaContextProvider } from '../../../../../kibana_react/public';
import { MAX_DOC_FIELDS_DISPLAYED } from '../../../../common';
import { MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS } from '../../../../common';
import { DiscoverGridDocumentToolbarBtn, getDocId } from './discover_grid_document_selection';
import { SortPairArr } from '../../apps/main/components/doc_table/lib/get_sort';
import { getFieldsToShow } from '../../helpers/get_fields_to_show';
interface SortObj {
id: string;
@ -256,6 +257,13 @@ export const DiscoverGrid = ({
[onSort, isSortEnabled]
);
const showMultiFields = services.uiSettings.get(SHOW_MULTIFIELDS);
const fieldsToShow = useMemo(() => {
const indexPatternFields = indexPattern.fields.getAll().map((fld) => fld.name);
return getFieldsToShow(indexPatternFields, indexPattern, showMultiFields);
}, [indexPattern, showMultiFields]);
/**
* Cell rendering
*/
@ -266,9 +274,10 @@ export const DiscoverGrid = ({
displayedRows,
displayedRows ? displayedRows.map((hit) => indexPattern.flattenHit(hit)) : [],
useNewFieldsApi,
fieldsToShow,
services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)
),
[displayedRows, indexPattern, useNewFieldsApi, services.uiSettings]
[indexPattern, displayedRows, useNewFieldsApi, fieldsToShow, services.uiSettings]
);
/**

View file

@ -75,6 +75,7 @@ describe('Discover grid cell rendering', function () {
rowsSource,
rowsSource.map((row) => indexPatternMock.flattenHit(row)),
false,
[],
100
);
const component = shallow(
@ -96,6 +97,7 @@ describe('Discover grid cell rendering', function () {
rowsSource,
rowsSource.map((row) => indexPatternMock.flattenHit(row)),
false,
[],
100
);
const component = shallow(
@ -146,6 +148,7 @@ describe('Discover grid cell rendering', function () {
rowsSource,
rowsSource.map((row) => indexPatternMock.flattenHit(row)),
false,
[],
100
);
const component = shallow(
@ -188,6 +191,7 @@ describe('Discover grid cell rendering', function () {
rowsFields,
rowsFields.map((row) => indexPatternMock.flattenHit(row)),
true,
[],
100
);
const component = shallow(
@ -242,6 +246,7 @@ describe('Discover grid cell rendering', function () {
rowsFields,
rowsFields.map((row) => indexPatternMock.flattenHit(row)),
true,
[],
// this is the number of rendered items
1
);
@ -284,6 +289,7 @@ describe('Discover grid cell rendering', function () {
rowsFields,
rowsFields.map((row) => indexPatternMock.flattenHit(row)),
true,
[],
100
);
const component = shallow(
@ -331,6 +337,7 @@ describe('Discover grid cell rendering', function () {
rowsFieldsWithTopLevelObject,
rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)),
true,
[],
100
);
const component = shallow(
@ -371,6 +378,7 @@ describe('Discover grid cell rendering', function () {
rowsFieldsWithTopLevelObject,
rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)),
true,
[],
100
);
const component = shallow(
@ -410,6 +418,7 @@ describe('Discover grid cell rendering', function () {
rowsFieldsWithTopLevelObject,
rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)),
true,
[],
100
);
const component = shallow(
@ -440,6 +449,7 @@ describe('Discover grid cell rendering', function () {
rowsFieldsWithTopLevelObject,
rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)),
true,
[],
100
);
const component = shallow(
@ -469,6 +479,7 @@ describe('Discover grid cell rendering', function () {
rowsSource,
rowsSource.map((row) => indexPatternMock.flattenHit(row)),
false,
[],
100
);
const component = shallow(
@ -490,6 +501,7 @@ describe('Discover grid cell rendering', function () {
rowsSource,
rowsSource.map((row) => indexPatternMock.flattenHit(row)),
false,
[],
100
);
const component = shallow(

View file

@ -28,6 +28,7 @@ export const getRenderCellValueFn = (
rows: ElasticSearchHit[] | undefined,
rowsFlattened: Array<Record<string, unknown>>,
useNewFieldsApi: boolean,
fieldsToShow: string[],
maxDocFieldsDisplayed: number
) => ({ rowIndex, columnId, isDetails, setCellProps }: EuiDataGridCellValueElementProps) => {
const row = rows ? rows[rowIndex] : undefined;
@ -99,7 +100,13 @@ export const getRenderCellValueFn = (
)
.join(', ');
const pairs = highlights[key] ? highlightPairs : sourcePairs;
pairs.push([displayKey ? displayKey : key, formatted]);
if (displayKey) {
if (fieldsToShow.includes(displayKey)) {
pairs.push([displayKey, formatted]);
}
} else {
pairs.push([key, formatted]);
}
});
return (
@ -137,13 +144,18 @@ export const getRenderCellValueFn = (
const highlights: Record<string, unknown> = (row.highlight as Record<string, unknown>) ?? {};
const highlightPairs: Array<[string, string]> = [];
const sourcePairs: Array<[string, string]> = [];
Object.entries(formatted).forEach(([key, val]) => {
const pairs = highlights[key] ? highlightPairs : sourcePairs;
const displayKey = indexPattern.fields.getByName
? indexPattern.fields.getByName(key)?.displayName
: undefined;
pairs.push([displayKey ? displayKey : key, val as string]);
if (displayKey) {
if (fieldsToShow.includes(displayKey)) {
pairs.push([displayKey, val as string]);
}
} else {
pairs.push([key, val as string]);
}
});
return (

View file

@ -475,11 +475,13 @@ describe('DocViewTable at Discover Doc with Fields API', () => {
.length
).toBe(0);
expect(findTestSubject(component, 'tableDocViewRow-customer_first_name').length).toBe(1);
expect(
findTestSubject(component, 'tableDocViewRow-customer_first_name.nickname-multifieldBadge')
.length
).toBe(0);
expect(findTestSubject(component, 'tableDocViewRow-city').length).toBe(0);
expect(findTestSubject(component, 'tableDocViewRow-city.raw').length).toBe(1);
});
});

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useMemo } from 'react';
import { EuiInMemoryTable } from '@elastic/eui';
import { IndexPattern, IndexPatternField } from '../../../../../data/public';
import { SHOW_MULTIFIELDS } from '../../../../common';
@ -18,6 +18,7 @@ import {
DocViewRenderProps,
} from '../../doc_views/doc_views_types';
import { ACTIONS_COLUMN, MAIN_COLUMNS } from './table_columns';
import { getFieldsToShow } from '../../helpers/get_fields_to_show';
export interface DocViewerTableProps {
columns?: string[];
@ -61,8 +62,6 @@ export const DocViewerTable = ({
indexPattern?.fields,
]);
const [childParentFieldsMap] = useState({} as Record<string, string>);
const formattedHit = useMemo(() => indexPattern?.formatHit(hit, 'html'), [hit, indexPattern]);
const tableColumns = useMemo(() => {
@ -95,22 +94,12 @@ export const DocViewerTable = ({
return null;
}
const flattened = indexPattern.flattenHit(hit);
Object.keys(flattened).forEach((key) => {
const field = mapping(key);
if (field && field.spec?.subType?.multi?.parent) {
childParentFieldsMap[field.name] = field.spec.subType.multi.parent;
}
});
const flattened = indexPattern?.flattenHit(hit);
const fieldsToShow = getFieldsToShow(Object.keys(flattened), indexPattern, showMultiFields);
const items: FieldRecord[] = Object.keys(flattened)
.filter((fieldName) => {
const fieldMapping = mapping(fieldName);
const isMultiField = !!fieldMapping?.spec?.subType?.multi;
if (!isMultiField) {
return true;
}
const parent = childParentFieldsMap[fieldName];
return showMultiFields || (parent && !flattened.hasOwnProperty(parent));
return fieldsToShow.includes(fieldName);
})
.sort((fieldA, fieldB) => {
const mappingA = mapping(fieldA);

View file

@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { IndexPattern, IndexPatternField } from '../../../../data/common';
import { getFieldsToShow } from './get_fields_to_show';
describe('get fields to show', () => {
let indexPattern: IndexPattern;
const indexPatternFields: Record<string, IndexPatternField> = {
'machine.os': {
name: 'machine.os',
esTypes: ['text'],
type: 'string',
aggregatable: false,
searchable: false,
filterable: true,
} as IndexPatternField,
'machine.os.raw': {
name: 'machine.os.raw',
type: 'string',
esTypes: ['keyword'],
aggregatable: true,
searchable: true,
filterable: true,
spec: {
subType: {
multi: {
parent: 'machine.os',
},
},
},
} as IndexPatternField,
acknowledged: {
name: 'acknowledged',
type: 'boolean',
esTypes: ['boolean'],
aggregatable: true,
searchable: true,
filterable: true,
} as IndexPatternField,
bytes: {
name: 'bytes',
type: 'number',
esTypes: ['long'],
aggregatable: true,
searchable: true,
filterable: true,
} as IndexPatternField,
clientip: {
name: 'clientip',
type: 'ip',
esTypes: ['ip'],
aggregatable: true,
searchable: true,
filterable: true,
} as IndexPatternField,
};
const stubIndexPattern = {
id: 'logstash-*',
fields: Object.keys(indexPatternFields).map((key) => indexPatternFields[key]),
title: 'logstash-*',
timeFieldName: '@timestamp',
getTimeField: () => ({ name: '@timestamp', type: 'date' }),
};
beforeEach(() => {
indexPattern = stubIndexPattern as IndexPattern;
indexPattern.fields.getByName = (name) => indexPatternFields[name];
});
it('shows multifields when showMultiFields is true', () => {
const fieldsToShow = getFieldsToShow(
['machine.os', 'machine.os.raw', 'clientip'],
indexPattern,
true
);
expect(fieldsToShow).toEqual(['machine.os', 'machine.os.raw', 'clientip']);
});
it('do not show multifields when showMultiFields is false', () => {
const fieldsToShow = getFieldsToShow(
['machine.os', 'machine.os.raw', 'acknowledged', 'clientip'],
indexPattern,
false
);
expect(fieldsToShow).toEqual(['machine.os', 'acknowledged', 'clientip']);
});
});

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { IndexPattern } from '../../../../data/common';
export const getFieldsToShow = (
fields: string[],
indexPattern: IndexPattern,
showMultiFields: boolean
) => {
const childParentFieldsMap = {} as Record<string, string>;
const mapping = (name: string) => indexPattern.fields.getByName(name);
fields.forEach((key) => {
const mapped = mapping(key);
if (mapped && mapped.spec?.subType?.multi?.parent) {
childParentFieldsMap[mapped.name] = mapped.spec.subType.multi.parent;
}
});
return fields.filter((key: string) => {
const fieldMapping = mapping(key);
const isMultiField = !!fieldMapping?.spec?.subType?.multi;
if (!isMultiField) {
return true;
}
const parent = childParentFieldsMap[key];
return showMultiFields || (parent && !fields.includes(parent));
});
};