[data.search.searchSource] Update SearchSource to use Fields API. (#82383)

This commit is contained in:
Luke Elmers 2020-12-03 08:09:23 -07:00 committed by GitHub
parent e83bbfd289
commit 7393c230a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 833 additions and 311 deletions

View file

@ -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;

View file

@ -4,8 +4,10 @@
## SearchSourceFields.fields property
Retrieve fields via the search Fields API
<b>Signature:</b>
```typescript
fields?: NameList;
fields?: SearchFieldValue[];
```

View file

@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) &gt; [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)
<b>Signature:</b>
```typescript
fieldsFromSource?: NameList;
```

View file

@ -17,7 +17,8 @@ export interface SearchSourceFields
| Property | Type | Description |
| --- | --- | --- |
| [aggs](./kibana-plugin-plugins-data-public.searchsourcefields.aggs.md) | <code>any</code> | [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) |
| [fields](./kibana-plugin-plugins-data-public.searchsourcefields.fields.md) | <code>NameList</code> | |
| [fields](./kibana-plugin-plugins-data-public.searchsourcefields.fields.md) | <code>SearchFieldValue[]</code> | Retrieve fields via the search Fields API |
| [fieldsFromSource](./kibana-plugin-plugins-data-public.searchsourcefields.fieldsfromsource.md) | <code>NameList</code> | Retreive fields directly from \_source (legacy behavior) |
| [filter](./kibana-plugin-plugins-data-public.searchsourcefields.filter.md) | <code>Filter[] &#124; Filter &#124; (() =&gt; Filter[] &#124; Filter &#124; undefined)</code> | [Filter](./kibana-plugin-plugins-data-public.filter.md) |
| [from](./kibana-plugin-plugins-data-public.searchsourcefields.from.md) | <code>number</code> | |
| [highlight](./kibana-plugin-plugins-data-public.searchsourcefields.highlight.md) | <code>any</code> | |

View file

