diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index b9b8841635406..7fb64d1613d32 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -623,6 +623,35 @@ describe('IndexPattern Data Panel', () => { ).toEqual(['client', 'source', 'timestampLabel']); }); + it('should show meta fields accordion', async () => { + const wrapper = mountWithIntl( + + ); + wrapper + .find('[data-test-subj="lnsIndexPatternMetaFields"]') + .find('button') + .first() + .simulate('click'); + expect( + wrapper + .find('[data-test-subj="lnsIndexPatternMetaFields"]') + .find(FieldItem) + .first() + .prop('field').name + ).toEqual('_id'); + }); + it('should display NoFieldsCallout when all fields are empty', async () => { const wrapper = mountWithIntl( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index edc058047f891..ae9311594b7aa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -5,14 +5,13 @@ */ import './datapanel.scss'; -import { uniq, keyBy, groupBy, throttle } from 'lodash'; -import React, { useState, useEffect, memo, useCallback, useMemo } from 'react'; +import { uniq, keyBy, groupBy } from 'lodash'; +import React, { useState, memo, useCallback, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiContextMenuPanel, EuiContextMenuItem, - EuiContextMenuPanelProps, EuiPopover, EuiCallOut, EuiFormControlLayout, @@ -25,8 +24,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { DataPublicPluginStart, EsQueryConfig, Query, Filter } from 'src/plugins/data/public'; import { DatasourceDataPanelProps, DataType, StateSetter } from '../types'; import { ChildDragDropProvider, DragContextState } from '../drag_drop'; -import { FieldItem } from './field_item'; -import { NoFieldsCallout } from './no_fields_callout'; import { IndexPattern, IndexPatternPrivateState, @@ -37,7 +34,6 @@ import { trackUiEvent } from '../lens_ui_telemetry'; import { syncExistingFields } from './loader'; import { fieldExists } from './pure_helpers'; import { Loader } from '../loader'; -import { FieldsAccordion } from './fields_accordion'; import { esQuery, IIndexPattern } from '../../../../../src/plugins/data/public'; export type Props = DatasourceDataPanelProps & { @@ -52,18 +48,13 @@ export type Props = DatasourceDataPanelProps & { import { LensFieldIcon } from './lens_field_icon'; import { ChangeIndexPattern } from './change_indexpattern'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; - -// TODO the typings for EuiContextMenuPanel are incorrect - watchedItemProps is missing. This can be removed when the types are adjusted -const FixedEuiContextMenuPanel = (EuiContextMenuPanel as unknown) as React.FunctionComponent< - EuiContextMenuPanelProps & { watchedItemProps: string[] } ->; +import { FieldGroups, FieldList } from './field_list'; function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) { return fieldA.displayName.localeCompare(fieldB.displayName, undefined, { sensitivity: 'base' }); } const supportedFieldTypes = new Set(['string', 'number', 'boolean', 'date', 'ip', 'document']); -const PAGINATION_SIZE = 50; const fieldTypeNames: Record = { document: i18n.translate('xpack.lens.datatypes.record', { defaultMessage: 'record' }), @@ -215,14 +206,12 @@ interface DataPanelState { isMetaAccordionOpen: boolean; } -export interface FieldsGroup { +const defaultFieldGroups: { specialFields: IndexPatternField[]; availableFields: IndexPatternField[]; emptyFields: IndexPatternField[]; metaFields: IndexPatternField[]; -} - -const defaultFieldGroups = { +} = { specialFields: [], availableFields: [], emptyFields: [], @@ -266,8 +255,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ isEmptyAccordionOpen: false, isMetaAccordionOpen: false, }); - const [pageSize, setPageSize] = useState(PAGINATION_SIZE); - const [scrollContainer, setScrollContainer] = useState(undefined); const currentIndexPattern = indexPatterns[currentIndexPatternId]; const allFields = currentIndexPattern.fields; const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] })); @@ -276,15 +263,9 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ (type) => type in fieldTypeNames ); - useEffect(() => { - // Reset the scroll if we have made material changes to the field list - if (scrollContainer) { - scrollContainer.scrollTop = 0; - setPageSize(PAGINATION_SIZE); - } - }, [localState.nameFilter, localState.typeFilter, currentIndexPatternId, scrollContainer]); + const fieldInfoUnavailable = existenceFetchFailed || currentIndexPattern.hasRestrictions; - const fieldGroups: FieldsGroup = useMemo(() => { + const fieldGroups: FieldGroups = useMemo(() => { const containsData = (field: IndexPatternField) => { const fieldByName = keyBy(allFields, 'name'); const overallField = fieldByName[field.name]; @@ -298,9 +279,10 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ supportedFieldTypes.has(field.type) ); const sorted = allSupportedTypesFields.sort(sortFields); + let groupedFields; // optimization before existingFields are synced if (!hasSyncedExistingFields) { - return { + groupedFields = { ...defaultFieldGroups, ...groupBy(sorted, (field) => { if (field.type === 'document') { @@ -313,7 +295,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }), }; } - return { + groupedFields = { ...defaultFieldGroups, ...groupBy(sorted, (field) => { if (field.type === 'document') { @@ -325,9 +307,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ } else return 'emptyFields'; }), }; - }, [allFields, existingFields, currentIndexPattern, hasSyncedExistingFields]); - const filteredFieldGroups: FieldsGroup = useMemo(() => { const filterFieldGroup = (fieldGroup: IndexPatternField[]) => fieldGroup.filter((field) => { if ( @@ -344,79 +324,82 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ return true; }); - return Object.entries(fieldGroups).reduce((acc, [name, fields]) => { + const filteredGroupedFields = Object.entries(groupedFields).reduce((acc, [name, fields]) => { return { ...acc, [name]: filterFieldGroup(fields), }; }, defaultFieldGroups); - }, [fieldGroups, localState.nameFilter, localState.typeFilter]); - const lazyScroll = useCallback(() => { - if (scrollContainer) { - const nearBottom = - scrollContainer.scrollTop + scrollContainer.clientHeight > - scrollContainer.scrollHeight * 0.9; - if (nearBottom) { - const displayedFieldsLength = - (localState.isAvailableAccordionOpen ? filteredFieldGroups.availableFields.length : 0) + - (localState.isMetaAccordionOpen ? filteredFieldGroups.metaFields.length : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max( - PAGINATION_SIZE, - Math.min(pageSize + PAGINATION_SIZE * 0.5, displayedFieldsLength) - ) - ); - } - } - }, [ - scrollContainer, - localState.isAvailableAccordionOpen, - localState.isEmptyAccordionOpen, - localState.isMetaAccordionOpen, - filteredFieldGroups, - pageSize, - setPageSize, - ]); - - const [paginatedAvailableFields, paginatedEmptyFields, paginatedMetaFields]: [ - IndexPatternField[], - IndexPatternField[], - IndexPatternField[] - ] = useMemo(() => { - const { availableFields, emptyFields, metaFields } = filteredFieldGroups; - const isAvailableAccordionOpen = localState.isAvailableAccordionOpen; - const isEmptyAccordionOpen = localState.isEmptyAccordionOpen; - const isMetaAccordionOpen = localState.isMetaAccordionOpen; - - let slicedAvailableFields: IndexPatternField[] = []; - let slicedEmptyFields: IndexPatternField[] = []; - let slicedMetaFields: IndexPatternField[] = []; - - let remainingItems = pageSize; - - if (isAvailableAccordionOpen) { - slicedAvailableFields = availableFields.slice(0, remainingItems); - remainingItems = remainingItems - availableFields.length; - } - - if (isEmptyAccordionOpen && remainingItems > 0) { - slicedEmptyFields = emptyFields.slice(0, remainingItems); - remainingItems = remainingItems - slicedEmptyFields.length; - } + const fieldGroupDefinitions: FieldGroups = { + SpecialFields: { + fields: filteredGroupedFields.specialFields, + fieldCount: 1, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: false, + title: '', + hideDetails: true, + }, + AvailableFields: { + fields: filteredGroupedFields.availableFields, + fieldCount: groupedFields.availableFields.length, + isInitiallyOpen: true, + showInAccordion: true, + title: fieldInfoUnavailable + ? i18n.translate('xpack.lens.indexPattern.allFieldsLabel', { + defaultMessage: 'All fields', + }) + : i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', { + defaultMessage: 'Available fields', + }), + + isAffectedByGlobalFilter: !!filters.length, + isAffectedByTimeFilter: true, + hideDetails: fieldInfoUnavailable, + }, + EmptyFields: { + fields: filteredGroupedFields.emptyFields, + fieldCount: groupedFields.emptyFields.length, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: true, + hideDetails: false, + title: i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', { + defaultMessage: 'Empty fields', + }), + }, + MetaFields: { + fields: filteredGroupedFields.metaFields, + fieldCount: groupedFields.metaFields.length, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: true, + hideDetails: false, + title: i18n.translate('xpack.lens.indexPattern.metaFieldsLabel', { + defaultMessage: 'Meta fields', + }), + }, + }; - if (isMetaAccordionOpen && remainingItems > 0) { - slicedMetaFields = metaFields.slice(0, remainingItems); + // do not show empty field accordion if there is no existence information + if (fieldInfoUnavailable) { + delete fieldGroupDefinitions.emptyFields; } - return [slicedAvailableFields, slicedEmptyFields, slicedMetaFields]; + return fieldGroupDefinitions; }, [ - localState.isAvailableAccordionOpen, - localState.isEmptyAccordionOpen, - localState.isMetaAccordionOpen, - filteredFieldGroups, - pageSize, + allFields, + existingFields, + currentIndexPattern, + hasSyncedExistingFields, + fieldInfoUnavailable, + filters.length, + localState.nameFilter, + localState.typeFilter, ]); const fieldProps = useMemo( @@ -442,8 +425,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ ] ); - const fieldInfoUnavailable = existenceFetchFailed || currentIndexPattern.hasRestrictions; - return ( } > - ( @@ -564,157 +545,21 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ -
{ - if (el && !el.dataset.dynamicScroll) { - el.dataset.dynamicScroll = 'true'; - setScrollContainer(el); - } + + field.type === 'document' || + fieldExists(existingFields, currentIndexPattern.title, field.name) + } + fieldProps={fieldProps} + fieldGroups={fieldGroups} + hasSyncedExistingFields={!!hasSyncedExistingFields} + filter={{ + nameFilter: localState.nameFilter, + typeFilter: localState.typeFilter, }} - onScroll={throttle(lazyScroll, 100)} - > -
- {filteredFieldGroups.specialFields.map((field: IndexPatternField) => ( - - ))} - - - true} - hideDetails={fieldInfoUnavailable} - hasLoaded={!!hasSyncedExistingFields} - fieldsCount={filteredFieldGroups.availableFields.length} - isFiltered={ - filteredFieldGroups.availableFields.length !== fieldGroups.availableFields.length - } - paginatedFields={paginatedAvailableFields} - fieldProps={fieldProps} - onToggle={(open) => { - setLocalState((s) => ({ - ...s, - isAvailableAccordionOpen: open, - })); - const displayedFieldLength = - (open ? filteredFieldGroups.availableFields.length : 0) + - (localState.isMetaAccordionOpen ? filteredFieldGroups.metaFields.length : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - showExistenceFetchError={existenceFetchFailed} - renderCallout={ - - } - /> - - {!fieldInfoUnavailable && ( - false} - fieldProps={fieldProps} - id="lnsIndexPatternEmptyFields" - label={i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', { - defaultMessage: 'Empty fields', - })} - onToggle={(open) => { - setLocalState((s) => ({ - ...s, - isEmptyAccordionOpen: open, - })); - const displayedFieldLength = - (localState.isAvailableAccordionOpen - ? filteredFieldGroups.availableFields.length - : 0) + - (localState.isMetaAccordionOpen ? filteredFieldGroups.metaFields.length : 0) + - (open ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - renderCallout={ - - } - /> - )} - - - fieldExists(existingFields, currentIndexPattern.title, field.name) - } - fieldProps={fieldProps} - id="lnsIndexPatternMetaFields" - label={i18n.translate('xpack.lens.indexPattern.metaFieldsLabel', { - defaultMessage: 'Meta fields', - })} - onToggle={(open) => { - setLocalState((s) => ({ - ...s, - isMetaAccordionOpen: open, - })); - const displayedFieldLength = - (localState.isAvailableAccordionOpen - ? filteredFieldGroups.availableFields.length - : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0) + - (open ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - renderCallout={ - - } - /> - -
-
+ currentIndexPatternId={currentIndexPatternId} + existenceFetchFailed={existenceFetchFailed} + />
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx index edc058047f891..b3f159106a7bc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -5,216 +5,15 @@ */ import './datapanel.scss'; -import { uniq, keyBy, groupBy, throttle } from 'lodash'; -import React, { useState, useEffect, memo, useCallback, useMemo } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiContextMenuPanel, - EuiContextMenuItem, - EuiContextMenuPanelProps, - EuiPopover, - EuiCallOut, - EuiFormControlLayout, - EuiSpacer, - EuiFilterGroup, - EuiFilterButton, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { DataPublicPluginStart, EsQueryConfig, Query, Filter } from 'src/plugins/data/public'; -import { DatasourceDataPanelProps, DataType, StateSetter } from '../types'; -import { ChildDragDropProvider, DragContextState } from '../drag_drop'; +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 { - IndexPattern, - IndexPatternPrivateState, - IndexPatternField, - IndexPatternRef, -} from './types'; -import { trackUiEvent } from '../lens_ui_telemetry'; -import { syncExistingFields } from './loader'; -import { fieldExists } from './pure_helpers'; -import { Loader } from '../loader'; -import { FieldsAccordion } from './fields_accordion'; -import { esQuery, IIndexPattern } from '../../../../../src/plugins/data/public'; - -export type Props = DatasourceDataPanelProps & { - data: DataPublicPluginStart; - changeIndexPattern: ( - id: string, - state: IndexPatternPrivateState, - setState: StateSetter - ) => void; - charts: ChartsPluginSetup; -}; -import { LensFieldIcon } from './lens_field_icon'; -import { ChangeIndexPattern } from './change_indexpattern'; -import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; - -// TODO the typings for EuiContextMenuPanel are incorrect - watchedItemProps is missing. This can be removed when the types are adjusted -const FixedEuiContextMenuPanel = (EuiContextMenuPanel as unknown) as React.FunctionComponent< - EuiContextMenuPanelProps & { watchedItemProps: string[] } ->; - -function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) { - return fieldA.displayName.localeCompare(fieldB.displayName, undefined, { sensitivity: 'base' }); -} - -const supportedFieldTypes = new Set(['string', 'number', 'boolean', 'date', 'ip', 'document']); +import { IndexPatternField } from './types'; +import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; const PAGINATION_SIZE = 50; -const fieldTypeNames: Record = { - document: i18n.translate('xpack.lens.datatypes.record', { defaultMessage: 'record' }), - string: i18n.translate('xpack.lens.datatypes.string', { defaultMessage: 'string' }), - number: i18n.translate('xpack.lens.datatypes.number', { defaultMessage: 'number' }), - boolean: i18n.translate('xpack.lens.datatypes.boolean', { defaultMessage: 'boolean' }), - date: i18n.translate('xpack.lens.datatypes.date', { defaultMessage: 'date' }), - ip: i18n.translate('xpack.lens.datatypes.ipAddress', { defaultMessage: 'IP' }), -}; - -// Wrapper around esQuery.buildEsQuery, handling errors (e.g. because a query can't be parsed) by -// returning a query dsl object not matching anything -function buildSafeEsQuery( - indexPattern: IIndexPattern, - query: Query, - filters: Filter[], - queryConfig: EsQueryConfig -) { - try { - return esQuery.buildEsQuery(indexPattern, query, filters, queryConfig); - } catch (e) { - return { - bool: { - must_not: { - match_all: {}, - }, - }, - }; - } -} - -export function IndexPatternDataPanel({ - setState, - state, - dragDropContext, - core, - data, - query, - filters, - dateRange, - changeIndexPattern, - charts, - showNoDataPopover, -}: Props) { - const { indexPatternRefs, indexPatterns, currentIndexPatternId } = state; - const onChangeIndexPattern = useCallback( - (id: string) => changeIndexPattern(id, state, setState), - [state, setState, changeIndexPattern] - ); - - const indexPatternList = uniq( - Object.values(state.layers) - .map((l) => l.indexPatternId) - .concat(currentIndexPatternId) - ) - .sort((a, b) => a.localeCompare(b)) - .filter((id) => !!indexPatterns[id]) - .map((id) => ({ - id, - title: indexPatterns[id].title, - timeFieldName: indexPatterns[id].timeFieldName, - fields: indexPatterns[id].fields, - hasRestrictions: indexPatterns[id].hasRestrictions, - })); - - const dslQuery = buildSafeEsQuery( - indexPatterns[currentIndexPatternId] as IIndexPattern, - query, - filters, - esQuery.getEsQueryConfig(core.uiSettings) - ); - - return ( - <> - - syncExistingFields({ - dateRange, - setState, - isFirstExistenceFetch: state.isFirstExistenceFetch, - currentIndexPatternTitle: indexPatterns[currentIndexPatternId].title, - showNoDataPopover, - indexPatterns: indexPatternList, - fetchJson: core.http.post, - dslQuery, - }) - } - loadDeps={[ - query, - filters, - dateRange.fromDate, - dateRange.toDate, - indexPatternList.map((x) => `${x.title}:${x.timeFieldName}`).join(','), - ]} - /> - - {Object.keys(indexPatterns).length === 0 ? ( - - - -

- -

-
-
-
- ) : ( - - )} - - ); -} - -interface DataPanelState { - nameFilter: string; - typeFilter: DataType[]; - isTypeFilterOpen: boolean; - isAvailableAccordionOpen: boolean; - isEmptyAccordionOpen: boolean; - isMetaAccordionOpen: boolean; -} - export interface FieldsGroup { specialFields: IndexPatternField[]; availableFields: IndexPatternField[]; @@ -222,135 +21,75 @@ export interface FieldsGroup { metaFields: IndexPatternField[]; } -const defaultFieldGroups = { - specialFields: [], - availableFields: [], - emptyFields: [], - metaFields: [], -}; +export type FieldGroups = Record< + string, + { + fields: IndexPatternField[]; + fieldCount: number; + showInAccordion: boolean; + isInitiallyOpen: boolean; + title: string; + isAffectedByGlobalFilter: boolean; + isAffectedByTimeFilter: boolean; + hideDetails?: boolean; + } +>; -const fieldFiltersLabel = i18n.translate('xpack.lens.indexPatterns.fieldFiltersLabel', { - defaultMessage: 'Field filters', -}); +function getDisplayedFieldsLength( + fieldGroups: FieldGroups, + accordionState: Partial> +) { + return Object.entries(fieldGroups) + .filter(([key]) => accordionState[key]) + .reduce((allFieldCount, [, { fields }]) => allFieldCount + fields.length, 0); +} -export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ - currentIndexPatternId, - indexPatternRefs, - indexPatterns, +export function FieldList({ + exists, + fieldGroups, existenceFetchFailed, - query, - dateRange, - filters, - dragDropContext, - onChangeIndexPattern, - core, - data, - existingFields, - charts, -}: Omit & { - data: DataPublicPluginStart; - currentIndexPatternId: string; - indexPatternRefs: IndexPatternRef[]; - indexPatterns: Record; - dragDropContext: DragContextState; - onChangeIndexPattern: (newId: string) => void; - existingFields: IndexPatternPrivateState['existingFields']; - charts: ChartsPluginSetup; + fieldProps, + hasSyncedExistingFields, + filter, + currentIndexPatternId, +}: { + exists: (field: IndexPatternField) => boolean; + fieldGroups: FieldGroups; + fieldProps: FieldItemSharedProps; + hasSyncedExistingFields: boolean; existenceFetchFailed?: boolean; + filter: { + nameFilter: string; + typeFilter: string[]; + }; + currentIndexPatternId: string; }) { - const [localState, setLocalState] = useState({ - nameFilter: '', - typeFilter: [], - isTypeFilterOpen: false, - isAvailableAccordionOpen: true, - isEmptyAccordionOpen: false, - isMetaAccordionOpen: false, - }); const [pageSize, setPageSize] = useState(PAGINATION_SIZE); const [scrollContainer, setScrollContainer] = useState(undefined); - const currentIndexPattern = indexPatterns[currentIndexPatternId]; - const allFields = currentIndexPattern.fields; - const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] })); - const hasSyncedExistingFields = existingFields[currentIndexPattern.title]; - const availableFieldTypes = uniq(allFields.map(({ type }) => type)).filter( - (type) => type in fieldTypeNames + const [accordionState, setAccordionState] = useState>>(() => + Object.fromEntries( + Object.entries(fieldGroups) + .filter(([, { showInAccordion }]) => showInAccordion) + .map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen]) + ) ); + const isAffectedByFieldFilter = !!(filter.typeFilter.length || filter.nameFilter.length); + useEffect(() => { // Reset the scroll if we have made material changes to the field list if (scrollContainer) { scrollContainer.scrollTop = 0; setPageSize(PAGINATION_SIZE); } - }, [localState.nameFilter, localState.typeFilter, currentIndexPatternId, scrollContainer]); - - const fieldGroups: FieldsGroup = useMemo(() => { - const containsData = (field: IndexPatternField) => { - const fieldByName = keyBy(allFields, 'name'); - const overallField = fieldByName[field.name]; - - return ( - overallField && fieldExists(existingFields, currentIndexPattern.title, overallField.name) - ); - }; - - const allSupportedTypesFields = allFields.filter((field) => - supportedFieldTypes.has(field.type) - ); - const sorted = allSupportedTypesFields.sort(sortFields); - // optimization before existingFields are synced - if (!hasSyncedExistingFields) { - return { - ...defaultFieldGroups, - ...groupBy(sorted, (field) => { - if (field.type === 'document') { - return 'specialFields'; - } else if (field.meta) { - return 'metaFields'; - } else { - return 'emptyFields'; - } - }), - }; - } - return { - ...defaultFieldGroups, - ...groupBy(sorted, (field) => { - if (field.type === 'document') { - return 'specialFields'; - } else if (field.meta) { - return 'metaFields'; - } else if (containsData(field)) { - return 'availableFields'; - } else return 'emptyFields'; - }), - }; - }, [allFields, existingFields, currentIndexPattern, hasSyncedExistingFields]); - - const filteredFieldGroups: FieldsGroup = useMemo(() => { - const filterFieldGroup = (fieldGroup: IndexPatternField[]) => - fieldGroup.filter((field) => { - if ( - localState.nameFilter.length && - !field.name.toLowerCase().includes(localState.nameFilter.toLowerCase()) && - !field.displayName.toLowerCase().includes(localState.nameFilter.toLowerCase()) - ) { - return false; - } - - if (localState.typeFilter.length > 0) { - return localState.typeFilter.includes(field.type as DataType); - } - return true; - }); - - return Object.entries(fieldGroups).reduce((acc, [name, fields]) => { - return { - ...acc, - [name]: filterFieldGroup(fields), - }; - }, defaultFieldGroups); - }, [fieldGroups, localState.nameFilter, localState.typeFilter]); + }, [filter.nameFilter, filter.typeFilter, currentIndexPatternId, scrollContainer]); + // const currentIndexPattern = indexPatterns[currentIndexPatternId]; + // const allFields = currentIndexPattern.fields; + // const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] })); + // const hasSyncedExistingFields = existingFields[currentIndexPattern.title]; + // const availableFieldTypes = uniq(allFields.map(({ type }) => type)).filter( + // (type) => type in fieldTypeNames + // ); const lazyScroll = useCallback(() => { if (scrollContainer) { @@ -358,264 +97,86 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ scrollContainer.scrollTop + scrollContainer.clientHeight > scrollContainer.scrollHeight * 0.9; if (nearBottom) { - const displayedFieldsLength = - (localState.isAvailableAccordionOpen ? filteredFieldGroups.availableFields.length : 0) + - (localState.isMetaAccordionOpen ? filteredFieldGroups.metaFields.length : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0); setPageSize( Math.max( PAGINATION_SIZE, - Math.min(pageSize + PAGINATION_SIZE * 0.5, displayedFieldsLength) + Math.min( + pageSize + PAGINATION_SIZE * 0.5, + getDisplayedFieldsLength(fieldGroups, accordionState) + ) ) ); } } - }, [ - scrollContainer, - localState.isAvailableAccordionOpen, - localState.isEmptyAccordionOpen, - localState.isMetaAccordionOpen, - filteredFieldGroups, - pageSize, - setPageSize, - ]); - - const [paginatedAvailableFields, paginatedEmptyFields, paginatedMetaFields]: [ - IndexPatternField[], - IndexPatternField[], - IndexPatternField[] - ] = useMemo(() => { - const { availableFields, emptyFields, metaFields } = filteredFieldGroups; - const isAvailableAccordionOpen = localState.isAvailableAccordionOpen; - const isEmptyAccordionOpen = localState.isEmptyAccordionOpen; - const isMetaAccordionOpen = localState.isMetaAccordionOpen; - - let slicedAvailableFields: IndexPatternField[] = []; - let slicedEmptyFields: IndexPatternField[] = []; - let slicedMetaFields: IndexPatternField[] = []; + }, [scrollContainer, pageSize, setPageSize, fieldGroups, accordionState]); + const paginatedFields = useMemo(() => { let remainingItems = pageSize; - - if (isAvailableAccordionOpen) { - slicedAvailableFields = availableFields.slice(0, remainingItems); - remainingItems = remainingItems - availableFields.length; - } - - if (isEmptyAccordionOpen && remainingItems > 0) { - slicedEmptyFields = emptyFields.slice(0, remainingItems); - remainingItems = remainingItems - slicedEmptyFields.length; - } - - if (isMetaAccordionOpen && remainingItems > 0) { - slicedMetaFields = metaFields.slice(0, remainingItems); - } - - return [slicedAvailableFields, slicedEmptyFields, slicedMetaFields]; - }, [ - localState.isAvailableAccordionOpen, - localState.isEmptyAccordionOpen, - localState.isMetaAccordionOpen, - filteredFieldGroups, - pageSize, - ]); - - const fieldProps = useMemo( - () => ({ - core, - data, - indexPattern: currentIndexPattern, - highlight: localState.nameFilter.toLowerCase(), - dateRange, - query, - filters, - chartsThemeService: charts.theme, - }), - [ - core, - data, - currentIndexPattern, - dateRange, - query, - filters, - localState.nameFilter, - charts.theme, - ] - ); - - const fieldInfoUnavailable = existenceFetchFailed || currentIndexPattern.hasRestrictions; + 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 ( - - - -
- { - onChangeIndexPattern(newId); - clearLocalState(); - }} - /> -
-
- - { - trackUiEvent('indexpattern_filters_cleared'); - clearLocalState(); - }, - }} - > - { - setLocalState({ ...localState, nameFilter: e.target.value }); - }} - aria-label={i18n.translate('xpack.lens.indexPatterns.filterByNameAriaLabel', { - defaultMessage: 'Search fields', - })} - /> - - - - - - setLocalState(() => ({ ...localState, isTypeFilterOpen: false }))} - button={ - { - setLocalState((s) => ({ - ...s, - isTypeFilterOpen: !localState.isTypeFilterOpen, - })); - }} - > - {fieldFiltersLabel} - - } - > - ( - { - trackUiEvent('indexpattern_type_filter_toggled'); - setLocalState((s) => ({ - ...s, - typeFilter: localState.typeFilter.includes(type) - ? localState.typeFilter.filter((t) => t !== type) - : [...localState.typeFilter, type], - })); - }} - > - - {fieldTypeNames[type]} - - - ))} +
{ + 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) => ( + - - - - -
{ - if (el && !el.dataset.dynamicScroll) { - el.dataset.dynamicScroll = 'true'; - setScrollContainer(el); - } - }} - onScroll={throttle(lazyScroll, 100)} - > -
- {filteredFieldGroups.specialFields.map((field: IndexPatternField) => ( - - ))} - - + )) + )} + + {Object.entries(fieldGroups) + .filter(([, { showInAccordion }]) => showInAccordion) + .map(([key, fieldGroup]) => ( + true} - hideDetails={fieldInfoUnavailable} + initialIsOpen={Boolean(accordionState[key])} + key={key} + id={`lnsIndexPattern${key}`} + label={fieldGroup.title} + exists={exists} + hideDetails={fieldGroup.hideDetails} hasLoaded={!!hasSyncedExistingFields} - fieldsCount={filteredFieldGroups.availableFields.length} - isFiltered={ - filteredFieldGroups.availableFields.length !== fieldGroups.availableFields.length - } - paginatedFields={paginatedAvailableFields} + fieldsCount={fieldGroup.fields.length} + isFiltered={fieldGroup.fieldCount !== fieldGroup.fields.length} + paginatedFields={paginatedFields[key]} fieldProps={fieldProps} onToggle={(open) => { - setLocalState((s) => ({ + setAccordionState((s) => ({ ...s, - isAvailableAccordionOpen: open, + [key]: open, })); - const displayedFieldLength = - (open ? filteredFieldGroups.availableFields.length : 0) + - (localState.isMetaAccordionOpen ? filteredFieldGroups.metaFields.length : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0); + const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, { + ...accordionState, + [key]: open, + }); setPageSize( Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) ); @@ -623,102 +184,17 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ showExistenceFetchError={existenceFetchFailed} renderCallout={ } /> - {!fieldInfoUnavailable && ( - false} - fieldProps={fieldProps} - id="lnsIndexPatternEmptyFields" - label={i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', { - defaultMessage: 'Empty fields', - })} - onToggle={(open) => { - setLocalState((s) => ({ - ...s, - isEmptyAccordionOpen: open, - })); - const displayedFieldLength = - (localState.isAvailableAccordionOpen - ? filteredFieldGroups.availableFields.length - : 0) + - (localState.isMetaAccordionOpen ? filteredFieldGroups.metaFields.length : 0) + - (open ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - renderCallout={ - - } - /> - )} - - - fieldExists(existingFields, currentIndexPattern.title, field.name) - } - fieldProps={fieldProps} - id="lnsIndexPatternMetaFields" - label={i18n.translate('xpack.lens.indexPattern.metaFieldsLabel', { - defaultMessage: 'Meta fields', - })} - onToggle={(open) => { - setLocalState((s) => ({ - ...s, - isMetaAccordionOpen: open, - })); - const displayedFieldLength = - (localState.isAvailableAccordionOpen - ? filteredFieldGroups.availableFields.length - : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0) + - (open ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - renderCallout={ - - } - /> - -
-
-
- - + + ))} +
+
); -}; - -export const MemoizedDataPanel = memo(InnerIndexPatternDataPanel); +}