mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
6ff21726b7
commit
51afb2e484
@ -1,6 +1,8 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
import { store } from '@grafana/data';
|
||||||
|
|
||||||
import { ContentOutline } from './ContentOutline';
|
import { ContentOutline } from './ContentOutline';
|
||||||
|
|
||||||
jest.mock('./ContentOutlineContext', () => ({
|
jest.mock('./ContentOutlineContext', () => ({
|
||||||
@ -73,15 +75,15 @@ const setup = (mergeSingleChild = false) => {
|
|||||||
describe('<ContentOutline />', () => {
|
describe('<ContentOutline />', () => {
|
||||||
it('toggles content on button click', async () => {
|
it('toggles content on button click', async () => {
|
||||||
setup();
|
setup();
|
||||||
let showContentOutlineButton = screen.getByRole('button', { name: 'Expand outline' });
|
let showContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' });
|
||||||
expect(showContentOutlineButton).toBeInTheDocument();
|
expect(showContentOutlineButton).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(showContentOutlineButton);
|
await userEvent.click(showContentOutlineButton);
|
||||||
const hideContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' });
|
const hideContentOutlineButton = screen.getByRole('button', { name: 'Expand outline' });
|
||||||
expect(hideContentOutlineButton).toBeInTheDocument();
|
expect(hideContentOutlineButton).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(hideContentOutlineButton);
|
await userEvent.click(hideContentOutlineButton);
|
||||||
showContentOutlineButton = screen.getByRole('button', { name: 'Expand outline' });
|
showContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' });
|
||||||
expect(showContentOutlineButton).toBeInTheDocument();
|
expect(showContentOutlineButton).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -157,4 +159,15 @@ describe('<ContentOutline />', () => {
|
|||||||
|
|
||||||
expect(unregisterMock).toHaveBeenCalledWith('item-2-1');
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,12 +2,11 @@ import { css, cx } from '@emotion/css';
|
|||||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
import { useToggle, useScroll } from 'react-use';
|
import { useToggle, useScroll } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, store } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { useStyles2, PanelContainer, CustomScrollbar } from '@grafana/ui';
|
import { useStyles2, PanelContainer, CustomScrollbar } from '@grafana/ui';
|
||||||
|
|
||||||
import { ContentOutlineItemContextProps, useContentOutlineContext } from './ContentOutlineContext';
|
import { ContentOutlineItemContextProps, useContentOutlineContext } from './ContentOutlineContext';
|
||||||
import { ITEM_TYPES } from './ContentOutlineItem';
|
|
||||||
import { ContentOutlineItemButton } from './ContentOutlineItemButton';
|
import { ContentOutlineItemButton } from './ContentOutlineItemButton';
|
||||||
|
|
||||||
function scrollableChildren(item: ContentOutlineItemContextProps) {
|
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 }) {
|
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 styles = useStyles2(getStyles, contentOutlineExpanded);
|
||||||
const scrollerRef = useRef(scroller || null);
|
const scrollerRef = useRef(scroller || null);
|
||||||
const { y: verticalScroll } = useScroll(scrollerRef);
|
const { y: verticalScroll } = useScroll(scrollerRef);
|
||||||
@ -57,12 +63,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
|||||||
}, {});
|
}, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
const scrollIntoView = (
|
const scrollIntoView = (ref: HTMLElement | null, customOffsetTop = 0) => {
|
||||||
ref: HTMLElement | null,
|
|
||||||
itemPanelId: string,
|
|
||||||
itemType: ITEM_TYPES | undefined,
|
|
||||||
customOffsetTop = 0
|
|
||||||
) => {
|
|
||||||
let scrollValue = 0;
|
let scrollValue = 0;
|
||||||
let el: HTMLElement | null | undefined = ref;
|
let el: HTMLElement | null | undefined = ref;
|
||||||
|
|
||||||
@ -88,10 +89,10 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (activeParent) {
|
if (activeParent) {
|
||||||
scrollIntoView(activeParent.ref, activeParent.panelId, activeParent.type, activeParent.customTopOffset);
|
scrollIntoView(activeParent.ref, activeParent.customTopOffset);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scrollIntoView(item.ref, item.panelId, item.type, item.customTopOffset);
|
scrollIntoView(item.ref, item.customTopOffset);
|
||||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||||
item: 'select_section',
|
item: 'select_section',
|
||||||
type: item.panelId,
|
type: item.panelId,
|
||||||
@ -100,6 +101,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
|
store.set(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.expanded, !contentOutlineExpanded);
|
||||||
toggleContentOutlineExpanded();
|
toggleContentOutlineExpanded();
|
||||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||||
item: 'outline',
|
item: 'outline',
|
||||||
|
@ -2,7 +2,15 @@ import { render, screen } from '@testing-library/react';
|
|||||||
import { Props as AutoSizerProps } from 'react-virtualized-auto-sizer';
|
import { Props as AutoSizerProps } from 'react-virtualized-auto-sizer';
|
||||||
import { TestProvider } from 'test/helpers/TestProvider';
|
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 { selectors } from '@grafana/e2e-selectors';
|
||||||
import { usePluginLinkExtensions } from '@grafana/runtime';
|
import { usePluginLinkExtensions } from '@grafana/runtime';
|
||||||
import { configureStore } from 'app/store/configureStore';
|
import { configureStore } from 'app/store/configureStore';
|
||||||
@ -225,4 +233,14 @@ describe('Explore', () => {
|
|||||||
expect(dataSourcePicker).toBeInTheDocument();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
QueryFixAction,
|
QueryFixAction,
|
||||||
RawTimeRange,
|
RawTimeRange,
|
||||||
SplitOpenOptions,
|
SplitOpenOptions,
|
||||||
|
store,
|
||||||
SupplementaryQueryType,
|
SupplementaryQueryType,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
@ -34,7 +35,7 @@ import { StoreState } from 'app/types';
|
|||||||
|
|
||||||
import { getTimeZone } from '../profile/state/selectors';
|
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 { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext';
|
||||||
import { ContentOutlineItem } from './ContentOutline/ContentOutlineItem';
|
import { ContentOutlineItem } from './ContentOutline/ContentOutlineItem';
|
||||||
import { CorrelationHelper } from './CorrelationHelper';
|
import { CorrelationHelper } from './CorrelationHelper';
|
||||||
@ -147,7 +148,7 @@ export class Explore extends PureComponent<Props, ExploreState> {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
contentOutlineVisible: false,
|
contentOutlineVisible: store.getBool(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.visible, true),
|
||||||
};
|
};
|
||||||
this.graphEventBus = props.eventBus.newScopedBus('graph', { onlyLocal: false });
|
this.graphEventBus = props.eventBus.newScopedBus('graph', { onlyLocal: false });
|
||||||
this.logsEventBus = props.eventBus.newScopedBus('logs', { onlyLocal: false });
|
this.logsEventBus = props.eventBus.newScopedBus('logs', { onlyLocal: false });
|
||||||
@ -175,6 +176,7 @@ export class Explore extends PureComponent<Props, ExploreState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onContentOutlineToogle = () => {
|
onContentOutlineToogle = () => {
|
||||||
|
store.set(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.visible, !this.state.contentOutlineVisible);
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||||
item: 'outline',
|
item: 'outline',
|
||||||
|
Loading…
Reference in New Issue
Block a user