grafana/public/app/features/explore/ExploreToolbar.tsx
kay delaney cc1d468041
Explore: Ensures queries aren't updated when returning to dashboard if browser back is used (#20897)
* Explore: Ensures queries aren't updated when returning to dashboard if browser back is used
Closes #20873
2020-01-16 11:44:05 +00:00

397 lines
13 KiB
TypeScript

import omitBy from 'lodash/omitBy';
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import memoizeOne from 'memoize-one';
import classNames from 'classnames';
import { css } from 'emotion';
import { ExploreId, ExploreItemState, ExploreMode } from 'app/types/explore';
import { ToggleButtonGroup, ToggleButton, Tooltip, ButtonSelect, SetInterval } from '@grafana/ui';
import { RawTimeRange, TimeZone, TimeRange, DataQuery } from '@grafana/data';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { StoreState } from 'app/types/store';
import {
changeDatasource,
clearQueries,
splitClose,
runQueries,
splitOpen,
syncTimes,
changeRefreshInterval,
changeMode,
} from './state/actions';
import { updateLocation } from 'app/core/actions';
import { getTimeZone } from '../profile/state/selectors';
import { getDashboardSrv } from '../dashboard/services/DashboardSrv';
import kbn from '../../core/utils/kbn';
import { ExploreTimeControls } from './ExploreTimeControls';
import { LiveTailButton } from './LiveTailButton';
import { ResponsiveButton } from './ResponsiveButton';
import { RunButton } from './RunButton';
import { LiveTailControls } from './useLiveTailControls';
import { getExploreDatasources } from './state/selectors';
import { setDashboardQueriesToUpdateOnLoad } from '../dashboard/state/actions';
const getStyles = memoizeOne(() => {
return {
liveTailButtons: css`
margin-left: 10px;
@media (max-width: 1110px) {
margin-left: 4px;
}
`,
};
});
interface OwnProps {
exploreId: ExploreId;
onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void;
}
interface StateProps {
datasourceMissing: boolean;
loading: boolean;
range: TimeRange;
timeZone: TimeZone;
splitted: boolean;
syncedTimes: boolean;
refreshInterval?: string;
supportedModes: ExploreMode[];
selectedMode: ExploreMode;
hasLiveOption: boolean;
isLive: boolean;
isPaused: boolean;
originPanelId?: number;
queries: DataQuery[];
datasourceLoading?: boolean;
containerWidth: number;
datasourceName?: string;
}
interface DispatchProps {
changeDatasource: typeof changeDatasource;
clearAll: typeof clearQueries;
runQueries: typeof runQueries;
closeSplit: typeof splitClose;
split: typeof splitOpen;
syncTimes: typeof syncTimes;
changeRefreshInterval: typeof changeRefreshInterval;
changeMode: typeof changeMode;
updateLocation: typeof updateLocation;
setDashboardQueriesToUpdateOnLoad: typeof setDashboardQueriesToUpdateOnLoad;
}
type Props = StateProps & DispatchProps & OwnProps;
export class UnConnectedExploreToolbar extends PureComponent<Props> {
onChangeDatasource = async (option: { value: any }) => {
this.props.changeDatasource(this.props.exploreId, option.value);
};
onClearAll = () => {
this.props.clearAll(this.props.exploreId);
};
onRunQuery = () => {
return this.props.runQueries(this.props.exploreId);
};
onChangeRefreshInterval = (item: string) => {
const { changeRefreshInterval, exploreId } = this.props;
changeRefreshInterval(exploreId, item);
};
onModeChange = (mode: ExploreMode) => {
const { changeMode, exploreId } = this.props;
changeMode(exploreId, mode);
};
onChangeTimeSync = () => {
const { syncTimes, exploreId } = this.props;
syncTimes(exploreId);
};
returnToPanel = async ({ withChanges = false } = {}) => {
const { originPanelId, queries } = this.props;
const dashboardSrv = getDashboardSrv();
const dash = dashboardSrv.getCurrent();
const titleSlug = kbn.slugifyForUrl(dash.title);
if (withChanges) {
this.props.setDashboardQueriesToUpdateOnLoad(originPanelId, this.cleanQueries(queries));
}
const dashViewOptions = {
fullscreen: withChanges || dash.meta.fullscreen,
edit: withChanges || dash.meta.isEditing,
};
this.props.updateLocation({
path: `/d/${dash.uid}/:${titleSlug}`,
query: {
...omitBy(dashViewOptions, v => !v),
panelId: originPanelId,
},
});
};
// Remove explore specific parameters from queries
private cleanQueries(queries: DataQuery[]) {
return queries.map((query: DataQuery & { context?: string }) => {
delete query.context;
delete query.key;
return query;
});
}
getSelectedDatasource = () => {
const { datasourceName } = this.props;
const exploreDatasources = getExploreDatasources();
return datasourceName ? exploreDatasources.find(datasource => datasource.name === datasourceName) : undefined;
};
render() {
const {
datasourceMissing,
closeSplit,
exploreId,
loading,
range,
timeZone,
splitted,
syncedTimes,
refreshInterval,
onChangeTime,
split,
supportedModes,
selectedMode,
hasLiveOption,
isLive,
isPaused,
originPanelId,
datasourceLoading,
containerWidth,
} = this.props;
const styles = getStyles();
const originDashboardIsEditable = originPanelId && Number.isInteger(originPanelId);
const panelReturnClasses = classNames('btn', 'navbar-button', {
'btn--radius-right-0': originDashboardIsEditable,
'navbar-button navbar-button--border-right-0': originDashboardIsEditable,
});
const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false;
const showSmallTimePicker = splitted || containerWidth < 1210;
return (
<div className={splitted ? 'explore-toolbar splitted' : 'explore-toolbar'}>
<div className="explore-toolbar-item">
<div className="explore-toolbar-header">
<div className="explore-toolbar-header-title">
{exploreId === 'left' && (
<span className="navbar-page-btn">
<i className="gicon gicon-explore" />
Explore
</span>
)}
</div>
{splitted && (
<a className="explore-toolbar-header-close" onClick={() => closeSplit(exploreId)}>
<i className="fa fa-times fa-fw" />
</a>
)}
</div>
</div>
<div className="explore-toolbar-item">
<div className="explore-toolbar-content">
{!datasourceMissing ? (
<div className="explore-toolbar-content-item">
<div
className={classNames(
'explore-ds-picker',
showSmallDataSourcePicker ? 'explore-ds-picker--small' : ''
)}
>
<DataSourcePicker
onChange={this.onChangeDatasource}
datasources={getExploreDatasources()}
current={this.getSelectedDatasource()}
showLoading={datasourceLoading}
hideTextValue={showSmallDataSourcePicker}
/>
</div>
{supportedModes.length > 1 ? (
<div className="query-type-toggle">
<ToggleButtonGroup label="" transparent={true}>
<ToggleButton
key={ExploreMode.Metrics}
value={ExploreMode.Metrics}
onChange={this.onModeChange}
selected={selectedMode === ExploreMode.Metrics}
>
{'Metrics'}
</ToggleButton>
<ToggleButton
key={ExploreMode.Logs}
value={ExploreMode.Logs}
onChange={this.onModeChange}
selected={selectedMode === ExploreMode.Logs}
>
{'Logs'}
</ToggleButton>
</ToggleButtonGroup>
</div>
) : null}
</div>
) : null}
{originPanelId && Number.isInteger(originPanelId) && !splitted && (
<div className="explore-toolbar-content-item">
<Tooltip content={'Return to panel'} placement="bottom">
<button className={panelReturnClasses} onClick={() => this.returnToPanel()}>
<i className="fa fa-arrow-left" />
</button>
</Tooltip>
{originDashboardIsEditable && (
<ButtonSelect
className="navbar-button--attached btn--radius-left-0$"
options={[{ label: 'Return to panel with changes', value: '' }]}
onChange={() => this.returnToPanel({ withChanges: true })}
maxMenuHeight={380}
/>
)}
</div>
)}
{exploreId === 'left' && !splitted ? (
<div className="explore-toolbar-content-item explore-icon-align">
<ResponsiveButton
splitted={splitted}
title="Split"
onClick={split}
iconClassName="fa fa-fw fa-columns icon-margin-right"
disabled={isLive}
/>
</div>
) : null}
{!isLive && (
<div className="explore-toolbar-content-item">
<ExploreTimeControls
exploreId={exploreId}
range={range}
timeZone={timeZone}
onChangeTime={onChangeTime}
splitted={splitted}
syncedTimes={syncedTimes}
onChangeTimeSync={this.onChangeTimeSync}
hideText={showSmallTimePicker}
/>
</div>
)}
{!isLive && (
<div className="explore-toolbar-content-item explore-icon-align">
<ResponsiveButton
splitted={splitted}
title="Clear All"
onClick={this.onClearAll}
iconClassName="fa fa-fw fa-trash icon-margin-right"
/>
</div>
)}
<div className="explore-toolbar-content-item">
<RunButton
refreshInterval={refreshInterval}
onChangeRefreshInterval={this.onChangeRefreshInterval}
splitted={splitted}
loading={loading || (isLive && !isPaused)}
onRun={this.onRunQuery}
showDropdown={!isLive}
/>
{refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
</div>
{hasLiveOption && (
<div className={`explore-toolbar-content-item ${styles.liveTailButtons}`}>
<LiveTailControls exploreId={exploreId}>
{controls => (
<LiveTailButton
splitted={splitted}
isLive={isLive}
isPaused={isPaused}
start={controls.start}
pause={controls.pause}
resume={controls.resume}
stop={controls.stop}
/>
)}
</LiveTailControls>
</div>
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
const splitted = state.explore.split;
const syncedTimes = state.explore.syncedTimes;
const exploreItem: ExploreItemState = state.explore[exploreId];
const {
datasourceInstance,
datasourceMissing,
range,
refreshInterval,
loading,
supportedModes,
mode,
isLive,
isPaused,
originPanelId,
queries,
datasourceLoading,
containerWidth,
} = exploreItem;
const hasLiveOption = datasourceInstance?.meta?.streaming && mode === ExploreMode.Logs;
return {
datasourceMissing,
datasourceName: datasourceInstance?.name,
loading,
range,
timeZone: getTimeZone(state.user),
splitted,
refreshInterval,
supportedModes,
selectedMode: supportedModes.includes(mode) ? mode : supportedModes[0],
hasLiveOption,
isLive,
isPaused,
originPanelId,
queries,
syncedTimes,
datasourceLoading,
containerWidth,
};
};
const mapDispatchToProps: DispatchProps = {
changeDatasource,
updateLocation,
changeRefreshInterval,
clearAll: clearQueries,
runQueries,
closeSplit: splitClose,
split: splitOpen,
syncTimes,
changeMode: changeMode,
setDashboardQueriesToUpdateOnLoad,
};
export const ExploreToolbar = hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnConnectedExploreToolbar));