ExploreToolbar.tsx 6.3 KB

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