ExploreToolbar.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import React, { PureComponent } from 'react';
  2. import { connect } from 'react-redux';
  3. import { hot } from 'react-hot-loader';
  4. import { ExploreId } from 'app/types/explore';
  5. import { DataSourceSelectItem, RawTimeRange, TimeRange, ClickOutsideWrapper } from '@grafana/ui';
  6. import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
  7. import { StoreState } from 'app/types/store';
  8. import {
  9. changeDatasource,
  10. clearQueries,
  11. splitClose,
  12. runQueries,
  13. splitOpen,
  14. changeRefreshInterval,
  15. } from './state/actions';
  16. import TimePicker from './TimePicker';
  17. import { RefreshPicker, SetInterval } from '@grafana/ui';
  18. enum IconSide {
  19. left = 'left',
  20. right = 'right',
  21. }
  22. const createResponsiveButton = (options: {
  23. splitted: boolean;
  24. title: string;
  25. onClick: () => void;
  26. buttonClassName?: string;
  27. iconClassName?: string;
  28. iconSide?: IconSide;
  29. }) => {
  30. const defaultOptions = {
  31. iconSide: IconSide.left,
  32. };
  33. const props = { ...options, defaultOptions };
  34. const { title, onClick, buttonClassName, iconClassName, splitted, iconSide } = props;
  35. return (
  36. <button className={`btn navbar-button ${buttonClassName ? buttonClassName : ''}`} onClick={onClick}>
  37. {iconClassName && iconSide === IconSide.left ? <i className={`${iconClassName} icon-margin-right`} /> : null}
  38. <span className="btn-title">{!splitted ? title : ''}</span>
  39. {iconClassName && iconSide === IconSide.right ? <i className={`${iconClassName} icon-margin-left`} /> : null}
  40. </button>
  41. );
  42. };
  43. interface OwnProps {
  44. exploreId: ExploreId;
  45. timepickerRef: React.RefObject<TimePicker>;
  46. onChangeTime: (range: TimeRange, changedByScanner?: boolean) => void;
  47. }
  48. interface StateProps {
  49. datasourceMissing: boolean;
  50. exploreDatasources: DataSourceSelectItem[];
  51. loading: boolean;
  52. range: RawTimeRange;
  53. selectedDatasource: DataSourceSelectItem;
  54. splitted: boolean;
  55. refreshInterval: string;
  56. }
  57. interface DispatchProps {
  58. changeDatasource: typeof changeDatasource;
  59. clearAll: typeof clearQueries;
  60. runQueries: typeof runQueries;
  61. closeSplit: typeof splitClose;
  62. split: typeof splitOpen;
  63. changeRefreshInterval: typeof changeRefreshInterval;
  64. }
  65. type Props = StateProps & DispatchProps & OwnProps;
  66. export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
  67. constructor(props: Props) {
  68. super(props);
  69. }
  70. onChangeDatasource = async option => {
  71. this.props.changeDatasource(this.props.exploreId, option.value);
  72. };
  73. onClearAll = () => {
  74. this.props.clearAll(this.props.exploreId);
  75. };
  76. onRunQuery = () => {
  77. return this.props.runQueries(this.props.exploreId);
  78. };
  79. onCloseTimePicker = () => {
  80. this.props.timepickerRef.current.setState({ isOpen: false });
  81. };
  82. onChangeRefreshInterval = (item: string) => {
  83. const { changeRefreshInterval, exploreId } = this.props;
  84. changeRefreshInterval(exploreId, item);
  85. };
  86. render() {
  87. const {
  88. datasourceMissing,
  89. exploreDatasources,
  90. closeSplit,
  91. exploreId,
  92. loading,
  93. range,
  94. selectedDatasource,
  95. splitted,
  96. timepickerRef,
  97. refreshInterval,
  98. onChangeTime,
  99. split,
  100. } = this.props;
  101. return (
  102. <div className={splitted ? 'explore-toolbar splitted' : 'explore-toolbar'}>
  103. <div className="explore-toolbar-item">
  104. <div className="explore-toolbar-header">
  105. <div className="explore-toolbar-header-title">
  106. {exploreId === 'left' && (
  107. <span className="navbar-page-btn">
  108. <i className="gicon gicon-explore" />
  109. Explore
  110. </span>
  111. )}
  112. </div>
  113. {splitted && (
  114. <a className="explore-toolbar-header-close" onClick={() => closeSplit(exploreId)}>
  115. <i className="fa fa-times fa-fw" />
  116. </a>
  117. )}
  118. </div>
  119. </div>
  120. <div className="explore-toolbar-item">
  121. <div className="explore-toolbar-content">
  122. {!datasourceMissing ? (
  123. <div className="explore-toolbar-content-item">
  124. <div className="datasource-picker">
  125. <DataSourcePicker
  126. onChange={this.onChangeDatasource}
  127. datasources={exploreDatasources}
  128. current={selectedDatasource}
  129. />
  130. </div>
  131. </div>
  132. ) : null}
  133. {exploreId === 'left' && !splitted ? (
  134. <div className="explore-toolbar-content-item">
  135. {createResponsiveButton({
  136. splitted,
  137. title: 'Split',
  138. onClick: split,
  139. iconClassName: 'fa fa-fw fa-columns icon-margin-right',
  140. iconSide: IconSide.left,
  141. })}
  142. </div>
  143. ) : null}
  144. <div className="explore-toolbar-content-item timepicker">
  145. <ClickOutsideWrapper onClick={this.onCloseTimePicker}>
  146. <TimePicker ref={timepickerRef} range={range} onChangeTime={onChangeTime} />
  147. </ClickOutsideWrapper>
  148. <RefreshPicker
  149. onIntervalChanged={this.onChangeRefreshInterval}
  150. onRefresh={this.onRunQuery}
  151. value={refreshInterval}
  152. tooltip="Refresh"
  153. />
  154. {refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} />}
  155. </div>
  156. <div className="explore-toolbar-content-item">
  157. <button className="btn navbar-button navbar-button--no-icon" onClick={this.onClearAll}>
  158. Clear All
  159. </button>
  160. </div>
  161. <div className="explore-toolbar-content-item">
  162. {createResponsiveButton({
  163. splitted,
  164. title: 'Run Query',
  165. onClick: this.onRunQuery,
  166. buttonClassName: 'navbar-button--secondary',
  167. iconClassName: loading ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon',
  168. iconSide: IconSide.right,
  169. })}
  170. </div>
  171. </div>
  172. </div>
  173. </div>
  174. );
  175. }
  176. }
  177. const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
  178. const splitted = state.explore.split;
  179. const exploreItem = state.explore[exploreId];
  180. const {
  181. datasourceInstance,
  182. datasourceMissing,
  183. exploreDatasources,
  184. queryTransactions,
  185. range,
  186. refreshInterval,
  187. } = exploreItem;
  188. const selectedDatasource = datasourceInstance
  189. ? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
  190. : undefined;
  191. const loading = queryTransactions.some(qt => !qt.done);
  192. return {
  193. datasourceMissing,
  194. exploreDatasources,
  195. loading,
  196. range,
  197. selectedDatasource,
  198. splitted,
  199. refreshInterval,
  200. };
  201. };
  202. const mapDispatchToProps: DispatchProps = {
  203. changeDatasource,
  204. changeRefreshInterval,
  205. clearAll: clearQueries,
  206. runQueries,
  207. closeSplit: splitClose,
  208. split: splitOpen,
  209. };
  210. export const ExploreToolbar = hot(module)(
  211. connect(
  212. mapStateToProps,
  213. mapDispatchToProps
  214. )(UnConnectedExploreToolbar)
  215. );