grafana/public/app/features/explore/ExplorePage.tsx
Piotr Jamróz f6e472f879
Explore: Show a drawer with tabs for the library and query history (#86279)
* Create basic feature toggle

* Rename context to reflect it contains query history and query library

* Update icons and variants

* Rename hooks

* Update tests

* Fix mock

* Add tracking

* Turn button into a toggle

* Make dropdown active as well

This is required to have better UI and an indication of selected state in split view

* Update Query Library icon

This is to make it consistent with the toolbar button

* Hide query history button when query library is available

This is to avoid confusing UX with 2 button triggering the drawer but with slightly different behavior

* Make the drawer bigger for query library

To avoid confusion for current users and test it internally a bit more it's behind a feature toggle. Bigger drawer may obstruct the view and add more friction in the UX.

* Fix tests

The test was failing because queryLibraryAvailable was set to true for tests. This change makes it more explicit what use case is being tested

* Remove active state underline from the dropdown

* Allow closing Query Library drawer from the toolbar

* Simplify dropdown design
2024-04-24 10:32:11 +02:00

130 lines
5.0 KiB
TypeScript

import { css, cx } from '@emotion/css';
import React, { useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { ErrorBoundaryAlert, useStyles2, useTheme2 } from '@grafana/ui';
import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { useNavModel } from 'app/core/hooks/useNavModel';
import { Trans } from 'app/core/internationalization';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { useSelector } from 'app/types';
import { ExploreQueryParams } from 'app/types/explore';
import { CorrelationEditorModeBar } from './CorrelationEditorModeBar';
import { ExploreActions } from './ExploreActions';
import { ExploreDrawer } from './ExploreDrawer';
import { ExplorePaneContainer } from './ExplorePaneContainer';
import { QueriesDrawerContextProvider, useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext';
import RichHistoryContainer from './RichHistory/RichHistoryContainer';
import { useExplorePageTitle } from './hooks/useExplorePageTitle';
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';
import { useSplitSizeUpdater } from './hooks/useSplitSizeUpdater';
import { useStateSync } from './hooks/useStateSync';
import { useTimeSrvFix } from './hooks/useTimeSrvFix';
import { isSplit, selectCorrelationDetails, selectPanesEntries } from './state/selectors';
const MIN_PANE_WIDTH = 200;
export default function ExplorePage(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
return (
<QueriesDrawerContextProvider>
<ExplorePageContent {...props} />
</QueriesDrawerContextProvider>
);
}
function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
const styles = useStyles2(getStyles);
const theme = useTheme2();
useTimeSrvFix();
useStateSync(props.queryParams);
// We want to set the title according to the URL and not to the state because the URL itself may lag
// (due to how useStateSync above works) by a few milliseconds.
// When a URL is pushed to the history, the browser also saves the title of the page and
// if we were to update the URL on state change, the title would not match the URL.
// Ultimately the URL is the single source of truth from which state is derived, the page title is not different
useExplorePageTitle(props.queryParams);
const { chrome } = useGrafana();
const navModel = useNavModel('explore');
const { updateSplitSize, widthCalc } = useSplitSizeUpdater(MIN_PANE_WIDTH);
const panes = useSelector(selectPanesEntries);
const hasSplit = useSelector(isSplit);
const correlationDetails = useSelector(selectCorrelationDetails);
const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext();
const showCorrelationEditorBar = config.featureToggles.correlations && (correlationDetails?.editorMode || false);
useEffect(() => {
//This is needed for breadcrumbs and topnav.
//We should probably abstract this out at some point
chrome.update({
sectionNav: navModel,
});
}, [chrome, navModel]);
useKeyboardShortcuts();
return (
<div
className={cx(styles.pageScrollbarWrapper, {
[styles.correlationsEditorIndicator]: showCorrelationEditorBar,
})}
>
<h1 className="sr-only">
<Trans i18nKey="nav.explore.title" />
</h1>
<ExploreActions />
{showCorrelationEditorBar && <CorrelationEditorModeBar panes={panes} />}
<SplitPaneWrapper
splitOrientation="vertical"
paneSize={widthCalc}
minSize={MIN_PANE_WIDTH}
maxSize={MIN_PANE_WIDTH * -1}
primary="second"
splitVisible={hasSplit}
parentStyle={showCorrelationEditorBar ? { height: `calc(100% - ${theme.spacing(6)}` } : {}} // button = 4, padding = 1 x 2
paneStyle={{ overflow: 'auto', display: 'flex', flexDirection: 'column' }}
onDragFinished={(size) => size && updateSplitSize(size)}
>
{panes.map(([exploreId]) => {
return (
<ErrorBoundaryAlert key={exploreId} style="page">
<ExplorePaneContainer exploreId={exploreId} />
</ErrorBoundaryAlert>
);
})}
</SplitPaneWrapper>
{drawerOpened && (
<ExploreDrawer initialHeight={queryLibraryAvailable ? '75vh' : undefined}>
<RichHistoryContainer
onClose={() => {
setDrawerOpened(false);
}}
/>
</ExploreDrawer>
)}
</div>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
pageScrollbarWrapper: css({
width: '100%',
flexGrow: 1,
minHeight: 0,
height: '100%',
position: 'relative',
overflow: 'hidden',
}),
correlationsEditorIndicator: css({
borderLeft: `4px solid ${theme.colors.primary.main}`,
borderRight: `4px solid ${theme.colors.primary.main}`,
borderBottom: `4px solid ${theme.colors.primary.main}`,
overflow: 'scroll',
}),
};
};