Make content outline visible and in expanded mode by default (#90283)

* Make content outline visible and in expanded mode by default

* Clean up unused args

* Save content outline visibility in local storage

* Add test

* Expanded state relies on local storage;
This commit is contained in:
Haris Rozajac 2024-07-16 07:15:30 -06:00 committed by GitHub
parent 6ff21726b7
commit 51afb2e484
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 52 additions and 17 deletions

View File

@ -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('<ContentOutline />', () => {
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('<ContentOutline />', () => {
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();
});
});

View File

@ -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',

View File

@ -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();
});
});
});

View File

@ -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<Props, ExploreState> {
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<Props, ExploreState> {
};
onContentOutlineToogle = () => {
store.set(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.visible, !this.state.contentOutlineVisible);
this.setState((state) => {
reportInteraction('explore_toolbar_contentoutline_clicked', {
item: 'outline',