diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts b/src/plugins/discover/public/application/angular/doc_table/doc_table.ts
index 29ea9d5db9a34..6c2a3cbfc9512 100644
--- a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts
+++ b/src/plugins/discover/public/application/angular/doc_table/doc_table.ts
@@ -59,6 +59,10 @@ export function createDocTableDirective(pagerFactory: any, $filter: any) {
$scope.limit += 50;
};
+ $scope.$watch('minimumVisibleRows', (minimumVisibleRows: number) => {
+ $scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50);
+ });
+
$scope.$watch('hits', (hits: any) => {
if (!hits) return;
diff --git a/src/plugins/discover/public/application/components/create_discover_directive.ts b/src/plugins/discover/public/application/components/create_discover_directive.ts
index 10439488f4bc7..84f29780f1745 100644
--- a/src/plugins/discover/public/application/components/create_discover_directive.ts
+++ b/src/plugins/discover/public/application/components/create_discover_directive.ts
@@ -40,5 +40,6 @@ export function createDiscoverDirective(reactDirective: any) {
['topNavMenu', { watchDepth: 'reference' }],
['updateQuery', { watchDepth: 'reference' }],
['updateSavedQueryId', { watchDepth: 'reference' }],
+ ['unmappedFieldsConfig', { watchDepth: 'value' }],
]);
}
diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx
index 5653ef4f57435..6365cc0448b83 100644
--- a/src/plugins/discover/public/application/components/discover.tsx
+++ b/src/plugins/discover/public/application/components/discover.tsx
@@ -7,7 +7,7 @@
*/
import './discover.scss';
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useMemo } from 'react';
import {
EuiButtonEmpty,
EuiButtonIcon,
@@ -80,6 +80,7 @@ export function Discover({
topNavMenu,
updateQuery,
updateSavedQueryId,
+ unmappedFieldsConfig,
}: DiscoverProps) {
const scrollableDesktop = useRef
(null);
const collapseIcon = useRef(null);
@@ -102,6 +103,13 @@ export function Discover({
const contentCentered = resultState === 'uninitialized';
const isLegacy = services.uiSettings.get('doc_table:legacy');
const useNewFieldsApi = !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE);
+
+ const columns = useMemo(() => {
+ if (!state.columns) {
+ return [];
+ }
+ return useNewFieldsApi ? state.columns.filter((col) => col !== '_source') : state.columns;
+ }, [state, useNewFieldsApi]);
return (
@@ -127,7 +135,7 @@ export function Discover({
@@ -277,7 +286,7 @@ export function Discover({
{isLegacy && rows && rows.length && (
{
search: mockSearchApi,
},
},
+ docLinks: {
+ links: {
+ apis: {
+ indexExists: 'mockUrl',
+ },
+ },
+ },
}),
getDocViewsRegistry: () => ({
addDocView(view: any) {
diff --git a/src/plugins/discover/public/application/components/doc/doc.tsx b/src/plugins/discover/public/application/components/doc/doc.tsx
index aad5b5e95ba36..9f78ae0e29ced 100644
--- a/src/plugins/discover/public/application/components/doc/doc.tsx
+++ b/src/plugins/discover/public/application/components/doc/doc.tsx
@@ -36,7 +36,7 @@ export interface DocProps {
export function Doc(props: DocProps) {
const [reqState, hit, indexPattern] = useEsDocSearch(props);
-
+ const indexExistsLink = getServices().docLinks.links.apis.indexExists;
return (
@@ -91,12 +91,7 @@ export function Doc(props: DocProps) {
defaultMessage="{indexName} is missing."
values={{ indexName: props.index }}
/>{' '}
-
+
{
popover = component.find(EuiPopover);
expect(popover.prop('isOpen')).toBe(false);
});
+
+ test('unmapped fields', () => {
+ const onChangeUnmappedFields = jest.fn();
+ const componentProps = {
+ ...defaultProps,
+ showUnmappedFields: true,
+ useNewFieldsApi: false,
+ onChangeUnmappedFields,
+ };
+ const component = mountComponent(componentProps);
+ const btn = findTestSubject(component, 'toggleFieldFilterButton');
+ btn.simulate('click');
+ const unmappedFieldsSwitch = findTestSubject(component, 'unmappedFieldsSwitch');
+ act(() => {
+ unmappedFieldsSwitch.simulate('click');
+ });
+ expect(onChangeUnmappedFields).toHaveBeenCalledWith(false);
+ });
});
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx
index 03a5198f1b1d9..22e08bf4abf67 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx
@@ -27,6 +27,8 @@ import {
EuiOutsideClickDetector,
EuiFilterButton,
EuiSpacer,
+ EuiIcon,
+ EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -35,6 +37,7 @@ export interface State {
aggregatable: string;
type: string;
missing: boolean;
+ unmappedFields: boolean;
[index: string]: string | boolean;
}
@@ -53,13 +56,36 @@ export interface Props {
* types for the type filter
*/
types: string[];
+
+ /**
+ * use new fields api
+ */
+ useNewFieldsApi?: boolean;
+
+ /**
+ * callback funtion to change the value of unmapped fields switch
+ * @param value new value to set
+ */
+ onChangeUnmappedFields?: (value: boolean) => void;
+
+ /**
+ * should unmapped fields switch be rendered
+ */
+ showUnmappedFields?: boolean;
}
/**
* Component is Discover's side bar to search of available fields
* Additionally there's a button displayed that allows the user to show/hide more filter fields
*/
-export function DiscoverFieldSearch({ onChange, value, types }: Props) {
+export function DiscoverFieldSearch({
+ onChange,
+ value,
+ types,
+ useNewFieldsApi,
+ showUnmappedFields,
+ onChangeUnmappedFields,
+}: Props) {
const searchPlaceholder = i18n.translate('discover.fieldChooser.searchPlaceHolder', {
defaultMessage: 'Search field names',
});
@@ -85,6 +111,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
aggregatable: 'any',
type: 'any',
missing: true,
+ unmappedFields: !!showUnmappedFields,
});
if (typeof value !== 'string') {
@@ -154,6 +181,14 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
handleValueChange('missing', missingValue);
};
+ const handleUnmappedFieldsChange = (e: EuiSwitchEvent) => {
+ const unmappedFieldsValue = e.target.checked;
+ handleValueChange('unmappedFields', unmappedFieldsValue);
+ if (onChangeUnmappedFields) {
+ onChangeUnmappedFields(unmappedFieldsValue);
+ }
+ };
+
const buttonContent = (
{
+ if (!showUnmappedFields && useNewFieldsApi) {
+ return null;
+ }
+ return (
+
+ {showUnmappedFields ? (
+
+
+
+
+
+
+
+
+
+
+ ) : null}
+ {useNewFieldsApi ? null : (
+
+ )}
+
+ );
+ };
+
const selectionPanel = (
@@ -277,16 +357,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
})}
{selectionPanel}
-
-
-
+ {footer()}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index d03d92892f56a..d6d29dbd88a67 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -104,6 +104,27 @@ export interface DiscoverSidebarProps {
* Shows index pattern and a button that displays the sidebar in a flyout
*/
useFlyout?: boolean;
+
+ /**
+ * an object containing properties for proper handling of unmapped fields in the UI
+ */
+ unmappedFieldsConfig?: {
+ /**
+ * callback function to change the value of `showUnmappedFields` flag
+ * @param value new value to set
+ */
+ onChangeUnmappedFields: (value: boolean) => void;
+ /**
+ * determines whether to display unmapped fields
+ * configurable through the switch in the UI
+ */
+ showUnmappedFields: boolean;
+ /**
+ * determines if we should display an option to toggle showUnmappedFields value in the first place
+ * this value is not configurable through the UI
+ */
+ showUnmappedFieldsDefaultValue: boolean;
+ };
}
export function DiscoverSidebar({
@@ -123,6 +144,7 @@ export function DiscoverSidebar({
trackUiMetric,
useNewFieldsApi = false,
useFlyout = false,
+ unmappedFieldsConfig,
}: DiscoverSidebarProps) {
const [fields, setFields] = useState(null);
@@ -145,14 +167,30 @@ export function DiscoverSidebar({
);
const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING);
-
const {
selected: selectedFields,
popular: popularFields,
unpopular: unpopularFields,
} = useMemo(
- () => groupFields(fields, columns, popularLimit, fieldCounts, fieldFilter, useNewFieldsApi),
- [fields, columns, popularLimit, fieldCounts, fieldFilter, useNewFieldsApi]
+ () =>
+ groupFields(
+ fields,
+ columns,
+ popularLimit,
+ fieldCounts,
+ fieldFilter,
+ useNewFieldsApi,
+ !!unmappedFieldsConfig?.showUnmappedFields
+ ),
+ [
+ fields,
+ columns,
+ popularLimit,
+ fieldCounts,
+ fieldFilter,
+ useNewFieldsApi,
+ unmappedFieldsConfig?.showUnmappedFields,
+ ]
);
const fieldTypes = useMemo(() => {
@@ -239,6 +277,9 @@ export function DiscoverSidebar({
onChange={onChangeFieldSearch}
value={fieldFilter.name}
types={fieldTypes}
+ useNewFieldsApi={useNewFieldsApi}
+ onChangeUnmappedFields={unmappedFieldsConfig?.onChangeUnmappedFields}
+ showUnmappedFields={unmappedFieldsConfig?.showUnmappedFieldsDefaultValue}
/>
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx
index 919476ffb458e..aaf992b03b119 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx
@@ -15,7 +15,7 @@ import realHits from 'fixtures/real_hits.js';
import stubbedLogstashFields from 'fixtures/logstash_fields';
import { mountWithIntl } from '@kbn/test/jest';
import React from 'react';
-import { DiscoverSidebarProps } from './discover_sidebar';
+import { DiscoverSidebar, DiscoverSidebarProps } from './discover_sidebar';
import { coreMock } from '../../../../../../core/public/mocks';
import { IndexPatternAttributes } from '../../../../../data/common';
import { getStubIndexPattern } from '../../../../../data/public/test_utils';
@@ -131,4 +131,16 @@ describe('discover responsive sidebar', function () {
findTestSubject(comp, 'plus-extension-gif').simulate('click');
expect(props.onAddFilter).toHaveBeenCalled();
});
+ it('renders sidebar with unmapped fields config', function () {
+ const unmappedFieldsConfig = {
+ onChangeUnmappedFields: jest.fn(),
+ showUnmappedFields: false,
+ showUnmappedFieldsDefaultValue: false,
+ };
+ const componentProps = { ...props, unmappedFieldsConfig };
+ const component = mountWithIntl();
+ const discoverSidebar = component.find(DiscoverSidebar);
+ expect(discoverSidebar).toHaveLength(1);
+ expect(discoverSidebar.props().unmappedFieldsConfig).toEqual(unmappedFieldsConfig);
+ });
});
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx
index e2bb641d5d578..600343db4e256 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx
@@ -97,6 +97,27 @@ export interface DiscoverSidebarResponsiveProps {
* Read from the Fields API
*/
useNewFieldsApi?: boolean;
+
+ /**
+ * an object containing properties for proper handling of unmapped fields in the UI
+ */
+ unmappedFieldsConfig?: {
+ /**
+ * callback function to change the value of `showUnmappedFields` flag
+ * @param value new value to set
+ */
+ onChangeUnmappedFields: (value: boolean) => void;
+ /**
+ * determines whether to display unmapped fields
+ * configurable through the switch in the UI
+ */
+ showUnmappedFields: boolean;
+ /**
+ * determines if we should display an option to toggle showUnmappedFields value in the first place
+ * this value is not configurable through the UI
+ */
+ showUnmappedFieldsDefaultValue: boolean;
+ };
}
/**
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts
index 923b0636734ea..22979f2b7eef1 100644
--- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts
+++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts
@@ -178,6 +178,7 @@ describe('group_fields', function () {
fieldFilterState,
true
);
+
expect(actual.popular).toEqual([category]);
expect(actual.selected).toEqual([currency]);
expect(actual.unpopular).toEqual([]);
@@ -214,4 +215,30 @@ describe('group_fields', function () {
'unknown',
]);
});
+
+ it('excludes unmapped fields if showUnmappedFields set to false', function () {
+ const fieldFilterState = getDefaultFieldFilter();
+ const fieldsWithUnmappedField = [...fields];
+ fieldsWithUnmappedField.push({
+ name: 'unknown_field',
+ type: 'unknown',
+ esTypes: ['unknown'],
+ count: 1,
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ });
+
+ const actual = groupFields(
+ fieldsWithUnmappedField as IndexPatternField[],
+ ['customer_birth_date', 'currency', 'unknown'],
+ 5,
+ fieldCounts,
+ fieldFilterState,
+ true,
+ false
+ );
+ expect(actual.unpopular).toEqual([]);
+ });
});
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx
index ba7000b4fc6df..0e9a015a230ed 100644
--- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx
@@ -24,7 +24,8 @@ export function groupFields(
popularLimit: number,
fieldCounts: Record,
fieldFilterState: FieldFilterState,
- useNewFieldsApi: boolean
+ useNewFieldsApi: boolean,
+ showUnmappedFields = true
): GroupedFields {
const result: GroupedFields = {
selected: [],
@@ -61,7 +62,9 @@ export function groupFields(
result.popular.push(field);
}
} else if (field.type !== '_source') {
- if (!isSubfield) {
+ // do not show unmapped fields unless explicitly specified
+ // do not add subfields to this list
+ if ((field.type !== 'unknown' || showUnmappedFields) && !isSubfield) {
result.unpopular.push(field);
}
}
diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts
index fe0d40f222f23..256a6dc8f6bb7 100644
--- a/src/plugins/discover/public/application/components/types.ts
+++ b/src/plugins/discover/public/application/components/types.ts
@@ -176,4 +176,24 @@ export interface DiscoverProps {
* Function to update the actual savedQuery id
*/
updateSavedQueryId: (savedQueryId?: string) => void;
+ /**
+ * An object containing properties for proper handling of unmapped fields in the UI
+ */
+ unmappedFieldsConfig?: {
+ /**
+ * determines whether to display unmapped fields
+ * configurable through the switch in the UI
+ */
+ showUnmappedFields: boolean;
+ /**
+ * determines if we should display an option to toggle showUnmappedFields value in the first place
+ * this value is not configurable through the UI
+ */
+ showUnmappedFieldsDefaultValue: boolean;
+ /**
+ * callback function to change the value of `showUnmappedFields` flag
+ * @param value new value to set
+ */
+ onChangeUnmappedFields: (value: boolean) => void;
+ };
}
diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts
index f9976d1a04087..cfb8882c83e2a 100644
--- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts
+++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts
@@ -38,7 +38,11 @@ import {
} from '../../kibana_services';
import { SEARCH_EMBEDDABLE_TYPE } from './constants';
import { SavedSearch } from '../..';
-import { SAMPLE_SIZE_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common';
+import {
+ SAMPLE_SIZE_SETTING,
+ SEARCH_FIELDS_FROM_SOURCE,
+ SORT_DEFAULT_ORDER_SETTING,
+} from '../../../common';
import { DiscoverGridSettings } from '../components/discover_grid/types';
import { DiscoverServices } from '../../build_services';
import { ElasticSearchHit } from '../doc_views/doc_views_types';
@@ -62,6 +66,7 @@ interface SearchScope extends ng.IScope {
totalHitCount?: number;
isLoading?: boolean;
showTimeCol?: boolean;
+ useNewFieldsApi?: boolean;
}
interface SearchEmbeddableConfig {
@@ -220,11 +225,14 @@ export class SearchEmbeddable
this.updateInput({ sort });
};
+ const useNewFieldsApi = !getServices().uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false);
+ searchScope.useNewFieldsApi = useNewFieldsApi;
+
searchScope.addColumn = (columnName: string) => {
if (!searchScope.columns) {
return;
}
- const columns = columnActions.addColumn(searchScope.columns, columnName);
+ const columns = columnActions.addColumn(searchScope.columns, columnName, useNewFieldsApi);
this.updateInput({ columns });
};
@@ -232,7 +240,7 @@ export class SearchEmbeddable
if (!searchScope.columns) {
return;
}
- const columns = columnActions.removeColumn(searchScope.columns, columnName);
+ const columns = columnActions.removeColumn(searchScope.columns, columnName, useNewFieldsApi);
this.updateInput({ columns });
};
@@ -280,10 +288,10 @@ export class SearchEmbeddable
private fetch = async () => {
const searchSessionId = this.input.searchSessionId;
-
+ const useNewFieldsApi = !this.services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false);
if (!this.searchScope) return;
- const { searchSource } = this.savedSearch;
+ const { searchSource, pre712 } = this.savedSearch;
// Abort any in-progress requests
if (this.abortController) this.abortController.abort();
@@ -298,6 +306,20 @@ export class SearchEmbeddable
this.services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING)
)
);
+ if (useNewFieldsApi) {
+ searchSource.removeField('fieldsFromSource');
+ const fields: Record = { field: '*' };
+ if (pre712) {
+ fields.include_unmapped = true;
+ }
+ searchSource.setField('fields', [fields]);
+ } else {
+ searchSource.removeField('fields');
+ if (this.searchScope.indexPattern) {
+ const fieldNames = this.searchScope.indexPattern.fields.map((field) => field.name);
+ searchSource.setField('fieldsFromSource', fieldNames);
+ }
+ }
// Log request to inspector
this.inspectorAdapters.requests!.reset();
diff --git a/src/plugins/discover/public/application/embeddable/search_template.html b/src/plugins/discover/public/application/embeddable/search_template.html
index be2f5cceac080..3e37b3645650f 100644
--- a/src/plugins/discover/public/application/embeddable/search_template.html
+++ b/src/plugins/discover/public/application/embeddable/search_template.html
@@ -16,5 +16,6 @@
render-complete
sorting="sort"
total-hit-count="totalHitCount"
+ use-new-fields-api="useNewFieldsApi"
>
diff --git a/src/plugins/discover/public/application/helpers/update_search_source.test.ts b/src/plugins/discover/public/application/helpers/update_search_source.test.ts
index 5e27ec94154a8..14b826524706b 100644
--- a/src/plugins/discover/public/application/helpers/update_search_source.test.ts
+++ b/src/plugins/discover/public/application/helpers/update_search_source.test.ts
@@ -63,7 +63,61 @@ describe('updateSearchSource', () => {
});
expect(result.getField('index')).toEqual(indexPatternMock);
expect(result.getField('size')).toEqual(sampleSize);
- expect(result.getField('fields')).toEqual(['*']);
+ expect(result.getField('fields')).toEqual([{ field: '*' }]);
+ expect(result.getField('fieldsFromSource')).toBe(undefined);
+ });
+
+ test('requests unmapped fields when the flag is provided, using the new fields api', async () => {
+ const searchSourceMock = createSearchSourceMock({});
+ const sampleSize = 250;
+ const result = updateSearchSource(searchSourceMock, {
+ indexPattern: indexPatternMock,
+ services: ({
+ data: dataPluginMock.createStartContract(),
+ uiSettings: ({
+ get: (key: string) => {
+ if (key === SAMPLE_SIZE_SETTING) {
+ return sampleSize;
+ }
+ return false;
+ },
+ } as unknown) as IUiSettingsClient,
+ } as unknown) as DiscoverServices,
+ sort: [] as SortOrder[],
+ columns: [],
+ useNewFieldsApi: true,
+ showUnmappedFields: true,
+ });
+ expect(result.getField('index')).toEqual(indexPatternMock);
+ expect(result.getField('size')).toEqual(sampleSize);
+ expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]);
+ expect(result.getField('fieldsFromSource')).toBe(undefined);
+ });
+
+ test('updates a given search source when showUnmappedFields option is set to true', async () => {
+ const searchSourceMock = createSearchSourceMock({});
+ const sampleSize = 250;
+ const result = updateSearchSource(searchSourceMock, {
+ indexPattern: indexPatternMock,
+ services: ({
+ data: dataPluginMock.createStartContract(),
+ uiSettings: ({
+ get: (key: string) => {
+ if (key === SAMPLE_SIZE_SETTING) {
+ return sampleSize;
+ }
+ return false;
+ },
+ } as unknown) as IUiSettingsClient,
+ } as unknown) as DiscoverServices,
+ sort: [] as SortOrder[],
+ columns: [],
+ useNewFieldsApi: true,
+ showUnmappedFields: true,
+ });
+ expect(result.getField('index')).toEqual(indexPatternMock);
+ expect(result.getField('size')).toEqual(sampleSize);
+ expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]);
expect(result.getField('fieldsFromSource')).toBe(undefined);
});
});
diff --git a/src/plugins/discover/public/application/helpers/update_search_source.ts b/src/plugins/discover/public/application/helpers/update_search_source.ts
index b4794b1b26311..aa46e4641bf51 100644
--- a/src/plugins/discover/public/application/helpers/update_search_source.ts
+++ b/src/plugins/discover/public/application/helpers/update_search_source.ts
@@ -23,12 +23,14 @@ export function updateSearchSource(
sort,
columns,
useNewFieldsApi,
+ showUnmappedFields,
}: {
indexPattern: IndexPattern;
services: DiscoverServices;
sort: SortOrder[];
columns: string[];
useNewFieldsApi: boolean;
+ showUnmappedFields?: boolean;
}
) {
const { uiSettings, data } = services;
@@ -46,7 +48,11 @@ export function updateSearchSource(
.setField('filter', data.query.filterManager.getFilters());
if (useNewFieldsApi) {
searchSource.removeField('fieldsFromSource');
- searchSource.setField('fields', ['*']);
+ const fields: Record = { field: '*' };
+ if (showUnmappedFields) {
+ fields.include_unmapped = 'true';
+ }
+ searchSource.setField('fields', [fields]);
} else {
searchSource.removeField('fields');
const fieldNames = indexPattern.fields.map((field) => field.name);
diff --git a/src/plugins/discover/public/saved_searches/_saved_search.ts b/src/plugins/discover/public/saved_searches/_saved_search.ts
index fb4ca519ad71c..95213b5119fea 100644
--- a/src/plugins/discover/public/saved_searches/_saved_search.ts
+++ b/src/plugins/discover/public/saved_searches/_saved_search.ts
@@ -19,6 +19,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) {
grid: 'object',
sort: 'keyword',
version: 'integer',
+ pre712: 'boolean',
};
// Order these fields to the top, the rest are alphabetical
public static fieldOrder = ['title', 'description'];
@@ -39,6 +40,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) {
grid: 'object',
sort: 'keyword',
version: 'integer',
+ pre712: 'boolean',
},
searchSource: true,
defaults: {
@@ -48,6 +50,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) {
hits: 0,
sort: [],
version: 1,
+ pre712: false,
},
});
this.showInRecentlyAccessed = true;
diff --git a/src/plugins/discover/public/saved_searches/types.ts b/src/plugins/discover/public/saved_searches/types.ts
index b273f15e8d68f..cbebc9da20f77 100644
--- a/src/plugins/discover/public/saved_searches/types.ts
+++ b/src/plugins/discover/public/saved_searches/types.ts
@@ -23,6 +23,7 @@ export interface SavedSearch {
save: (saveOptions: SavedObjectSaveOpts) => Promise;
lastSavedTitle?: string;
copyOnSave?: boolean;
+ pre712?: boolean;
}
export interface SavedSearchLoader {
get: (id: string) => Promise;
diff --git a/src/plugins/discover/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts
index 967be8806e0ab..4faf8e404c1ce 100644
--- a/src/plugins/discover/server/saved_objects/search.ts
+++ b/src/plugins/discover/server/saved_objects/search.ts
@@ -44,6 +44,7 @@ export const searchSavedObjectType: SavedObjectsType = {
title: { type: 'text' },
grid: { type: 'object', enabled: false },
version: { type: 'integer' },
+ pre712: { type: 'boolean' },
},
},
migrations: searchMigrations as any,
diff --git a/src/plugins/discover/server/saved_objects/search_migrations.test.ts b/src/plugins/discover/server/saved_objects/search_migrations.test.ts
index 68177b0aa4ddb..3e6cc4ad71315 100644
--- a/src/plugins/discover/server/saved_objects/search_migrations.test.ts
+++ b/src/plugins/discover/server/saved_objects/search_migrations.test.ts
@@ -350,4 +350,41 @@ Object {
testMigrateMatchAllQuery(migrationFn);
});
});
+
+ describe('7.12.0', () => {
+ const migrationFn = searchMigrations['7.12.0'];
+
+ describe('migrateExistingSavedSearch', () => {
+ it('should add a new flag to existing saved searches', () => {
+ const migratedDoc = migrationFn(
+ {
+ type: 'search',
+ attributes: {
+ kibanaSavedObjectMeta: {},
+ },
+ },
+ savedObjectMigrationContext
+ );
+ const migratedPre712Flag = migratedDoc.attributes.pre712;
+
+ expect(migratedPre712Flag).toEqual(true);
+ });
+
+ it('should not modify a flag if it already exists', () => {
+ const migratedDoc = migrationFn(
+ {
+ type: 'search',
+ attributes: {
+ kibanaSavedObjectMeta: {},
+ pre712: false,
+ },
+ },
+ savedObjectMigrationContext
+ );
+ const migratedPre712Flag = migratedDoc.attributes.pre712;
+
+ expect(migratedPre712Flag).toEqual(false);
+ });
+ });
+ });
});
diff --git a/src/plugins/discover/server/saved_objects/search_migrations.ts b/src/plugins/discover/server/saved_objects/search_migrations.ts
index 151ac83f7c327..fd6f8756963c8 100644
--- a/src/plugins/discover/server/saved_objects/search_migrations.ts
+++ b/src/plugins/discover/server/saved_objects/search_migrations.ts
@@ -117,9 +117,28 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = (doc) =
};
};
+const migrateExistingSavedSearch: SavedObjectMigrationFn = (doc) => {
+ if (!doc.attributes) {
+ return doc;
+ }
+ const pre712 = doc.attributes.pre712;
+ // pre712 already has a value
+ if (pre712 !== undefined) {
+ return doc;
+ }
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ pre712: true,
+ },
+ };
+};
+
export const searchMigrations = {
'6.7.2': flow(migrateMatchAllQuery),
'7.0.0': flow(setNewReferences),
'7.4.0': flow(migrateSearchSortToNestedArray),
'7.9.3': flow(migrateMatchAllQuery),
+ '7.12.0': flow(migrateExistingSavedSearch),
};
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
index 36841f8b1d521..4945b7a059e8c 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
@@ -287,4 +287,55 @@ describe('', () => {
expect(formHook?.getFormData()).toEqual({ name: 'myName' });
});
});
+
+ describe('change handlers', () => {
+ const onError = jest.fn();
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ const getTestComp = (fieldConfig: FieldConfig) => {
+ const TestComp = () => {
+ const { form } = useForm();
+
+ return (
+
+ );
+ };
+ return TestComp;
+ };
+
+ const setup = (fieldConfig: FieldConfig) => {
+ return registerTestBed(getTestComp(fieldConfig), {
+ memoryRouter: { wrapComponent: false },
+ })() as TestBed;
+ };
+
+ it('calls onError when validation state changes', async () => {
+ const {
+ form: { setInputValue },
+ } = setup({
+ validations: [
+ {
+ validator: ({ value }) => (value === '1' ? undefined : { message: 'oops!' }),
+ },
+ ],
+ });
+
+ expect(onError).toBeCalledTimes(0);
+ await act(async () => {
+ setInputValue('myField', '0');
+ });
+ expect(onError).toBeCalledTimes(1);
+ expect(onError).toBeCalledWith(['oops!']);
+ await act(async () => {
+ setInputValue('myField', '1');
+ });
+ expect(onError).toBeCalledTimes(2);
+ expect(onError).toBeCalledWith(null);
+ });
+ });
});
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx
index 94c2bc42d2855..cc79ed24b5d0c 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx
@@ -20,6 +20,7 @@ export interface Props {
componentProps?: Record;
readDefaultValueOnForm?: boolean;
onChange?: (value: I) => void;
+ onError?: (errors: string[] | null) => void;
children?: (field: FieldHook) => JSX.Element | null;
[key: string]: any;
}
@@ -33,6 +34,7 @@ function UseFieldComp(props: Props(props: Props(form, path, fieldConfig, onChange);
+ const field = useField(form, path, fieldConfig, onChange, onError);
// Children prevails over anything else provided.
if (children) {
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts
index c396f223e97fd..db7b0b2820a47 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts
@@ -27,7 +27,8 @@ export const useField = (
form: FormHook,
path: string,
config: FieldConfig & InternalFieldConfig = {},
- valueChangeListener?: (value: I) => void
+ valueChangeListener?: (value: I) => void,
+ errorChangeListener?: (errors: string[] | null) => void
) => {
const {
type = FIELD_TYPES.TEXT,
@@ -596,6 +597,15 @@ export const useField = (
};
}, [onValueChange]);
+ useEffect(() => {
+ if (!isMounted.current) {
+ return;
+ }
+ if (errorChangeListener) {
+ errorChangeListener(errors.length ? errors.map((error) => error.message) : null);
+ }
+ }, [errors, errorChangeListener]);
+
useEffect(() => {
isMounted.current = true;
diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts
index 69158acfffc73..9aebb0e07b094 100644
--- a/src/plugins/expressions/public/render.ts
+++ b/src/plugins/expressions/public/render.ts
@@ -83,7 +83,7 @@ export class ExpressionRenderHandler {
reload: () => {
this.updateSubject.next(null);
},
- update: (params) => {
+ update: (params: UpdateValue) => {
this.updateSubject.next(params);
},
event: (data) => {
diff --git a/src/plugins/kibana_overview/tsconfig.json b/src/plugins/kibana_overview/tsconfig.json
new file mode 100644
index 0000000000000..ac3ac109cb35f
--- /dev/null
+++ b/src/plugins/kibana_overview/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "./target/types",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": [
+ "public/**/*",
+ "common/**/*",
+ ],
+ "references": [
+ { "path": "../../core/tsconfig.json" },
+ { "path": "../../plugins/navigation/tsconfig.json" },
+ { "path": "../../plugins/data/tsconfig.json" },
+ { "path": "../../plugins/home/tsconfig.json" },
+ { "path": "../../plugins/newsfeed/tsconfig.json" },
+ { "path": "../../plugins/usage_collection/tsconfig.json" },
+ { "path": "../../plugins/kibana_react/tsconfig.json" },
+ ]
+}
diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
index e2061487bfcee..8bc6c130d318f 100644
--- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
+++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx
@@ -518,6 +518,7 @@ class TableListView extends React.Component