From 7393c230a43b00fdb53d01aa0b2820c010624110 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Thu, 3 Dec 2020 08:09:23 -0700 Subject: [PATCH] [data.search.searchSource] Update SearchSource to use Fields API. (#82383) --- ...gins-data-public.searchsource.getfields.md | 6 +- ...s-data-public.searchsourcefields.fields.md | 4 +- ...lic.searchsourcefields.fieldsfromsource.md | 18 + ...-plugins-data-public.searchsourcefields.md | 3 +- .../search_examples/public/components/app.tsx | 380 ++++++++++----- .../filter_docvalue_fields.test.ts | 30 -- .../search_source/filter_docvalue_fields.ts | 33 -- .../search_source/search_source.test.ts | 444 +++++++++++++++--- .../search/search_source/search_source.ts | 154 ++++-- .../data/common/search/search_source/types.ts | 18 +- src/plugins/data/public/public.api.md | 9 +- .../helpers/get_sharing_data.test.ts | 8 +- .../application/helpers/get_sharing_data.ts | 2 +- .../__snapshots__/add_filter.test.tsx.snap | 4 +- .../components/add_filter/add_filter.tsx | 2 +- .../confirmation_modal.test.tsx.snap | 2 +- .../confirmation_modal/confirmation_modal.tsx | 2 +- .../header/__snapshots__/header.test.tsx.snap | 4 +- .../components/header/header.tsx | 9 +- .../edit_index_pattern/tabs/utils.ts | 2 +- .../es_search_source/es_search_source.test.ts | 2 +- .../es_search_source/es_search_source.tsx | 6 +- .../functional/apps/maps/mvt_super_fine.js | 2 +- 23 files changed, 833 insertions(+), 311 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fieldsfromsource.md delete mode 100644 src/plugins/data/common/search/search_source/filter_docvalue_fields.test.ts delete mode 100644 src/plugins/data/common/search/search_source/filter_docvalue_fields.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md index 1980227bee62..13b97ab4121c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md @@ -21,7 +21,8 @@ getFields(): { size?: number | undefined; source?: string | boolean | string[] | undefined; version?: boolean | undefined; - fields?: string | boolean | string[] | undefined; + fields?: SearchFieldValue[] | undefined; + fieldsFromSource?: string | boolean | string[] | undefined; index?: import("../..").IndexPattern | undefined; searchAfter?: import("./types").EsQuerySearchAfter | undefined; timeout?: string | undefined; @@ -42,7 +43,8 @@ getFields(): { size?: number | undefined; source?: string | boolean | string[] | undefined; version?: boolean | undefined; - fields?: string | boolean | string[] | undefined; + fields?: SearchFieldValue[] | undefined; + fieldsFromSource?: string | boolean | string[] | undefined; index?: import("../..").IndexPattern | undefined; searchAfter?: import("./types").EsQuerySearchAfter | undefined; timeout?: string | undefined; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fields.md index 21d09910bd2b..87f6a0cb7b80 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fields.md @@ -4,8 +4,10 @@ ## SearchSourceFields.fields property +Retrieve fields via the search Fields API + Signature: ```typescript -fields?: NameList; +fields?: SearchFieldValue[]; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fieldsfromsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fieldsfromsource.md new file mode 100644 index 000000000000..d343d8ce180d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.fieldsfromsource.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) > [fieldsFromSource](./kibana-plugin-plugins-data-public.searchsourcefields.fieldsfromsource.md) + +## SearchSourceFields.fieldsFromSource property + +> Warning: This API is now obsolete. +> +> It is recommended to use `fields` wherever possible. +> + +Retreive fields directly from \_source (legacy behavior) + +Signature: + +```typescript +fieldsFromSource?: NameList; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md index d19f1da439ce..683a35fabf57 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md @@ -17,7 +17,8 @@ export interface SearchSourceFields | Property | Type | Description | | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-public.searchsourcefields.aggs.md) | any | [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) | -| [fields](./kibana-plugin-plugins-data-public.searchsourcefields.fields.md) | NameList | | +| [fields](./kibana-plugin-plugins-data-public.searchsourcefields.fields.md) | SearchFieldValue[] | Retrieve fields via the search Fields API | +| [fieldsFromSource](./kibana-plugin-plugins-data-public.searchsourcefields.fieldsfromsource.md) | NameList | Retreive fields directly from \_source (legacy behavior) | | [filter](./kibana-plugin-plugins-data-public.searchsourcefields.filter.md) | Filter[] | Filter | (() => Filter[] | Filter | undefined) | [Filter](./kibana-plugin-plugins-data-public.filter.md) | | [from](./kibana-plugin-plugins-data-public.searchsourcefields.from.md) | number | | | [highlight](./kibana-plugin-plugins-data-public.searchsourcefields.highlight.md) | any | | diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx index 2425f3bbad8a..33ad8bbfe3d3 100644 --- a/examples/search_examples/public/components/app.tsx +++ b/examples/search_examples/public/components/app.tsx @@ -23,7 +23,8 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { BrowserRouter as Router } from 'react-router-dom'; import { - EuiButton, + EuiButtonEmpty, + EuiCodeBlock, EuiPage, EuiPageBody, EuiPageContent, @@ -32,6 +33,7 @@ import { EuiTitle, EuiText, EuiFlexGrid, + EuiFlexGroup, EuiFlexItem, EuiCheckbox, EuiSpacer, @@ -68,6 +70,11 @@ interface SearchExamplesAppDeps { data: DataPublicPluginStart; } +function getNumeric(fields?: IndexPatternField[]) { + if (!fields) return []; + return fields?.filter((f) => f.type === 'number' && f.aggregatable); +} + function formatFieldToComboBox(field?: IndexPatternField | null) { if (!field) return []; return formatFieldsToComboBox([field]); @@ -95,8 +102,13 @@ export const SearchExamplesApp = ({ const [getCool, setGetCool] = useState(false); const [timeTook, setTimeTook] = useState(); const [indexPattern, setIndexPattern] = useState(); - const [numericFields, setNumericFields] = useState(); - const [selectedField, setSelectedField] = useState(); + const [fields, setFields] = useState(); + const [selectedFields, setSelectedFields] = useState([]); + const [selectedNumericField, setSelectedNumericField] = useState< + IndexPatternField | null | undefined + >(); + const [request, setRequest] = useState>({}); + const [response, setResponse] = useState>({}); // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. useEffect(() => { @@ -110,24 +122,23 @@ export const SearchExamplesApp = ({ // Update the fields list every time the index pattern is modified. useEffect(() => { - const fields = indexPattern?.fields.filter( - (field) => field.type === 'number' && field.aggregatable - ); - setNumericFields(fields); - setSelectedField(fields?.length ? fields[0] : null); + setFields(indexPattern?.fields); }, [indexPattern]); + useEffect(() => { + setSelectedNumericField(fields?.length ? getNumeric(fields)[0] : null); + }, [fields]); const doAsyncSearch = async (strategy?: string) => { - if (!indexPattern || !selectedField) return; + if (!indexPattern || !selectedNumericField) return; // Constuct the query portion of the search request const query = data.query.getEsQuery(indexPattern); // Constuct the aggregations portion of the search request by using the `data.search.aggs` service. - const aggs = [{ type: 'avg', params: { field: selectedField.name } }]; + const aggs = [{ type: 'avg', params: { field: selectedNumericField!.name } }]; const aggsDsl = data.search.aggs.createAggConfigs(indexPattern, aggs).toDsl(); - const request = { + const req = { params: { index: indexPattern.title, body: { @@ -140,23 +151,26 @@ export const SearchExamplesApp = ({ }; // Submit the search request using the `data.search` service. + setRequest(req.params.body); const searchSubscription$ = data.search - .search(request, { + .search(req, { strategy, }) .subscribe({ - next: (response) => { - if (isCompleteResponse(response)) { - setTimeTook(response.rawResponse.took); - const avgResult: number | undefined = response.rawResponse.aggregations - ? response.rawResponse.aggregations[1].value + next: (res) => { + if (isCompleteResponse(res)) { + setResponse(res.rawResponse); + setTimeTook(res.rawResponse.took); + const avgResult: number | undefined = res.rawResponse.aggregations + ? res.rawResponse.aggregations[1].value : undefined; const message = ( - Searched {response.rawResponse.hits.total} documents.
- The average of {selectedField.name} is {avgResult ? Math.floor(avgResult) : 0}. + Searched {res.rawResponse.hits.total} documents.
+ The average of {selectedNumericField!.name} is{' '} + {avgResult ? Math.floor(avgResult) : 0}.
- Is it Cool? {String((response as IMyStrategyResponse).cool)} + Is it Cool? {String((res as IMyStrategyResponse).cool)}
); notifications.toasts.addSuccess({ @@ -164,7 +178,7 @@ export const SearchExamplesApp = ({ text: mountReactNode(message), }); searchSubscription$.unsubscribe(); - } else if (isErrorResponse(response)) { + } else if (isErrorResponse(res)) { // TODO: Make response error status clearer notifications.toasts.addWarning('An error has occurred'); searchSubscription$.unsubscribe(); @@ -176,6 +190,50 @@ export const SearchExamplesApp = ({ }); }; + const doSearchSourceSearch = async () => { + if (!indexPattern) return; + + const query = data.query.queryString.getQuery(); + const filters = data.query.filterManager.getFilters(); + const timefilter = data.query.timefilter.timefilter.createFilter(indexPattern); + if (timefilter) { + filters.push(timefilter); + } + + try { + const searchSource = await data.search.searchSource.create(); + + searchSource + .setField('index', indexPattern) + .setField('filter', filters) + .setField('query', query) + .setField('fields', selectedFields.length ? selectedFields.map((f) => f.name) : ['*']); + + if (selectedNumericField) { + searchSource.setField('aggs', () => { + return data.search.aggs + .createAggConfigs(indexPattern, [ + { type: 'avg', params: { field: selectedNumericField.name } }, + ]) + .toDsl(); + }); + } + + setRequest(await searchSource.getSearchRequestBody()); + const res = await searchSource.fetch(); + setResponse(res); + + const message = Searched {res.hits.total} documents.; + notifications.toasts.addSuccess({ + title: 'Query result', + text: mountReactNode(message), + }); + } catch (e) { + setResponse(e.body); + notifications.toasts.addWarning(`An error has occurred: ${e.message}`); + } + }; + const onClickHandler = () => { doAsyncSearch(); }; @@ -185,22 +243,24 @@ export const SearchExamplesApp = ({ }; const onServerClickHandler = async () => { - if (!indexPattern || !selectedField) return; + if (!indexPattern || !selectedNumericField) return; try { - const response = await http.get(SERVER_SEARCH_ROUTE_PATH, { + const res = await http.get(SERVER_SEARCH_ROUTE_PATH, { query: { index: indexPattern.title, - field: selectedField.name, + field: selectedNumericField!.name, }, }); - notifications.toasts.addSuccess(`Server returned ${JSON.stringify(response)}`); + notifications.toasts.addSuccess(`Server returned ${JSON.stringify(res)}`); } catch (e) { notifications.toasts.addDanger('Failed to run search'); } }; - if (!indexPattern) return null; + const onSearchSourceClickHandler = () => { + doSearchSourceSearch(); + }; return ( @@ -212,7 +272,7 @@ export const SearchExamplesApp = ({ useDefaultBehaviors={true} indexPatterns={indexPattern ? [indexPattern] : undefined} /> - + @@ -227,106 +287,178 @@ export const SearchExamplesApp = ({ - - - - Index Pattern - { - const newIndexPattern = await data.indexPatterns.get(newIndexPatternId); - setIndexPattern(newIndexPattern); - }} - isClearable={false} + + + + + + Index Pattern + { + const newIndexPattern = await data.indexPatterns.get( + newIndexPatternId + ); + setIndexPattern(newIndexPattern); + }} + isClearable={false} + /> + + + Numeric Field to Aggregate + { + const fld = indexPattern?.getFieldByName(option[0].label); + setSelectedNumericField(fld || null); + }} + sortMatchesBy="startsWith" + /> + + + + + + Fields to query (leave blank to include all fields) + + { + const flds = option + .map((opt) => indexPattern?.getFieldByName(opt?.label)) + .filter((f) => f); + setSelectedFields(flds.length ? (flds as IndexPatternField[]) : []); + }} + sortMatchesBy="startsWith" + /> + + + + + +

+ Searching Elasticsearch using data.search +

+
+ + If you want to fetch data from Elasticsearch, you can use the different + services provided by the data plugin. These help you get + the index pattern and search bar configuration, format them into a DSL query + and send it to Elasticsearch. + + + + + + + + + + +

Writing a custom search strategy

+
+ + If you want to do some pre or post processing on the server, you might want + to create a custom search strategy. This example uses such a strategy, + passing in custom input and receiving custom output back. + + + } + checked={getCool} + onChange={(event) => setGetCool(event.target.checked)} /> -
- - Numeric Fields - { - const field = indexPattern.getFieldByName(option[0].label); - setSelectedField(field || null); - }} - sortMatchesBy="startsWith" - /> - -
-
- - - - - -

