diff --git a/public/app/app.ts b/public/app/app.ts index 88f85dee5a0..97bff0eb9cc 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -82,6 +82,7 @@ import { preloadPlugins } from './features/plugins/pluginPreloader'; import { QueryRunner } from './features/query/state/QueryRunner'; import { runRequest } from './features/query/state/runRequest'; import { initWindowRuntime } from './features/runtime/init'; +import { cleanupOldExpandedFolders } from './features/search/utils'; import { variableAdapters } from './features/variables/adapters'; import { createAdHocVariableAdapter } from './features/variables/adhoc/adapter'; import { createConstantVariableAdapter } from './features/variables/constant/adapter'; @@ -215,6 +216,13 @@ export class GrafanaApp { // Read initial kiosk mode from url at app startup chromeService.setKioskModeFromUrl(queryParams.kiosk); + // Clean up old search local storage values + try { + cleanupOldExpandedFolders(); + } catch (err) { + console.warn('Failed to clean up old expanded folders', err); + } + this.context = { backend: backendSrv, location: locationService, diff --git a/public/app/features/search/constants.ts b/public/app/features/search/constants.ts index 2580147cf36..8b66939e15d 100644 --- a/public/app/features/search/constants.ts +++ b/public/app/features/search/constants.ts @@ -6,6 +6,7 @@ export const SEARCH_ITEM_HEIGHT = 58; export const SEARCH_ITEM_MARGIN = 8; export const DEFAULT_SORT = { label: 'A\u2013Z', value: 'alpha-asc' }; export const SECTION_STORAGE_KEY = 'search.sections'; +export const SEARCH_EXPANDED_FOLDER_STORAGE_KEY = 'grafana.search.expanded-folder'; export const GENERAL_FOLDER_ID = 0; export const GENERAL_FOLDER_UID = 'general'; export const GENERAL_FOLDER_TITLE = 'General'; diff --git a/public/app/features/search/page/components/FolderSection.tsx b/public/app/features/search/page/components/FolderSection.tsx index 55dbf0aaa8b..9f3ea0b8d17 100644 --- a/public/app/features/search/page/components/FolderSection.tsx +++ b/public/app/features/search/page/components/FolderSection.tsx @@ -1,16 +1,15 @@ import { css } from '@emotion/css'; -import React, { useCallback, useId } from 'react'; -import { useAsync, useLocalStorage } from 'react-use'; +import React, { useCallback, useId, useState } from 'react'; +import { useAsync } from 'react-use'; import { GrafanaTheme2, toIconName } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Card, Checkbox, CollapsableSection, Icon, Spinner, useStyles2 } from '@grafana/ui'; import { config } from 'app/core/config'; import { t } from 'app/core/internationalization'; -import { getSectionStorageKey } from 'app/features/search/utils'; import { SearchItem } from '../..'; -import { GENERAL_FOLDER_UID } from '../../constants'; +import { GENERAL_FOLDER_UID, SEARCH_EXPANDED_FOLDER_STORAGE_KEY } from '../../constants'; import { getGrafanaSearcher } from '../../service'; import { getFolderChildren } from '../../service/folders'; import { queryResultToViewItem } from '../../service/utils'; @@ -57,9 +56,14 @@ export const FolderSection = ({ renderStandaloneBody, tags, }: SectionHeaderProps) => { + const uid = section.uid; const editable = selectionToggle != null; + const styles = useStyles2(useCallback((theme: GrafanaTheme2) => getSectionHeaderStyles(theme, editable), [editable])); - const [sectionExpanded, setSectionExpanded] = useLocalStorage(getSectionStorageKey(section.title), false); + const [sectionExpanded, setSectionExpanded] = useState(() => { + const lastExpandedFolder = window.localStorage.getItem(SEARCH_EXPANDED_FOLDER_STORAGE_KEY); + return lastExpandedFolder === uid; + }); const results = useAsync(async () => { if (!sectionExpanded && !renderStandaloneBody) { @@ -72,7 +76,20 @@ export const FolderSection = ({ }, [sectionExpanded, tags]); const onSectionExpand = () => { - setSectionExpanded(!sectionExpanded); + const newExpandedValue = !sectionExpanded; + + if (newExpandedValue) { + // If we've just expanded the section, remember it to local storage + window.localStorage.setItem(SEARCH_EXPANDED_FOLDER_STORAGE_KEY, uid); + } else { + // Else, when closing a section, remove it from local storage only if this folder was the most recently opened + const lastExpandedFolder = window.localStorage.getItem(SEARCH_EXPANDED_FOLDER_STORAGE_KEY); + if (lastExpandedFolder === uid) { + window.localStorage.removeItem(SEARCH_EXPANDED_FOLDER_STORAGE_KEY); + } + } + + setSectionExpanded(newExpandedValue); }; const onToggleFolder = (evt: React.FormEvent) => { diff --git a/public/app/features/search/utils.ts b/public/app/features/search/utils.ts index 7c942ec1174..c93ec642ed8 100644 --- a/public/app/features/search/utils.ts +++ b/public/app/features/search/utils.ts @@ -14,6 +14,18 @@ export const hasFilters = (query: SearchState) => { return Boolean(query.query || query.tag?.length > 0 || query.starred || query.sort); }; +/** Cleans up old local storage values that remembered many open folders */ +export const cleanupOldExpandedFolders = () => { + const keyPrefix = SECTION_STORAGE_KEY + '.'; + + for (let index = 0; index < window.localStorage.length; index++) { + const lsKey = window.localStorage.key(index); + if (lsKey?.startsWith(keyPrefix)) { + window.localStorage.removeItem(lsKey); + } + } +}; + /** * Get storage key for a dashboard folder by its title * @param title