QueriesTab.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import React, { PureComponent } from 'react';
  2. import DataSourceOption from './DataSourceOption';
  3. import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
  4. import { EditorTabBody } from './EditorTabBody';
  5. import { DataSourcePicker } from './DataSourcePicker';
  6. import { PanelModel } from '../panel_model';
  7. import { DashboardModel } from '../dashboard_model';
  8. import './../../panel/metrics_tab';
  9. import config from 'app/core/config';
  10. // Services
  11. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  12. import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
  13. import { DataSourceSelectItem } from 'app/types';
  14. import Remarkable from 'remarkable';
  15. interface Props {
  16. panel: PanelModel;
  17. dashboard: DashboardModel;
  18. }
  19. interface Help {
  20. isLoading: boolean;
  21. helpHtml: any;
  22. }
  23. interface State {
  24. currentDatasource: DataSourceSelectItem;
  25. help: Help;
  26. }
  27. export class QueriesTab extends PureComponent<Props, State> {
  28. element: any;
  29. component: AngularComponent;
  30. datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
  31. backendSrv: BackendSrv = getBackendSrv();
  32. constructor(props) {
  33. super(props);
  34. const { panel } = props;
  35. this.state = {
  36. currentDatasource: this.datasources.find(datasource => datasource.value === panel.datasource),
  37. help: {
  38. isLoading: false,
  39. helpHtml: null,
  40. },
  41. };
  42. }
  43. componentDidMount() {
  44. if (!this.element) {
  45. return;
  46. }
  47. const { panel, dashboard } = this.props;
  48. const loader = getAngularLoader();
  49. const template = '<metrics-tab />';
  50. const scopeProps = {
  51. ctrl: {
  52. panel: panel,
  53. dashboard: dashboard,
  54. refresh: () => panel.refresh(),
  55. },
  56. };
  57. this.component = loader.load(this.element, scopeProps, template);
  58. }
  59. componentWillUnmount() {
  60. if (this.component) {
  61. this.component.destroy();
  62. }
  63. }
  64. onChangeDataSource = datasource => {
  65. const { panel } = this.props;
  66. const { currentDatasource } = this.state;
  67. // switching to mixed
  68. if (datasource.meta.mixed) {
  69. panel.targets.forEach(target => {
  70. target.datasource = panel.datasource;
  71. if (!target.datasource) {
  72. target.datasource = config.defaultDatasource;
  73. }
  74. });
  75. } else if (currentDatasource && currentDatasource.meta.mixed) {
  76. panel.targets.forEach(target => {
  77. delete target.datasource;
  78. });
  79. }
  80. panel.datasource = datasource.value;
  81. panel.refresh();
  82. this.setState(prevState => ({
  83. ...prevState,
  84. currentDatasource: datasource,
  85. }));
  86. };
  87. loadHelp = () => {
  88. const { currentDatasource } = this.state;
  89. const hasHelp = currentDatasource.meta.hasQueryHelp;
  90. if (hasHelp) {
  91. this.setState(prevState => ({
  92. ...prevState,
  93. help: {
  94. helpHtml: <h2>Loading help...</h2>,
  95. isLoading: true,
  96. },
  97. }));
  98. this.backendSrv
  99. .get(`/api/plugins/${currentDatasource.meta.id}/markdown/query_help`)
  100. .then(res => {
  101. const md = new Remarkable();
  102. const helpHtml = md.render(res); // TODO: Clean out dangerous code? Previous: this.helpHtml = this.$sce.trustAsHtml(md.render(res));
  103. this.setState(prevState => ({
  104. ...prevState,
  105. help: {
  106. helpHtml: <div className="markdown-html" dangerouslySetInnerHTML={{ __html: helpHtml }} />,
  107. isLoading: false,
  108. },
  109. }));
  110. })
  111. .catch(() => {
  112. this.setState(prevState => ({
  113. ...prevState,
  114. help: {
  115. helpHtml: 'Error occured when loading help',
  116. isLoading: false,
  117. },
  118. }));
  119. });
  120. }
  121. };
  122. renderOptions = close => {
  123. const { currentDatasource } = this.state;
  124. const { queryOptions } = currentDatasource.meta;
  125. const { panel } = this.props;
  126. const onChangeFn = (panelKey: string) => {
  127. return (value: string | number) => {
  128. panel[panelKey] = value;
  129. panel.refresh();
  130. };
  131. };
  132. const allOptions = {
  133. cacheTimeout: {
  134. label: 'Cache timeout',
  135. placeholder: '60',
  136. name: 'cacheTimeout',
  137. value: panel.cacheTimeout,
  138. tooltipInfo: (
  139. <>
  140. If your time series store has a query cache this option can override the default cache timeout. Specify a
  141. numeric value in seconds.
  142. </>
  143. ),
  144. },
  145. maxDataPoints: {
  146. label: 'Max data points',
  147. placeholder: 'auto',
  148. name: 'maxDataPoints',
  149. value: panel.maxDataPoints,
  150. tooltipInfo: (
  151. <>
  152. The maximum data points the query should return. For graphs this is automatically set to one data point per
  153. pixel.
  154. </>
  155. ),
  156. },
  157. minInterval: {
  158. label: 'Min time interval',
  159. placeholder: '0',
  160. name: 'minInterval',
  161. value: panel.interval,
  162. panelKey: 'interval',
  163. tooltipInfo: (
  164. <>
  165. A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '}
  166. <code>1m</code> if your data is written every minute. Access auto interval via variable{' '}
  167. <code>$__interval</code> for time range string and <code>$__interval_ms</code> for numeric variable that can
  168. be used in math expressions.
  169. </>
  170. ),
  171. },
  172. };
  173. return Object.keys(queryOptions).map(key => {
  174. const options = allOptions[key];
  175. return <DataSourceOption key={key} {...options} onChange={onChangeFn(allOptions[key].panelKey || key)} />;
  176. });
  177. };
  178. render() {
  179. const { currentDatasource } = this.state;
  180. const { helpHtml } = this.state.help;
  181. const { hasQueryHelp, queryOptions } = currentDatasource.meta;
  182. const hasQueryOptions = !!queryOptions;
  183. const dsInformation = {
  184. title: currentDatasource.name,
  185. imgSrc: currentDatasource.meta.info.logos.small,
  186. render: closeOpenView => (
  187. <DataSourcePicker
  188. datasources={this.datasources}
  189. onChangeDataSource={ds => {
  190. closeOpenView();
  191. this.onChangeDataSource(ds);
  192. }}
  193. />
  194. ),
  195. };
  196. const queryInspector = {
  197. title: 'Query Inspector',
  198. render: () => <h2>hello</h2>,
  199. };
  200. const dsHelp = {
  201. title: '',
  202. icon: 'fa fa-question',
  203. disabled: !hasQueryHelp,
  204. onClick: this.loadHelp,
  205. render: () => helpHtml,
  206. };
  207. const options = {
  208. title: 'Options',
  209. disabled: !hasQueryOptions,
  210. render: this.renderOptions,
  211. };
  212. return (
  213. <EditorTabBody heading="Queries" main={dsInformation} toolbarItems={[options, queryInspector, dsHelp]}>
  214. <div ref={element => (this.element = element)} style={{ width: '100%' }} />
  215. </EditorTabBody>
  216. );
  217. }
  218. }