diff --git a/public/app/features/explore/ContentOutline/ContentOutline.test.tsx b/public/app/features/explore/ContentOutline/ContentOutline.test.tsx
index 537755195bf..6b4107b6d3a 100644
--- a/public/app/features/explore/ContentOutline/ContentOutline.test.tsx
+++ b/public/app/features/explore/ContentOutline/ContentOutline.test.tsx
@@ -1,6 +1,8 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import { store } from '@grafana/data';
+
import { ContentOutline } from './ContentOutline';
jest.mock('./ContentOutlineContext', () => ({
@@ -73,15 +75,15 @@ const setup = (mergeSingleChild = false) => {
describe('', () => {
it('toggles content on button click', async () => {
setup();
- let showContentOutlineButton = screen.getByRole('button', { name: 'Expand outline' });
+ let showContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' });
expect(showContentOutlineButton).toBeInTheDocument();
await userEvent.click(showContentOutlineButton);
- const hideContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' });
+ const hideContentOutlineButton = screen.getByRole('button', { name: 'Expand outline' });
expect(hideContentOutlineButton).toBeInTheDocument();
await userEvent.click(hideContentOutlineButton);
- showContentOutlineButton = screen.getByRole('button', { name: 'Expand outline' });
+ showContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' });
expect(showContentOutlineButton).toBeInTheDocument();
});
@@ -157,4 +159,15 @@ describe('', () => {
expect(unregisterMock).toHaveBeenCalledWith('item-2-1');
});
+
+ it('should retrieve the last expanded state from local storage', async () => {
+ const getBoolMock = jest.spyOn(store, 'getBool').mockReturnValue(false);
+ setup();
+ const collapseContentOutlineButton = screen.queryByRole('button', { name: 'Collapse outline' });
+ const expandContentOutlineButton = screen.queryByRole('button', { name: 'Expand outline' });
+ expect(collapseContentOutlineButton).not.toBeInTheDocument();
+ expect(expandContentOutlineButton).toBeInTheDocument();
+
+ getBoolMock.mockRestore();
+ });
});
diff --git a/public/app/features/explore/ContentOutline/ContentOutline.tsx b/public/app/features/explore/ContentOutline/ContentOutline.tsx
index 415f34f3e52..ddd82ce01a3 100644
--- a/public/app/features/explore/ContentOutline/ContentOutline.tsx
+++ b/public/app/features/explore/ContentOutline/ContentOutline.tsx
@@ -2,12 +2,11 @@ import { css, cx } from '@emotion/css';
import { Fragment, useEffect, useRef, useState } from 'react';
import { useToggle, useScroll } from 'react-use';
-import { GrafanaTheme2 } from '@grafana/data';
+import { GrafanaTheme2, store } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { useStyles2, PanelContainer, CustomScrollbar } from '@grafana/ui';
import { ContentOutlineItemContextProps, useContentOutlineContext } from './ContentOutlineContext';
-import { ITEM_TYPES } from './ContentOutlineItem';
import { ContentOutlineItemButton } from './ContentOutlineItemButton';
function scrollableChildren(item: ContentOutlineItemContextProps) {
@@ -35,8 +34,15 @@ function shouldBeActive(
}
}
+export const CONTENT_OUTLINE_LOCAL_STORAGE_KEYS = {
+ visible: 'grafana.explore.contentOutline.visible',
+ expanded: 'grafana.explore.contentOutline.expanded',
+};
+
export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement | undefined; panelId: string }) {
- const [contentOutlineExpanded, toggleContentOutlineExpanded] = useToggle(false);
+ const [contentOutlineExpanded, toggleContentOutlineExpanded] = useToggle(
+ store.getBool(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.expanded, true)
+ );
const styles = useStyles2(getStyles, contentOutlineExpanded);
const scrollerRef = useRef(scroller || null);
const { y: verticalScroll } = useScroll(scrollerRef);
@@ -57,12 +63,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
}, {});
});
- const scrollIntoView = (
- ref: HTMLElement | null,
- itemPanelId: string,
- itemType: ITEM_TYPES | undefined,
- customOffsetTop = 0
- ) => {
+ const scrollIntoView = (ref: HTMLElement | null, customOffsetTop = 0) => {
let scrollValue = 0;
let el: HTMLElement | null | undefined = ref;
@@ -88,10 +89,10 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
});
if (activeParent) {
- scrollIntoView(activeParent.ref, activeParent.panelId, activeParent.type, activeParent.customTopOffset);
+ scrollIntoView(activeParent.ref, activeParent.customTopOffset);
}
} else {
- scrollIntoView(item.ref, item.panelId, item.type, item.customTopOffset);
+ scrollIntoView(item.ref, item.customTopOffset);
reportInteraction('explore_toolbar_contentoutline_clicked', {
item: 'select_section',
type: item.panelId,
@@ -100,6 +101,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
};
const toggle = () => {
+ store.set(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.expanded, !contentOutlineExpanded);
toggleContentOutlineExpanded();
reportInteraction('explore_toolbar_contentoutline_clicked', {
item: 'outline',
diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx
index 4ea74b48fc4..8cf7d3382e7 100644
--- a/public/app/features/explore/Explore.test.tsx
+++ b/public/app/features/explore/Explore.test.tsx
@@ -2,7 +2,15 @@ import { render, screen } from '@testing-library/react';
import { Props as AutoSizerProps } from 'react-virtualized-auto-sizer';
import { TestProvider } from 'test/helpers/TestProvider';
-import { CoreApp, createTheme, DataSourceApi, EventBusSrv, LoadingState, PluginExtensionTypes } from '@grafana/data';
+import {
+ CoreApp,
+ createTheme,
+ DataSourceApi,
+ EventBusSrv,
+ LoadingState,
+ PluginExtensionTypes,
+ store,
+} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { usePluginLinkExtensions } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore';
@@ -225,4 +233,14 @@ describe('Explore', () => {
expect(dataSourcePicker).toBeInTheDocument();
});
});
+
+ describe('Content Outline', () => {
+ it('should retrieve the last visible state from local storage', async () => {
+ const getBoolMock = jest.spyOn(store, 'getBool').mockReturnValue(false);
+ setup();
+ const showContentOutlineButton = screen.queryByRole('button', { name: 'Collapse outline' });
+ expect(showContentOutlineButton).not.toBeInTheDocument();
+ getBoolMock.mockRestore();
+ });
+ });
});
diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx
index 22ce2d7688b..7bf13532be5 100644
--- a/public/app/features/explore/Explore.tsx
+++ b/public/app/features/explore/Explore.tsx
@@ -14,6 +14,7 @@ import {
QueryFixAction,
RawTimeRange,
SplitOpenOptions,
+ store,
SupplementaryQueryType,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@@ -34,7 +35,7 @@ import { StoreState } from 'app/types';
import { getTimeZone } from '../profile/state/selectors';
-import { ContentOutline } from './ContentOutline/ContentOutline';
+import { CONTENT_OUTLINE_LOCAL_STORAGE_KEYS, ContentOutline } from './ContentOutline/ContentOutline';
import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext';
import { ContentOutlineItem } from './ContentOutline/ContentOutlineItem';
import { CorrelationHelper } from './CorrelationHelper';
@@ -147,7 +148,7 @@ export class Explore extends PureComponent {
constructor(props: Props) {
super(props);
this.state = {
- contentOutlineVisible: false,
+ contentOutlineVisible: store.getBool(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.visible, true),
};
this.graphEventBus = props.eventBus.newScopedBus('graph', { onlyLocal: false });
this.logsEventBus = props.eventBus.newScopedBus('logs', { onlyLocal: false });
@@ -175,6 +176,7 @@ export class Explore extends PureComponent {
};
onContentOutlineToogle = () => {
+ store.set(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.visible, !this.state.contentOutlineVisible);
this.setState((state) => {
reportInteraction('explore_toolbar_contentoutline_clicked', {
item: 'outline',