diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx index 886aeffc0666..803694db177a 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx @@ -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( { @@ -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( () => ( )); }, @@ -206,6 +221,7 @@ export const DocTableWrapper = ({ onRemoveColumn, filterManager, addBasePath, + fieldsToShow, ] ); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.test.ts index e4424ad0b9d0..5874be19b0b7 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.test.ts @@ -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 \\"quotes\\" or 'single qoutes'', @@ -69,7 +71,7 @@ describe('Row formatter', () => { }); it('formats document properly', () => { - expect(formatRow(hit, indexPattern)).toMatchInlineSnapshot(` + expect(formatRow(hit, indexPattern, fieldsToShow)).toMatchInlineSnapshot(` { get: () => 1, }, } as unknown) as DiscoverServices); - expect(formatRow(hit, indexPattern)).toMatchInlineSnapshot(` + expect(formatRow(hit, indexPattern, [])).toMatchInlineSnapshot(` { }); it('formats document with highlighted fields first', () => { - expect(formatRow({ ...hit, highlight: { number: '42' } }, indexPattern)).toMatchInlineSnapshot(` + expect(formatRow({ ...hit, highlight: { number: '42' } }, indexPattern, fieldsToShow)) + .toMatchInlineSnapshot(` { ); }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const formatRow = (hit: Record, indexPattern: IndexPattern) => { +export const formatRow = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + hit: Record, + 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, 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 ; diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index c727e7784cca..e33d25c8693a 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -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] ); /** diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx index b7e37a28fe53..5aca237d4658 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx @@ -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( diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx index b3c205e07250..0dfbdffd175a 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx @@ -28,6 +28,7 @@ export const getRenderCellValueFn = ( rows: ElasticSearchHit[] | undefined, rowsFlattened: Array>, 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 = (row.highlight as Record) ?? {}; 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 ( diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 5a8d3e7d2db4..da6820ba4a70 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -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); }); }); diff --git a/src/plugins/discover/public/application/components/table/table.tsx b/src/plugins/discover/public/application/components/table/table.tsx index d1b2d2724561..456103c77656 100644 --- a/src/plugins/discover/public/application/components/table/table.tsx +++ b/src/plugins/discover/public/application/components/table/table.tsx @@ -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); - 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); diff --git a/src/plugins/discover/public/application/helpers/get_fields_to_show.test.ts b/src/plugins/discover/public/application/helpers/get_fields_to_show.test.ts new file mode 100644 index 000000000000..13c2dbaac612 --- /dev/null +++ b/src/plugins/discover/public/application/helpers/get_fields_to_show.test.ts @@ -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 = { + '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']); + }); +}); diff --git a/src/plugins/discover/public/application/helpers/get_fields_to_show.ts b/src/plugins/discover/public/application/helpers/get_fields_to_show.ts new file mode 100644 index 000000000000..bee9bd0c1f9f --- /dev/null +++ b/src/plugins/discover/public/application/helpers/get_fields_to_show.ts @@ -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; + 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)); + }); +};