diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx
index 51cdaadd1cda2..705299c1566d2 100644
--- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx
@@ -377,6 +377,7 @@ export function DiscoverSidebarComponent({
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
screenReaderDescriptionId={fieldSearchDescriptionId}
+ localStorageKeyPrefix="discover"
/>
)}
diff --git a/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx b/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx
index 778f38168e6c1..9190c6de2859e 100644
--- a/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx
+++ b/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx
@@ -431,4 +431,54 @@ describe('UnifiedFieldList + 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]);
+ });
});
diff --git a/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.tsx b/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.tsx
index 9e81cb8c5d476..1bc84a37ed7e0 100644
--- a/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.tsx
+++ b/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.tsx
@@ -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';
@@ -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;
function getDisplayedFieldsLength(
fieldGroups: FieldListGroups,
- accordionState: Partial>
+ accordionState: InitiallyOpenSections
) {
return Object.entries(fieldGroups)
.filter(([key]) => accordionState[key])
@@ -35,6 +39,7 @@ export interface FieldListGroupedProps {
renderFieldItem: FieldsAccordionProps['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;
}
@@ -45,6 +50,7 @@ function InnerFieldListGrouped({
renderFieldItem,
scrollToTopResetCounter,
screenReaderDescriptionId,
+ localStorageKeyPrefix,
'data-test-subj': dataTestSubject = 'fieldListGrouped',
}: FieldListGroupedProps) {
const hasSyncedExistingFields =
@@ -56,9 +62,22 @@ function InnerFieldListGrouped({
);
const [pageSize, setPageSize] = useState(PAGINATION_SIZE);
const [scrollContainer, setScrollContainer] = useState(undefined);
- const [accordionState, setAccordionState] = useState>>(() =>
+ const [storedInitiallyOpenSections, storeInitiallyOpenSections] =
+ useLocalStorage(
+ `${localStorageKeyPrefix ? localStorageKeyPrefix + '.' : ''}${LOCAL_STORAGE_KEY_SECTIONS}`,
+ {}
+ );
+ const [accordionState, setAccordionState] = useState(() =>
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,
+ ];
+ })
)
);
@@ -256,6 +275,12 @@ function InnerFieldListGrouped({
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?
diff --git a/test/functional/apps/discover/group1/_sidebar.ts b/test/functional/apps/discover/group1/_sidebar.ts
index 66c60d22b4d3f..a62a379c20224 100644
--- a/test/functional/apps/discover/group1/_sidebar.ts
+++ b/test/functional/apps/discover/group1/_sidebar.ts
@@ -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 () {
diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts
index b9be3c673448d..7db95f8063c12 100644
--- a/test/functional/page_objects/discover_page.ts
+++ b/test/functional/page_objects/discover_page.ts
@@ -463,6 +463,10 @@ export class DiscoverPageObject extends FtrService {
).getAttribute('innerText');
}
+ public async cleanSidebarLocalStorage(): Promise {
+ await this.browser.setLocalStorageItem('discover.unifiedFieldList.initiallyOpenSections', '{}');
+ }
+
public async waitUntilSidebarHasLoaded() {
await this.retry.waitFor('sidebar is loaded', async () => {
return (await this.getSidebarAriaDescription()).length > 0;
diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx
index 411b8cd094ab2..f774525518e49 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx
@@ -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 () => {
diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx
index 01feaa4187627..374eb430dae9c 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx
@@ -428,6 +428,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
data-test-subj="lnsIndexPattern"
+ localStorageKeyPrefix="lens"
/>
diff --git a/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx b/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx
index b278284bad8e9..aad9bae11faf4 100644
--- a/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx
+++ b/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx
@@ -161,6 +161,7 @@ export function TextBasedDataPanel({
{...fieldListGroupedProps}
renderFieldItem={renderFieldItem}
data-test-subj="lnsTextBasedLanguages"
+ localStorageKeyPrefix="lens"
/>