@ -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<boolean>(false);
const [timeTook, setTimeTook] = useState<number | undefined>();
const [indexPattern, setIndexPattern] = useState<IndexPattern | null>();
const [numericFields, setNumericFields] = useState<IndexPatternField[]>();
const [selectedField, setSelectedField] = useState<IndexPatternField | null | undefined>();
const [fields, setFields] = useState<IndexPatternField[]>();
const [selectedFields, setSelectedFields] = useState<IndexPatternField[]>([]);
const [selectedNumericField, setSelectedNumericField] = useState<
IndexPatternField | null | undefined
>();
const [request, setRequest] = useState<Record<string, any>>({});
const [response, setResponse] = useState<Record<string, any>>({});
// 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 = (
<EuiText>
Searched {response.rawResponse.hits.total} documents. <br />
The average of {selectedField.name} is {avgResult ? Math.floor(avgResult) : 0}.
Searched {res.rawResponse.hits.total} documents. <br />
The average of {selectedNumericField!.name} is{' '}
{avgResult ? Math.floor(avgResult) : 0}.
<br />
Is it Cool? {String((response as IMyStrategyResponse).cool)}
Is it Cool? {String((res as IMyStrategyResponse).cool)}
</EuiText>
);
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 = <EuiText>Searched {res.hits.total} documents.</EuiText>;
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 (
<Router basename={basename}>
@ -212,7 +272,7 @@ export const SearchExamplesApp = ({
useDefaultBehaviors={true}
indexPatterns={indexPattern ? [indexPattern] : undefined}
/>
<EuiPage restrictWidth="1000px">
<EuiPage>
<EuiPageBody>
<EuiPageHeader>
<EuiTitle size="l">
@ -227,106 +287,178 @@ export const SearchExamplesApp = ({
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
<EuiFlexGrid columns={1}>
<EuiFlexItem>
<EuiFormLabel>Index Pattern</EuiFormLabel>
<IndexPatternSelect
placeholder={i18n.translate(
'backgroundSessionExample.selectIndexPatternPlaceholder',
{
defaultMessage: 'Select index pattern',
}
)}
indexPatternId={indexPattern?.id || ''}
onChange={async (newIndexPatternId: any) => {
const newIndexPattern = await data.indexPatterns.get(newIndexPatternId);
setIndexPattern(newIndexPattern);
}}
isClearable={false}
<EuiFlexGrid columns={3}>
<EuiFlexItem style={{ width: '40%' }}>
<EuiText>
<EuiFlexGrid columns={2}>
<EuiFlexItem>
<EuiFormLabel>Index Pattern</EuiFormLabel>
<IndexPatternSelect
placeholder={i18n.translate(
'backgroundSessionExample.selectIndexPatternPlaceholder',
{
defaultMessage: 'Select index pattern',
}
)}
indexPatternId={indexPattern?.id || ''}
onChange={async (newIndexPatternId: any) => {
const newIndexPattern = await data.indexPatterns.get(
newIndexPatternId
);
setIndexPattern(newIndexPattern);
}}
isClearable={false}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormLabel>Numeric Field to Aggregate</EuiFormLabel>
<EuiComboBox
options={formatFieldsToComboBox(getNumeric(fields))}
selectedOptions={formatFieldToComboBox(selectedNumericField)}
singleSelection={true}
onChange={(option) => {
const fld = indexPattern?.getFieldByName(option[0].label);
setSelectedNumericField(fld || null);
}}
sortMatchesBy="startsWith"
/>
</EuiFlexItem>
</EuiFlexGrid>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormLabel>
Fields to query (leave blank to include all fields)
</EuiFormLabel>
<EuiComboBox
options={formatFieldsToComboBox(fields)}
selectedOptions={formatFieldsToComboBox(selectedFields)}
singleSelection={false}
onChange={(option) => {
const flds = option
.map((opt) => indexPattern?.getFieldByName(opt?.label))
.filter((f) => f);
setSelectedFields(flds.length ? (flds as IndexPatternField[]) : []);
}}
sortMatchesBy="startsWith"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>
Searching Elasticsearch using <EuiCode>data.search</EuiCode>
</h3>
</EuiTitle>
<EuiText>
If you want to fetch data from Elasticsearch, you can use the different
services provided by the <EuiCode>data</EuiCode> plugin. These help you get
the index pattern and search bar configuration, format them into a DSL query
and send it to Elasticsearch.
<EuiSpacer />
<EuiButtonEmpty size="xs" onClick={onClickHandler} iconType="play">
<FormattedMessage
id="searchExamples.buttonText"
defaultMessage="Request from low-level client (data.search.search)"
/>
</EuiButtonEmpty>
<EuiButtonEmpty
size="xs"
onClick={onSearchSourceClickHandler}
iconType="play"
>
<FormattedMessage
id="searchExamples.searchSource.buttonText"
defaultMessage="Request from high-level client (data.search.searchSource)"
/>
</EuiButtonEmpty>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>Writing a custom search strategy</h3>
</EuiTitle>
<EuiText>
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.
<EuiSpacer />
<EuiCheckbox
id="GetCool"
label={
<FormattedMessage
id="searchExamples.getCoolCheckbox"
defaultMessage="Get cool parameter?"
/>
}
checked={getCool}
onChange={(event) => setGetCool(event.target.checked)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormLabel>Numeric Fields</EuiFormLabel>
<EuiComboBox
options={formatFieldsToComboBox(numericFields)}
selectedOptions={formatFieldToComboBox(selectedField)}
singleSelection={true}
onChange={(option) => {
const field = indexPattern.getFieldByName(option[0].label);
setSelectedField(field || null);
}}
sortMatchesBy="startsWith"
/>
</EuiFlexItem>
</EuiFlexGrid>
</EuiText>
<EuiText>
<FormattedMessage
id="searchExamples.timestampText"
defaultMessage="Last query took: {time} ms"
values={{ time: timeTook || 'Unknown' }}
/>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>
Searching Elasticsearch using <EuiCode>data.search</EuiCode>
</h3>
</EuiTitle>
<EuiText>
If you want to fetch data from Elasticsearch, you can use the different services
provided by the <EuiCode>data</EuiCode> plugin. These help you get the index
pattern and search bar configuration, format them into a DSL query and send it
to Elasticsearch.
<EuiSpacer />
<EuiButton type="primary" size="s" onClick={onClickHandler}>
<FormattedMessage id="searchExamples.buttonText" defaultMessage="Get data" />
</EuiButton>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>Writing a custom search strategy</h3>
</EuiTitle>
<EuiText>
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.
<EuiSpacer />
<EuiCheckbox
id="GetCool"
label={
<EuiButtonEmpty
size="xs"
onClick={onMyStrategyClickHandler}
iconType="play"
>
<FormattedMessage
id="searchExamples.myStrategyButtonText"
defaultMessage="Request from low-level client via My Strategy"
/>
</EuiButtonEmpty>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>Using search on the server</h3>
</EuiTitle>
<EuiText>
You can also run your search request from the server, without registering a
search strategy. This request does not take the configuration of{' '}
<EuiCode>TopNavMenu</EuiCode> into account, but you could pass those down to
the server as well.
<EuiSpacer />
<EuiButtonEmpty size="xs" onClick={onServerClickHandler} iconType="play">
<FormattedMessage
id="searchExamples.myServerButtonText"
defaultMessage="Request from low-level client on the server"
/>
</EuiButtonEmpty>
</EuiText>
</EuiFlexItem>
<EuiFlexItem style={{ width: '30%' }}>
<EuiTitle size="xs">
<h4>Request</h4>
</EuiTitle>
<EuiText size="xs">Search body sent to ES</EuiText>
<EuiCodeBlock
language="json"
fontSize="s"
paddingSize="s"
overflowHeight={450}
isCopyable
>
{JSON.stringify(request, null, 2)}
</EuiCodeBlock>
</EuiFlexItem>
<EuiFlexItem style={{ width: '30%' }}>
<EuiTitle size="xs">
<h4>Response</h4>
</EuiTitle>
<EuiText size="xs">
<FormattedMessage
id="searchExamples.getCoolCheckbox"
defaultMessage="Get cool parameter?"
id="searchExamples.timestampText"
defaultMessage="Took: {time} ms"
values={{ time: timeTook || 'Unknown' }}
/>
}
checked={getCool}
onChange={(event) => setGetCool(event.target.checked)}
/>
<EuiButton type="primary" size="s" onClick={onMyStrategyClickHandler}>
<FormattedMessage
id="searchExamples.myStrategyButtonText"
defaultMessage="Get data via My Strategy"
/>
</EuiButton>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>Using search on the server</h3>
</EuiTitle>
<EuiText>
You can also run your search request from the server, without registering a
search strategy. This request does not take the configuration of{' '}
<EuiCode>TopNavMenu</EuiCode> into account, but you could pass those down to the
server as well.
<EuiButton type="primary" size="s" onClick={onServerClickHandler}>
<FormattedMessage
id="searchExamples.myServerButtonText"
defaultMessage="Get data on the server"
/>
</EuiButton>
</EuiText>
</EuiText>
<EuiCodeBlock
language="json"
fontSize="s"
paddingSize="s"
overflowHeight={450}
isCopyable
>
{JSON.stringify(response, null, 2)}
</EuiCodeBlock>
</EuiFlexItem>
</EuiFlexGrid>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>

View file

@ -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' }]);
});

View file

@ -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<string | DocvalueField>,
fields: string[]
) {
return docvalueFields.filter((docValue) => {
const docvalueFieldName = typeof docValue === 'string' ? docValue : docValue.field;
return fields.includes(docvalueFieldName);
});
}

View file

@ -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 = [

View file

@ -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, any>): 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<string, any>) =>
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<string, any>) => 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 });

