SingleTopNav: Handle explore action placement (#94812)

* move query library + share buttons to toolbar

* add share text to button

* need spacer when not in singleTopNav

* fix unit tests

* use secondary button styling when singleTopNav is enabled
This commit is contained in:
Ashley Harrison 2024-10-18 13:58:54 +01:00 committed by GitHub
parent 54c500cd94
commit 65ce173d3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 83 additions and 56 deletions

View File

@ -21,7 +21,7 @@ import { CorrelationEditorModeBar } from './CorrelationEditorModeBar';
import { ExploreActions } from './ExploreActions';
import { ExploreDrawer } from './ExploreDrawer';
import { ExplorePaneContainer } from './ExplorePaneContainer';
import { QueriesDrawerContextProvider, useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext';
import { useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext';
import { QUERY_LIBRARY_LOCAL_STORAGE_KEYS } from './QueryLibrary/QueryLibrary';
import { queryLibraryTrackAddFromQueryRow } from './QueryLibrary/QueryLibraryAnalyticsEvents';
import { QueryTemplateForm } from './QueryLibrary/QueryTemplateForm';
@ -37,11 +37,7 @@ const MIN_PANE_WIDTH = 200;
const QUERY_LIBRARY_ACTION_KEY = 'queryLibraryAction';
export default function ExplorePage(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
return (
<QueriesDrawerContextProvider>
<ExplorePageContent {...props} />
</QueriesDrawerContextProvider>
);
return <ExplorePageContent {...props} />;
}
function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {

View File

@ -4,6 +4,7 @@ import { useMemo } from 'react';
import { shallowEqual } from 'react-redux';
import { DataSourceInstanceSettings, RawTimeRange, GrafanaTheme2 } from '@grafana/data';
import { Components } from '@grafana/e2e-selectors';
import { config, reportInteraction } from '@grafana/runtime';
import {
defaultIntervals,
@ -26,6 +27,7 @@ import { getFiscalYearStartMonth, getTimeZone } from '../profile/state/selectors
import { ExploreTimeControls } from './ExploreTimeControls';
import { LiveTailButton } from './LiveTailButton';
import { useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext';
import { QueriesDrawerDropdown } from './QueriesDrawer/QueriesDrawerDropdown';
import { ShortLinkButtonMenu } from './ShortLinkButtonMenu';
import { ToolbarExtensionPoint } from './extensions/ToolbarExtensionPoint';
@ -90,6 +92,7 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
const isCorrelationsEditorMode = correlationDetails?.editorMode || false;
const isLeftPane = useSelector(isLeftPaneSelector(exploreId));
const isSingleTopNav = config.featureToggles.singleTopNav;
const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext();
const shouldRotateSplitIcon = useMemo(
() => (isLeftPane && isLargerPane) || (!isLeftPane && !isLargerPane),
@ -202,16 +205,32 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
dispatch(changeRefreshInterval({ exploreId, refreshInterval }));
};
const navBarActions = [<ShortLinkButtonMenu key="share" />, <div style={{ flex: 1 }} key="spacer0" />];
const navBarActions = [<ShortLinkButtonMenu key="share" />];
if (isSingleTopNav) {
if (queryLibraryAvailable) {
navBarActions.unshift(<QueriesDrawerDropdown key="queryLibrary" variant="full" />);
} else {
navBarActions.unshift(
<ToolbarButton
variant={drawerOpened ? 'active' : 'canvas'}
aria-label={t('explore.secondary-actions.query-history-button-aria-label', 'Query history')}
onClick={() => setDrawerOpened(!drawerOpened)}
data-testid={Components.QueryTab.queryHistoryButton}
icon="history"
>
<Trans i18nKey="explore.secondary-actions.query-history-button">Query history</Trans>
</ToolbarButton>
);
}
} else {
navBarActions.push(<div style={{ flex: 1 }} key="spacer0" />);
}
return (
<div>
{refreshInterval && <SetInterval func={onRunQuery} interval={refreshInterval} loading={loading} />}
{!isSingleTopNav && (
<div>
<AppChromeUpdate actions={navBarActions} />
</div>
)}
<AppChromeUpdate actions={navBarActions} />
<PageToolbar
aria-label={t('explore.toolbar.aria-label', 'Explore toolbar')}
leftItems={[
@ -236,12 +255,11 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
hideTextValue={showSmallDataSourcePicker}
width={showSmallDataSourcePicker ? 8 : undefined}
/>,
isSingleTopNav && <ShortLinkButtonMenu key="share" />,
].filter(Boolean)}
forceShowLeftItems
>
{[
<QueriesDrawerDropdown key="queryLibrary" variant={splitted ? 'compact' : 'full'} />,
!isSingleTopNav && <QueriesDrawerDropdown key="queryLibrary" variant={splitted ? 'compact' : 'full'} />,
!splitted ? (
<ToolbarButton
variant="canvas"

View File

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Components } from '@grafana/e2e-selectors';
import { config } from '@grafana/runtime';
import { ToolbarButton, useTheme2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
@ -32,9 +33,10 @@ export function SecondaryActions(props: Props) {
const theme = useTheme2();
const styles = getStyles(theme);
const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext();
const isSingleTopNav = config.featureToggles.singleTopNav;
// When queryLibraryAvailable=true we show the button in the toolbar (see QueriesDrawerDropdown)
const showHistoryButton = !props.richHistoryRowButtonHidden && !queryLibraryAvailable;
const showHistoryButton = !props.richHistoryRowButtonHidden && !queryLibraryAvailable && !isSingleTopNav;
return (
<div className={styles.containerMargin}>

View File

@ -2,8 +2,8 @@ import { useState } from 'react';
import { IconName } from '@grafana/data';
import { reportInteraction, config } from '@grafana/runtime';
import { ToolbarButton, Dropdown, Menu, Stack, ToolbarButtonRow, MenuGroup } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { ToolbarButton, Dropdown, Menu, MenuGroup, ButtonGroup } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { copyStringToClipboard } from 'app/core/utils/explore';
import { createAndCopyShortLink } from 'app/core/utils/shortLinks';
import { useSelector } from 'app/types';
@ -39,6 +39,7 @@ export function ShortLinkButtonMenu() {
const panes = useSelector(selectPanes);
const [isOpen, setIsOpen] = useState(false);
const [lastSelected, setLastSelected] = useState(defaultMode);
const isSingleTopNav = config.featureToggles.singleTopNav;
const onCopyLink = (shorten: boolean, absTime: boolean, url?: string) => {
if (shorten) {
createAndCopyShortLink(url || global.location.href);
@ -131,27 +132,29 @@ export function ShortLinkButtonMenu() {
// we need the Toolbar button click to be an action separate from opening/closing the menu
return (
<ToolbarButtonRow>
<Stack gap={0} direction="row" alignItems="center" wrap="nowrap">
<ButtonGroup>
<ToolbarButton
tooltip={lastSelected.label}
icon={lastSelected.icon}
iconOnly={!isSingleTopNav}
variant={isSingleTopNav ? 'canvas' : 'default'}
narrow={true}
onClick={() => {
const url = lastSelected.getUrl();
onCopyLink(lastSelected.shorten, lastSelected.absTime, url);
}}
aria-label={t('explore.toolbar.copy-shortened-link', 'Copy shortened URL')}
>
{isSingleTopNav && <Trans i18nKey="explore.toolbar.copy-shortened-link-label">Share</Trans>}
</ToolbarButton>
<Dropdown overlay={MenuActions} placement="bottom-end" onVisibleChange={setIsOpen}>
<ToolbarButton
tooltip={lastSelected.label}
icon={lastSelected.icon}
iconOnly={true}
narrow={true}
onClick={() => {
const url = lastSelected.getUrl();
onCopyLink(lastSelected.shorten, lastSelected.absTime, url);
}}
aria-label={t('explore.toolbar.copy-shortened-link', 'Copy shortened URL')}
variant={isSingleTopNav ? 'canvas' : 'default'}
isOpen={isOpen}
aria-label={t('explore.toolbar.copy-shortened-link-menu', 'Open copy link options')}
/>
<Dropdown overlay={MenuActions} placement="bottom-end" onVisibleChange={setIsOpen}>
<ToolbarButton
narrow={true}
isOpen={isOpen}
aria-label={t('explore.toolbar.copy-shortened-link-menu', 'Open copy link options')}
/>
</Dropdown>
</Stack>
</ToolbarButtonRow>
</Dropdown>
</ButtonGroup>
);
}

View File

@ -43,6 +43,7 @@ import { LokiQuery } from '../../../../plugins/datasource/loki/types';
import { ExploreQueryParams } from '../../../../types';
import { initialUserState } from '../../../profile/state/reducers';
import ExplorePage from '../../ExplorePage';
import { QueriesDrawerContextProvider } from '../../QueriesDrawer/QueriesDrawerContext';
type DatasourceSetup = { settings: DataSourceInstanceSettings; api: DataSourceApi };
@ -174,11 +175,13 @@ export function setupExplore(options?: SetupOptions): {
<Provider store={storeState}>
<GrafanaContext.Provider value={contextMock}>
<Router history={history}>
<Route
path="/explore"
exact
render={(props) => <GrafanaRoute {...props} route={{ component: ExplorePage, path: '/explore' }} />}
/>
<QueriesDrawerContextProvider>
<Route
path="/explore"
exact
render={(props) => <GrafanaRoute {...props} route={{ component: ExplorePage, path: '/explore' }} />}
/>
</QueriesDrawerContextProvider>
</Router>
</GrafanaContext.Provider>
</Provider>

View File

@ -12,6 +12,7 @@ import { AppChrome } from '../core/components/AppChrome/AppChrome';
import { AppNotificationList } from '../core/components/AppNotifications/AppNotificationList';
import { ModalsContextProvider } from '../core/context/ModalsContextProvider';
import { useSidecar } from '../core/context/SidecarContext';
import { QueriesDrawerContextProvider } from '../features/explore/QueriesDrawer/QueriesDrawerContext';
import AppRootPage from '../features/plugins/components/AppRootPage';
import { createLocationStorageHistory } from './utils';
@ -26,22 +27,24 @@ export function RouterWrapper(props: RouterWrapperProps) {
<Router history={locationService.getHistory()}>
<LocationServiceProvider service={locationService}>
<CompatRouter>
<ModalsContextProvider>
<AppChrome>
<AngularRoot />
<AppNotificationList />
<Stack gap={0} grow={1} direction="column">
{props.pageBanners.map((Banner, index) => (
<Banner key={index.toString()} />
<QueriesDrawerContextProvider>
<ModalsContextProvider>
<AppChrome>
<AngularRoot />
<AppNotificationList />
<Stack gap={0} grow={1} direction="column">
{props.pageBanners.map((Banner, index) => (
<Banner key={index.toString()} />
))}
{props.routes}
</Stack>
{props.bodyRenderHooks.map((Hook, index) => (
<Hook key={index.toString()} />
))}
{props.routes}
</Stack>
{props.bodyRenderHooks.map((Hook, index) => (
<Hook key={index.toString()} />
))}
</AppChrome>
<ModalRoot />
</ModalsContextProvider>
</AppChrome>
<ModalRoot />
</ModalsContextProvider>
</QueriesDrawerContextProvider>
</CompatRouter>
</LocationServiceProvider>
</Router>

View File

@ -1018,6 +1018,7 @@
"copy-links-normal-category": "Normal URL links",
"copy-shortened-link": "Copy shortened URL",
"copy-shortened-link-abs-time": "Copy absolute shortened URL",
"copy-shortened-link-label": "Share",
"copy-shortened-link-menu": "Open copy link options",
"refresh-picker-cancel": "Cancel",
"refresh-picker-run": "Run query",

View File

@ -1018,6 +1018,7 @@
"copy-links-normal-category": "Ńőřmäľ ŮŖĿ ľįʼnĸş",
"copy-shortened-link": "Cőpy şĥőřŧęʼnęđ ŮŖĿ",
"copy-shortened-link-abs-time": "Cőpy äþşőľūŧę şĥőřŧęʼnęđ ŮŖĿ",
"copy-shortened-link-label": "Ŝĥäřę",
"copy-shortened-link-menu": "Øpęʼn čőpy ľįʼnĸ őpŧįőʼnş",
"refresh-picker-cancel": "Cäʼnčęľ",
"refresh-picker-run": "Ŗūʼn qūęřy",