/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ import './field_list.scss'; import { throttle } from 'lodash'; import React, { useState, Fragment, useCallback, useMemo, useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { FieldItem } from './field_item'; import { NoFieldsCallout } from './no_fields_callout'; import { IndexPatternField } from './types'; import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; const PAGINATION_SIZE = 50; export type FieldGroups = Record< string, { fields: IndexPatternField[]; fieldCount: number; showInAccordion: boolean; isInitiallyOpen: boolean; title: string; helpText?: string; isAffectedByGlobalFilter: boolean; isAffectedByTimeFilter: boolean; hideDetails?: boolean; defaultNoFieldsMessage?: string; } >; function getDisplayedFieldsLength( fieldGroups: FieldGroups, accordionState: Partial> ) { return Object.entries(fieldGroups) .filter(([key]) => accordionState[key]) .reduce((allFieldCount, [, { fields }]) => allFieldCount + fields.length, 0); } export function FieldList({ exists, fieldGroups, existenceFetchFailed, fieldProps, hasSyncedExistingFields, filter, currentIndexPatternId, existFieldsInIndex, }: { exists: (field: IndexPatternField) => boolean; fieldGroups: FieldGroups; fieldProps: FieldItemSharedProps; hasSyncedExistingFields: boolean; existenceFetchFailed?: boolean; filter: { nameFilter: string; typeFilter: string[]; }; currentIndexPatternId: string; existFieldsInIndex: boolean; }) { const [pageSize, setPageSize] = useState(PAGINATION_SIZE); const [scrollContainer, setScrollContainer] = useState(undefined); const [accordionState, setAccordionState] = useState>>(() => Object.fromEntries( Object.entries(fieldGroups) .filter(([, { showInAccordion }]) => showInAccordion) .map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen]) ) ); useEffect(() => { // Reset the scroll if we have made material changes to the field list if (scrollContainer) { scrollContainer.scrollTop = 0; setPageSize(PAGINATION_SIZE); } }, [filter.nameFilter, filter.typeFilter, currentIndexPatternId, scrollContainer]); const lazyScroll = useCallback(() => { if (scrollContainer) { const nearBottom = scrollContainer.scrollTop + scrollContainer.clientHeight > scrollContainer.scrollHeight * 0.9; if (nearBottom) { setPageSize( Math.max( PAGINATION_SIZE, Math.min( pageSize + PAGINATION_SIZE * 0.5, getDisplayedFieldsLength(fieldGroups, accordionState) ) ) ); } } }, [scrollContainer, pageSize, setPageSize, fieldGroups, accordionState]); const paginatedFields = useMemo(() => { let remainingItems = pageSize; return Object.fromEntries( Object.entries(fieldGroups) .filter(([, { showInAccordion }]) => showInAccordion) .map(([key, fieldGroup]) => { if (!accordionState[key] || remainingItems <= 0) { return [key, []]; } const slicedFieldList = fieldGroup.fields.slice(0, remainingItems); remainingItems = remainingItems - slicedFieldList.length; return [key, slicedFieldList]; }) ); }, [pageSize, fieldGroups, accordionState]); return (
{ if (el && !el.dataset.dynamicScroll) { el.dataset.dynamicScroll = 'true'; setScrollContainer(el); } }} onScroll={throttle(lazyScroll, 100)} >
    {Object.entries(fieldGroups) .filter(([, { showInAccordion }]) => !showInAccordion) .flatMap(([, { fields }]) => fields.map((field) => ( )) )}
{Object.entries(fieldGroups) .filter(([, { showInAccordion }]) => showInAccordion) .map(([key, fieldGroup]) => ( { setAccordionState((s) => ({ ...s, [key]: open, })); const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, { ...accordionState, [key]: open, }); setPageSize( Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) ); }} showExistenceFetchError={existenceFetchFailed} renderCallout={ } /> ))}
); }