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..ec508d46cf782 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 @@ -156,6 +156,43 @@ describe('getSharingData', () => { ]); }); + test('getSearchSource supports nested fields', async () => { + const fields = [ + ...indexPatternMock.fields, + { + name: 'cool-field-2.field', + type: 'keyword', + subType: { + nested: { + path: 'cool-field-2.field.path', + }, + }, + }, + ]; + const index = { + ...indexPatternMock, + name: 'the-data-view', + timeFieldName: 'cool-timefield', + fields: { + getAll: () => fields, + getByName: (name: string) => fields.find((field) => field.name === name), + }, + } as unknown as IndexPattern; + const searchSourceMock = createSearchSourceMock({ index }); + const { getSearchSource } = await getSharingData( + searchSourceMock, + { + columns: ['cool-field-1', 'cool-field-2'], + }, + services + ); + expect(getSearchSource().fields).toStrictEqual([ + 'cool-timefield', + 'cool-field-1', + 'cool-field-2.*', + ]); + }); + test('fields have prepended timeField', async () => { const index = { ...indexPatternMock } as IndexPattern; index.timeFieldName = 'cool-timefield'; 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..b527822838e62 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 @@ -10,6 +10,9 @@ import type { Capabilities } from 'kibana/public'; import type { IUiSettingsClient } from 'src/core/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { Filter, ISearchSource, SearchSourceFields } from 'src/plugins/data/common'; +import { escapeRegExp } from 'lodash'; +import type { IndexPattern } from 'src/plugins/data/public'; +import { getDataViewFieldSubtypeNested } from '@kbn/es-query'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING, @@ -92,7 +95,20 @@ export async function getSharingData( */ const useFieldsApi = !config.get(SEARCH_FIELDS_FROM_SOURCE); if (useFieldsApi && columns.length) { - searchSource.setField('fields', columns); + searchSource.setField( + 'fields', + 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; + }) + ); } return searchSource.getSerializedFields(true); }, @@ -115,3 +131,18 @@ export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => return !!discover.show; }; + +function isNestedFieldParent(fieldName: string, dataView: IndexPattern): boolean { + const nestedRootRegex = new RegExp(escapeRegExp(fieldName) + '(\\.|$)'); + return ( + !dataView.fields.getByName(fieldName) && + !!dataView.fields.getAll().find((patternField) => { + // We only want to match a full path segment + const subTypeNested = getDataViewFieldSubtypeNested(patternField); + if (!subTypeNested) { + return false; + } + return nestedRootRegex.test(subTypeNested?.nested.path ?? ''); + }) + ); +} 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..74b49d8fc9894 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 @@ -119,7 +119,9 @@ describe('GetCsvReportPanelAction', () => { createCopy: () => mockSearchSource, removeField: jest.fn(), setField: jest.fn(), - getField: jest.fn(), + getField: jest.fn((name) => + name === 'index' ? { fields: { getAll: () => [], getByName: () => undefined } } : undefined + ), getSerializedFields: jest.fn().mockImplementation(() => ({ testData: 'testDataValue' })), } as unknown as SearchSource; context.embeddable.getSavedSearch = () => {