View file

@ -59,6 +59,13 @@ export interface SortDirectionNumeric {
export type EsQuerySortValue = Record<string, SortDirection | SortDirectionNumeric>;
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}
*/

View file

@ -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

View file

@ -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",
},

View file

@ -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))

View file

@ -8,7 +8,7 @@ exports[`AddFilter should ignore strings with just spaces 1`] = `
<EuiFieldText
fullWidth={true}
onChange={[Function]}
placeholder="source filter, accepts wildcards (e.g., \`user*\` to filter fields starting with 'user')"
placeholder="field filter, accepts wildcards (e.g., \`user*\` to filter fields starting with 'user')"
value=""
/>
</EuiFlexItem>
@ -35,7 +35,7 @@ exports[`AddFilter should render normally 1`] = `
<EuiFieldText
fullWidth={true}
onChange={[Function]}
placeholder="source filter, accepts wildcards (e.g., \`user*\` to filter fields starting with 'user')"
placeholder="field filter, accepts wildcards (e.g., \`user*\` to filter fields starting with 'user')"
value=""
/>
</EuiFlexItem>

View file

@ -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')",
}
);

View file

@ -23,7 +23,7 @@ exports[`Header should render normally 1`] = `
onConfirm={[Function]}
title={
<FormattedMessage
defaultMessage="Delete source filter '{value}'?"
defaultMessage="Delete field filter '{value}'?"
id="indexPatternManagement.editIndexPattern.source.deleteSourceFilterLabel"
values={
Object {

View file

@ -42,7 +42,7 @@ export const DeleteFilterConfirmationModal = ({
title={
<FormattedMessage
id="indexPatternManagement.editIndexPattern.source.deleteSourceFilterLabel"
defaultMessage="Delete source filter '{value}'?"
defaultMessage="Delete field filter '{value}'?"
values={{
value: filterToDeleteValue,
}}

View file

@ -7,7 +7,7 @@ exports[`Header should render normally 1`] = `
>
<h3>
<FormattedMessage
defaultMessage="Source filters"
defaultMessage="Field filters"
id="indexPatternManagement.editIndexPattern.sourceHeader"
values={Object {}}
/>
@ -16,7 +16,7 @@ exports[`Header should render normally 1`] = `
<EuiText>
<p>
<FormattedMessage
defaultMessage="Source filters can be used to exclude one or more fields when fetching the document source. This happens when viewing a document in the Discover app, or with a table displaying results from a saved search in the Dashboard app. Each row is built using the source of a single document, and if you have documents with large or unimportant fields you may benefit from filtering those out at this lower level."
defaultMessage="Field filters can be used to exclude one or more fields when fetching a document. This happens when viewing a document in the Discover app, or with a table displaying results from a saved search in the Dashboard app. If you have documents with large or unimportant fields you may benefit from filtering those out at this lower level."
id="indexPatternManagement.editIndexPattern.sourceLabel"
values={Object {}}
/>

View file

@ -28,7 +28,7 @@ export const Header = () => (
<h3>
<FormattedMessage
id="indexPatternManagement.editIndexPattern.sourceHeader"
defaultMessage="Source filters"
defaultMessage="Field filters"
/>
</h3>
</EuiTitle>
@ -36,10 +36,9 @@ export const Header = () => (
<p>
<FormattedMessage
id="indexPatternManagement.editIndexPattern.sourceLabel"
defaultMessage="Source filters can be used to exclude one or more fields when fetching the document source. This happens when
viewing a document in the Discover app, or with a table displaying results from a saved search in the Dashboard app. Each row is
built using the source of a single document, and if you have documents with large or unimportant fields you may benefit from
filtering those out at this lower level."
defaultMessage="Field filters can be used to exclude one or more fields when fetching a document. This happens when
viewing a document in the Discover app, or with a table displaying results from a saved search in the Dashboard app.
If you have documents with large or unimportant fields you may benefit from filtering those out at this lower level."
/>
</p>
<p>

View file

@ -67,7 +67,7 @@ function getTitle(type: string, filteredCount: Dictionary<number>, totalCount: D
break;
case 'sourceFilters':
title = i18n.translate('indexPatternManagement.editIndexPattern.tabs.sourceHeader', {
defaultMessage: 'Source filters',
defaultMessage: 'Field filters',
});
break;
}

View file

@ -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`
);
});
});

View file

@ -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 {

View file

@ -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)