mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Navigation: Integrate Explore actions into topnav (#56381)
* integrate Explore actions into topnav toolbar * add keys * fix unit test * fix bug with ToolbarButtonRow overflow * make the render function a bit more readable * fix overflow not updating when children change
This commit is contained in:
parent
d6fa12c5bc
commit
90cf76e05e
@ -105,7 +105,7 @@ export const PageToolbar: FC<Props> = React.memo(
|
||||
</>
|
||||
)}
|
||||
|
||||
{(title || leftItems?.length) && (
|
||||
{(title || Boolean(leftItems?.length)) && (
|
||||
<div className={styles.titleWrapper}>
|
||||
{title && (
|
||||
<h1 className={styles.h1Styles}>
|
||||
|
@ -15,6 +15,8 @@ export interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
alignment?: 'left' | 'right';
|
||||
}
|
||||
|
||||
const OVERFLOW_BUTTON_ID = 'overflow-button';
|
||||
|
||||
export const ToolbarButtonRow = forwardRef<HTMLDivElement, Props>(
|
||||
({ alignment = 'left', className, children, ...rest }, ref) => {
|
||||
const [childVisibility, setChildVisibility] = useState<boolean[]>(
|
||||
@ -53,7 +55,10 @@ export const ToolbarButtonRow = forwardRef<HTMLDivElement, Props>(
|
||||
);
|
||||
if (containerRef.current) {
|
||||
Array.from(containerRef.current.children).forEach((item) => {
|
||||
intersectionObserver.observe(item);
|
||||
// don't observe the overflow button
|
||||
if (item instanceof HTMLElement && item.dataset.testid !== OVERFLOW_BUTTON_ID) {
|
||||
intersectionObserver.observe(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
return () => intersectionObserver.disconnect();
|
||||
@ -70,12 +75,11 @@ export const ToolbarButtonRow = forwardRef<HTMLDivElement, Props>(
|
||||
</div>
|
||||
))}
|
||||
{childVisibility.includes(false) && (
|
||||
<>
|
||||
<div data-testid={OVERFLOW_BUTTON_ID} className={styles.overflowButton}>
|
||||
<ToolbarButton
|
||||
variant={showOverflowItems ? 'active' : 'default'}
|
||||
tooltip="Show more items"
|
||||
onClick={() => setShowOverflowItems(!showOverflowItems)}
|
||||
className={styles.overflowButton}
|
||||
icon="ellipsis-v"
|
||||
iconOnly
|
||||
narrow
|
||||
@ -87,7 +91,7 @@ export const ToolbarButtonRow = forwardRef<HTMLDivElement, Props>(
|
||||
</div>
|
||||
</FocusScope>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -82,6 +82,7 @@ const dummyProps: Props = {
|
||||
showNodeGraph: true,
|
||||
showFlameGraph: true,
|
||||
splitOpen: (() => {}) as any,
|
||||
splitted: false,
|
||||
changeGraphStyle: () => {},
|
||||
graphStyle: 'lines',
|
||||
};
|
||||
|
@ -38,6 +38,7 @@ import { TraceViewContainer } from './TraceView/TraceViewContainer';
|
||||
import { changeSize, changeGraphStyle } from './state/explorePane';
|
||||
import { splitOpen } from './state/main';
|
||||
import { addQueryRow, modifyQueries, scanStart, scanStopAction, setQueries } from './state/query';
|
||||
import { isSplit } from './state/selectors';
|
||||
import { makeAbsoluteTime, updateTimeRange } from './state/time';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
@ -67,6 +68,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
padding: ${theme.spacing(2)};
|
||||
padding-top: 0;
|
||||
`,
|
||||
exploreContainerTopnav: css`
|
||||
padding-top: ${theme.spacing(2)};
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
@ -349,6 +353,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
showTrace,
|
||||
showNodeGraph,
|
||||
showFlameGraph,
|
||||
splitted,
|
||||
timeZone,
|
||||
} = this.props;
|
||||
const { openDrawer } = this.state;
|
||||
@ -377,7 +382,11 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
<ExploreToolbar exploreId={exploreId} onChangeTime={this.onChangeTime} topOfViewRef={this.topOfViewRef} />
|
||||
{datasourceMissing ? this.renderEmptyState(styles.exploreContainer) : null}
|
||||
{datasourceInstance && (
|
||||
<div className={cx(styles.exploreContainer)}>
|
||||
<div
|
||||
className={cx(styles.exploreContainer, {
|
||||
[styles.exploreContainerTopnav]: Boolean(config.featureToggles.topnav && !splitted),
|
||||
})}
|
||||
>
|
||||
<PanelContainer className={styles.queryContainer}>
|
||||
<QueryRows exploreId={exploreId} />
|
||||
<SecondaryActions
|
||||
@ -486,6 +495,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
|
||||
showTrace,
|
||||
showNodeGraph,
|
||||
showFlameGraph,
|
||||
splitted: isSplit(state),
|
||||
loading,
|
||||
graphStyle,
|
||||
};
|
||||
|
@ -3,7 +3,15 @@ import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { DataSourceInstanceSettings, RawTimeRange } from '@grafana/data';
|
||||
import { config, DataSourcePicker, reportInteraction } from '@grafana/runtime';
|
||||
import { defaultIntervals, PageToolbar, RefreshPicker, SetInterval, ToolbarButton } from '@grafana/ui';
|
||||
import {
|
||||
defaultIntervals,
|
||||
PageToolbar,
|
||||
RefreshPicker,
|
||||
SetInterval,
|
||||
ToolbarButton,
|
||||
ToolbarButtonRow,
|
||||
} from '@grafana/ui';
|
||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { createAndCopyShortLink } from 'app/core/utils/shortLinks';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
@ -92,6 +100,7 @@ class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<RefreshPicker
|
||||
key="refreshPicker"
|
||||
onIntervalChanged={this.onChangeRefreshInterval}
|
||||
value={refreshInterval}
|
||||
isLoading={loading}
|
||||
@ -107,126 +116,168 @@ class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
renderActions = () => {
|
||||
const {
|
||||
datasourceMissing,
|
||||
splitted,
|
||||
isLive,
|
||||
exploreId,
|
||||
loading,
|
||||
range,
|
||||
timeZone,
|
||||
fiscalYearStartMonth,
|
||||
splitted,
|
||||
syncedTimes,
|
||||
refreshInterval,
|
||||
onChangeTime,
|
||||
hasLiveOption,
|
||||
isLive,
|
||||
isPaused,
|
||||
containerWidth,
|
||||
syncedTimes,
|
||||
onChangeTimeZone,
|
||||
onChangeFiscalYearStartMonth,
|
||||
topOfViewRef,
|
||||
refreshInterval,
|
||||
loading,
|
||||
isPaused,
|
||||
hasLiveOption,
|
||||
containerWidth,
|
||||
} = this.props;
|
||||
|
||||
const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false;
|
||||
const showSmallTimePicker = splitted || containerWidth < 1210;
|
||||
|
||||
const showExploreToDashboard =
|
||||
contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor) ||
|
||||
contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor);
|
||||
|
||||
return (
|
||||
return [
|
||||
!splitted ? (
|
||||
<ToolbarButton
|
||||
key="split"
|
||||
tooltip="Split the pane"
|
||||
onClick={this.onOpenSplitView}
|
||||
icon="columns"
|
||||
disabled={isLive}
|
||||
>
|
||||
Split
|
||||
</ToolbarButton>
|
||||
) : (
|
||||
<ToolbarButton key="split" tooltip="Close split pane" onClick={this.onCloseSplitView} icon="times">
|
||||
Close
|
||||
</ToolbarButton>
|
||||
),
|
||||
|
||||
config.featureToggles.explore2Dashboard && showExploreToDashboard && (
|
||||
<Suspense key="addToDashboard" fallback={null}>
|
||||
<AddToDashboard exploreId={exploreId} />
|
||||
</Suspense>
|
||||
),
|
||||
|
||||
!isLive && (
|
||||
<ExploreTimeControls
|
||||
key="timeControls"
|
||||
exploreId={exploreId}
|
||||
range={range}
|
||||
timeZone={timeZone}
|
||||
fiscalYearStartMonth={fiscalYearStartMonth}
|
||||
onChangeTime={onChangeTime}
|
||||
splitted={splitted}
|
||||
syncedTimes={syncedTimes}
|
||||
onChangeTimeSync={this.onChangeTimeSync}
|
||||
hideText={showSmallTimePicker}
|
||||
onChangeTimeZone={onChangeTimeZone}
|
||||
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
||||
/>
|
||||
),
|
||||
|
||||
this.renderRefreshPicker(showSmallTimePicker),
|
||||
|
||||
refreshInterval && (
|
||||
<SetInterval key="setInterval" func={this.onRunQuery} interval={refreshInterval} loading={loading} />
|
||||
),
|
||||
|
||||
hasLiveOption && (
|
||||
<LiveTailControls key="liveControls" exploreId={exploreId}>
|
||||
{(c) => {
|
||||
const controls = {
|
||||
...c,
|
||||
start: () => {
|
||||
reportInteraction('grafana_explore_logs_live_tailing_clicked', {
|
||||
datasourceType: this.props.datasourceType,
|
||||
});
|
||||
c.start();
|
||||
},
|
||||
};
|
||||
return (
|
||||
<LiveTailButton
|
||||
splitted={splitted}
|
||||
isLive={isLive}
|
||||
isPaused={isPaused}
|
||||
start={controls.start}
|
||||
pause={controls.pause}
|
||||
resume={controls.resume}
|
||||
stop={controls.stop}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</LiveTailControls>
|
||||
),
|
||||
].filter(Boolean);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { datasourceMissing, exploreId, splitted, containerWidth, topOfViewRef } = this.props;
|
||||
|
||||
const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false;
|
||||
const isTopnav = config.featureToggles.topnav;
|
||||
|
||||
const getDashNav = () => (
|
||||
<DashNavButton
|
||||
key="share"
|
||||
tooltip="Copy shortened link"
|
||||
icon="share-alt"
|
||||
onClick={this.onCopyShortLink}
|
||||
aria-label="Copy shortened link"
|
||||
/>
|
||||
);
|
||||
|
||||
const getDataSourcePicker = () =>
|
||||
!datasourceMissing && (
|
||||
<DataSourcePicker
|
||||
key={`${exploreId}-ds-picker`}
|
||||
mixed={config.featureToggles.exploreMixedDatasource === true}
|
||||
onChange={this.onChangeDatasource}
|
||||
current={this.props.datasourceRef}
|
||||
hideTextValue={showSmallDataSourcePicker}
|
||||
width={showSmallDataSourcePicker ? 8 : undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
const topNavActions = [
|
||||
getDashNav(),
|
||||
!splitted && getDataSourcePicker(),
|
||||
<div style={{ flex: 1 }} key="spacer" />,
|
||||
<ToolbarButtonRow key="actions" alignment="right">
|
||||
{this.renderActions()}
|
||||
</ToolbarButtonRow>,
|
||||
].filter(Boolean);
|
||||
|
||||
const toolbarLeftItems = [exploreId === ExploreId.left && getDashNav(), getDataSourcePicker()].filter(Boolean);
|
||||
|
||||
const toolbarLeftItemsTopNav = [
|
||||
exploreId === ExploreId.left && (
|
||||
<AppChromeUpdate
|
||||
actions={[getDashNav(), !splitted && getDataSourcePicker(), <div style={{ flex: 1 }} key="spacer" />].filter(
|
||||
Boolean
|
||||
)}
|
||||
/>
|
||||
),
|
||||
getDataSourcePicker(),
|
||||
].filter(Boolean);
|
||||
|
||||
return isTopnav && !splitted ? (
|
||||
<div ref={topOfViewRef}>
|
||||
<AppChromeUpdate actions={topNavActions} />
|
||||
</div>
|
||||
) : (
|
||||
<div ref={topOfViewRef}>
|
||||
<PageToolbar
|
||||
aria-label="Explore toolbar"
|
||||
title={exploreId === ExploreId.left ? 'Explore' : undefined}
|
||||
pageIcon={exploreId === ExploreId.left ? 'compass' : undefined}
|
||||
leftItems={[
|
||||
exploreId === ExploreId.left && (
|
||||
<DashNavButton
|
||||
key="share"
|
||||
tooltip="Copy shortened link"
|
||||
icon="share-alt"
|
||||
onClick={this.onCopyShortLink}
|
||||
aria-label="Copy shortened link"
|
||||
/>
|
||||
),
|
||||
!datasourceMissing && (
|
||||
<DataSourcePicker
|
||||
key={`${exploreId}-ds-picker`}
|
||||
mixed={config.featureToggles.exploreMixedDatasource === true}
|
||||
onChange={this.onChangeDatasource}
|
||||
current={this.props.datasourceRef}
|
||||
hideTextValue={showSmallDataSourcePicker}
|
||||
width={showSmallDataSourcePicker ? 8 : undefined}
|
||||
/>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
title={exploreId === ExploreId.left && !isTopnav ? 'Explore' : undefined}
|
||||
pageIcon={exploreId === ExploreId.left && !isTopnav ? 'compass' : undefined}
|
||||
leftItems={isTopnav ? toolbarLeftItemsTopNav : toolbarLeftItems}
|
||||
>
|
||||
<>
|
||||
{!splitted ? (
|
||||
<ToolbarButton tooltip="Split the pane" onClick={this.onOpenSplitView} icon="columns" disabled={isLive}>
|
||||
Split
|
||||
</ToolbarButton>
|
||||
) : (
|
||||
<ToolbarButton tooltip="Close split pane" onClick={this.onCloseSplitView} icon="times">
|
||||
Close
|
||||
</ToolbarButton>
|
||||
)}
|
||||
|
||||
{config.featureToggles.explore2Dashboard && showExploreToDashboard && (
|
||||
<Suspense fallback={null}>
|
||||
<AddToDashboard exploreId={exploreId} />
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
{!isLive && (
|
||||
<ExploreTimeControls
|
||||
exploreId={exploreId}
|
||||
range={range}
|
||||
timeZone={timeZone}
|
||||
fiscalYearStartMonth={fiscalYearStartMonth}
|
||||
onChangeTime={onChangeTime}
|
||||
splitted={splitted}
|
||||
syncedTimes={syncedTimes}
|
||||
onChangeTimeSync={this.onChangeTimeSync}
|
||||
hideText={showSmallTimePicker}
|
||||
onChangeTimeZone={onChangeTimeZone}
|
||||
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.renderRefreshPicker(showSmallTimePicker)}
|
||||
|
||||
{refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
|
||||
|
||||
{hasLiveOption && (
|
||||
<LiveTailControls exploreId={exploreId}>
|
||||
{(c) => {
|
||||
const controls = {
|
||||
...c,
|
||||
start: () => {
|
||||
reportInteraction('grafana_explore_logs_live_tailing_clicked', {
|
||||
datasourceType: this.props.datasourceType,
|
||||
});
|
||||
c.start();
|
||||
},
|
||||
};
|
||||
return (
|
||||
<LiveTailButton
|
||||
splitted={splitted}
|
||||
isLive={isLive}
|
||||
isPaused={isPaused}
|
||||
start={controls.start}
|
||||
pause={controls.pause}
|
||||
resume={controls.resume}
|
||||
stop={controls.stop}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</LiveTailControls>
|
||||
)}
|
||||
</>
|
||||
{this.renderActions()}
|
||||
</PageToolbar>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user