Explore.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import React from 'react';
  2. import { hot } from 'react-hot-loader';
  3. import colors from 'app/core/utils/colors';
  4. import TimeSeries from 'app/core/time_series2';
  5. import ElapsedTime from './ElapsedTime';
  6. import QueryRows from './QueryRows';
  7. import Graph from './Graph';
  8. import Table from './Table';
  9. import TimePicker, { DEFAULT_RANGE } from './TimePicker';
  10. import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
  11. import { buildQueryOptions, ensureQueries, generateQueryKey, hasQuery } from './utils/query';
  12. import { decodePathComponent } from 'app/core/utils/location_util';
  13. function makeTimeSeriesList(dataList, options) {
  14. return dataList.map((seriesData, index) => {
  15. const datapoints = seriesData.datapoints || [];
  16. const alias = seriesData.target;
  17. const colorIndex = index % colors.length;
  18. const color = colors[colorIndex];
  19. const series = new TimeSeries({
  20. datapoints,
  21. alias,
  22. color,
  23. unit: seriesData.unit,
  24. });
  25. return series;
  26. });
  27. }
  28. function parseInitialState(initial) {
  29. try {
  30. const parsed = JSON.parse(decodePathComponent(initial));
  31. return {
  32. queries: parsed.queries.map(q => q.query),
  33. range: parsed.range,
  34. };
  35. } catch (e) {
  36. console.error(e);
  37. return { queries: [], range: DEFAULT_RANGE };
  38. }
  39. }
  40. interface IExploreState {
  41. datasource: any;
  42. datasourceError: any;
  43. datasourceLoading: any;
  44. graphResult: any;
  45. latency: number;
  46. loading: any;
  47. queries: any;
  48. queryError: any;
  49. range: any;
  50. requestOptions: any;
  51. showingGraph: boolean;
  52. showingTable: boolean;
  53. tableResult: any;
  54. }
  55. // @observer
  56. export class Explore extends React.Component<any, IExploreState> {
  57. datasourceSrv: DatasourceSrv;
  58. constructor(props) {
  59. super(props);
  60. const { range, queries } = parseInitialState(props.routeParams.initial);
  61. this.state = {
  62. datasource: null,
  63. datasourceError: null,
  64. datasourceLoading: true,
  65. graphResult: null,
  66. latency: 0,
  67. loading: false,
  68. queries: ensureQueries(queries),
  69. queryError: null,
  70. range: range || { ...DEFAULT_RANGE },
  71. requestOptions: null,
  72. showingGraph: true,
  73. showingTable: true,
  74. tableResult: null,
  75. ...props.initialState,
  76. };
  77. }
  78. async componentDidMount() {
  79. const datasource = await this.props.datasourceSrv.get();
  80. const testResult = await datasource.testDatasource();
  81. if (testResult.status === 'success') {
  82. this.setState({ datasource, datasourceError: null, datasourceLoading: false }, () => this.handleSubmit());
  83. } else {
  84. this.setState({ datasource: null, datasourceError: testResult.message, datasourceLoading: false });
  85. }
  86. }
  87. componentDidCatch(error) {
  88. console.error(error);
  89. }
  90. handleAddQueryRow = index => {
  91. const { queries } = this.state;
  92. const nextQueries = [
  93. ...queries.slice(0, index + 1),
  94. { query: '', key: generateQueryKey() },
  95. ...queries.slice(index + 1),
  96. ];
  97. this.setState({ queries: nextQueries });
  98. };
  99. handleChangeQuery = (query, index) => {
  100. const { queries } = this.state;
  101. const nextQuery = {
  102. ...queries[index],
  103. query,
  104. };
  105. const nextQueries = [...queries];
  106. nextQueries[index] = nextQuery;
  107. this.setState({ queries: nextQueries });
  108. };
  109. handleChangeTime = nextRange => {
  110. const range = {
  111. from: nextRange.from,
  112. to: nextRange.to,
  113. };
  114. this.setState({ range }, () => this.handleSubmit());
  115. };
  116. handleClickCloseSplit = () => {
  117. const { onChangeSplit } = this.props;
  118. if (onChangeSplit) {
  119. onChangeSplit(false);
  120. }
  121. };
  122. handleClickGraphButton = () => {
  123. this.setState(state => ({ showingGraph: !state.showingGraph }));
  124. };
  125. handleClickSplit = () => {
  126. const { onChangeSplit } = this.props;
  127. if (onChangeSplit) {
  128. onChangeSplit(true, this.state);
  129. }
  130. };
  131. handleClickTableButton = () => {
  132. this.setState(state => ({ showingTable: !state.showingTable }));
  133. };
  134. handleRemoveQueryRow = index => {
  135. const { queries } = this.state;
  136. if (queries.length <= 1) {
  137. return;
  138. }
  139. const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
  140. this.setState({ queries: nextQueries }, () => this.handleSubmit());
  141. };
  142. handleSubmit = () => {
  143. const { showingGraph, showingTable } = this.state;
  144. if (showingTable) {
  145. this.runTableQuery();
  146. }
  147. if (showingGraph) {
  148. this.runGraphQuery();
  149. }
  150. };
  151. async runGraphQuery() {
  152. const { datasource, queries, range } = this.state;
  153. if (!hasQuery(queries)) {
  154. return;
  155. }
  156. this.setState({ latency: 0, loading: true, graphResult: null, queryError: null });
  157. const now = Date.now();
  158. const options = buildQueryOptions({
  159. format: 'time_series',
  160. interval: datasource.interval,
  161. instant: false,
  162. range,
  163. queries: queries.map(q => q.query),
  164. });
  165. try {
  166. const res = await datasource.query(options);
  167. const result = makeTimeSeriesList(res.data, options);
  168. const latency = Date.now() - now;
  169. this.setState({ latency, loading: false, graphResult: result, requestOptions: options });
  170. } catch (response) {
  171. console.error(response);
  172. const queryError = response.data ? response.data.error : response;
  173. this.setState({ loading: false, queryError });
  174. }
  175. }
  176. async runTableQuery() {
  177. const { datasource, queries, range } = this.state;
  178. if (!hasQuery(queries)) {
  179. return;
  180. }
  181. this.setState({ latency: 0, loading: true, queryError: null, tableResult: null });
  182. const now = Date.now();
  183. const options = buildQueryOptions({
  184. format: 'table',
  185. interval: datasource.interval,
  186. instant: true,
  187. range,
  188. queries: queries.map(q => q.query),
  189. });
  190. try {
  191. const res = await datasource.query(options);
  192. const tableModel = res.data[0];
  193. const latency = Date.now() - now;
  194. this.setState({ latency, loading: false, tableResult: tableModel, requestOptions: options });
  195. } catch (response) {
  196. console.error(response);
  197. const queryError = response.data ? response.data.error : response;
  198. this.setState({ loading: false, queryError });
  199. }
  200. }
  201. request = url => {
  202. const { datasource } = this.state;
  203. return datasource.metadataRequest(url);
  204. };
  205. render() {
  206. const { position, split } = this.props;
  207. const {
  208. datasource,
  209. datasourceError,
  210. datasourceLoading,
  211. graphResult,
  212. latency,
  213. loading,
  214. queries,
  215. queryError,
  216. range,
  217. requestOptions,
  218. showingGraph,
  219. showingTable,
  220. tableResult,
  221. } = this.state;
  222. const showingBoth = showingGraph && showingTable;
  223. const graphHeight = showingBoth ? '200px' : '400px';
  224. const graphButtonActive = showingBoth || showingGraph ? 'active' : '';
  225. const tableButtonActive = showingBoth || showingTable ? 'active' : '';
  226. const exploreClass = split ? 'explore explore-split' : 'explore';
  227. return (
  228. <div className={exploreClass}>
  229. <div className="navbar">
  230. {position === 'left' ? (
  231. <div>
  232. <a className="navbar-page-btn">
  233. <i className="fa fa-rocket" />
  234. Explore
  235. </a>
  236. </div>
  237. ) : (
  238. <div className="navbar-buttons explore-first-button">
  239. <button className="btn navbar-button" onClick={this.handleClickCloseSplit}>
  240. Close Split
  241. </button>
  242. </div>
  243. )}
  244. <div className="navbar__spacer" />
  245. {position === 'left' && !split ? (
  246. <div className="navbar-buttons">
  247. <button className="btn navbar-button" onClick={this.handleClickSplit}>
  248. Split
  249. </button>
  250. </div>
  251. ) : null}
  252. <div className="navbar-buttons">
  253. <button className={`btn navbar-button ${graphButtonActive}`} onClick={this.handleClickGraphButton}>
  254. Graph
  255. </button>
  256. <button className={`btn navbar-button ${tableButtonActive}`} onClick={this.handleClickTableButton}>
  257. Table
  258. </button>
  259. </div>
  260. <TimePicker range={range} onChangeTime={this.handleChangeTime} />
  261. <div className="navbar-buttons relative">
  262. <button className="btn navbar-button--primary" onClick={this.handleSubmit}>
  263. Run Query <i className="fa fa-level-down run-icon" />
  264. </button>
  265. {loading || latency ? <ElapsedTime time={latency} className="text-info" /> : null}
  266. </div>
  267. </div>
  268. {datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
  269. {datasourceError ? (
  270. <div className="explore-container" title={datasourceError}>
  271. Error connecting to datasource.
  272. </div>
  273. ) : null}
  274. {datasource ? (
  275. <div className="explore-container">
  276. <QueryRows
  277. queries={queries}
  278. request={this.request}
  279. onAddQueryRow={this.handleAddQueryRow}
  280. onChangeQuery={this.handleChangeQuery}
  281. onExecuteQuery={this.handleSubmit}
  282. onRemoveQueryRow={this.handleRemoveQueryRow}
  283. />
  284. {queryError ? <div className="text-warning m-a-2">{queryError}</div> : null}
  285. <main className="m-t-2">
  286. {showingGraph ? (
  287. <Graph
  288. data={graphResult}
  289. id={`explore-graph-${position}`}
  290. options={requestOptions}
  291. height={graphHeight}
  292. split={split}
  293. />
  294. ) : null}
  295. {showingTable ? <Table data={tableResult} className="m-t-3" /> : null}
  296. </main>
  297. </div>
  298. ) : null}
  299. </div>
  300. );
  301. }
  302. }
  303. export default hot(module)(Explore);