ExploreToolbar.tsx 13 KB

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