Skip to content

Commit

Permalink
[7.x] [Discover] Add support for unmapped fields using the fields API (
Browse files Browse the repository at this point in the history
…#89074) (#90184)

* [Discover] Add support for unmapped fields using the fields API (#89074)

* Add search source to example plugin.

* Add uiSetting for fields API.

* Update SearchSource to support fields API.

* [PoC] reading from the fields API in Discover

* Add N fields as a default column

* Make fields column non-removeable

* Do not add 'fields' to state

* Remove fields from app state and read from source when needed

* Remove fields column if a new column is added

* Add search source to example plugin.

* Add uiSetting for fields API.

* Update SearchSource to support fields API.

* Improve error handling in search examples plugin.

* Add unit tests for legacy behavior.

* Remove uiSettings feature flag; add fieldsFromSource config.

* Rewrite flatten() based on final API design.

* Update example app based on final API design.

* Update maps app to use legacy fieldsFromSource.

* Update Discover to use legacy fieldsFromSource.

* Rename source filters to field filters.

* Address feedback.

* Update generated docs.

* Update maps functional test.

* Formatting fields column similar to _source

* Moving logic for using search API to updating search source

* Fix small merge error

* Move useSource switch to Discover section of advanced settings

* Do not use fields and source at the same time

* Remove unmapped fields switch

* Add basic support for grouping multifields

* Remove output.txt

* Fix some merge leftovers

* Fix some merge leftovers

* Fix merge errors

* Fix typescript errors and update nested fields logic

* Add a unit test

* Fixing field formats

* Fix multifield selection logic

* Request all fields from source

* Fix eslint

* Fix default columns when switching between _source and fields

* More unit tests

* Update API changes

* Add unit test for discover field details footer

* Remove unused file

* Remove fields formatting from index pattern

* Remove unnecessary check

* Addressing design comments

* Fixing fields column display and renaming it to Document

* Adding more unit tests

* Adding a missing check for useNewFieldsAPI; minor fixes

* Fixing typescript error

* Remove unnecessary console statement

* Add missing prop

* Fixing import order

* Adding functional test to test fields API

* [Functional test] Clean up in after

* Fixing context app

* Addressing PR comments

* Add support for unmapped fields

* Add data migration

* Add toggle unmapped fields logic

* Adding more unit tests

* Some cleanup

* More unit tests

* Fixing failing snapshot

* Add tooltip next to unmapped switch

* Add functional test for the feature

* Fixing a typo in a functional test

* Refetch data when unmapped fields value changes

* Updating mapping

* Support for fields API in search embeddable

* Addressing PR comments

* Fix failing unit test

* Updating the text

Co-authored-by: Luke Elmers <luke.elmers@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

* Fixing ES license

Co-authored-by: Luke Elmers <luke.elmers@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 4, 2021
1 parent f5bd166 commit b6038f3
Show file tree
Hide file tree
Showing 29 changed files with 1,065 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ getAngularModule().directive('contextApp', function ContextApp() {

function ContextAppController($scope, Private) {
const { filterManager, indexPatterns, uiSettings, navigation } = getServices();
const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE);
const queryParameterActions = getQueryParameterActions(filterManager, indexPatterns);
const queryActions = Private(QueryActionsProvider);
const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE);
this.state = createInitialState(
parseInt(uiSettings.get(CONTEXT_STEP_SETTING), 10),
getFirstSortableField(this.indexPattern, uiSettings.get(CONTEXT_TIE_BREAKER_FIELDS_SETTING)),
useNewFieldsApi
);
this.state.useNewFieldsApi = useNewFieldsApi;
this.topNavMenu = navigation.ui.TopNavMenu;

this.actions = _.mapValues(
Expand Down
16 changes: 16 additions & 0 deletions src/plugins/discover/public/application/angular/discover.js
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,21 @@ function discoverController($route, $scope, Promise) {
history.push('/');
};

const showUnmappedFieldsDefaultValue = $scope.useNewFieldsApi && !!$scope.opts.savedSearch.pre712;
let showUnmappedFields = showUnmappedFieldsDefaultValue;

const onChangeUnmappedFields = (value) => {
showUnmappedFields = value;
$scope.unmappedFieldsConfig.showUnmappedFields = value;
$scope.fetch();
};

$scope.unmappedFieldsConfig = {
showUnmappedFieldsDefaultValue,
showUnmappedFields,
onChangeUnmappedFields,
};

$scope.updateDataSource = () => {
const { indexPattern, searchSource, useNewFieldsApi } = $scope;
const { columns, sort } = $scope.state;
Expand All @@ -748,6 +763,7 @@ function discoverController($route, $scope, Promise) {
sort,
columns,
useNewFieldsApi,
showUnmappedFields,
});
return Promise.resolve();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
update-query="handleRefresh"
update-saved-query-id="updateSavedQueryId"
use-new-fields-api="useNewFieldsApi"
unmapped-fields-config="unmappedFieldsConfig"
>
</discover>
</discover-app>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { i18n } from '@kbn/i18n';
import { IndexPattern } from '../../../../../kibana_services';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ export function createDiscoverDirective(reactDirective: any) {
['topNavMenu', { watchDepth: 'reference' }],
['updateQuery', { watchDepth: 'reference' }],
['updateSavedQueryId', { watchDepth: 'reference' }],
['unmappedFieldsConfig', { watchDepth: 'value' }],
]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export function Discover({
topNavMenu,
updateQuery,
updateSavedQueryId,
unmappedFieldsConfig,
}: DiscoverProps) {
const scrollableDesktop = useRef<HTMLDivElement>(null);
const collapseIcon = useRef<HTMLButtonElement>(null);
Expand Down Expand Up @@ -146,6 +147,7 @@ export function Discover({
setIndexPattern={setIndexPattern}
isClosed={isSidebarClosed}
trackUiMetric={trackUiMetric}
unmappedFieldsConfig={unmappedFieldsConfig}
useNewFieldsApi={useNewFieldsApi}
/>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,22 @@ describe('DiscoverFieldSearch', () => {
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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
EuiOutsideClickDetector,
EuiFilterButton,
EuiSpacer,
EuiIcon,
EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

Expand All @@ -35,6 +37,7 @@ export interface State {
aggregatable: string;
type: string;
missing: boolean;
unmappedFields: boolean;
[index: string]: string | boolean;
}

Expand All @@ -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',
});
Expand All @@ -85,6 +111,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
aggregatable: 'any',
type: 'any',
missing: true,
unmappedFields: !!showUnmappedFields,
});

if (typeof value !== 'string') {
Expand Down Expand Up @@ -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 = (
<EuiFilterButton
aria-label={filterBtnAriaLabel}
Expand Down Expand Up @@ -226,6 +261,51 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
);
};

const footer = () => {
if (!showUnmappedFields && useNewFieldsApi) {
return null;
}
return (
<EuiPopoverFooter>
{showUnmappedFields ? (
<EuiFlexGroup>
<EuiFlexItem component="span">
<EuiSwitch
label={i18n.translate('discover.fieldChooser.filter.showUnmappedFields', {
defaultMessage: 'Show unmapped fields',
})}
checked={values.unmappedFields}
onChange={handleUnmappedFieldsChange}
data-test-subj="unmappedFieldsSwitch"
/>
</EuiFlexItem>
<EuiFlexItem component="span" grow={false}>
<EuiToolTip
position="right"
content={i18n.translate('discover.fieldChooser.filter.unmappedFieldsWarning', {
defaultMessage:
'Unmapped fields will be deprecated and removed in a future release.',
})}
>
<EuiIcon type="alert" />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
) : null}
{useNewFieldsApi ? null : (
<EuiSwitch
label={i18n.translate('discover.fieldChooser.filter.hideMissingFieldsLabel', {
defaultMessage: 'Hide missing fields',
})}
checked={values.missing}
onChange={handleMissingChange}
data-test-subj="missingSwitch"
/>
)}
</EuiPopoverFooter>
);
};

const selectionPanel = (
<div className="dscFieldSearch__formWrapper">
<EuiForm data-test-subj="filterSelectionPanel">
Expand Down Expand Up @@ -277,16 +357,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
})}
</EuiPopoverTitle>
{selectionPanel}
<EuiPopoverFooter>
<EuiSwitch
label={i18n.translate('discover.fieldChooser.filter.hideMissingFieldsLabel', {
defaultMessage: 'Hide missing fields',
})}
checked={values.missing}
onChange={handleMissingChange}
data-test-subj="missingSwitch"
/>
</EuiPopoverFooter>
{footer()}
</EuiPopover>
</EuiFilterGroup>
</EuiOutsideClickDetector>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -123,6 +144,7 @@ export function DiscoverSidebar({
trackUiMetric,
useNewFieldsApi = false,
useFlyout = false,
unmappedFieldsConfig,
}: DiscoverSidebarProps) {
const [fields, setFields] = useState<IndexPatternField[] | null>(null);

Expand All @@ -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(() => {
Expand Down Expand Up @@ -239,6 +277,9 @@ export function DiscoverSidebar({
onChange={onChangeFieldSearch}
value={fieldFilter.name}
types={fieldTypes}
useNewFieldsApi={useNewFieldsApi}
onChangeUnmappedFields={unmappedFieldsConfig?.onChangeUnmappedFields}
showUnmappedFields={unmappedFieldsConfig?.showUnmappedFieldsDefaultValue}
/>
</form>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(<DiscoverSidebarResponsive {...componentProps} />);
const discoverSidebar = component.find(DiscoverSidebar);
expect(discoverSidebar).toHaveLength(1);
expect(discoverSidebar.props().unmappedFieldsConfig).toEqual(unmappedFieldsConfig);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

/**
Expand Down
Loading

0 comments on commit b6038f3

Please sign in to comment.