Skip to content

Commit

Permalink
[Discover][UnifiedFieldList] Integrate unified field list sections in…
Browse files Browse the repository at this point in the history
…to Discover (#144412)

Closes #135678

## Summary

This PR continues the work started in
#142758 to bring field list
grouping from Lens into Discover.

- [x] Integrate new components and hooks into Discover page
- [x] Refactor fields grouping logic
- [x] Render Popular fields under a new separate section
- [x] Remove "Hide empty fields" switch
- [x] Adjust filtering logic
- [x] Refactor fields existence logic in Discover
- [x] Add "Unmapped fields" section
- [x] Highlight the matching term when searching for a field 
- [x] Show field icons when in SQL mode
- [x] Add tooltips to field list section headings
- [x] Add tests, clean up

<img width="340" alt="Screenshot 2022-11-15 at 15 39 27"
src="https://user-images.githubusercontent.com/1415710/201947349-726ffc3a-a17f-411b-be92-81d97879765a.png">

For testing on Discover page:
Please check different use cases and toggling Advanced Settings:
- regular vs ad-hoc data views
- data views with and without a time field
- data views with unmapped and empty fields
- data views with a lot of fields
- data views with some fields being filtered out via data view
configuration
- updating query, filters, and time range
- regular and SQL mode
- searching by a field name in the sidebar
- applying a field filter in the sidebar
- adding, editing, and removing a field
- Field Statistics table when some columns are selected or no columns
are selected
- multifields in the field popover should work as before (icon should
change from "+" to "x" when subfield is selected as a column)
- `discover:searchOnPageLoad` should not show fields if turned off
- `discover:searchFieldsFromSource` should show multifields right in the
fields list if enabled
- `discover:enableSql` should show Selected and Available fields only
when enabled
- `discover:showLegacyFieldTopValues` should show old (green) field
stats in its popover
- `doc_table:legacy`

On Lens page:
- scroll position should reset when data view is switched or when
searching by a field name
- regular and SQL mode

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

Co-authored-by: Michael Marcialis <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Stratoula Kalafateli <[email protected]>
  • Loading branch information
4 people authored Dec 1, 2022
1 parent 08805d0 commit 66718fc
Show file tree
Hide file tree
Showing 72 changed files with 3,177 additions and 1,655 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
/src/plugins/discover/ @elastic/kibana-data-discovery
/src/plugins/saved_search/ @elastic/kibana-data-discovery
/x-pack/plugins/discover_enhanced/ @elastic/kibana-data-discovery
/x-pack/test/functional/apps/discover/ @elastic/kibana-data-discovery
/test/functional/apps/discover/ @elastic/kibana-data-discovery
/test/functional/apps/context/ @elastic/kibana-data-discovery
/test/api_integration/apis/unified_field_list/ @elastic/kibana-data-discovery
Expand Down
43 changes: 24 additions & 19 deletions src/plugins/discover/public/__mocks__/data_views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,27 @@ import { dataViewMock } from './data_view';
import { dataViewComplexMock } from './data_view_complex';
import { dataViewWithTimefieldMock } from './data_view_with_timefield';

export const dataViewsMock = {
getCache: async () => {
return [dataViewMock];
},
get: async (id: string) => {
if (id === 'the-data-view-id') {
return Promise.resolve(dataViewMock);
} else if (id === 'invalid-data-view-id') {
return Promise.reject('Invald');
}
},
updateSavedObject: jest.fn(),
getIdsWithTitle: jest.fn(() => {
return Promise.resolve([dataViewMock, dataViewComplexMock, dataViewWithTimefieldMock]);
}),
createFilter: jest.fn(),
create: jest.fn(),
clearInstanceCache: jest.fn(),
} as unknown as jest.Mocked<DataViewsContract>;
export function createDiscoverDataViewsMock() {
return {
getCache: async () => {
return [dataViewMock];
},
get: async (id: string) => {
if (id === 'the-data-view-id') {
return Promise.resolve(dataViewMock);
} else if (id === 'invalid-data-view-id') {
return Promise.reject('Invald');
}
},
updateSavedObject: jest.fn(),
getIdsWithTitle: jest.fn(() => {
return Promise.resolve([dataViewMock, dataViewComplexMock, dataViewWithTimefieldMock]);
}),
createFilter: jest.fn(),
create: jest.fn(),
clearInstanceCache: jest.fn(),
getFieldsForIndexPattern: jest.fn((dataView) => dataView.fields),
} as unknown as jest.Mocked<DataViewsContract>;
}

export const dataViewsMock = createDiscoverDataViewsMock();
225 changes: 123 additions & 102 deletions src/plugins/discover/public/__mocks__/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Observable, of } from 'rxjs';
import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
import { DiscoverServices } from '../build_services';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
Expand All @@ -20,117 +21,137 @@ import {
SORT_DEFAULT_ORDER_SETTING,
HIDE_ANNOUNCEMENTS,
} from '../../common';
import { UI_SETTINGS } from '@kbn/data-plugin/public';
import { UI_SETTINGS, calculateBounds } from '@kbn/data-plugin/public';
import { TopNavMenu } from '@kbn/navigation-plugin/public';
import { FORMATS_UI_SETTINGS } from '@kbn/field-formats-plugin/common';
import { LocalStorageMock } from './local_storage_mock';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { dataViewsMock } from './data_views';
import { Observable, of } from 'rxjs';
const dataPlugin = dataPluginMock.createStartContract();
const expressionsPlugin = expressionsPluginMock.createStartContract();
import { LocalStorageMock } from './local_storage_mock';
import { createDiscoverDataViewsMock } from './data_views';

