From a86847aff2a8db36ddca149ad7546e783fc5539a Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 13 Sep 2023 10:55:53 +0000 Subject: [PATCH] [v9.4.x] BrowseDashboards: Only remember the most recent expanded folder (#74812) BrowseDashboards: Only remember the most recent expanded folder (#74617) * BrowseDashboards: Only remember the most recent expanded folder * set to null * cleanup * only clear removed folder if it was the most recently opened * comments + variable (cherry picked from commit 5cb7eb588435f699f05b3fe9334cf6cf9bc2e60f) --- public/app/app.ts | 8 ++++++ public/app/features/search/constants.ts | 1 + .../search/page/components/FolderSection.tsx | 27 +++++++++++++++---- public/app/features/search/utils.ts | 12 +++++++++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/public/app/app.ts b/public/app/app.ts index d3d60af14b6..81e6ac703d0 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -74,6 +74,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'; @@ -187,6 +188,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 d67ce5186c1..650c381d4a4 100644 --- a/public/app/features/search/page/components/FolderSection.tsx +++ b/public/app/features/search/page/components/FolderSection.tsx @@ -1,11 +1,11 @@ import { css, cx } from '@emotion/css'; -import React, { useCallback } from 'react'; -import { useAsync, useLocalStorage } from 'react-use'; +import React, { useCallback, useState } from 'react'; +import { useAsync } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Card, Checkbox, CollapsableSection, Icon, IconName, Spinner, useStyles2 } from '@grafana/ui'; -import { getSectionStorageKey } from 'app/features/search/utils'; +import { SEARCH_EXPANDED_FOLDER_STORAGE_KEY } from 'app/features/search/constants'; import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId'; import { SearchItem } from '../..'; @@ -42,6 +42,7 @@ export const FolderSection = ({ renderStandaloneBody, tags, }: SectionHeaderProps) => { + const uid = section.uid; const editable = selectionToggle != null; const styles = useStyles2( useCallback( @@ -49,7 +50,10 @@ export const FolderSection = ({ [section.selected, 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) { @@ -89,7 +93,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