Explore.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. // Libraries
  2. import React, { ComponentClass } from 'react';
  3. import { hot } from 'react-hot-loader';
  4. import { connect } from 'react-redux';
  5. import _ from 'lodash';
  6. import { AutoSizer } from 'react-virtualized';
  7. // Services & Utils
  8. import store from 'app/core/store';
  9. // Components
  10. import { Alert } from './Error';
  11. import ErrorBoundary from './ErrorBoundary';
  12. import GraphContainer from './GraphContainer';
  13. import LogsContainer from './LogsContainer';
  14. import QueryRows from './QueryRows';
  15. import TableContainer from './TableContainer';
  16. import TimePicker, { parseTime } from './TimePicker';
  17. // Actions
  18. import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions';
  19. // Types
  20. import { RawTimeRange, TimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi } from '@grafana/ui';
  21. import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore';
  22. import { StoreState } from 'app/types';
  23. import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE, DEFAULT_UI_STATE } from 'app/core/utils/explore';
  24. import { Emitter } from 'app/core/utils/emitter';
  25. import { ExploreToolbar } from './ExploreToolbar';
  26. import { scanStopAction } from './state/actionTypes';
  27. interface ExploreProps {
  28. StartPage?: ComponentClass<ExploreStartPageProps>;
  29. changeSize: typeof changeSize;
  30. changeTime: typeof changeTime;
  31. datasourceError: string;
  32. datasourceInstance: ExploreDataSourceApi;
  33. datasourceLoading: boolean | null;
  34. datasourceMissing: boolean;
  35. exploreId: ExploreId;
  36. initializeExplore: typeof initializeExplore;
  37. initialized: boolean;
  38. modifyQueries: typeof modifyQueries;
  39. range: RawTimeRange;
  40. scanner?: RangeScanner;
  41. scanning?: boolean;
  42. scanRange?: RawTimeRange;
  43. scanStart: typeof scanStart;
  44. scanStopAction: typeof scanStopAction;
  45. setQueries: typeof setQueries;
  46. split: boolean;
  47. showingStartPage?: boolean;
  48. supportsGraph: boolean | null;
  49. supportsLogs: boolean | null;
  50. supportsTable: boolean | null;
  51. urlState: ExploreUrlState;
  52. queryKeys: string[];
  53. }
  54. /**
  55. * Explore provides an area for quick query iteration for a given datasource.
  56. * Once a datasource is selected it populates the query section at the top.
  57. * When queries are run, their results are being displayed in the main section.
  58. * The datasource determines what kind of query editor it brings, and what kind
  59. * of results viewers it supports. The state is managed entirely in Redux.
  60. *
  61. * SPLIT VIEW
  62. *
  63. * Explore can have two Explore areas side-by-side. This is handled in `Wrapper.tsx`.
  64. * Since there can be multiple Explores (e.g., left and right) each action needs
  65. * the `exploreId` as first parameter so that the reducer knows which Explore state
  66. * is affected.
  67. *
  68. * DATASOURCE REQUESTS
  69. *
  70. * A click on Run Query creates transactions for all DataQueries for all expanded
  71. * result viewers. New runs are discarding previous runs. Upon completion a transaction
  72. * saves the result. The result viewers construct their data from the currently existing
  73. * transactions.
  74. *
  75. * The result viewers determine some of the query options sent to the datasource, e.g.,
  76. * `format`, to indicate eventual transformations by the datasources' result transformers.
  77. */
  78. export class Explore extends React.PureComponent<ExploreProps> {
  79. el: any;
  80. exploreEvents: Emitter;
  81. /**
  82. * Timepicker to control scanning
  83. */
  84. timepickerRef: React.RefObject<TimePicker>;
  85. constructor(props) {
  86. super(props);
  87. this.exploreEvents = new Emitter();
  88. this.timepickerRef = React.createRef();
  89. }
  90. async componentDidMount() {
  91. const { exploreId, initialized, urlState } = this.props;
  92. // Don't initialize on split, but need to initialize urlparameters when present
  93. if (!initialized) {
  94. // Load URL state and parse range
  95. const { datasource, queries, range = DEFAULT_RANGE, ui = DEFAULT_UI_STATE } = (urlState || {}) as ExploreUrlState;
  96. const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
  97. const initialQueries: DataQuery[] = ensureQueries(queries);
  98. const initialRange = { from: parseTime(range.from), to: parseTime(range.to) };
  99. const width = this.el ? this.el.offsetWidth : 0;
  100. this.props.initializeExplore(
  101. exploreId,
  102. initialDatasource,
  103. initialQueries,
  104. initialRange,
  105. width,
  106. this.exploreEvents,
  107. ui
  108. );
  109. }
  110. }
  111. componentWillUnmount() {
  112. this.exploreEvents.removeAllListeners();
  113. }
  114. getRef = el => {
  115. this.el = el;
  116. };
  117. onChangeTime = (range: TimeRange, changedByScanner?: boolean) => {
  118. if (this.props.scanning && !changedByScanner) {
  119. this.onStopScanning();
  120. }
  121. this.props.changeTime(this.props.exploreId, range);
  122. };
  123. // Use this in help pages to set page to a single query
  124. onClickExample = (query: DataQuery) => {
  125. this.props.setQueries(this.props.exploreId, [query]);
  126. };
  127. onClickLabel = (key: string, value: string) => {
  128. this.onModifyQueries({ type: 'ADD_FILTER', key, value });
  129. };
  130. onModifyQueries = (action, index?: number) => {
  131. const { datasourceInstance } = this.props;
  132. if (datasourceInstance && datasourceInstance.modifyQuery) {
  133. const modifier = (queries: DataQuery, modification: any) => datasourceInstance.modifyQuery(queries, modification);
  134. this.props.modifyQueries(this.props.exploreId, action, index, modifier);
  135. }
  136. };
  137. onResize = (size: { height: number; width: number }) => {
  138. this.props.changeSize(this.props.exploreId, size);
  139. };
  140. onStartScanning = () => {
  141. // Scanner will trigger a query
  142. const scanner = this.scanPreviousRange;
  143. this.props.scanStart(this.props.exploreId, scanner);
  144. };
  145. scanPreviousRange = (): RawTimeRange => {
  146. // Calling move() on the timepicker will trigger this.onChangeTime()
  147. return this.timepickerRef.current.move(-1, true);
  148. };
  149. onStopScanning = () => {
  150. this.props.scanStopAction({ exploreId: this.props.exploreId });
  151. };
  152. render() {
  153. const {
  154. StartPage,
  155. datasourceInstance,
  156. datasourceError,
  157. datasourceLoading,
  158. datasourceMissing,
  159. exploreId,
  160. showingStartPage,
  161. split,
  162. supportsGraph,
  163. supportsLogs,
  164. supportsTable,
  165. queryKeys,
  166. } = this.props;
  167. const exploreClass = split ? 'explore explore-split' : 'explore';
  168. return (
  169. <div className={exploreClass} ref={this.getRef}>
  170. <ExploreToolbar exploreId={exploreId} timepickerRef={this.timepickerRef} onChangeTime={this.onChangeTime} />
  171. {datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
  172. {datasourceMissing ? (
  173. <div className="explore-container">Please add a datasource that supports Explore (e.g., Prometheus).</div>
  174. ) : null}
  175. {datasourceError && (
  176. <div className="explore-container">
  177. <Alert message={`Error connecting to datasource: ${datasourceError}`} />
  178. </div>
  179. )}
  180. {datasourceInstance &&
  181. !datasourceError && (
  182. <div className="explore-container">
  183. <QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} queryKeys={queryKeys} />
  184. <AutoSizer onResize={this.onResize} disableHeight>
  185. {({ width }) => {
  186. if (width === 0) {
  187. return null;
  188. }
  189. return (
  190. <main className="m-t-2" style={{ width }}>
  191. <ErrorBoundary>
  192. {showingStartPage && <StartPage onClickExample={this.onClickExample} />}
  193. {!showingStartPage && (
  194. <>
  195. {supportsGraph && !supportsLogs && <GraphContainer width={width} exploreId={exploreId} />}
  196. {supportsTable && <TableContainer exploreId={exploreId} onClickCell={this.onClickLabel} />}
  197. {supportsLogs && (
  198. <LogsContainer
  199. width={width}
  200. exploreId={exploreId}
  201. onChangeTime={this.onChangeTime}
  202. onClickLabel={this.onClickLabel}
  203. onStartScanning={this.onStartScanning}
  204. onStopScanning={this.onStopScanning}
  205. />
  206. )}
  207. </>
  208. )}
  209. </ErrorBoundary>
  210. </main>
  211. );
  212. }}
  213. </AutoSizer>
  214. </div>
  215. )}
  216. </div>
  217. );
  218. }
  219. }
  220. function mapStateToProps(state: StoreState, { exploreId }) {
  221. const explore = state.explore;
  222. const { split } = explore;
  223. const item: ExploreItemState = explore[exploreId];
  224. const {
  225. StartPage,
  226. datasourceError,
  227. datasourceInstance,
  228. datasourceLoading,
  229. datasourceMissing,
  230. initialized,
  231. range,
  232. showingStartPage,
  233. supportsGraph,
  234. supportsLogs,
  235. supportsTable,
  236. queryKeys,
  237. } = item;
  238. return {
  239. StartPage,
  240. datasourceError,
  241. datasourceInstance,
  242. datasourceLoading,
  243. datasourceMissing,
  244. initialized,
  245. range,
  246. showingStartPage,
  247. split,
  248. supportsGraph,
  249. supportsLogs,
  250. supportsTable,
  251. queryKeys,
  252. };
  253. }
  254. const mapDispatchToProps = {
  255. changeSize,
  256. changeTime,
  257. initializeExplore,
  258. modifyQueries,
  259. scanStart,
  260. scanStopAction,
  261. setQueries,
  262. };
  263. export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Explore));