Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UnifiedFieldList] Migrate field filters and field search from Lens #147255

Merged
merged 36 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
98d9224
[UnifiedFieldList] Migrate fields search and filters from Lens to Uni…
jughosta Dec 8, 2022
50c3809
[UnifiedFieldList] Refactor for SQL mode too
jughosta Dec 8, 2022
62902a7
[UnifiedFieldList] Create a hook for filtering logic
jughosta Dec 8, 2022
4086433
Merge remote-tracking branch 'upstream/main' into 145089-migrate-fiel…
jughosta Dec 12, 2022
462074a
[UnifiedFieldList] Small fix
jughosta Dec 12, 2022
224e139
[UnifiedFieldList] Unify timeSeriesMetric logic
jughosta Dec 12, 2022
613c28f
[UnifiedFieldList] Clean up labels
jughosta Dec 12, 2022
891af5f
[UnifiedFieldList] Refactor FieldIcon code
jughosta Dec 12, 2022
0fd2b46
[UnifiedFieldList] More refactoring for FieldIcon code
jughosta Dec 12, 2022
5580a43
[UnifiedFieldList] Refactor field filters
jughosta Dec 12, 2022
bbe7060
[UnifiedFieldList] Optimize field filters and allow to filter SQL fields
jughosta Dec 13, 2022
5205356
[UnifiedFieldList] Clean up locals
jughosta Dec 13, 2022
146646e
[UnifiedFieldList] Extend with more types
jughosta Dec 13, 2022
0f1df28
[UnifiedFieldList] Skip unsupported fields when calculating available…
jughosta Dec 13, 2022
5bfb237
[UnifiedFieldList] Render counts
jughosta Dec 13, 2022
59fc458
[UnifiedFieldList] Add docLinks. Revert Discover changes.
jughosta Dec 14, 2022
8992c60
[UnifiedFieldList] Update styles and data-test-subj
jughosta Dec 14, 2022
61813d5
[UnifiedFieldList] Revert some locale changes
jughosta Dec 14, 2022
89d317b
[UnifiedFieldList] Update tests
jughosta Dec 14, 2022
d6bf276
[UnifiedFieldList] Some updates
jughosta Dec 15, 2022
0d8d718
[UnifiedFieldList] Refactor how hooks communicate
jughosta Dec 15, 2022
a2e1aaa
[UnifiedFieldList] Add tests
jughosta Dec 15, 2022
52d5a05
[UnifiedFieldList] Add a top level FieldList component
jughosta Dec 16, 2022
35e825d
[UnifiedFieldList] Simplify description id configuration
jughosta Dec 16, 2022
e242486
[UnifiedFieldList] Add tests
jughosta Dec 16, 2022
d520180
[UnifiedFieldList] Add tests
jughosta Dec 16, 2022
4327b66
[UnifiedFieldList] Add tests
jughosta Dec 16, 2022
57c3335
[UnifiedFieldList] Lazy load some modules
jughosta Dec 16, 2022
d902971
Merge branch 'main' into 145089-migrate-field-filters
jughosta Dec 16, 2022
5a325eb
[UnifiedFieldList] Fix tests
jughosta Dec 16, 2022
14a3e7d
Merge branch 'main' into 145089-migrate-field-filters
jughosta Dec 16, 2022
d5ca525
Merge branch 'main' into 145089-migrate-field-filters
jughosta Dec 19, 2022
c5134be
Merge branch 'main' into 145089-migrate-field-filters
jughosta Dec 19, 2022
c5bb461
Merge branch 'main' into 145089-migrate-field-filters
jughosta Dec 20, 2022
6424ee6
Update src/plugins/unified_field_list/README.md
jughosta Dec 20, 2022
884d95c
[UnifiedFieldList] Remove the question
jughosta Dec 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
FieldPopoverHeader,
FieldPopoverHeaderProps,
FieldPopoverVisualize,
wrapFieldNameOnDot,
} from '@kbn/unified-field-list-plugin/public';
import { DiscoverFieldStats } from './discover_field_stats';
import { getTypeForFieldIcon } from '../../../../utils/get_type_for_field_icon';
Expand All @@ -37,13 +38,6 @@ import { SHOW_LEGACY_FIELD_TOP_VALUES, PLUGIN_ID } from '../../../../../common';
import { getUiActions } from '../../../../kibana_services';
import { type DataDocuments$ } from '../../hooks/use_saved_search';

function wrapOnDot(str?: string) {
// u200B is a non-width white-space character, which allows
// the browser to efficiently word-wrap right after the dot
// without us having to draw a lot of extra DOM elements, etc
return str ? str.replace(/\./g, '.\u200B') : '';
}

const FieldInfoIcon: React.FC = memo(() => (
<EuiToolTip
position="bottom"
Expand Down Expand Up @@ -86,12 +80,12 @@ const FieldName: React.FC<{ field: DataViewField; highlight?: string }> = memo(

return (
<EuiHighlight
search={wrapOnDot(highlight)}
search={wrapFieldNameOnDot(highlight)}
data-test-subj={`field-${field.name}`}
title={title}
className="dscSidebarField__name"
>
{wrapOnDot(field.displayName)}
{wrapFieldNameOnDot(field.displayName)}
</EuiHighlight>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
FieldsGroupNames,
GroupedFieldsParams,
triggerVisualizeActionsTextBasedLanguages,
useExistingFieldsReader,
useGroupedFields,
} from '@kbn/unified-field-list-plugin/public';
import { useAppStateSelector } from '../../services/discover_app_state_container';
Expand Down Expand Up @@ -132,7 +131,7 @@ export function DiscoverSidebarComponent({
showFieldList,
isAffectedByGlobalFilter,
}: DiscoverSidebarProps) {
const { uiSettings, dataViewFieldEditor, dataViews } = useDiscoverServices();
const { uiSettings, dataViewFieldEditor, dataViews, core } = useDiscoverServices();
const isPlainRecord = useAppStateSelector(
(state) => getRawRecordType(state.query) === RecordRawType.PLAIN
);
Expand Down Expand Up @@ -268,16 +267,15 @@ export function DiscoverSidebarComponent({
};
}
}, []);
const fieldsExistenceReader = useExistingFieldsReader();
const fieldListGroupedProps = useGroupedFields({
const { fieldListGroupedProps } = useGroupedFields({
dataViewId: (!isPlainRecord && selectedDataView?.id) || null, // passing `null` for text-based queries
fieldsExistenceReader: !isPlainRecord ? fieldsExistenceReader : undefined,
allFields,
popularFieldsLimit: !isPlainRecord ? popularFieldsLimit : 0,
sortedSelectedFields: selectedFieldsState.selectedFields,
isAffectedByGlobalFilter,
services: {
dataViews,
core,
},
onFilterField,
onSupportedFieldFilter,
Expand Down Expand Up @@ -378,7 +376,7 @@ export function DiscoverSidebarComponent({
<FieldListGrouped
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
screenReaderDescriptionForSearchInputId={fieldSearchDescriptionId}
screenReaderDescriptionId={fieldSearchDescriptionId}
/>
)}
</EuiFlexItem>
Expand Down
93 changes: 66 additions & 27 deletions src/plugins/unified_field_list/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ This Kibana plugin contains components and services for field list UI (as in fie

---

## Components
## Field Stats and Field Popover Components

* `<FieldListGrouped .../>` - renders a fields list which is split in sections (Selected, Special, Available, Empty, Meta fields). It accepts already grouped fields, please use `useGroupedFields` hook for it.

* `<FieldStats .../>` - loads and renders stats (Top values, Distribution) for a data view field.

* `<FieldVisualizeButton .../>` - renders a button to open this field in Lens.
Expand Down Expand Up @@ -55,6 +53,54 @@ These components can be combined and customized as the following:
/>
```

## Field List components

* `<FieldList .../>` - a top-level component to render field filters and field list sections.

* `<FieldListFilters .../>` - renders a field search input and field filters by type. Please use `useGroupedFields` hook for it. For a more direct control, see `useFieldFilters` hook.

* `<FieldListGrouped .../>` - renders a fields list which is split in sections (Special, Selected, Popular, Available, Empty, Meta fields). It accepts already grouped fields, please use `useGroupedFields` hook for it.

* `<FieldIcon type={getFieldIconType(field)} />` - renders a field icon.

```
const { isProcessing } = useExistingFieldsFetcher({ // this hook fetches fields info to understand which fields are empty.
dataViews: [currentDataView],
...
});

const { fieldListFiltersProps, fieldListGroupedProps } = useGroupedFields({
dataViewId: currentDataViewId, // pass `null` here for text-based queries to skip fields existence check
allFields, // pass `null` to show loading indicators
...
});

// and now we can render a field list
<FieldList
isProcessing={isProcessing}
prepend={
<FieldListFilters
{...fieldListFiltersProps}
/>
}
>
<FieldListGrouped
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
/>
</FieldList>
```

## Utils

* `getFieldIconProps(field)` - gets icon's props to use with `<FieldIcon {...getFieldIconProps(field)} />` component

* `getFieldIconType(field)` - gets icon's type for the field

* `getFieldTypeName(field)` - gets a field type label to show to the user

* `getFieldTypeDescription(field)` - gets a field type description to show to the user as help info

## Public Services

* `loadStats(...)` - returns the loaded field stats (can also work with Ad-hoc data views)
Expand All @@ -63,41 +109,34 @@ These components can be combined and customized as the following:

## Hooks

* `useExistingFieldsFetcher(...)` - this hook is responsible for fetching fields existence info for specified data views. It can be used higher in components tree than `useExistingFieldsReader` hook.
* `useGroupedFields(...)` - this hook groups fields list into sections of Selected, Special, Available, Empty, Meta fields.

* `useFieldFilters(...)` - manages state of `FieldListFilters` component. It is included into `useGroupedFields`.

* `useExistingFieldsReader(...)` - you can call this hook to read fields existence info which was fetched by `useExistingFieldsFetcher` hook. Using multiple "reader" hooks from different children components is supported. So you would need only one "fetcher" and as many "reader" hooks as necessary.
* `useQuerySubscriber(...)` - memorizes current query, filters and absolute date range which are set via UnifiedSearch.

* `useGroupedFields(...)` - this hook groups fields list into sections of Selected, Special, Available, Empty, Meta fields.
* `useExistingFieldsFetcher(...)` - this hook is responsible for fetching fields existence info for specified data views. It can be used higher in components tree than `useExistingFieldsReader` hook.

* `useExistingFieldsReader(...)` - you can call this hook to read fields existence info which was fetched by `useExistingFieldsFetcher` hook. Using multiple "reader" hooks from different children components is supported. So you would need only one "fetcher" and as many "reader" hooks as necessary. It is included into `useGroupedFields`.

An example of using hooks together with `<FieldListGrouped .../>`:
An example of using hooks for fetching and reading info whether a field is empty or not:

```
// `useQuerySubscriber` hook simplifies working with current query state which is required for `useExistingFieldsFetcher`
const querySubscriberResult = useQuerySubscriber(...);
// define a fetcher in any of your components
const { refetchFieldsExistenceInfo, isProcessing } = useExistingFieldsFetcher({
dataViews,
query,
filters,
fromDate,
toDate,
query: querySubscriberResult.query,
filters: querySubscriberResult.filters,
fromDate: querySubscriberResult.fromDate,
toDate: querySubscriberResult.toDate,
...
});
const fieldsExistenceReader = useExistingFieldsReader()
const fieldListGroupedProps = useGroupedFields({
dataViewId: currentDataViewId, // pass `null` here for text-based queries to skip fields existence check
allFields, // pass `null` to show loading indicators
fieldsExistenceReader, // pass `undefined` for text-based queries
...
});

// and now we can render a field list
<FieldListGrouped
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
screenReaderDescriptionForSearchInputId={fieldSearchDescriptionId}
/>

// or check whether a field contains data
// define a reader in any of your components on the same page to check whether a field contains data
const { hasFieldData } = useExistingFieldsReader();
const hasData = hasFieldData(currentDataViewId, fieldName) // return a boolean
const hasData = hasFieldData(currentDataViewId, fieldName) // returns a boolean
```

## Server APIs
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { shallow } from 'enzyme';
import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub';
import FieldIcon from './field_icon';
import { getFieldIconProps } from './get_field_icon_props';

const dateField = dataView.getFieldByName('@timestamp')!;
const scriptedField = dataView.getFieldByName('script date')!;

describe('UnifiedFieldList <FieldIcon />', () => {
test('renders properly', () => {
const component = shallow(<FieldIcon {...getFieldIconProps(dateField)} />);
expect(component).toMatchSnapshot();
});

test('renders properly scripted fields', () => {
const component = shallow(<FieldIcon {...getFieldIconProps(scriptedField)} />);
expect(component).toMatchSnapshot();
});

test('accepts additional props', () => {
const component = shallow(<FieldIcon {...getFieldIconProps(dateField)} fill="none" />);
expect(component).toMatchSnapshot();
});

test('renders Document type properly', () => {
const component = shallow(<FieldIcon type="document" />);
expect(component).toMatchSnapshot();
});

test('renders Histogram type properly', () => {
const component = shallow(<FieldIcon type="histogram" />);
expect(component).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { FieldIcon as KbnFieldIcon, FieldIconProps as KbnFieldIconProps } from '@kbn/react-field';
import { getFieldTypeName } from '../../utils/field_types';

export type FieldIconProps = KbnFieldIconProps;

const InnerFieldIcon: React.FC<FieldIconProps> = ({ type, ...rest }) => {
return <KbnFieldIcon type={normalizeFieldType(type)} label={getFieldTypeName(type)} {...rest} />;
};

export type GenericFieldIcon = typeof InnerFieldIcon;
const FieldIcon = React.memo(InnerFieldIcon) as GenericFieldIcon;

// Necessary for React.lazy
// eslint-disable-next-line import/no-default-export
export default FieldIcon;

function normalizeFieldType(type: string) {
if (type === 'histogram') {
return 'number';
}
return type === 'document' ? 'number' : type;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { type DataViewField } from '@kbn/data-views-plugin/common';
import { FieldListItem } from '../../types';
import { getFieldIconType } from '../../utils/field_types';
import { type FieldIconProps } from './field_icon';

export function getFieldIconProps<T extends FieldListItem = DataViewField>(
field: T
): FieldIconProps {
return {
type: getFieldIconType(field),
scripted: field.scripted,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { Fragment } from 'react';
import { type DataViewField } from '@kbn/data-views-plugin/common';
import type { FieldIconProps, GenericFieldIcon } from './field_icon';
import { type FieldListItem } from '../../types';

const Fallback = () => <Fragment />;

const LazyFieldIcon = React.lazy(() => import('./field_icon')) as GenericFieldIcon;

function WrappedFieldIcon<T extends FieldListItem = DataViewField>(props: FieldIconProps) {
return (
<React.Suspense fallback={<Fallback />}>
<LazyFieldIcon {...props} />
</React.Suspense>
);
}

export const FieldIcon = WrappedFieldIcon;
export type { FieldIconProps };
export { getFieldIconProps } from './get_field_icon_props';
Loading