mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: make ExploreToolbar a function component (#66867)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { lazy, PureComponent, RefObject, Suspense } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { pick } from 'lodash';
|
||||
import React, { lazy, RefObject, Suspense, useMemo } from 'react';
|
||||
import { shallowEqual } from 'react-redux';
|
||||
|
||||
import { DataSourceInstanceSettings, RawTimeRange } from '@grafana/data';
|
||||
import { config, DataSourcePicker, reportInteraction } from '@grafana/runtime';
|
||||
@@ -10,7 +11,7 @@ import { contextSrv } from 'app/core/core';
|
||||
import { createAndCopyShortLink } from 'app/core/utils/shortLinks';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
import { StoreState } from 'app/types/store';
|
||||
import { StoreState, useDispatch, useSelector } from 'app/types/store';
|
||||
|
||||
import { DashNavButton } from '../dashboard/components/DashNav/DashNavButton';
|
||||
import { getTimeSrv } from '../dashboard/services/TimeSrv';
|
||||
@@ -30,299 +31,226 @@ const AddToDashboard = lazy(() =>
|
||||
import('./AddToDashboard').then(({ AddToDashboard }) => ({ default: AddToDashboard }))
|
||||
);
|
||||
|
||||
const getStyles = (exploreId: ExploreId, isLargerExploreId: boolean) => {
|
||||
return {
|
||||
rotateIcon: css({
|
||||
'> div > svg': {
|
||||
transform:
|
||||
(exploreId === 'left' && isLargerExploreId) || (exploreId === 'right' && !isLargerExploreId)
|
||||
? 'rotate(180deg)'
|
||||
: 'none',
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
const rotateIcon = css({
|
||||
'> div > svg': {
|
||||
transform: 'rotate(180deg)',
|
||||
},
|
||||
});
|
||||
|
||||
interface OwnProps {
|
||||
interface Props {
|
||||
exploreId: ExploreId;
|
||||
onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void;
|
||||
topOfViewRef: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||
export function ExploreToolbar({ exploreId, topOfViewRef, onChangeTime }: Props) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => {
|
||||
const { changeDatasource, exploreId } = this.props;
|
||||
changeDatasource(exploreId, dsSettings.uid, { importQueries: true });
|
||||
};
|
||||
const splitted = useSelector(isSplit);
|
||||
const timeZone = useSelector((state: StoreState) => getTimeZone(state.user));
|
||||
const fiscalYearStartMonth = useSelector((state: StoreState) => getFiscalYearStartMonth(state.user));
|
||||
const { refreshInterval, loading, datasourceInstance, range, isLive, isPaused, syncedTimes } = useSelector(
|
||||
(state: StoreState) => ({
|
||||
...pick(
|
||||
state.explore[exploreId]!,
|
||||
'refreshInterval',
|
||||
'loading',
|
||||
'datasourceInstance',
|
||||
'range',
|
||||
'isLive',
|
||||
'isPaused'
|
||||
),
|
||||
syncedTimes: state.explore.syncedTimes,
|
||||
}),
|
||||
shallowEqual
|
||||
);
|
||||
const isLargerPane = useSelector((state: StoreState) => state.explore.largerExploreId === exploreId);
|
||||
const showSmallTimePicker = useSelector((state) => splitted || state.explore[exploreId]!.containerWidth < 1210);
|
||||
const showSmallDataSourcePicker = useSelector(
|
||||
(state) => state.explore[exploreId]!.containerWidth < (splitted ? 700 : 800)
|
||||
);
|
||||
|
||||
onRunQuery = (loading = false) => {
|
||||
const { runQueries, cancelQueries, exploreId } = this.props;
|
||||
if (loading) {
|
||||
return cancelQueries(exploreId);
|
||||
} else {
|
||||
return runQueries(exploreId);
|
||||
}
|
||||
};
|
||||
const shouldRotateSplitIcon = useMemo(
|
||||
() => (exploreId === 'left' && isLargerPane) || (exploreId === 'right' && !isLargerPane),
|
||||
[isLargerPane, exploreId]
|
||||
);
|
||||
|
||||
onChangeRefreshInterval = (item: string) => {
|
||||
const { changeRefreshInterval, exploreId } = this.props;
|
||||
changeRefreshInterval(exploreId, item);
|
||||
};
|
||||
|
||||
onChangeTimeSync = () => {
|
||||
const { syncTimes, exploreId } = this.props;
|
||||
syncTimes(exploreId);
|
||||
};
|
||||
|
||||
onCopyShortLink = async () => {
|
||||
await createAndCopyShortLink(window.location.href);
|
||||
const onCopyShortLink = () => {
|
||||
createAndCopyShortLink(global.location.href);
|
||||
reportInteraction('grafana_explore_shortened_link_clicked');
|
||||
};
|
||||
|
||||
onOpenSplitView = () => {
|
||||
const { split } = this.props;
|
||||
split();
|
||||
const onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => {
|
||||
dispatch(changeDatasource(exploreId, dsSettings.uid, { importQueries: true }));
|
||||
};
|
||||
|
||||
const onRunQuery = (loading = false) => {
|
||||
if (loading) {
|
||||
return dispatch(cancelQueries(exploreId));
|
||||
} else {
|
||||
return dispatch(runQueries(exploreId));
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeTimeZone = (timezone: string) => dispatch(updateTimeZoneForSession(timezone));
|
||||
|
||||
const onOpenSplitView = () => {
|
||||
dispatch(splitOpen());
|
||||
reportInteraction('grafana_explore_split_view_opened', { origin: 'menu' });
|
||||
};
|
||||
|
||||
onCloseSplitView = () => {
|
||||
const { closeSplit, exploreId } = this.props;
|
||||
closeSplit(exploreId);
|
||||
const onCloseSplitView = () => {
|
||||
dispatch(splitClose(exploreId));
|
||||
reportInteraction('grafana_explore_split_view_closed');
|
||||
};
|
||||
|
||||
renderRefreshPicker = (showSmallTimePicker: boolean) => {
|
||||
const { loading, refreshInterval, isLive } = this.props;
|
||||
|
||||
let refreshPickerText: string | undefined = loading ? 'Cancel' : 'Run query';
|
||||
let refreshPickerTooltip = undefined;
|
||||
let refreshPickerWidth = '108px';
|
||||
if (showSmallTimePicker) {
|
||||
refreshPickerTooltip = refreshPickerText;
|
||||
refreshPickerText = undefined;
|
||||
refreshPickerWidth = '35px';
|
||||
const onClickResize = () => {
|
||||
if (isLargerPane) {
|
||||
dispatch(evenPaneResizeAction());
|
||||
} else {
|
||||
dispatch(maximizePaneAction({ exploreId }));
|
||||
}
|
||||
|
||||
return (
|
||||
<RefreshPicker
|
||||
key="refreshPicker"
|
||||
onIntervalChanged={this.onChangeRefreshInterval}
|
||||
value={refreshInterval}
|
||||
isLoading={loading}
|
||||
text={refreshPickerText}
|
||||
tooltip={refreshPickerTooltip}
|
||||
intervals={getTimeSrv().getValidIntervals(defaultIntervals)}
|
||||
isLive={isLive}
|
||||
onRefresh={() => this.onRunQuery(loading)}
|
||||
noIntervalPicker={isLive}
|
||||
primary={true}
|
||||
width={refreshPickerWidth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderActions = () => {
|
||||
const {
|
||||
splitted,
|
||||
isLive,
|
||||
exploreId,
|
||||
range,
|
||||
timeZone,
|
||||
fiscalYearStartMonth,
|
||||
onChangeTime,
|
||||
syncedTimes,
|
||||
onChangeTimeZone,
|
||||
onChangeFiscalYearStartMonth,
|
||||
isPaused,
|
||||
hasLiveOption,
|
||||
containerWidth,
|
||||
largerExploreId,
|
||||
} = this.props;
|
||||
const showSmallTimePicker = splitted || containerWidth < 1210;
|
||||
const isLargerExploreId = largerExploreId === exploreId;
|
||||
const styles = getStyles(exploreId, isLargerExploreId);
|
||||
const onChangeTimeSync = () => {
|
||||
dispatch(syncTimes(exploreId));
|
||||
};
|
||||
|
||||
const showExploreToDashboard =
|
||||
const onChangeFiscalYearStartMonth = (fiscalyearStartMonth: number) =>
|
||||
dispatch(updateFiscalYearStartMonthForSession(fiscalyearStartMonth));
|
||||
|
||||
const onChangeRefreshInterval = (item: string) => {
|
||||
dispatch(changeRefreshInterval(exploreId, item));
|
||||
};
|
||||
|
||||
const showExploreToDashboard = useMemo(
|
||||
() =>
|
||||
contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor) ||
|
||||
contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor);
|
||||
contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor),
|
||||
[]
|
||||
);
|
||||
|
||||
const onClickResize = () => {
|
||||
if (isLargerExploreId) {
|
||||
this.props.evenPaneResizeAction();
|
||||
} else {
|
||||
this.props.maximizePaneAction({ exploreId: exploreId });
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
!splitted ? (
|
||||
<ToolbarButton
|
||||
variant="canvas"
|
||||
key="split"
|
||||
tooltip="Split the pane"
|
||||
onClick={this.onOpenSplitView}
|
||||
icon="columns"
|
||||
disabled={isLive}
|
||||
>
|
||||
Split
|
||||
</ToolbarButton>
|
||||
) : (
|
||||
<ButtonGroup key="split-controls">
|
||||
<ToolbarButton
|
||||
variant="canvas"
|
||||
tooltip={`${isLargerExploreId ? 'Narrow' : 'Widen'} pane`}
|
||||
onClick={onClickResize}
|
||||
icon={isLargerExploreId ? 'gf-movepane-left' : 'gf-movepane-right'}
|
||||
iconOnly={true}
|
||||
className={styles.rotateIcon}
|
||||
/>
|
||||
<ToolbarButton tooltip="Close split pane" onClick={this.onCloseSplitView} icon="times" variant="canvas">
|
||||
Close
|
||||
</ToolbarButton>
|
||||
</ButtonGroup>
|
||||
),
|
||||
|
||||
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),
|
||||
|
||||
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, refreshInterval, loading } =
|
||||
this.props;
|
||||
|
||||
const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false;
|
||||
|
||||
const shareButton = (
|
||||
<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 toolbarLeftItems = [getDataSourcePicker()].filter(Boolean);
|
||||
|
||||
return (
|
||||
return (
|
||||
<div ref={topOfViewRef}>
|
||||
{refreshInterval && <SetInterval func={onRunQuery} interval={refreshInterval} loading={loading} />}
|
||||
<div ref={topOfViewRef}>
|
||||
{refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
|
||||
<div ref={topOfViewRef}>
|
||||
<AppChromeUpdate actions={[shareButton, <div style={{ flex: 1 }} key="spacer" />]} />
|
||||
</div>
|
||||
<PageToolbar aria-label="Explore toolbar" leftItems={toolbarLeftItems} forceShowLeftItems>
|
||||
{this.renderActions()}
|
||||
</PageToolbar>
|
||||
<AppChromeUpdate
|
||||
actions={[
|
||||
<DashNavButton
|
||||
key="share"
|
||||
tooltip="Copy shortened link"
|
||||
icon="share-alt"
|
||||
onClick={onCopyShortLink}
|
||||
aria-label="Copy shortened link"
|
||||
/>,
|
||||
<div style={{ flex: 1 }} key="spacer" />,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<PageToolbar
|
||||
aria-label="Explore toolbar"
|
||||
leftItems={[
|
||||
<DataSourcePicker
|
||||
key={`${exploreId}-ds-picker`}
|
||||
mixed={config.featureToggles.exploreMixedDatasource === true}
|
||||
onChange={onChangeDatasource}
|
||||
current={datasourceInstance?.getRef()}
|
||||
hideTextValue={showSmallDataSourcePicker}
|
||||
width={showSmallDataSourcePicker ? 8 : undefined}
|
||||
/>,
|
||||
]}
|
||||
forceShowLeftItems
|
||||
>
|
||||
{[
|
||||
!splitted ? (
|
||||
<ToolbarButton
|
||||
variant="canvas"
|
||||
key="split"
|
||||
tooltip="Split the pane"
|
||||
onClick={onOpenSplitView}
|
||||
icon="columns"
|
||||
disabled={isLive}
|
||||
>
|
||||
Split
|
||||
</ToolbarButton>
|
||||
) : (
|
||||
<ButtonGroup key="split-controls">
|
||||
<ToolbarButton
|
||||
variant="canvas"
|
||||
tooltip={`${isLargerPane ? 'Narrow' : 'Widen'} pane`}
|
||||
onClick={onClickResize}
|
||||
icon={isLargerPane ? 'gf-movepane-left' : 'gf-movepane-right'}
|
||||
iconOnly={true}
|
||||
className={cx(shouldRotateSplitIcon && rotateIcon)}
|
||||
/>
|
||||
<ToolbarButton tooltip="Close split pane" onClick={onCloseSplitView} icon="times" variant="canvas">
|
||||
Close
|
||||
</ToolbarButton>
|
||||
</ButtonGroup>
|
||||
),
|
||||
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={onChangeTimeSync}
|
||||
hideText={showSmallTimePicker}
|
||||
onChangeTimeZone={onChangeTimeZone}
|
||||
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
||||
/>
|
||||
),
|
||||
<RefreshPicker
|
||||
key="refreshPicker"
|
||||
onIntervalChanged={onChangeRefreshInterval}
|
||||
value={refreshInterval}
|
||||
isLoading={loading}
|
||||
text={showSmallTimePicker ? undefined : loading ? 'Cancel' : 'Run query'}
|
||||
tooltip={showSmallTimePicker ? (loading ? 'Cancel' : 'Run query') : undefined}
|
||||
intervals={getTimeSrv().getValidIntervals(defaultIntervals)}
|
||||
isLive={isLive}
|
||||
onRefresh={() => onRunQuery(loading)}
|
||||
noIntervalPicker={isLive}
|
||||
primary={true}
|
||||
width={(showSmallTimePicker ? 35 : 108) + 'px'}
|
||||
/>,
|
||||
datasourceInstance?.meta.streaming && (
|
||||
<LiveTailControls key="liveControls" exploreId={exploreId}>
|
||||
{(c) => {
|
||||
const controls = {
|
||||
...c,
|
||||
start: () => {
|
||||
reportInteraction('grafana_explore_logs_live_tailing_clicked', {
|
||||
datasourceType: datasourceInstance?.type,
|
||||
});
|
||||
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)}
|
||||
</PageToolbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState, { exploreId }: OwnProps) => {
|
||||
const { syncedTimes, largerExploreId } = state.explore;
|
||||
const exploreItem = state.explore[exploreId]!;
|
||||
const { datasourceInstance, datasourceMissing, range, refreshInterval, loading, isLive, isPaused, containerWidth } =
|
||||
exploreItem;
|
||||
|
||||
const hasLiveOption = !!datasourceInstance?.meta?.streaming;
|
||||
|
||||
return {
|
||||
datasourceMissing,
|
||||
datasourceRef: datasourceInstance?.getRef(),
|
||||
datasourceType: datasourceInstance?.type,
|
||||
loading,
|
||||
range,
|
||||
timeZone: getTimeZone(state.user),
|
||||
fiscalYearStartMonth: getFiscalYearStartMonth(state.user),
|
||||
splitted: isSplit(state),
|
||||
refreshInterval,
|
||||
hasLiveOption,
|
||||
isLive,
|
||||
isPaused,
|
||||
syncedTimes,
|
||||
containerWidth,
|
||||
largerExploreId,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
changeDatasource,
|
||||
changeRefreshInterval,
|
||||
cancelQueries,
|
||||
runQueries,
|
||||
closeSplit: splitClose,
|
||||
split: splitOpen,
|
||||
syncTimes,
|
||||
onChangeTimeZone: updateTimeZoneForSession,
|
||||
onChangeFiscalYearStartMonth: updateFiscalYearStartMonthForSession,
|
||||
maximizePaneAction,
|
||||
evenPaneResizeAction,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const ExploreToolbar = connector(UnConnectedExploreToolbar);
|
||||
|
||||
Reference in New Issue
Block a user