* 50 fields are rendered initially, more fields are rendered while user is scrolling * This is a big performance improvement when there are lots of fields to render
This commit is contained in:
parent
8961505b55
commit
630f00897e
|
@ -7,7 +7,8 @@
|
|||
*/
|
||||
|
||||
import './discover_sidebar.scss';
|
||||
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { throttle } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiAccordion,
|
||||
|
@ -18,7 +19,9 @@ import {
|
|||
EuiSpacer,
|
||||
EuiNotificationBadge,
|
||||
EuiPageSideBar,
|
||||
useResizeObserver,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { isEqual, sortBy } from 'lodash';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { DiscoverField } from './discover_field';
|
||||
|
@ -32,6 +35,11 @@ import { FieldFilterState, getDefaultFieldFilter, setFieldFilterProp } from './l
|
|||
import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list';
|
||||
import { DiscoverSidebarResponsiveProps } from './discover_sidebar_responsive';
|
||||
|
||||
/**
|
||||
* Default number of available fields displayed and added on scroll
|
||||
*/
|
||||
const FIELDS_PER_PAGE = 50;
|
||||
|
||||
export interface DiscoverSidebarProps extends DiscoverSidebarResponsiveProps {
|
||||
/**
|
||||
* Current state of the field filter, filtering fields by name, type, ...
|
||||
|
@ -66,18 +74,25 @@ export function DiscoverSidebar({
|
|||
unmappedFieldsConfig,
|
||||
}: DiscoverSidebarProps) {
|
||||
const [fields, setFields] = useState<IndexPatternField[] | null>(null);
|
||||
const [scrollContainer, setScrollContainer] = useState<Element | null>(null);
|
||||
const [fieldsToRender, setFieldsToRender] = useState(FIELDS_PER_PAGE);
|
||||
const [fieldsPerPage, setFieldsPerPage] = useState(FIELDS_PER_PAGE);
|
||||
const availableFieldsContainer = useRef<HTMLUListElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newFields = getIndexPatternFieldList(selectedIndexPattern, fieldCounts);
|
||||
setFields(newFields);
|
||||
}, [selectedIndexPattern, fieldCounts, hits]);
|
||||
|
||||
const scrollDimensions = useResizeObserver(scrollContainer);
|
||||
|
||||
const onChangeFieldSearch = useCallback(
|
||||
(field: string, value: string | boolean | undefined) => {
|
||||
const newState = setFieldFilterProp(fieldFilter, field, value);
|
||||
setFieldFilter(newState);
|
||||
setFieldsToRender(fieldsPerPage);
|
||||
},
|
||||
[fieldFilter, setFieldFilter]
|
||||
[fieldFilter, setFieldFilter, setFieldsToRender, fieldsPerPage]
|
||||
);
|
||||
|
||||
const getDetailsByField = useCallback(
|
||||
|
@ -85,7 +100,10 @@ export function DiscoverSidebar({
|
|||
[hits, columns, selectedIndexPattern]
|
||||
);
|
||||
|
||||
const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING);
|
||||
const popularLimit = useMemo(() => services.uiSettings.get(FIELDS_LIMIT_SETTING), [
|
||||
services.uiSettings,
|
||||
]);
|
||||
|
||||
const {
|
||||
selected: selectedFields,
|
||||
popular: popularFields,
|
||||
|
@ -112,6 +130,50 @@ export function DiscoverSidebar({
|
|||
]
|
||||
);
|
||||
|
||||
const paginate = useCallback(() => {
|
||||
const newFieldsToRender = fieldsToRender + Math.round(fieldsPerPage * 0.5);
|
||||
setFieldsToRender(Math.max(fieldsPerPage, Math.min(newFieldsToRender, unpopularFields.length)));
|
||||
}, [setFieldsToRender, fieldsToRender, unpopularFields, fieldsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollContainer && unpopularFields.length && availableFieldsContainer.current) {
|
||||
const { clientHeight, scrollHeight } = scrollContainer;
|
||||
const isScrollable = scrollHeight > clientHeight; // there is no scrolling currently
|
||||
const allFieldsRendered = fieldsToRender >= unpopularFields.length;
|
||||
|
||||
if (!isScrollable && !allFieldsRendered) {
|
||||
// Not all available fields were rendered with the given fieldsPerPage number
|
||||
// and no scrolling is available due to the a high zoom out factor of the browser
|
||||
// In this case the fieldsPerPage needs to be adapted
|
||||
const fieldsRenderedHeight = availableFieldsContainer.current.clientHeight;
|
||||
const avgHeightPerItem = Math.round(fieldsRenderedHeight / fieldsToRender);
|
||||
const newFieldsPerPage = Math.round(clientHeight / avgHeightPerItem) + 10;
|
||||
if (newFieldsPerPage >= FIELDS_PER_PAGE && newFieldsPerPage !== fieldsPerPage) {
|
||||
setFieldsPerPage(newFieldsPerPage);
|
||||
setFieldsToRender(newFieldsPerPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
fieldsPerPage,
|
||||
scrollContainer,
|
||||
unpopularFields,
|
||||
fieldsToRender,
|
||||
setFieldsPerPage,
|
||||
setFieldsToRender,
|
||||
scrollDimensions,
|
||||
]);
|
||||
|
||||
const lazyScroll = useCallback(() => {
|
||||
if (scrollContainer) {
|
||||
const { scrollTop, clientHeight, scrollHeight } = scrollContainer;
|
||||
const nearBottom = scrollTop + clientHeight > scrollHeight * 0.9;
|
||||
if (nearBottom && unpopularFields) {
|
||||
paginate();
|
||||
}
|
||||
}
|
||||
}, [paginate, scrollContainer, unpopularFields]);
|
||||
|
||||
const fieldTypes = useMemo(() => {
|
||||
const result = ['any'];
|
||||
if (Array.isArray(fields)) {
|
||||
|
@ -145,12 +207,19 @@ export function DiscoverSidebar({
|
|||
return map;
|
||||
}, [fields, useNewFieldsApi, selectedFields]);
|
||||
|
||||
const getPaginated = useCallback(
|
||||
(list) => {
|
||||
return list.slice(0, fieldsToRender);
|
||||
},
|
||||
[fieldsToRender]
|
||||
);
|
||||
|
||||
const filterChanged = useMemo(() => isEqual(fieldFilter, getDefaultFieldFilter()), [fieldFilter]);
|
||||
|
||||
if (!selectedIndexPattern || !fields) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filterChanged = isEqual(fieldFilter, getDefaultFieldFilter());
|
||||
|
||||
if (useFlyout) {
|
||||
return (
|
||||
<section
|
||||
|
@ -207,9 +276,18 @@ export function DiscoverSidebar({
|
|||
</form>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="eui-yScroll">
|
||||
<div>
|
||||
<div
|
||||
ref={(el) => {
|
||||
if (el && !el.dataset.dynamicScroll) {
|
||||
el.dataset.dynamicScroll = 'true';
|
||||
setScrollContainer(el);
|
||||
}
|
||||
}}
|
||||
onScroll={throttle(lazyScroll, 100)}
|
||||
className="eui-yScroll"
|
||||
>
|
||||
{fields.length > 0 && (
|
||||
<>
|
||||
<div>
|
||||
{selectedFields &&
|
||||
selectedFields.length > 0 &&
|
||||
selectedFields[0].displayName !== '_source' ? (
|
||||
|
@ -241,11 +319,7 @@ export function DiscoverSidebar({
|
|||
>
|
||||
{selectedFields.map((field: IndexPatternField) => {
|
||||
return (
|
||||
<li
|
||||
key={`field${field.name}`}
|
||||
data-attr-field={field.name}
|
||||
className="dscSidebar__item"
|
||||
>
|
||||
<li key={`field${field.name}`} data-attr-field={field.name}>
|
||||
<DiscoverField
|
||||
alwaysShowActionButton={alwaysShowActionButtons}
|
||||
field={field}
|
||||
|
@ -303,11 +377,7 @@ export function DiscoverSidebar({
|
|||
>
|
||||
{popularFields.map((field: IndexPatternField) => {
|
||||
return (
|
||||
<li
|
||||
key={`field${field.name}`}
|
||||
data-attr-field={field.name}
|
||||
className="dscSidebar__item"
|
||||
>
|
||||
<li key={`field${field.name}`} data-attr-field={field.name}>
|
||||
<DiscoverField
|
||||
alwaysShowActionButton={alwaysShowActionButtons}
|
||||
field={field}
|
||||
|
@ -329,14 +399,11 @@ export function DiscoverSidebar({
|
|||
className="dscFieldList dscFieldList--unpopular"
|
||||
aria-labelledby="available_fields"
|
||||
data-test-subj={`fieldList-unpopular`}
|
||||
ref={availableFieldsContainer}
|
||||
>
|
||||
{unpopularFields.map((field: IndexPatternField) => {
|
||||
{getPaginated(unpopularFields).map((field: IndexPatternField) => {
|
||||
return (
|
||||
<li
|
||||
key={`field${field.name}`}
|
||||
data-attr-field={field.name}
|
||||
className="dscSidebar__item"
|
||||
>
|
||||
<li key={`field${field.name}`} data-attr-field={field.name}>
|
||||
<DiscoverField
|
||||
alwaysShowActionButton={alwaysShowActionButtons}
|
||||
field={field}
|
||||
|
@ -353,7 +420,7 @@ export function DiscoverSidebar({
|
|||
})}
|
||||
</ul>
|
||||
</EuiAccordion>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
|
|
47
test/functional/apps/discover/_huge_fields.ts
Normal file
47
test/functional/apps/discover/_huge_fields.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const security = getService('security');
|
||||
const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'timePicker']);
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('test large number of fields in sidebar', function () {
|
||||
before(async function () {
|
||||
await security.testUser.setRoles(['kibana_admin', 'test_testhuge_reader'], false);
|
||||
await esArchiver.loadIfNeeded('large_fields');
|
||||
await PageObjects.settings.navigateTo();
|
||||
await kibanaServer.uiSettings.update({
|
||||
'timepicker:timeDefaults': `{ "from": "2016-10-05T00:00:00", "to": "2016-10-06T00:00:00"}`,
|
||||
});
|
||||
await PageObjects.settings.createIndexPattern('*huge*', 'date', true);
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
});
|
||||
|
||||
it('test_huge data should have expected number of fields', async function () {
|
||||
await PageObjects.discover.selectIndexPattern('*huge*');
|
||||
// initially this field should not be rendered
|
||||
const fieldExistsBeforeScrolling = await testSubjects.exists('field-myvar1050');
|
||||
expect(fieldExistsBeforeScrolling).to.be(false);
|
||||
// scrolling down a little, should render this field
|
||||
await testSubjects.scrollIntoView('fieldToggle-myvar1029');
|
||||
const fieldExistsAfterScrolling = await testSubjects.exists('field-myvar1050');
|
||||
expect(fieldExistsAfterScrolling).to.be(true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await security.testUser.restoreDefaults();
|
||||
await esArchiver.unload('large_fields');
|
||||
await kibanaServer.uiSettings.replace({});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -47,5 +47,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./_data_grid_doc_navigation'));
|
||||
loadTestFile(require.resolve('./_data_grid_doc_table'));
|
||||
loadTestFile(require.resolve('./_indexpattern_with_unmapped_fields'));
|
||||
loadTestFile(require.resolve('./_huge_fields'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue