ExploreToolbar.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. import React, { PureComponent } from 'react';
  2. import { connect } from 'react-redux';
  3. import { hot } from 'react-hot-loader';
  4. import { ExploreId, ExploreMode } from 'app/types/explore';
  5. import {
  6. DataSourceSelectItem,
  7. RawTimeRange,
  8. ClickOutsideWrapper,
  9. TimeZone,
  10. TimeRange,
  11. SelectOptionItem,
  12. LoadingState,
  13. } from '@grafana/ui';
  14. import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
  15. import { StoreState } from 'app/types/store';
  16. import {
  17. changeDatasource,
  18. clearQueries,
  19. splitClose,
  20. runQueries,
  21. splitOpen,
  22. changeRefreshInterval,
  23. changeMode,
  24. } from './state/actions';
  25. import TimePicker from './TimePicker';
  26. import { getTimeZone } from '../profile/state/selectors';
  27. import { RefreshPicker, SetInterval } from '@grafana/ui';
  28. import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
  29. enum IconSide {
  30. left = 'left',
  31. right = 'right',
  32. }
  33. const createResponsiveButton = (options: {
  34. splitted: boolean;
  35. title: string;
  36. onClick: () => void;
  37. buttonClassName?: string;
  38. iconClassName?: string;
  39. iconSide?: IconSide;
  40. disabled?: boolean;
  41. }) => {
  42. const defaultOptions = {
  43. iconSide: IconSide.left,
  44. };
  45. const props = { ...options, defaultOptions };
  46. const { title, onClick, buttonClassName, iconClassName, splitted, iconSide, disabled } = props;
  47. return (
  48. <button
  49. className={`btn navbar-button ${buttonClassName ? buttonClassName : ''}`}
  50. onClick={onClick}
  51. disabled={disabled || false}
  52. >
  53. {iconClassName && iconSide === IconSide.left ? <i className={`${iconClassName}`} /> : null}
  54. <span className="btn-title">{!splitted ? title : ''}</span>
  55. {iconClassName && iconSide === IconSide.right ? <i className={`${iconClassName}`} /> : null}
  56. </button>
  57. );
  58. };
  59. interface OwnProps {
  60. exploreId: ExploreId;
  61. timepickerRef: React.RefObject<TimePicker>;
  62. onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void;
  63. }
  64. interface StateProps {
  65. datasourceMissing: boolean;
  66. exploreDatasources: DataSourceSelectItem[];
  67. loading: boolean;
  68. range: TimeRange;
  69. timeZone: TimeZone;
  70. selectedDatasource: DataSourceSelectItem;
  71. splitted: boolean;
  72. refreshInterval: string;
  73. supportedModeOptions: Array<SelectOptionItem<ExploreMode>>;
  74. selectedModeOption: SelectOptionItem<ExploreMode>;
  75. hasLiveOption: boolean;
  76. isLive: boolean;
  77. }
  78. interface DispatchProps {
  79. changeDatasource: typeof changeDatasource;
  80. clearAll: typeof clearQueries;
  81. runQueries: typeof runQueries;
  82. closeSplit: typeof splitClose;
  83. split: typeof splitOpen;
  84. changeRefreshInterval: typeof changeRefreshInterval;
  85. changeMode: typeof changeMode;
  86. }
  87. type Props = StateProps & DispatchProps & OwnProps;
  88. export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
  89. constructor(props: Props) {
  90. super(props);
  91. }
  92. onChangeDatasource = async option => {
  93. this.props.changeDatasource(this.props.exploreId, option.value);
  94. };
  95. onClearAll = () => {
  96. this.props.clearAll(this.props.exploreId);
  97. };
  98. onRunQuery = () => {
  99. return this.props.runQueries(this.props.exploreId);
  100. };
  101. onCloseTimePicker = () => {
  102. this.props.timepickerRef.current.setState({ isOpen: false });
  103. };
  104. onChangeRefreshInterval = (item: string) => {
  105. const { changeRefreshInterval, exploreId } = this.props;
  106. changeRefreshInterval(exploreId, item);
  107. };
  108. onModeChange = (mode: ExploreMode) => {
  109. const { changeMode, exploreId } = this.props;
  110. changeMode(exploreId, mode);
  111. };
  112. render() {
  113. const {
  114. datasourceMissing,
  115. exploreDatasources,
  116. closeSplit,
  117. exploreId,
  118. loading,
  119. range,
  120. timeZone,
  121. selectedDatasource,
  122. splitted,
  123. timepickerRef,
  124. refreshInterval,
  125. onChangeTime,
  126. split,
  127. supportedModeOptions,
  128. selectedModeOption,
  129. hasLiveOption,
  130. isLive,
  131. } = this.props;
  132. return (
  133. <div className={splitted ? 'explore-toolbar splitted' : 'explore-toolbar'}>
  134. <div className="explore-toolbar-item">
  135. <div className="explore-toolbar-header">
  136. <div className="explore-toolbar-header-title">
  137. {exploreId === 'left' && (
  138. <span className="navbar-page-btn">
  139. <i className="gicon gicon-explore" />
  140. Explore
  141. </span>
  142. )}
  143. </div>
  144. {splitted && (
  145. <a className="explore-toolbar-header-close" onClick={() => closeSplit(exploreId)}>
  146. <i className="fa fa-times fa-fw" />
  147. </a>
  148. )}
  149. </div>
  150. </div>
  151. <div className="explore-toolbar-item">
  152. <div className="explore-toolbar-content">
  153. {!datasourceMissing ? (
  154. <div className="explore-toolbar-content-item">
  155. <div className="datasource-picker">
  156. <DataSourcePicker
  157. onChange={this.onChangeDatasource}
  158. datasources={exploreDatasources}
  159. current={selectedDatasource}
  160. />
  161. </div>
  162. {supportedModeOptions.length > 1 ? (
  163. <div className="query-type-toggle">
  164. <ToggleButtonGroup label="" transparent={true}>
  165. <ToggleButton
  166. key={ExploreMode.Metrics}
  167. value={ExploreMode.Metrics}
  168. onChange={this.onModeChange}
  169. selected={selectedModeOption.value === ExploreMode.Metrics}
  170. >
  171. {'Metrics'}
  172. </ToggleButton>
  173. <ToggleButton
  174. key={ExploreMode.Logs}
  175. value={ExploreMode.Logs}
  176. onChange={this.onModeChange}
  177. selected={selectedModeOption.value === ExploreMode.Logs}
  178. >
  179. {'Logs'}
  180. </ToggleButton>
  181. </ToggleButtonGroup>
  182. </div>
  183. ) : null}
  184. </div>
  185. ) : null}
  186. {exploreId === 'left' && !splitted ? (
  187. <div className="explore-toolbar-content-item">
  188. {createResponsiveButton({
  189. splitted,
  190. title: 'Split',
  191. onClick: split,
  192. iconClassName: 'fa fa-fw fa-columns icon-margin-right',
  193. iconSide: IconSide.left,
  194. disabled: isLive,
  195. })}
  196. </div>
  197. ) : null}
  198. <div className="explore-toolbar-content-item timepicker">
  199. {!isLive && (
  200. <ClickOutsideWrapper onClick={this.onCloseTimePicker}>
  201. <TimePicker ref={timepickerRef} range={range} isUtc={timeZone.isUtc} onChangeTime={onChangeTime} />
  202. </ClickOutsideWrapper>
  203. )}
  204. <RefreshPicker
  205. onIntervalChanged={this.onChangeRefreshInterval}
  206. onRefresh={this.onRunQuery}
  207. value={refreshInterval}
  208. tooltip="Refresh"
  209. hasLiveOption={hasLiveOption}
  210. />
  211. {refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
  212. </div>
  213. <div className="explore-toolbar-content-item">
  214. <button className="btn navbar-button" onClick={this.onClearAll}>
  215. Clear All
  216. </button>
  217. </div>
  218. <div className="explore-toolbar-content-item">
  219. {createResponsiveButton({
  220. splitted,
  221. title: 'Run Query',
  222. onClick: this.onRunQuery,
  223. buttonClassName: 'navbar-button--secondary',
  224. iconClassName:
  225. loading && !isLive ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon',
  226. iconSide: IconSide.right,
  227. })}
  228. </div>
  229. </div>
  230. </div>
  231. </div>
  232. );
  233. }
  234. }
  235. const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
  236. const splitted = state.explore.split;
  237. const exploreItem = state.explore[exploreId];
  238. const {
  239. datasourceInstance,
  240. datasourceMissing,
  241. exploreDatasources,
  242. range,
  243. refreshInterval,
  244. loadingState,
  245. supportedModes,
  246. mode,
  247. isLive,
  248. } = exploreItem;
  249. const selectedDatasource = datasourceInstance
  250. ? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
  251. : undefined;
  252. const loading = loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming;
  253. const hasLiveOption =
  254. datasourceInstance && datasourceInstance.meta && datasourceInstance.meta.streaming ? true : false;
  255. const supportedModeOptions: Array<SelectOptionItem<ExploreMode>> = [];
  256. let selectedModeOption = null;
  257. for (const supportedMode of supportedModes) {
  258. switch (supportedMode) {
  259. case ExploreMode.Metrics:
  260. const option1 = {
  261. value: ExploreMode.Metrics,
  262. label: ExploreMode.Metrics,
  263. };
  264. supportedModeOptions.push(option1);
  265. if (mode === ExploreMode.Metrics) {
  266. selectedModeOption = option1;
  267. }
  268. break;
  269. case ExploreMode.Logs:
  270. const option2 = {
  271. value: ExploreMode.Logs,
  272. label: ExploreMode.Logs,
  273. };
  274. supportedModeOptions.push(option2);
  275. if (mode === ExploreMode.Logs) {
  276. selectedModeOption = option2;
  277. }
  278. break;
  279. }
  280. }
  281. return {
  282. datasourceMissing,
  283. exploreDatasources,
  284. loading,
  285. range,
  286. timeZone: getTimeZone(state.user),
  287. selectedDatasource,
  288. splitted,
  289. refreshInterval,
  290. supportedModeOptions,
  291. selectedModeOption,
  292. hasLiveOption,
  293. isLive,
  294. };
  295. };
  296. const mapDispatchToProps: DispatchProps = {
  297. changeDatasource,
  298. changeRefreshInterval,
  299. clearAll: clearQueries,
  300. runQueries,
  301. closeSplit: splitClose,
  302. split: splitOpen,
  303. changeMode: changeMode,
  304. };
  305. export const ExploreToolbar = hot(module)(
  306. connect(
  307. mapStateToProps,
  308. mapDispatchToProps
  309. )(UnConnectedExploreToolbar)
  310. );