ExploreToolbar.tsx 12 KB

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