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] Persist field list sections state in local storage #148373

Merged
merged 6 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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 @@ -377,6 +377,7 @@ export function DiscoverSidebarComponent({
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
screenReaderDescriptionId={fieldSearchDescriptionId}
localStorageKeyPrefix="discover"
/>
)}
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,4 +431,54 @@ describe('UnifiedFieldList <FieldListGrouped /> + useGroupedFields()', () => {
'2 selected fields. 10 popular fields. 25 available fields. 112 unmapped fields. 0 empty fields. 3 meta fields.'
);
});

it('persists sections state in local storage', async () => {
const wrapper = await mountGroupedList({
listProps: {
...defaultProps,
fieldsExistenceStatus: ExistenceFetchStatus.succeeded,
localStorageKeyPrefix: 'test',
},
hookParams: {
dataViewId: dataView.id!,
allFields: manyFields,
},
});

// only Available is open
expect(
wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('initialIsOpen'))
).toStrictEqual([true, false, false, false]);

await act(async () => {
await wrapper
.find('[data-test-subj="fieldListGroupedEmptyFields"]')
.find('button')
.first()
.simulate('click');
await wrapper.update();
});

// now Empty is open too
expect(
wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('initialIsOpen'))
).toStrictEqual([true, false, true, false]);

const wrapper2 = await mountGroupedList({
listProps: {
...defaultProps,
fieldsExistenceStatus: ExistenceFetchStatus.succeeded,
localStorageKeyPrefix: 'test',
},
hookParams: {
dataViewId: dataView.id!,
allFields: manyFields,
},
});

// both Available and Empty are open for the second instance
expect(
wrapper2.find(FieldsAccordion).map((accordion) => accordion.prop('initialIsOpen'))
).toStrictEqual([true, false, true, false]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { partition, throttle } from 'lodash';
import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import { i18n } from '@kbn/i18n';
import { EuiScreenReaderOnly, EuiSpacer } from '@elastic/eui';
import { type DataViewField } from '@kbn/data-views-plugin/common';
Expand All @@ -18,10 +19,13 @@ import { ExistenceFetchStatus, FieldsGroup, FieldsGroupNames } from '../../types
import './field_list_grouped.scss';

const PAGINATION_SIZE = 50;
export const LOCAL_STORAGE_KEY_SECTIONS = 'unifiedFieldList.initiallyOpenSections';

type InitiallyOpenSections = Record<string, boolean>;

function getDisplayedFieldsLength<T extends FieldListItem>(
fieldGroups: FieldListGroups<T>,
accordionState: Partial<Record<string, boolean>>
accordionState: InitiallyOpenSections
) {
return Object.entries(fieldGroups)
.filter(([key]) => accordionState[key])
Expand All @@ -35,6 +39,7 @@ export interface FieldListGroupedProps<T extends FieldListItem> {
renderFieldItem: FieldsAccordionProps<T>['renderFieldItem'];
scrollToTopResetCounter: number;
screenReaderDescriptionId?: string;
localStorageKeyPrefix?: string; // Your app name: "discover", "lens", etc. If not provided, sections state would not be persisted.
'data-test-subj'?: string;
}

Expand All @@ -45,6 +50,7 @@ function InnerFieldListGrouped<T extends FieldListItem = DataViewField>({
renderFieldItem,
scrollToTopResetCounter,
screenReaderDescriptionId,
localStorageKeyPrefix,
'data-test-subj': dataTestSubject = 'fieldListGrouped',
}: FieldListGroupedProps<T>) {
const hasSyncedExistingFields =
Expand All @@ -56,9 +62,22 @@ function InnerFieldListGrouped<T extends FieldListItem = DataViewField>({
);
const [pageSize, setPageSize] = useState(PAGINATION_SIZE);
const [scrollContainer, setScrollContainer] = useState<Element | undefined>(undefined);
const [accordionState, setAccordionState] = useState<Partial<Record<string, boolean>>>(() =>
const [storedInitiallyOpenSections, storeInitiallyOpenSections] =
useLocalStorage<InitiallyOpenSections>(
`${localStorageKeyPrefix ? localStorageKeyPrefix + '.' : ''}${LOCAL_STORAGE_KEY_SECTIONS}`,
{}
);
const [accordionState, setAccordionState] = useState<InitiallyOpenSections>(() =>
Object.fromEntries(
fieldGroupsToShow.map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen])
fieldGroupsToShow.map(([key, { isInitiallyOpen }]) => {
const storedInitiallyOpen = localStorageKeyPrefix
? storedInitiallyOpenSections?.[key]
: null; // from localStorage
return [
key,
typeof storedInitiallyOpen === 'boolean' ? storedInitiallyOpen : isInitiallyOpen,
];
})
)
);

Expand Down Expand Up @@ -256,6 +275,12 @@ function InnerFieldListGrouped<T extends FieldListItem = DataViewField>({
Math.min(Math.ceil(pageSize * 1.5), displayedFieldLength)
)
);
if (localStorageKeyPrefix) {
storeInitiallyOpenSections({
...storedInitiallyOpenSections,
[key]: open,
});
}
}}
showExistenceFetchError={fieldsExistenceStatus === ExistenceFetchStatus.failed}
showExistenceFetchTimeout={fieldsExistenceStatus === ExistenceFetchStatus.failed} // TODO: deprecate timeout logic?
Expand Down
1 change: 1 addition & 0 deletions test/functional/apps/discover/group1/_sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
await kibanaServer.savedObjects.cleanStandardList();
await kibanaServer.uiSettings.replace({});
await PageObjects.discover.cleanSidebarLocalStorage();
});

describe('field filtering', function () {
Expand Down
4 changes: 4 additions & 0 deletions test/functional/page_objects/discover_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,10 @@ export class DiscoverPageObject extends FtrService {
).getAttribute('innerText');
}

public async cleanSidebarLocalStorage(): Promise<void> {
await this.browser.setLocalStorageItem('discover.unifiedFieldList.initiallyOpenSections', '{}');
}

public async waitUntilSidebarHasLoaded() {
await this.retry.waitFor('sidebar is loaded', async () => {
return (await this.getSidebarAriaDescription()).length > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ describe('FormBased Data Panel', () => {
(UseExistingFieldsApi.useExistingFieldsReader as jest.Mock).mockClear();
(UseExistingFieldsApi.useExistingFieldsFetcher as jest.Mock).mockClear();
UseExistingFieldsApi.resetExistingFieldsCache();
window.localStorage.removeItem('lens.unifiedFieldList.initiallyOpenSections');
});

it('should render a warning if there are no index patterns', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
data-test-subj="lnsIndexPattern"
localStorageKeyPrefix="lens"
/>
</FieldList>
</ChildDragDropProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export function TextBasedDataPanel({
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
data-test-subj="lnsTextBasedLanguages"
localStorageKeyPrefix="lens"
/>
</FieldList>
</ChildDragDropProvider>
Expand Down