grafana/public/app/features/explore/ExploreToolbar.tsx
Johannes Schill 406ef962fc Explore & Dashboard: New Refresh picker (#16505)
* Added RefreshButton

* Added RefreshSelect

* Added RefreshSelectButton

* Added RefreshPicker

* Removed the magic string Paused

* Minor style changes and using Off instead of Pause

* Added HeadlessSelect

* Added HeadlessSelect story

* Added SelectButton

* Removed RefreshSelectButton

* Added TimePicker and moved ClickOutsideWrapper to ui/components

* Added TimePickerPopOver

* Added react-calendar

* Missed yarn lock file

* Added inputs to popover

* Added TimePicker and RefreshPicker to DashNav

* Moved TimePicker and RefreshPicker to app/core

* Added react-calendar to app and removed from ui/components

* Fixed PopOver onClick

* Moved everything back to ui components because of typings problems

* Exporing RefreshPicker and TimePicker

* Added Apply and inputs

* Added typings

* Added TimePickerInput and logic

* Fixed parsing of string to Moments

* Fixed range string

* Styling and connecting the calendars and inputs

* Changed Calendar styling

* Added backward forward and zoom

* Fixed responsive styles

* Moved TimePicker and RefreshPicker into app core

* Renamed menuIsOpen to isOpen

* Changed from className={} to className=""

* Moved Popover to TimePickerOptionGroup

* Renamed all PopOver to Popover

* Renamed popOver to popover and some minor refactorings

* Renamed files with git mv

* Added ButtonSelect and refactored RefreshPicker

* Refactored TimePicker to use new ButtonSelect

* Removed HeadlessSelect as suggested

* fix: Fix typings and misc errors after rebase

* wip: Enable time picker on dashboard and add tooltip

* Merge branch 'master' into hugoh/new-timepicker-and-unified-component

# Conflicts:
#	packages/grafana-ui/package.json
#	packages/grafana-ui/src/components/Input/Input.test.tsx
#	packages/grafana-ui/src/components/Input/Input.tsx
#	packages/grafana-ui/src/utils/validate.ts
#	public/app/features/dashboard/panel_editor/QueryOptions.tsx
#	yarn.lock

* fix: Snapshot update

* Move TimePicker default options into the TimePicker as statics, pass the tooltipContent down the line when wanted and wrap the button in a tooltip element

* fix: Override internal state prop if we provide one in a prop

* Updated snapshots

* Let dashnav control refreshPicker state

* feat: Add a stringToMs function

* wip: RefreshPicker

* wip: Move RefreshPicker to @grafana/ui

* wip: Move TimePicker to @grafana/ui

* wip: Remove comments

* wip: Add refreshPicker to explore

* wip: Use default intervals if the prop is missing

* wip: Nicer way of setting defaults

* fix: Control the select component

* wip: Add onMoveForward/onMoveBack

* Remove code related to the new time picker and refresh picker from dashnav

* Fix: Typings after merge

* chore: Minor fix after merge

* chore: Remove _.map usage

* chore: Moved refresh-picker logic out of the refresh picker since it will work a little differently in explore and dashboards until we have replaced the TimeSrv

* feat: Add an Interval component to @grafana/ui

* chore: Remove intervalId from redux state and move setInterval logic from ExploreToolbar to its own Interval component

* feat: Add refreshInterval to Explore's URL state

* feat: Pick up refreshInterval from url on page load

* fix: Set default refreshInterval when no value can be retained from URL

* fix: Update test initial state with refreshInterval

* fix: Handle URLs before RefreshPicker

* fix: Move RefreshInterval to url position 3 since the segments can take multiple positions

* fix: A better way of detecting urls without RefreshInterval in Explore

* chore: Some Explore typings

* fix: Attach refresh picker to interval picker

* chore: Sass fix for refresh button border radius

* fix: Remove refreshInterval from URL

* fix: Intervals now start when previous interval is finished

* fix: Use clearTimeout instead of clearInterval

* fix: Make sure there's a delay set before adding a timeout when we have slow explore queries

* wip: Add refresh picker to dashboard

* feat: Add util for removing keys with empty values

* feat: RefreshPicker in dashboards and tmp rem out old RefreshPicker

* fix: Remove the jumpy:ness in the refreshpicker

* Changed placement and made it hide when your in dashboard settings

* chore: Move logic related to refresh picker out of DashNav to its own component

* feat: Add tooltip to refreshpicker

* fix: Fix bug with refreshpicker not updating when setting to 'off'

* fix: Make it possible to override refresh intervals using the dashboard intervals

* chore: Change name of Interval to SetInterval to align with ecmascripts naming since its basically the same but declarative and async

* fix: Use default intervals when auto refresh is empty in dashboard settings

* fix: Hide time/interval picker when hidden is true on the model, such as on the home dashboard

* fix: Interval picker will have to handle location changes since timeSrv wont

* RefreshPicker: Refactoring refresh picker

* RefreshPicker: minor refactoring
2019-04-16 09:15:23 +02:00

238 lines
7.1 KiB
TypeScript

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import { ExploreId } from 'app/types/explore';
import { DataSourceSelectItem, RawTimeRange, TimeRange, ClickOutsideWrapper } from '@grafana/ui';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { StoreState } from 'app/types/store';
import {
changeDatasource,
clearQueries,
splitClose,
runQueries,
splitOpen,
changeRefreshInterval,
} from './state/actions';
import TimePicker from './TimePicker';
import { RefreshPicker, SetInterval } from '@grafana/ui';
enum IconSide {
left = 'left',
right = 'right',
}
const createResponsiveButton = (options: {
splitted: boolean;
title: string;
onClick: () => void;
buttonClassName?: string;
iconClassName?: string;
iconSide?: IconSide;
}) => {
const defaultOptions = {
iconSide: IconSide.left,
};
const props = { ...options, defaultOptions };
const { title, onClick, buttonClassName, iconClassName, splitted, iconSide } = props;
return (
<button className={`btn navbar-button ${buttonClassName ? buttonClassName : ''}`} onClick={onClick}>
{iconClassName && iconSide === IconSide.left ? <i className={`${iconClassName} icon-margin-right`} /> : null}
<span className="btn-title">{!splitted ? title : ''}</span>
{iconClassName && iconSide === IconSide.right ? <i className={`${iconClassName} icon-margin-left`} /> : null}
</button>
);
};
interface OwnProps {
exploreId: ExploreId;
timepickerRef: React.RefObject<TimePicker>;
onChangeTime: (range: TimeRange, changedByScanner?: boolean) => void;
}
interface StateProps {
datasourceMissing: boolean;
exploreDatasources: DataSourceSelectItem[];
loading: boolean;
range: RawTimeRange;
selectedDatasource: DataSourceSelectItem;
splitted: boolean;
refreshInterval: string;
}
interface DispatchProps {
changeDatasource: typeof changeDatasource;
clearAll: typeof clearQueries;
runQueries: typeof runQueries;
closeSplit: typeof splitClose;
split: typeof splitOpen;
changeRefreshInterval: typeof changeRefreshInterval;
}
type Props = StateProps & DispatchProps & OwnProps;
export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
constructor(props: Props) {
super(props);
}
onChangeDatasource = async option => {
this.props.changeDatasource(this.props.exploreId, option.value);
};
onClearAll = () => {
this.props.clearAll(this.props.exploreId);
};
onRunQuery = () => {
return this.props.runQueries(this.props.exploreId);
};
onCloseTimePicker = () => {
this.props.timepickerRef.current.setState({ isOpen: false });
};
onChangeRefreshInterval = (item: string) => {
const { changeRefreshInterval, exploreId } = this.props;
changeRefreshInterval(exploreId, item);
};
render() {
const {
datasourceMissing,
exploreDatasources,
closeSplit,
exploreId,
loading,
range,
selectedDatasource,
splitted,
timepickerRef,
refreshInterval,
onChangeTime,
split,
} = this.props;
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="datasource-picker">
<DataSourcePicker
onChange={this.onChangeDatasource}
datasources={exploreDatasources}
current={selectedDatasource}
/>
</div>
</div>
) : null}
{exploreId === 'left' && !splitted ? (
<div className="explore-toolbar-content-item">
{createResponsiveButton({
splitted,
title: 'Split',
onClick: split,
iconClassName: 'fa fa-fw fa-columns icon-margin-right',
iconSide: IconSide.left,
})}
</div>
) : null}
<div className="explore-toolbar-content-item timepicker">
<ClickOutsideWrapper onClick={this.onCloseTimePicker}>
<TimePicker ref={timepickerRef} range={range} onChangeTime={onChangeTime} />
</ClickOutsideWrapper>
<RefreshPicker
onIntervalChanged={this.onChangeRefreshInterval}
onRefresh={this.onRunQuery}
value={refreshInterval}
tooltip="Refresh"
/>
{refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} />}
</div>
<div className="explore-toolbar-content-item">
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClearAll}>
Clear All
</button>
</div>
<div className="explore-toolbar-content-item">
{createResponsiveButton({
splitted,
title: 'Run Query',
onClick: this.onRunQuery,
buttonClassName: 'navbar-button--secondary',
iconClassName: loading ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon',
iconSide: IconSide.right,
})}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
const splitted = state.explore.split;
const exploreItem = state.explore[exploreId];
const {
datasourceInstance,
datasourceMissing,
exploreDatasources,
queryTransactions,
range,
refreshInterval,
} = exploreItem;
const selectedDatasource = datasourceInstance
? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
: undefined;
const loading = queryTransactions.some(qt => !qt.done);
return {
datasourceMissing,
exploreDatasources,
loading,
range,
selectedDatasource,
splitted,
refreshInterval,
};
};
const mapDispatchToProps: DispatchProps = {
changeDatasource,
changeRefreshInterval,
clearAll: clearQueries,
runQueries,
closeSplit: splitClose,
split: splitOpen,
};
export const ExploreToolbar = hot(module)(
connect(
mapStateToProps,
mapDispatchToProps
)(UnConnectedExploreToolbar)
);