export function createDiscoverServicesMock(): DiscoverServices {
const dataPlugin = dataPluginMock.createStartContract();
const expressionsPlugin = expressionsPluginMock.createStartContract();

dataPlugin.query.filterManager.getFilters = jest.fn(() => []);
dataPlugin.query.filterManager.getUpdates$ = jest.fn(() => of({}) as unknown as Observable<void>);
dataPlugin.query.filterManager.getFilters = jest.fn(() => []);
dataPlugin.query.filterManager.getUpdates$ = jest.fn(() => of({}) as unknown as Observable<void>);
dataPlugin.query.timefilter.timefilter.createFilter = jest.fn();
dataPlugin.query.timefilter.timefilter.getAbsoluteTime = jest.fn(() => ({
from: '2021-08-31T22:00:00.000Z',
to: '2022-09-01T09:16:29.553Z',
}));
dataPlugin.query.timefilter.timefilter.getTime = jest.fn(() => {
return { from: 'now-15m', to: 'now' };
});
dataPlugin.query.timefilter.timefilter.calculateBounds = jest.fn(calculateBounds);
dataPlugin.query.getState = jest.fn(() => ({
query: { query: '', language: 'lucene' },
filters: [],
}));
dataPlugin.dataViews = createDiscoverDataViewsMock();

export const discoverServiceMock = {
core: coreMock.createStart(),
chrome: chromeServiceMock.createStartContract(),
history: () => ({
location: {
search: '',
return {
core: coreMock.createStart(),
charts: chartPluginMock.createSetupContract(),
chrome: chromeServiceMock.createStartContract(),
history: () => ({
location: {
search: '',
},
listen: jest.fn(),
}),
data: dataPlugin,
docLinks: docLinksServiceMock.createStartContract(),
capabilities: {
visualize: {
show: true,
},
discover: {
save: false,
},
advancedSettings: {
save: true,
},
},
listen: jest.fn(),
}),
data: dataPlugin,
docLinks: docLinksServiceMock.createStartContract(),
capabilities: {
visualize: {
show: true,
fieldFormats: fieldFormatsMock,
filterManager: dataPlugin.query.filterManager,
inspector: {
open: jest.fn(),
},
discover: {
save: false,
uiSettings: {
get: jest.fn((key: string) => {
if (key === 'fields:popularLimit') {
return 5;
} else if (key === DEFAULT_COLUMNS_SETTING) {
return ['default_column'];
} else if (key === UI_SETTINGS.META_FIELDS) {
return [];
} else if (key === DOC_HIDE_TIME_COLUMN_SETTING) {
return false;
} else if (key === CONTEXT_STEP_SETTING) {
return 5;
} else if (key === SORT_DEFAULT_ORDER_SETTING) {
return 'desc';
} else if (key === FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE) {
return false;
} else if (key === SAMPLE_SIZE_SETTING) {
return 250;
} else if (key === SAMPLE_ROWS_PER_PAGE_SETTING) {
return 150;
} else if (key === MAX_DOC_FIELDS_DISPLAYED) {
return 50;
} else if (key === HIDE_ANNOUNCEMENTS) {
return false;
}
}),
isDefault: (key: string) => {
return true;
},
},
advancedSettings: {
save: true,
http: {
basePath: '/',
},
},
fieldFormats: fieldFormatsMock,
filterManager: dataPlugin.query.filterManager,
inspector: {
open: jest.fn(),
},
uiSettings: {
get: jest.fn((key: string) => {
if (key === 'fields:popularLimit') {
return 5;
} else if (key === DEFAULT_COLUMNS_SETTING) {
return ['default_column'];
} else if (key === UI_SETTINGS.META_FIELDS) {
return [];
} else if (key === DOC_HIDE_TIME_COLUMN_SETTING) {
return false;
} else if (key === CONTEXT_STEP_SETTING) {
return 5;
} else if (key === SORT_DEFAULT_ORDER_SETTING) {
return 'desc';
} else if (key === FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE) {
return false;
} else if (key === SAMPLE_SIZE_SETTING) {
return 250;
} else if (key === SAMPLE_ROWS_PER_PAGE_SETTING) {
return 150;
} else if (key === MAX_DOC_FIELDS_DISPLAYED) {
return 50;
} else if (key === HIDE_ANNOUNCEMENTS) {
return false;
}
}),
isDefault: (key: string) => {
return true;
dataViewEditor: {
userPermissions: {
editDataView: () => true,
},
},
dataViewFieldEditor: {
openEditor: jest.fn(),
userPermissions: {
editIndexPattern: jest.fn(),
},
},
navigation: {
ui: { TopNavMenu, AggregateQueryTopNavMenu: TopNavMenu },
},
},
http: {
basePath: '/',
},
dataViewEditor: {
userPermissions: {
editDataView: () => true,
metadata: {
branch: 'test',
},
},
dataViewFieldEditor: {
openEditor: jest.fn(),
userPermissions: {
editIndexPattern: jest.fn(),
theme: {
useChartsTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
useChartsBaseTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
},
},
navigation: {
ui: { TopNavMenu, AggregateQueryTopNavMenu: TopNavMenu },
},
metadata: {
branch: 'test',
},
theme: {
useChartsTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
useChartsBaseTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
},
storage: new LocalStorageMock({}) as unknown as Storage,
addBasePath: jest.fn(),
toastNotifications: {
addInfo: jest.fn(),
addWarning: jest.fn(),
addDanger: jest.fn(),
addSuccess: jest.fn(),
},
expressions: expressionsPlugin,
savedObjectsTagging: {},
dataViews: dataViewsMock,
timefilter: { createFilter: jest.fn() },
locator: {
useUrl: jest.fn(() => ''),
navigate: jest.fn(),
getUrl: jest.fn(() => Promise.resolve('')),
},
contextLocator: { getRedirectUrl: jest.fn(() => '') },
singleDocLocator: { getRedirectUrl: jest.fn(() => '') },
} as unknown as DiscoverServices;
storage: new LocalStorageMock({}) as unknown as Storage,
addBasePath: jest.fn(),
toastNotifications: {
addInfo: jest.fn(),
addWarning: jest.fn(),
addDanger: jest.fn(),
addSuccess: jest.fn(),
},
expressions: expressionsPlugin,
savedObjectsTagging: {},
dataViews: dataPlugin.dataViews,
timefilter: dataPlugin.query.timefilter.timefilter,
locator: {
useUrl: jest.fn(() => ''),
navigate: jest.fn(),
getUrl: jest.fn(() => Promise.resolve('')),
},
contextLocator: { getRedirectUrl: jest.fn(() => '') },
singleDocLocator: { getRedirectUrl: jest.fn(() => '') },
} as unknown as DiscoverServices;
}

export const discoverServiceMock = createDiscoverServicesMock();
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ discover-app {
overflow: hidden;
}

.dscPageBody__sidebar {
position: relative;
}

.dscPageContent__wrapper {
padding: $euiSizeS $euiSizeS $euiSizeS 0;
overflow: hidden; // Ensures horizontal scroll of table
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
DataTotalHits$,
RecordRawType,
} from '../../hooks/use_saved_search';
import { discoverServiceMock } from '../../../../__mocks__/services';
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
import { FetchStatus } from '../../../types';
import { RequestAdapter } from '@kbn/inspector-plugin/common';
import { DiscoverSidebar } from '../sidebar/discover_sidebar';
Expand Down Expand Up @@ -118,14 +118,11 @@ async function mountComponent(
) {
const searchSourceMock = createSearchSourceMock({});
const services = {
...discoverServiceMock,
...createDiscoverServicesMock(),
storage: new LocalStorageMock({
[SIDEBAR_CLOSED_KEY]: prevSidebarClosed,
}) as unknown as Storage,
} as unknown as DiscoverServices;
services.data.query.timefilter.timefilter.getAbsoluteTime = () => {
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
};

const dataViewList = [dataView];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export function DiscoverLayout({
history={history}
/>
<EuiFlexGroup className="dscPageBody__contents" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiFlexItem grow={false} className="dscPageBody__sidebar">
<SidebarMemoized
columns={columns}
documents$={savedSearchData$.documents$}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
DataTotalHits$,
RecordRawType,
} from '../../hooks/use_saved_search';
import { discoverServiceMock } from '../../../../__mocks__/services';
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
import { FetchStatus } from '../../../types';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { buildDataTableRecord } from '../../../../utils/build_data_record';
Expand Down Expand Up @@ -110,10 +110,7 @@ const mountComponent = async ({
savedSearch?: SavedSearch;
resetSavedSearch?: () => void;
} = {}) => {
let services = discoverServiceMock;
services.data.query.timefilter.timefilter.getAbsoluteTime = () => {
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
};
let services = createDiscoverServicesMock();

if (storage) {
services = { ...services, storage };
Expand Down
Loading

0 comments on commit 66718fc

Please sign in to comment.