- Searching Elasticsearch using data.search -

-
- - If you want to fetch data from Elasticsearch, you can use the different services - provided by the data plugin. These help you get the index - pattern and search bar configuration, format them into a DSL query and send it - to Elasticsearch. - - - - - - - -

Writing a custom search strategy

-
- - If you want to do some pre or post processing on the server, you might want to - create a custom search strategy. This example uses such a strategy, passing in - custom input and receiving custom output back. - - + + + + + +

Using search on the server

+
+ + You can also run your search request from the server, without registering a + search strategy. This request does not take the configuration of{' '} + TopNavMenu into account, but you could pass those down to + the server as well. + + + + + + + + +

Request

+
+ Search body sent to ES + + {JSON.stringify(request, null, 2)} + +
+ + +

Response

+
+ - } - checked={getCool} - onChange={(event) => setGetCool(event.target.checked)} - /> - - - - - - -

Using search on the server

-
- - You can also run your search request from the server, without registering a - search strategy. This request does not take the configuration of{' '} - TopNavMenu into account, but you could pass those down to the - server as well. - - - - + + + {JSON.stringify(response, null, 2)} + +
+
diff --git a/src/plugins/data/common/search/search_source/filter_docvalue_fields.test.ts b/src/plugins/data/common/search/search_source/filter_docvalue_fields.test.ts deleted file mode 100644 index 522117fe2280..000000000000 --- a/src/plugins/data/common/search/search_source/filter_docvalue_fields.test.ts +++ /dev/null @@ -1,30 +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 { filterDocvalueFields } from './filter_docvalue_fields'; - -test('Should exclude docvalue_fields that are not contained in fields', () => { - const docvalueFields = [ - 'my_ip_field', - { field: 'my_keyword_field' }, - { field: 'my_date_field', format: 'epoch_millis' }, - ]; - const out = filterDocvalueFields(docvalueFields, ['my_ip_field', 'my_keyword_field']); - expect(out).toEqual(['my_ip_field', { field: 'my_keyword_field' }]); -}); diff --git a/src/plugins/data/common/search/search_source/filter_docvalue_fields.ts b/src/plugins/data/common/search/search_source/filter_docvalue_fields.ts deleted file mode 100644 index bbac30d7dfdc..000000000000 --- a/src/plugins/data/common/search/search_source/filter_docvalue_fields.ts +++ /dev/null @@ -1,33 +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. - */ - -interface DocvalueField { - field: string; - [key: string]: unknown; -} - -export function filterDocvalueFields( - docvalueFields: Array, - fields: string[] -) { - return docvalueFields.filter((docValue) => { - const docvalueFieldName = typeof docValue === 'string' ? docValue : docValue.field; - return fields.includes(docvalueFieldName); - }); -} diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index e7bdcb159f3c..d0c6f0456a8f 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -29,7 +29,7 @@ jest.mock('./legacy', () => ({ const getComputedFields = () => ({ storedFields: [], - scriptFields: [], + scriptFields: {}, docvalueFields: [], }); @@ -51,6 +51,7 @@ const indexPattern2 = ({ describe('SearchSource', () => { let mockSearchMethod: any; let searchSourceDependencies: SearchSourceDependencies; + let searchSource: SearchSource; beforeEach(() => { mockSearchMethod = jest.fn().mockReturnValue(of({ rawResponse: '' })); @@ -64,19 +65,12 @@ describe('SearchSource', () => { loadingCount$: new BehaviorSubject(0), }, }; - }); - describe('#setField()', () => { - test('sets the value for the property', () => { - const searchSource = new SearchSource({}, searchSourceDependencies); - searchSource.setField('aggs', 5); - expect(searchSource.getField('aggs')).toBe(5); - }); + searchSource = new SearchSource({}, searchSourceDependencies); }); describe('#getField()', () => { test('gets the value for the property', () => { - const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('aggs', 5); expect(searchSource.getField('aggs')).toBe(5); }); @@ -84,52 +78,391 @@ describe('SearchSource', () => { describe('#removeField()', () => { test('remove property', () => { - const searchSource = new SearchSource({}, searchSourceDependencies); + searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('aggs', 5); searchSource.removeField('aggs'); expect(searchSource.getField('aggs')).toBeFalsy(); }); }); - describe(`#setField('index')`, () => { - describe('auto-sourceFiltering', () => { - describe('new index pattern assigned', () => { - test('generates a searchSource filter', async () => { - const searchSource = new SearchSource({}, searchSourceDependencies); - expect(searchSource.getField('index')).toBe(undefined); - expect(searchSource.getField('source')).toBe(undefined); - searchSource.setField('index', indexPattern); - expect(searchSource.getField('index')).toBe(indexPattern); - const request = await searchSource.getSearchRequestBody(); - expect(request._source).toBe(mockSource); - }); + describe('#setField() / #flatten', () => { + test('sets the value for the property', () => { + searchSource.setField('aggs', 5); + expect(searchSource.getField('aggs')).toBe(5); + }); - test('removes created searchSource filter on removal', async () => { - const searchSource = new SearchSource({}, searchSourceDependencies); - searchSource.setField('index', indexPattern); - searchSource.setField('index', undefined); - const request = await searchSource.getSearchRequestBody(); - expect(request._source).toBe(undefined); + describe('computed fields handling', () => { + test('still provides computed fields when no fields are specified', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: ['hello'], + scriptFields: { world: {} }, + docvalueFields: ['@timestamp'], + }), + } as unknown) as IndexPattern); + + const request = await searchSource.getSearchRequestBody(); + expect(request.stored_fields).toEqual(['hello']); + expect(request.script_fields).toEqual({ world: {} }); + expect(request.fields).toEqual(['@timestamp']); + }); + + test('never includes docvalue_fields', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: ['@timestamp'], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['@timestamp']); + searchSource.setField('fieldsFromSource', ['foo']); + + const request = await searchSource.getSearchRequestBody(); + expect(request).not.toHaveProperty('docvalue_fields'); + }); + + test('overrides computed docvalue fields with ones that are provided', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: ['hello'], + }), + } as unknown) as IndexPattern); + // @ts-expect-error TS won't like using this field name, but technically it's possible. + searchSource.setField('docvalue_fields', ['world']); + + const request = await searchSource.getSearchRequestBody(); + expect(request).toHaveProperty('docvalue_fields'); + expect(request.docvalue_fields).toEqual(['world']); + }); + + test('allows explicitly provided docvalue fields to override fields API when fetching fieldsFromSource', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: 'a', format: 'date_time' }], + }), + } as unknown) as IndexPattern); + // @ts-expect-error TS won't like using this field name, but technically it's possible. + searchSource.setField('docvalue_fields', [{ field: 'b', format: 'date_time' }]); + searchSource.setField('fields', ['c']); + searchSource.setField('fieldsFromSource', ['a', 'b', 'd']); + + const request = await searchSource.getSearchRequestBody(); + expect(request).toHaveProperty('docvalue_fields'); + expect(request._source.includes).toEqual(['c', 'a', 'b', 'd']); + expect(request.docvalue_fields).toEqual([{ field: 'b', format: 'date_time' }]); + expect(request.fields).toEqual(['c', { field: 'a', format: 'date_time' }]); + }); + + test('allows you to override computed fields if you provide a format', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: 'hello', format: 'date_time' }], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', [{ field: 'hello', format: 'strict_date_time' }]); + + const request = await searchSource.getSearchRequestBody(); + expect(request).toHaveProperty('fields'); + expect(request.fields).toEqual([{ field: 'hello', format: 'strict_date_time' }]); + }); + + test('injects a date format for computed docvalue fields if none is provided', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: 'hello', format: 'date_time' }], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['hello']); + + const request = await searchSource.getSearchRequestBody(); + expect(request).toHaveProperty('fields'); + expect(request.fields).toEqual([{ field: 'hello', format: 'date_time' }]); + }); + + test('injects a date format for computed docvalue fields while merging other properties', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: 'hello', format: 'date_time', a: 'test', b: 'test' }], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', [{ field: 'hello', a: 'a', c: 'c' }]); + + const request = await searchSource.getSearchRequestBody(); + expect(request).toHaveProperty('fields'); + expect(request.fields).toEqual([ + { field: 'hello', format: 'date_time', a: 'a', b: 'test', c: 'c' }, + ]); + }); + + test('merges provided script fields with computed fields', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: { hello: {} }, + docvalueFields: [], + }), + } as unknown) as IndexPattern); + // @ts-expect-error TS won't like using this field name, but technically it's possible. + searchSource.setField('script_fields', { world: {} }); + + const request = await searchSource.getSearchRequestBody(); + expect(request).toHaveProperty('script_fields'); + expect(request.script_fields).toEqual({ + hello: {}, + world: {}, }); }); - describe('new index pattern assigned over another', () => { - test('replaces searchSource filter with new', async () => { - const searchSource = new SearchSource({}, searchSourceDependencies); - searchSource.setField('index', indexPattern); - searchSource.setField('index', indexPattern2); - expect(searchSource.getField('index')).toBe(indexPattern2); - const request = await searchSource.getSearchRequestBody(); - expect(request._source).toBe(mockSource2); + test(`requests any fields that aren't script_fields from stored_fields`, async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: { hello: {} }, + docvalueFields: [], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['hello', 'a', { field: 'c' }]); + + const request = await searchSource.getSearchRequestBody(); + expect(request.script_fields).toEqual({ hello: {} }); + expect(request.stored_fields).toEqual(['a', 'c']); + }); + + test('ignores objects without a `field` property when setting stored_fields', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: { hello: {} }, + docvalueFields: [], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['hello', 'a', { foo: 'c' }]); + + const request = await searchSource.getSearchRequestBody(); + expect(request.script_fields).toEqual({ hello: {} }); + expect(request.stored_fields).toEqual(['a']); + }); + + test(`requests any fields that aren't script_fields from stored_fields with fieldsFromSource`, async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: { hello: {} }, + docvalueFields: [], + }), + } as unknown) as IndexPattern); + searchSource.setField('fieldsFromSource', ['hello', 'a']); + + const request = await searchSource.getSearchRequestBody(); + expect(request.script_fields).toEqual({ hello: {} }); + expect(request.stored_fields).toEqual(['a']); + }); + + test('defaults to * for stored fields when no fields are provided', async () => { + const requestA = await searchSource.getSearchRequestBody(); + expect(requestA.stored_fields).toEqual(['*']); + + searchSource.setField('fields', ['*']); + const requestB = await searchSource.getSearchRequestBody(); + expect(requestB.stored_fields).toEqual(['*']); + }); + + test('defaults to * for stored fields when no fields are provided with fieldsFromSource', async () => { + searchSource.setField('fieldsFromSource', ['*']); + const request = await searchSource.getSearchRequestBody(); + expect(request.stored_fields).toEqual(['*']); + }); + }); + + describe('source filters handling', () => { + test('excludes docvalue fields based on source filtering', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: ['@timestamp', 'exclude-me'], + }), + } as unknown) as IndexPattern); + // @ts-expect-error Typings for excludes filters need to be fixed. + searchSource.setField('source', { excludes: ['exclude-*'] }); + + const request = await searchSource.getSearchRequestBody(); + expect(request.fields).toEqual(['@timestamp']); + }); + + test('defaults to source filters from index pattern', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: ['@timestamp', 'foo-bar', 'foo-baz'], + }), + } as unknown) as IndexPattern); + + const request = await searchSource.getSearchRequestBody(); + expect(request.fields).toEqual(['@timestamp']); + }); + + test('filters script fields to only include specified fields', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: { hello: {}, world: {} }, + docvalueFields: [], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['hello']); + + const request = await searchSource.getSearchRequestBody(); + expect(request.script_fields).toEqual({ hello: {} }); + }); + }); + + describe('handling for when specific fields are provided', () => { + test('fieldsFromSource will request any fields outside of script_fields from _source & stored fields', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: { hello: {}, world: {} }, + docvalueFields: ['@timestamp'], + }), + } as unknown) as IndexPattern); + searchSource.setField('fieldsFromSource', [ + 'hello', + 'world', + '@timestamp', + 'foo-a', + 'bar-b', + ]); + + const request = await searchSource.getSearchRequestBody(); + expect(request._source).toEqual({ + includes: ['@timestamp', 'bar-b'], + }); + expect(request.stored_fields).toEqual(['@timestamp', 'bar-b']); + }); + + test('filters request when a specific list of fields is provided', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: ['*'], + scriptFields: { hello: {}, world: {} }, + docvalueFields: ['@timestamp', 'date'], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['hello', '@timestamp', 'foo-a', 'bar']); + + const request = await searchSource.getSearchRequestBody(); + expect(request.fields).toEqual(['hello', '@timestamp', 'bar']); + expect(request.script_fields).toEqual({ hello: {} }); + expect(request.stored_fields).toEqual(['@timestamp', 'bar']); + }); + + test('filters request when a specific list of fields is provided with fieldsFromSource', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: ['*'], + scriptFields: { hello: {}, world: {} }, + docvalueFields: ['@timestamp', 'date'], + }), + } as unknown) as IndexPattern); + searchSource.setField('fieldsFromSource', ['hello', '@timestamp', 'foo-a', 'bar']); + + const request = await searchSource.getSearchRequestBody(); + expect(request._source).toEqual({ + includes: ['@timestamp', 'bar'], + }); + expect(request.fields).toEqual(['@timestamp']); + expect(request.script_fields).toEqual({ hello: {} }); + expect(request.stored_fields).toEqual(['@timestamp', 'bar']); + }); + + test('filters request when a specific list of fields is provided with fieldsFromSource or fields', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: ['*'], + scriptFields: { hello: {}, world: {} }, + docvalueFields: ['@timestamp', 'date', 'time'], + }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['hello', '@timestamp', 'foo-a', 'bar']); + searchSource.setField('fieldsFromSource', ['foo-b', 'date', 'baz']); + + const request = await searchSource.getSearchRequestBody(); + expect(request._source).toEqual({ + includes: ['@timestamp', 'bar', 'date', 'baz'], + }); + expect(request.fields).toEqual(['hello', '@timestamp', 'bar', 'date']); + expect(request.script_fields).toEqual({ hello: {} }); + expect(request.stored_fields).toEqual(['@timestamp', 'bar', 'date', 'baz']); + }); + }); + + describe(`#setField('index')`, () => { + describe('auto-sourceFiltering', () => { + describe('new index pattern assigned', () => { + test('generates a searchSource filter', async () => { + expect(searchSource.getField('index')).toBe(undefined); + expect(searchSource.getField('source')).toBe(undefined); + searchSource.setField('index', indexPattern); + expect(searchSource.getField('index')).toBe(indexPattern); + const request = await searchSource.getSearchRequestBody(); + expect(request._source).toBe(mockSource); + }); + + test('removes created searchSource filter on removal', async () => { + searchSource.setField('index', indexPattern); + searchSource.setField('index', undefined); + const request = await searchSource.getSearchRequestBody(); + expect(request._source).toBe(undefined); + }); }); - test('removes created searchSource filter on removal', async () => { - const searchSource = new SearchSource({}, searchSourceDependencies); - searchSource.setField('index', indexPattern); - searchSource.setField('index', indexPattern2); - searchSource.setField('index', undefined); - const request = await searchSource.getSearchRequestBody(); - expect(request._source).toBe(undefined); + describe('new index pattern assigned over another', () => { + test('replaces searchSource filter with new', async () => { + searchSource.setField('index', indexPattern); + searchSource.setField('index', indexPattern2); + expect(searchSource.getField('index')).toBe(indexPattern2); + const request = await searchSource.getSearchRequestBody(); + expect(request._source).toBe(mockSource2); + }); + + test('removes created searchSource filter on removal', async () => { + searchSource.setField('index', indexPattern); + searchSource.setField('index', indexPattern2); + searchSource.setField('index', undefined); + const request = await searchSource.getSearchRequestBody(); + expect(request._source).toBe(undefined); + }); }); }); }); @@ -137,7 +470,7 @@ describe('SearchSource', () => { describe('#onRequestStart()', () => { test('should be called when starting a request', async () => { - const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const fn = jest.fn(); searchSource.onRequestStart(fn); const options = {}; @@ -147,7 +480,7 @@ describe('SearchSource', () => { test('should not be called on parent searchSource', async () => { const parent = new SearchSource({}, searchSourceDependencies); - const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const fn = jest.fn(); searchSource.onRequestStart(fn); @@ -162,12 +495,12 @@ describe('SearchSource', () => { test('should be called on parent searchSource if callParentStartHandlers is true', async () => { const parent = new SearchSource({}, searchSourceDependencies); - const searchSource = new SearchSource( - { index: indexPattern }, - searchSourceDependencies - ).setParent(parent, { - callParentStartHandlers: true, - }); + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies).setParent( + parent, + { + callParentStartHandlers: true, + } + ); const fn = jest.fn(); searchSource.onRequestStart(fn); @@ -192,7 +525,7 @@ describe('SearchSource', () => { }); test('should call msearch', async () => { - const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const options = {}; await searchSource.fetch(options); expect(fetchSoon).toBeCalledTimes(1); @@ -201,7 +534,7 @@ describe('SearchSource', () => { describe('#search service fetch()', () => { test('should call msearch', async () => { - const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const options = {}; await searchSource.fetch(options); @@ -212,7 +545,6 @@ describe('SearchSource', () => { describe('#serialize', () => { test('should reference index patterns', () => { const indexPattern123 = { id: '123' } as IndexPattern; - const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('index', indexPattern123); const { searchSourceJSON, references } = searchSource.serialize(); expect(references[0].id).toEqual('123'); @@ -221,7 +553,6 @@ describe('SearchSource', () => { }); test('should add other fields', () => { - const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('highlightAll', true); searchSource.setField('from', 123456); const { searchSourceJSON } = searchSource.serialize(); @@ -230,7 +561,6 @@ describe('SearchSource', () => { }); test('should omit sort and size', () => { - const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('highlightAll', true); searchSource.setField('from', 123456); searchSource.setField('sort', { field: SortDirection.asc }); @@ -240,7 +570,6 @@ describe('SearchSource', () => { }); test('should serialize filters', () => { - const searchSource = new SearchSource({}, searchSourceDependencies); const filter = [ { query: 'query', @@ -257,7 +586,6 @@ describe('SearchSource', () => { }); test('should reference index patterns in filters separately from index field', () => { - const searchSource = new SearchSource({}, searchSourceDependencies); const indexPattern123 = { id: '123' } as IndexPattern; searchSource.setField('index', indexPattern123); const filter = [ diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 79ef3a3f11ca..2206d6d2816e 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -70,14 +70,18 @@ */ import { setWith } from '@elastic/safer-lodash-set'; -import { uniqueId, uniq, extend, pick, difference, omit, isObject, keys, isFunction } from 'lodash'; +import { uniqueId, keyBy, pick, difference, omit, isObject, isFunction } from 'lodash'; import { map } from 'rxjs/operators'; import { normalizeSortRequest } from './normalize_sort_request'; -import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; import { IIndexPattern } from '../../index_patterns'; import { ISearchGeneric, ISearchOptions } from '../..'; -import type { ISearchSource, SearchSourceOptions, SearchSourceFields } from './types'; +import type { + ISearchSource, + SearchFieldValue, + SearchSourceOptions, + SearchSourceFields, +} from './types'; import { FetchHandlers, RequestFailure, getSearchParamsFromRequest, SearchRequest } from './fetch'; import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common'; @@ -404,7 +408,11 @@ export class SearchSource { case 'query': return addToRoot(key, (data[key] || []).concat(val)); case 'fields': - const fields = uniq((data[key] || []).concat(val)); + // uses new Fields API + return addToBody('fields', val); + case 'fieldsFromSource': + // preserves legacy behavior + const fields = [...new Set((data[key] || []).concat(val))]; return addToRoot(key, fields); case 'index': case 'type': @@ -451,49 +459,127 @@ export class SearchSource { } private flatten() { + const { getConfig } = this.dependencies; const searchRequest = this.mergeProps(); searchRequest.body = searchRequest.body || {}; - const { body, index, fields, query, filters, highlightAll } = searchRequest; + const { body, index, query, filters, highlightAll } = searchRequest; searchRequest.indexType = this.getIndexType(index); - const computedFields = index ? index.getComputedFields() : {}; + // get some special field types from the index pattern + const { docvalueFields, scriptFields, storedFields } = index + ? index.getComputedFields() + : { + docvalueFields: [], + scriptFields: {}, + storedFields: ['*'], + }; - body.stored_fields = computedFields.storedFields; - body.script_fields = body.script_fields || {}; - extend(body.script_fields, computedFields.scriptFields); + const fieldListProvided = !!body.fields; + const getFieldName = (fld: string | Record): string => + typeof fld === 'string' ? fld : fld.field; - const defaultDocValueFields = computedFields.docvalueFields - ? computedFields.docvalueFields - : []; - body.docvalue_fields = body.docvalue_fields || defaultDocValueFields; + // set defaults + let fieldsFromSource = searchRequest.fieldsFromSource || []; + body.fields = body.fields || []; + body.script_fields = { + ...body.script_fields, + ...scriptFields, + }; + body.stored_fields = storedFields; - if (!body.hasOwnProperty('_source') && index) { - body._source = index.getSourceFiltering(); + // apply source filters from index pattern if specified by the user + let filteredDocvalueFields = docvalueFields; + if (index) { + const sourceFilters = index.getSourceFiltering(); + if (!body.hasOwnProperty('_source')) { + body._source = sourceFilters; + } + if (body._source.excludes) { + const filter = fieldWildcardFilter( + body._source.excludes, + getConfig(UI_SETTINGS.META_FIELDS) + ); + // also apply filters to provided fields & default docvalueFields + body.fields = body.fields.filter((fld: SearchFieldValue) => filter(getFieldName(fld))); + fieldsFromSource = fieldsFromSource.filter((fld: SearchFieldValue) => + filter(getFieldName(fld)) + ); + filteredDocvalueFields = filteredDocvalueFields.filter((fld: SearchFieldValue) => + filter(getFieldName(fld)) + ); + } } - const { getConfig } = this.dependencies; - - if (body._source) { - // exclude source fields for this index pattern specified by the user - const filter = fieldWildcardFilter(body._source.excludes, getConfig(UI_SETTINGS.META_FIELDS)); - body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) => - filter(docvalueField.field) + // specific fields were provided, so we need to exclude any others + if (fieldListProvided || fieldsFromSource.length) { + const bodyFieldNames = body.fields.map((field: string | Record) => + getFieldName(field) ); - } + const uniqFieldNames = [...new Set([...bodyFieldNames, ...fieldsFromSource])]; - // if we only want to search for certain fields - if (fields) { - // filter out the docvalue_fields, and script_fields to only include those that we are concerned with - body.docvalue_fields = filterDocvalueFields(body.docvalue_fields, fields); - body.script_fields = pick(body.script_fields, fields); - - // request the remaining fields from both stored_fields and _source - const remainingFields = difference(fields, keys(body.script_fields)); - body.stored_fields = remainingFields; - setWith(body, '_source.includes', remainingFields, (nsValue) => - isObject(nsValue) ? {} : nsValue + // filter down script_fields to only include items specified + body.script_fields = pick( + body.script_fields, + Object.keys(body.script_fields).filter((f) => uniqFieldNames.includes(f)) ); + + // request the remaining fields from stored_fields just in case, since the + // fields API does not handle stored fields + const remainingFields = difference(uniqFieldNames, Object.keys(body.script_fields)).filter( + Boolean + ); + + // only include unique values + body.stored_fields = [...new Set(remainingFields)]; + + if (fieldsFromSource.length) { + // include remaining fields in _source + setWith(body, '_source.includes', remainingFields, (nsValue) => + isObject(nsValue) ? {} : nsValue + ); + + // if items that are in the docvalueFields are provided, we should + // make sure those are added to the fields API unless they are + // already set in docvalue_fields + body.fields = [ + ...body.fields, + ...filteredDocvalueFields.filter((fld: SearchFieldValue) => { + return ( + fieldsFromSource.includes(getFieldName(fld)) && + !(body.docvalue_fields || []) + .map((d: string | Record) => getFieldName(d)) + .includes(getFieldName(fld)) + ); + }), + ]; + + // delete fields array if it is still set to the empty default + if (!fieldListProvided && body.fields.length === 0) delete body.fields; + } else { + // remove _source, since everything's coming from fields API, scripted, or stored fields + body._source = false; + + // if items that are in the docvalueFields are provided, we should + // inject the format from the computed fields if one isn't given + const docvaluesIndex = keyBy(filteredDocvalueFields, 'field'); + body.fields = body.fields.map((fld: SearchFieldValue) => { + const fieldName = getFieldName(fld); + if (Object.keys(docvaluesIndex).includes(fieldName)) { + // either provide the field object from computed docvalues, + // or merge the user-provided field with the one in docvalues + return typeof fld === 'string' + ? docvaluesIndex[fld] + : { + ...docvaluesIndex[fieldName], + ...fld, + }; + } + return fld; + }); + } + } else { + body.fields = filteredDocvalueFields; } const esQueryConfigs = getEsQueryConfig({ get: getConfig }); diff --git a/src/plugins/data/common/search/search_source/types.ts b/src/plugins/data/common/search/search_source/types.ts index 5fc747d454a0..c428dcf7fb48 100644 --- a/src/plugins/data/common/search/search_source/types.ts +++ b/src/plugins/data/common/search/search_source/types.ts @@ -59,6 +59,13 @@ export interface SortDirectionNumeric { export type EsQuerySortValue = Record; +interface SearchField { + [key: string]: SearchFieldValue; +} + +// @internal +export type SearchFieldValue = string | SearchField; + /** * search source fields */ @@ -86,7 +93,16 @@ export interface SearchSourceFields { size?: number; source?: NameList; version?: boolean; - fields?: NameList; + /** + * Retrieve fields via the search Fields API + */ + fields?: SearchFieldValue[]; + /** + * Retreive fields directly from _source (legacy behavior) + * + * @deprecated It is recommended to use `fields` wherever possible. + */ + fieldsFromSource?: NameList; /** * {@link IndexPatternService} */ diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index ad1861cecea0..484f07633e20 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2171,7 +2171,8 @@ export class SearchSource { size?: number | undefined; source?: string | boolean | string[] | undefined; version?: boolean | undefined; - fields?: string | boolean | string[] | undefined; + fields?: SearchFieldValue[] | undefined; + fieldsFromSource?: string | boolean | string[] | undefined; index?: import("../..").IndexPattern | undefined; searchAfter?: import("./types").EsQuerySearchAfter | undefined; timeout?: string | undefined; @@ -2205,8 +2206,9 @@ export class SearchSource { export interface SearchSourceFields { // (undocumented) aggs?: any; - // (undocumented) - fields?: NameList; + fields?: SearchFieldValue[]; + // @deprecated + fieldsFromSource?: NameList; // (undocumented) filter?: Filter[] | Filter | (() => Filter[] | Filter | undefined); // (undocumented) @@ -2406,6 +2408,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:135:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:113:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/search/search_source/search_source.ts:197:7 - (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts index 8ce9789d1dc8..b2aa3a05d7eb 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts @@ -46,10 +46,8 @@ describe('getSharingData', () => { ], "searchRequest": Object { "body": Object { - "_source": Object { - "includes": Array [], - }, - "docvalue_fields": Array [], + "_source": Object {}, + "fields": undefined, "query": Object { "bool": Object { "filter": Array [], @@ -60,7 +58,7 @@ describe('getSharingData', () => { }, "script_fields": Object {}, "sort": Array [], - "stored_fields": Array [], + "stored_fields": undefined, }, "index": "the-index-pattern-title", }, diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.ts index 0edaa356cba7..e8844eb4eb6b 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.ts @@ -63,7 +63,7 @@ export async function getSharingData( index.timeFieldName || '', config.get(DOC_HIDE_TIME_COLUMN_SETTING) ); - searchSource.setField('fields', searchFields); + searchSource.setField('fieldsFromSource', searchFields); searchSource.setField( 'sort', getSortForSearchSource(state.sort as SortOrder[], index, config.get(SORT_DEFAULT_ORDER_SETTING)) diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap index 92998bc3f07e..8c4e05085528 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap @@ -8,7 +8,7 @@ exports[`AddFilter should ignore strings with just spaces 1`] = ` @@ -35,7 +35,7 @@ exports[`AddFilter should render normally 1`] = ` diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx index 1d840743065a..56e33b9e20f5 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx @@ -31,7 +31,7 @@ const sourcePlaceholder = i18n.translate( 'indexPatternManagement.editIndexPattern.sourcePlaceholder', { defaultMessage: - "source filter, accepts wildcards (e.g., `user*` to filter fields starting with 'user')", + "field filter, accepts wildcards (e.g., `user*` to filter fields starting with 'user')", } ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap index 0020adb19983..9d92a3689b69 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap @@ -23,7 +23,7 @@ exports[`Header should render normally 1`] = ` onConfirm={[Function]} title={

@@ -16,7 +16,7 @@ exports[`Header should render normally 1`] = `

diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/header/header.tsx index 709908a1bb25..cf62ef86ade1 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/header/header.tsx @@ -28,7 +28,7 @@ export const Header = () => (

@@ -36,10 +36,9 @@ export const Header = () => (

diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts index a94ed60b7aed..ed51fc3be596 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts @@ -67,7 +67,7 @@ function getTitle(type: string, filteredCount: Dictionary, totalCount: D break; case 'sourceFilters': title = i18n.translate('indexPatternManagement.editIndexPattern.tabs.sourceHeader', { - defaultMessage: 'Source filters', + defaultMessage: 'Field filters', }); break; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index ec14a80ae761..3f8b9d3e28e1 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -113,7 +113,7 @@ describe('ESSearchSource', () => { }); const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters); expect(urlTemplateWithMeta.urlTemplate).toBe( - `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fields,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape` + `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fieldsFromSource,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape` ); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 1c0645ae797e..5a923f0ce429 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -375,7 +375,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye maxResultWindow, initialSearchContext ); - searchSource.setField('fields', searchFilters.fieldNames); // Setting "fields" filters out unused scripted fields + searchSource.setField('fieldsFromSource', searchFilters.fieldNames); // Setting "fields" filters out unused scripted fields if (sourceOnlyFields.length === 0) { searchSource.setField('source', false); // do not need anything from _source } else { @@ -505,7 +505,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye }; searchSource.setField('query', query); - searchSource.setField('fields', this._getTooltipPropertyNames()); + searchSource.setField('fieldsFromSource', this._getTooltipPropertyNames()); const resp = await searchSource.fetch(); @@ -708,7 +708,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye indexSettings.maxResultWindow, initialSearchContext ); - searchSource.setField('fields', searchFilters.fieldNames); // Setting "fields" filters out unused scripted fields + searchSource.setField('fieldsFromSource', searchFilters.fieldNames); // Setting "fields" filters out unused scripted fields if (sourceOnlyFields.length === 0) { searchSource.setField('source', false); // do not need anything from _source } else { diff --git a/x-pack/test/functional/apps/maps/mvt_super_fine.js b/x-pack/test/functional/apps/maps/mvt_super_fine.js index 6d86b93c3ec4..3de2f461bc85 100644 --- a/x-pack/test/functional/apps/maps/mvt_super_fine.js +++ b/x-pack/test/functional/apps/maps/mvt_super_fine.js @@ -32,7 +32,7 @@ export default function ({ getPageObjects, getService }) { //Source should be correct expect(mapboxStyle.sources[MB_VECTOR_SOURCE_ID].tiles[0]).to.equal( - "/api/maps/mvt/getGridTile?x={x}&y={y}&z={z}&geometryFieldName=geo.coordinates&index=logstash-*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:geo.coordinates)),max_of_bytes:(max:(field:bytes))),geotile_grid:(bounds:!n,field:geo.coordinates,precision:!n,shard_size:65535,size:65535))),docvalue_fields:!((field:'@timestamp',format:date_time),(field:'relatedContent.article:modified_time',format:date_time),(field:'relatedContent.article:published_time',format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(format:strict_date_optional_time,gte:'2015-09-20T00:00:00.000Z',lte:'2015-09-20T01:00:00.000Z')))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:'doc[!'@timestamp!'].value.getHour()'))),size:0,stored_fields:!('*'))&requestType=grid&geoFieldType=geo_point" + "/api/maps/mvt/getGridTile?x={x}&y={y}&z={z}&geometryFieldName=geo.coordinates&index=logstash-*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:geo.coordinates)),max_of_bytes:(max:(field:bytes))),geotile_grid:(bounds:!n,field:geo.coordinates,precision:!n,shard_size:65535,size:65535))),fields:!((field:'@timestamp',format:date_time),(field:'relatedContent.article:modified_time',format:date_time),(field:'relatedContent.article:published_time',format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(format:strict_date_optional_time,gte:'2015-09-20T00:00:00.000Z',lte:'2015-09-20T01:00:00.000Z')))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:'doc[!'@timestamp!'].value.getHour()'))),size:0,stored_fields:!('*'))&requestType=grid&geoFieldType=geo_point" ); //Should correctly load meta for style-rule (sigma is set to 1, opacity to 1)