ExploreToolbar.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import omitBy from 'lodash/omitBy';
  2. import React, { PureComponent } from 'react';
  3. import { connect } from 'react-redux';
  4. import { hot } from 'react-hot-loader';
  5. import memoizeOne from 'memoize-one';
  6. import classNames from 'classnames';
  7. import { css } from 'emotion';
  8. import { ExploreId, ExploreItemState, ExploreMode } from 'app/types/explore';
  9. import {
  10. DataSourceSelectItem,
  11. ToggleButtonGroup,
  12. ToggleButton,
  13. DataQuery,
  14. Tooltip,
  15. ButtonSelect,
  16. RefreshPicker,
  17. SetInterval,
  18. } from '@grafana/ui';
  19. import { RawTimeRange, TimeZone, TimeRange, SelectableValue } from '@grafana/data';
  20. import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
  21. import { StoreState } from 'app/types/store';
  22. import {
  23. changeDatasource,
  24. clearQueries,
  25. splitClose,
  26. runQueries,
  27. splitOpen,
  28. changeRefreshInterval,
  29. changeMode,
  30. clearOrigin,
  31. } from './state/actions';
  32. import { changeRefreshIntervalAction, setPausedStateAction } from './state/actionTypes';
  33. import { updateLocation } from 'app/core/actions';
  34. import { getTimeZone } from '../profile/state/selectors';
  35. import { getDashboardSrv } from '../dashboard/services/DashboardSrv';
  36. import kbn from '../../core/utils/kbn';
  37. import { ExploreTimeControls } from './ExploreTimeControls';
  38. import { LiveTailButton } from './LiveTailButton';
  39. import { ResponsiveButton } from './ResponsiveButton';
  40. import { RunButton } from './RunButton';
  41. const getStyles = memoizeOne(() => {
  42. return {
  43. liveTailButtons: css`
  44. margin-left: 10px;
  45. `,
  46. };
  47. });
  48. interface OwnProps {
  49. exploreId: ExploreId;
  50. onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void;
  51. }
  52. interface StateProps {
  53. datasourceMissing: boolean;
  54. exploreDatasources: DataSourceSelectItem[];
  55. loading: boolean;
  56. range: TimeRange;
  57. timeZone: TimeZone;
  58. selectedDatasource: DataSourceSelectItem;
  59. splitted: boolean;
  60. refreshInterval: string;
  61. supportedModeOptions: Array<SelectableValue<ExploreMode>>;
  62. selectedModeOption: SelectableValue<ExploreMode>;
  63. hasLiveOption: boolean;
  64. isLive: boolean;
  65. isPaused: boolean;
  66. originPanelId: number;
  67. queries: DataQuery[];
  68. }
  69. interface DispatchProps {
  70. changeDatasource: typeof changeDatasource;
  71. clearAll: typeof clearQueries;
  72. runQueries: typeof runQueries;
  73. closeSplit: typeof splitClose;
  74. split: typeof splitOpen;
  75. changeRefreshInterval: typeof changeRefreshInterval;
  76. changeMode: typeof changeMode;
  77. clearOrigin: typeof clearOrigin;
  78. updateLocation: typeof updateLocation;
  79. changeRefreshIntervalAction: typeof changeRefreshIntervalAction;
  80. setPausedStateAction: typeof setPausedStateAction;
  81. }
  82. type Props = StateProps & DispatchProps & OwnProps;
  83. export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
  84. constructor(props: Props) {
  85. super(props);
  86. }
  87. onChangeDatasource = async (option: { value: any }) => {
  88. this.props.changeDatasource(this.props.exploreId, option.value);
  89. };
  90. onClearAll = () => {
  91. this.props.clearAll(this.props.exploreId);
  92. };
  93. onRunQuery = () => {
  94. return this.props.runQueries(this.props.exploreId);
  95. };
  96. onChangeRefreshInterval = (item: string) => {
  97. const { changeRefreshInterval, exploreId } = this.props;
  98. changeRefreshInterval(exploreId, item);
  99. };
  100. onModeChange = (mode: ExploreMode) => {
  101. const { changeMode, exploreId } = this.props;
  102. changeMode(exploreId, mode);
  103. };
  104. returnToPanel = async ({ withChanges = false } = {}) => {
  105. const { originPanelId } = this.props;
  106. const dashboardSrv = getDashboardSrv();
  107. const dash = dashboardSrv.getCurrent();
  108. const titleSlug = kbn.slugifyForUrl(dash.title);
  109. if (!withChanges) {
  110. this.props.clearOrigin();
  111. }
  112. const dashViewOptions = {
  113. fullscreen: withChanges || dash.meta.fullscreen,
  114. edit: withChanges || dash.meta.isEditing,
  115. };
  116. this.props.updateLocation({
  117. path: `/d/${dash.uid}/:${titleSlug}`,
  118. query: {
  119. ...omitBy(dashViewOptions, v => !v),
  120. panelId: originPanelId,
  121. },
  122. });
  123. };
  124. stopLive = () => {
  125. const { exploreId } = this.props;
  126. this.pauseLive();
  127. // TODO referencing this from perspective of refresh picker when there is designated button for it now is not
  128. // great. Needs another refactor.
  129. this.props.changeRefreshIntervalAction({ exploreId, refreshInterval: RefreshPicker.offOption.value });
  130. };
  131. startLive = () => {
  132. const { exploreId } = this.props;
  133. this.props.changeRefreshIntervalAction({ exploreId, refreshInterval: RefreshPicker.liveOption.value });
  134. };
  135. pauseLive = () => {
  136. const { exploreId } = this.props;
  137. this.props.setPausedStateAction({ exploreId, isPaused: true });
  138. };
  139. resumeLive = () => {
  140. const { exploreId } = this.props;
  141. this.props.setPausedStateAction({ exploreId, isPaused: false });
  142. };
  143. render() {
  144. const {
  145. datasourceMissing,
  146. exploreDatasources,
  147. closeSplit,
  148. exploreId,
  149. loading,
  150. range,
  151. timeZone,
  152. selectedDatasource,
  153. splitted,
  154. refreshInterval,
  155. onChangeTime,
  156. split,
  157. supportedModeOptions,
  158. selectedModeOption,
  159. hasLiveOption,
  160. isLive,
  161. isPaused,
  162. originPanelId,
  163. } = this.props;
  164. const styles = getStyles();
  165. const originDashboardIsEditable = Number.isInteger(originPanelId);
  166. const panelReturnClasses = classNames('btn', 'navbar-button', {
  167. 'btn--radius-right-0': originDashboardIsEditable,
  168. 'navbar-button navbar-button--border-right-0': originDashboardIsEditable,
  169. });
  170. return (
  171. <div className={splitted ? 'explore-toolbar splitted' : 'explore-toolbar'}>
  172. <div className="explore-toolbar-item">
  173. <div className="explore-toolbar-header">
  174. <div className="explore-toolbar-header-title">
  175. {exploreId === 'left' && (
  176. <span className="navbar-page-btn">
  177. <i className="gicon gicon-explore" />
  178. Explore
  179. </span>
  180. )}
  181. </div>
  182. {splitted && (
  183. <a className="explore-toolbar-header-close" onClick={() => closeSplit(exploreId)}>
  184. <i className="fa fa-times fa-fw" />
  185. </a>
  186. )}
  187. </div>
  188. </div>
  189. <div className="explore-toolbar-item">
  190. <div className="explore-toolbar-content">
  191. {!datasourceMissing ? (
  192. <div className="explore-toolbar-content-item">
  193. <div className="datasource-picker">
  194. <DataSourcePicker
  195. onChange={this.onChangeDatasource}
  196. datasources={exploreDatasources}
  197. current={selectedDatasource}
  198. />
  199. </div>
  200. {supportedModeOptions.length > 1 ? (
  201. <div className="query-type-toggle">
  202. <ToggleButtonGroup label="" transparent={true}>
  203. <ToggleButton
  204. key={ExploreMode.Metrics}
  205. value={ExploreMode.Metrics}
  206. onChange={this.onModeChange}
  207. selected={selectedModeOption.value === ExploreMode.Metrics}
  208. >
  209. {'Metrics'}
  210. </ToggleButton>
  211. <ToggleButton
  212. key={ExploreMode.Logs}
  213. value={ExploreMode.Logs}
  214. onChange={this.onModeChange}
  215. selected={selectedModeOption.value === ExploreMode.Logs}
  216. >
  217. {'Logs'}
  218. </ToggleButton>
  219. </ToggleButtonGroup>
  220. </div>
  221. ) : null}
  222. </div>
  223. ) : null}
  224. {Number.isInteger(originPanelId) && !splitted && (
  225. <div className="explore-toolbar-content-item">
  226. <Tooltip content={'Return to panel'} placement="bottom">
  227. <button className={panelReturnClasses} onClick={() => this.returnToPanel()}>
  228. <i className="fa fa-arrow-left" />
  229. </button>
  230. </Tooltip>
  231. {originDashboardIsEditable && (
  232. <ButtonSelect
  233. className="navbar-button--attached btn--radius-left-0$"
  234. options={[{ label: 'Return to panel with changes', value: '' }]}
  235. onChange={() => this.returnToPanel({ withChanges: true })}
  236. maxMenuHeight={380}
  237. />
  238. )}
  239. </div>
  240. )}
  241. {exploreId === 'left' && !splitted ? (
  242. <div className="explore-toolbar-content-item">
  243. <ResponsiveButton
  244. splitted={splitted}
  245. title="Split"
  246. onClick={split}
  247. iconClassName="fa fa-fw fa-columns icon-margin-right"
  248. disabled={isLive}
  249. />
  250. </div>
  251. ) : null}
  252. {!isLive && (
  253. <div className="explore-toolbar-content-item">
  254. <ExploreTimeControls
  255. exploreId={exploreId}
  256. range={range}
  257. timeZone={timeZone}
  258. onChangeTime={onChangeTime}
  259. />
  260. </div>
  261. )}
  262. <div className="explore-toolbar-content-item">
  263. <button className="btn navbar-button" onClick={this.onClearAll}>
  264. Clear All
  265. </button>
  266. </div>
  267. <div className="explore-toolbar-content-item">
  268. <RunButton
  269. refreshInterval={refreshInterval}
  270. onChangeRefreshInterval={this.onChangeRefreshInterval}
  271. splitted={splitted}
  272. loading={loading || (isLive && !isPaused)}
  273. onRun={this.onRunQuery}
  274. showDropdown={!isLive}
  275. />
  276. {refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
  277. </div>
  278. {hasLiveOption && (
  279. <div className={`explore-toolbar-content-item ${styles.liveTailButtons}`}>
  280. <LiveTailButton
  281. isLive={isLive}
  282. isPaused={isPaused}
  283. start={this.startLive}
  284. pause={this.pauseLive}
  285. resume={this.resumeLive}
  286. stop={this.stopLive}
  287. />
  288. </div>
  289. )}
  290. </div>
  291. </div>
  292. </div>
  293. );
  294. }
  295. }
  296. const getModeOptionsMemoized = memoizeOne(
  297. (
  298. supportedModes: ExploreMode[],
  299. mode: ExploreMode
  300. ): [Array<SelectableValue<ExploreMode>>, SelectableValue<ExploreMode>] => {
  301. const supportedModeOptions: Array<SelectableValue<ExploreMode>> = [];
  302. let selectedModeOption = null;
  303. for (const supportedMode of supportedModes) {
  304. switch (supportedMode) {
  305. case ExploreMode.Metrics:
  306. const option1 = {
  307. value: ExploreMode.Metrics,
  308. label: ExploreMode.Metrics,
  309. };
  310. supportedModeOptions.push(option1);
  311. if (mode === ExploreMode.Metrics) {
  312. selectedModeOption = option1;
  313. }
  314. break;
  315. case ExploreMode.Logs:
  316. const option2 = {
  317. value: ExploreMode.Logs,
  318. label: ExploreMode.Logs,
  319. };
  320. supportedModeOptions.push(option2);
  321. if (mode === ExploreMode.Logs) {
  322. selectedModeOption = option2;
  323. }
  324. break;
  325. }
  326. }
  327. return [supportedModeOptions, selectedModeOption];
  328. }
  329. );
  330. const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
  331. const splitted = state.explore.split;
  332. const exploreItem: ExploreItemState = state.explore[exploreId];
  333. const {
  334. datasourceInstance,
  335. datasourceMissing,
  336. exploreDatasources,
  337. range,
  338. refreshInterval,
  339. loading,
  340. supportedModes,
  341. mode,
  342. isLive,
  343. isPaused,
  344. originPanelId,
  345. queries,
  346. } = exploreItem;
  347. const selectedDatasource = datasourceInstance
  348. ? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
  349. : undefined;
  350. const hasLiveOption =
  351. datasourceInstance && datasourceInstance.meta && datasourceInstance.meta.streaming ? true : false;
  352. const [supportedModeOptions, selectedModeOption] = getModeOptionsMemoized(supportedModes, mode);
  353. return {
  354. datasourceMissing,
  355. exploreDatasources,
  356. loading,
  357. range,
  358. timeZone: getTimeZone(state.user),
  359. selectedDatasource,
  360. splitted,
  361. refreshInterval,
  362. supportedModeOptions,
  363. selectedModeOption,
  364. hasLiveOption,
  365. isLive,
  366. isPaused,
  367. originPanelId,
  368. queries,
  369. };
  370. };
  371. const mapDispatchToProps: DispatchProps = {
  372. changeDatasource,
  373. updateLocation,
  374. changeRefreshInterval,
  375. clearAll: clearQueries,
  376. runQueries,
  377. closeSplit: splitClose,
  378. split: splitOpen,
  379. changeMode: changeMode,
  380. clearOrigin,
  381. changeRefreshIntervalAction,
  382. setPausedStateAction,
  383. };
  384. export const ExploreToolbar = hot(module)(
  385. connect(
  386. mapStateToProps,
  387. mapDispatchToProps
  388. )(UnConnectedExploreToolbar)
  389. );