From ea61476e9af40376e274e7c10f8e169a7b266278 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Thu, 30 Nov 2023 12:43:39 -0400 Subject: [PATCH] [Discover/CSV Reporting] Fix support for nested field columns in CSV reports (#172240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary When we generate the parameters for the report, we add all of the selected columns as entries in the search request `fields` array (or `*` if none are selected, which is why this case works), but this doesn't work for nested fields since [the fields API doesn't support nested field roots](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#search-fields-nested): >However, when the `fields` pattern targets the nested `user` field directly, no values will be returned because the pattern doesn’t match any leaf fields. Instead we can detect nested fields and add them to the `fields` array as `{nestedFieldName}.*`, ensuring that all of the leaf fields are returned in the response. Fixes #172236. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit ab5ff9ca626baa90c3cc0e92813ff70cb5956e23) # Conflicts: # src/plugins/discover/public/application/apps/main/utils/get_sharing_data.test.ts # src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts # x-pack/plugins/reporting/tsconfig.json --- .../apps/main/utils/get_sharing_data.test.ts | 33 +++++++++++++++++++ .../apps/main/utils/get_sharing_data.ts | 16 +++++++++ .../get_csv_panel_action.test.ts | 3 +- x-pack/plugins/reporting/tsconfig.json | 1 + 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.test.ts b/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.test.ts index 71c86217236a1..d0fe8610252a3 100644 --- a/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.test.ts @@ -17,6 +17,7 @@ import { SEARCH_FIELDS_FROM_SOURCE, } from '../../../../../common'; import { indexPatternMock } from '../../../../__mocks__/index_pattern'; +import { buildDataViewMock, dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { getSharingData, showPublicUrlSwitch } from './get_sharing_data'; describe('getSharingData', () => { @@ -147,6 +148,38 @@ describe('getSharingData', () => { ); expect(getSearchSource().fields).toStrictEqual([ 'cool-timefield', + ]); + }); + + test('getSearchSource supports nested fields', async () => { + const index = buildDataViewMock({ + name: 'the-data-view', + timeFieldName: 'cool-timefield', + fields: [ + ...dataViewMock.fields, + { + name: 'cool-field-2.field', + type: 'keyword', + subType: { + nested: { + path: 'cool-field-2.field.path', + }, + }, + }, + ] as DataView['fields'], + }); + const searchSourceMock = createSearchSourceMock({ index }); + const { getSearchSource } = await getSharingData( + searchSourceMock, + { + columns: ['cool-field-1', 'cool-field-2'], + }, + services + ); + expect(getSearchSource({}).fields).toStrictEqual([ + { field: 'cool-timefield', include_unmapped: 'true' }, + { field: 'cool-field-1', include_unmapped: 'true' }, + { field: 'cool-field-2.*', include_unmapped: 'true' }, 'cool-field-1', 'cool-field-2', 'cool-field-3', diff --git a/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts b/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts index a8d8a755a7666..7998c171b5535 100644 --- a/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts +++ b/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts @@ -12,6 +12,7 @@ import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { Filter, ISearchSource, SearchSourceFields } from 'src/plugins/data/common'; import { DOC_HIDE_TIME_COLUMN_SETTING, + isNestedFieldParent, SORT_DEFAULT_ORDER_SETTING, SEARCH_FIELDS_FROM_SOURCE, } from '../../../../../common'; @@ -93,6 +94,21 @@ export async function getSharingData( const useFieldsApi = !config.get(SEARCH_FIELDS_FROM_SOURCE); if (useFieldsApi && columns.length) { searchSource.setField('fields', columns); + const useFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); + if (useFieldsApi) { + searchSource.removeField('fieldsFromSource'); + const fields = columns.length + ? columns.map((column) => { + let field = column; + + // If this column is a nested field, add a wildcard to the field name in order to fetch + // all leaf fields for the report, since the fields API doesn't support nested field roots + if (isNestedFieldParent(column, index)) { + field = `${column}.*`; + } + + return { field, include_unmapped: 'true' }; + }) } return searchSource.getSerializedFields(true); }, diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts index 4fc909a3f97a4..601228a6e8916 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts @@ -17,6 +17,7 @@ import { ReportingAPIClient } from '../lib/reporting_api_client'; import type { ReportingPublicPluginStartDendencies } from '../plugin'; import type { ActionContext } from './get_csv_panel_action'; import { ReportingCsvPanelAction } from './get_csv_panel_action'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; type LicenseResults = 'valid' | 'invalid' | 'unavailable' | 'expired'; @@ -119,7 +120,7 @@ describe('GetCsvReportPanelAction', () => { createCopy: () => mockSearchSource, removeField: jest.fn(), setField: jest.fn(), - getField: jest.fn(), + getField: jest.fn((name) => (name === 'index' ? dataViewMock : undefined)), getSerializedFields: jest.fn().mockImplementation(() => ({ testData: 'testDataValue' })), } as unknown as SearchSource; context.embeddable.getSavedSearch = () => { diff --git a/x-pack/plugins/reporting/tsconfig.json b/x-pack/plugins/reporting/tsconfig.json index 068d4de077723..3810d30fe8710 100644 --- a/x-pack/plugins/reporting/tsconfig.json +++ b/x-pack/plugins/reporting/tsconfig.json @@ -11,6 +11,7 @@ "public/**/*", "server/**/*", "../../../typings/**/*" + "@kbn/discover-utils", ], "references": [ { "path": "../../../src/core/tsconfig.json" },