QueriesTab.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // Libraries
  2. import React, { PureComponent } from 'react';
  3. import _ from 'lodash';
  4. // Components
  5. import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
  6. import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
  7. import { QueryInspector } from './QueryInspector';
  8. import { QueryOptions } from './QueryOptions';
  9. import { PanelOptionsGroup } from '@grafana/ui';
  10. import { QueryEditorRow } from './QueryEditorRow';
  11. // Services
  12. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  13. import { getBackendSrv } from 'app/core/services/backend_srv';
  14. import config from 'app/core/config';
  15. // Types
  16. import { PanelModel } from '../state/PanelModel';
  17. import { DashboardModel } from '../state/DashboardModel';
  18. import { DataQuery, DataSourceSelectItem, PanelData, LoadingState } from '@grafana/ui/src/types';
  19. import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
  20. import { PanelQueryRunnerFormat } from '../state/PanelQueryRunner';
  21. import { Unsubscribable } from 'rxjs';
  22. interface Props {
  23. panel: PanelModel;
  24. dashboard: DashboardModel;
  25. }
  26. interface State {
  27. currentDS: DataSourceSelectItem;
  28. helpContent: JSX.Element;
  29. isLoadingHelp: boolean;
  30. isPickerOpen: boolean;
  31. isAddingMixed: boolean;
  32. scrollTop: number;
  33. data: PanelData;
  34. }
  35. export class QueriesTab extends PureComponent<Props, State> {
  36. datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
  37. backendSrv = getBackendSrv();
  38. querySubscription: Unsubscribable;
  39. state: State = {
  40. isLoadingHelp: false,
  41. currentDS: this.findCurrentDataSource(),
  42. helpContent: null,
  43. isPickerOpen: false,
  44. isAddingMixed: false,
  45. scrollTop: 0,
  46. data: {
  47. state: LoadingState.NotStarted,
  48. series: [],
  49. },
  50. };
  51. componentDidMount() {
  52. const { panel } = this.props;
  53. const queryRunner = panel.getQueryRunner();
  54. this.querySubscription = queryRunner.subscribe(this.panelDataObserver, PanelQueryRunnerFormat.both);
  55. }
  56. componentWillUnmount() {
  57. if (this.querySubscription) {
  58. this.querySubscription.unsubscribe();
  59. this.querySubscription = null;
  60. }
  61. }
  62. // Updates the response with information from the stream
  63. panelDataObserver = {
  64. next: (data: PanelData) => {
  65. const { panel } = this.props;
  66. if (data.state === LoadingState.Error) {
  67. panel.events.emit('data-error', data.error);
  68. } else if (data.state === LoadingState.Done) {
  69. panel.events.emit('data-received', data.legacy);
  70. }
  71. this.setState({ data });
  72. },
  73. };
  74. findCurrentDataSource(): DataSourceSelectItem {
  75. const { panel } = this.props;
  76. return this.datasources.find(datasource => datasource.value === panel.datasource) || this.datasources[0];
  77. }
  78. onChangeDataSource = datasource => {
  79. const { panel } = this.props;
  80. const { currentDS } = this.state;
  81. // switching to mixed
  82. if (datasource.meta.mixed) {
  83. panel.targets.forEach(target => {
  84. target.datasource = panel.datasource;
  85. if (!target.datasource) {
  86. target.datasource = config.defaultDatasource;
  87. }
  88. });
  89. } else if (currentDS) {
  90. // if switching from mixed
  91. if (currentDS.meta.mixed) {
  92. for (const target of panel.targets) {
  93. delete target.datasource;
  94. }
  95. } else if (currentDS.meta.id !== datasource.meta.id) {
  96. // we are changing data source type, clear queries
  97. panel.targets = [{ refId: 'A' }];
  98. }
  99. }
  100. panel.datasource = datasource.value;
  101. panel.refresh();
  102. this.setState({
  103. currentDS: datasource,
  104. });
  105. };
  106. renderQueryInspector = () => {
  107. const { panel } = this.props;
  108. return <QueryInspector panel={panel} />;
  109. };
  110. renderHelp = () => {
  111. return <PluginHelp plugin={this.state.currentDS.meta} type="query_help" />;
  112. };
  113. onAddQuery = (query?: Partial<DataQuery>) => {
  114. this.props.panel.addQuery(query);
  115. this.setState({ scrollTop: this.state.scrollTop + 100000 });
  116. };
  117. onAddQueryClick = () => {
  118. if (this.state.currentDS.meta.mixed) {
  119. this.setState({ isAddingMixed: true });
  120. return;
  121. }
  122. this.onAddQuery();
  123. };
  124. onRemoveQuery = (query: DataQuery) => {
  125. const { panel } = this.props;
  126. const index = _.indexOf(panel.targets, query);
  127. panel.targets.splice(index, 1);
  128. panel.refresh();
  129. this.forceUpdate();
  130. };
  131. onMoveQuery = (query: DataQuery, direction: number) => {
  132. const { panel } = this.props;
  133. const index = _.indexOf(panel.targets, query);
  134. // @ts-ignore
  135. _.move(panel.targets, index, index + direction);
  136. this.forceUpdate();
  137. };
  138. renderToolbar = () => {
  139. const { currentDS, isAddingMixed } = this.state;
  140. return (
  141. <>
  142. <DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
  143. <div className="flex-grow-1" />
  144. {!isAddingMixed && (
  145. <button className="btn navbar-button" onClick={this.onAddQueryClick}>
  146. Add Query
  147. </button>
  148. )}
  149. {isAddingMixed && this.renderMixedPicker()}
  150. </>
  151. );
  152. };
  153. renderMixedPicker = () => {
  154. return (
  155. <DataSourcePicker
  156. datasources={this.datasources}
  157. onChange={this.onAddMixedQuery}
  158. current={null}
  159. autoFocus={true}
  160. onBlur={this.onMixedPickerBlur}
  161. openMenuOnFocus={true}
  162. />
  163. );
  164. };
  165. onAddMixedQuery = datasource => {
  166. this.onAddQuery({ datasource: datasource.name });
  167. this.setState({ isAddingMixed: false, scrollTop: this.state.scrollTop + 10000 });
  168. };
  169. onMixedPickerBlur = () => {
  170. this.setState({ isAddingMixed: false });
  171. };
  172. onQueryChange = (query: DataQuery, index) => {
  173. this.props.panel.changeQuery(query, index);
  174. this.forceUpdate();
  175. };
  176. setScrollTop = (event: React.MouseEvent<HTMLElement>) => {
  177. const target = event.target as HTMLElement;
  178. this.setState({ scrollTop: target.scrollTop });
  179. };
  180. render() {
  181. const { panel, dashboard } = this.props;
  182. const { currentDS, scrollTop, data } = this.state;
  183. const queryInspector: EditorToolbarView = {
  184. title: 'Query Inspector',
  185. render: this.renderQueryInspector,
  186. };
  187. const dsHelp: EditorToolbarView = {
  188. heading: 'Help',
  189. icon: 'fa fa-question',
  190. render: this.renderHelp,
  191. };
  192. return (
  193. <EditorTabBody
  194. heading="Query"
  195. renderToolbar={this.renderToolbar}
  196. toolbarItems={[queryInspector, dsHelp]}
  197. setScrollTop={this.setScrollTop}
  198. scrollTop={scrollTop}
  199. >
  200. <>
  201. <div className="query-editor-rows">
  202. {panel.targets.map((query, index) => (
  203. <QueryEditorRow
  204. dataSourceValue={query.datasource || panel.datasource}
  205. key={query.refId}
  206. panel={panel}
  207. dashboard={dashboard}
  208. data={data}
  209. query={query}
  210. onChange={query => this.onQueryChange(query, index)}
  211. onRemoveQuery={this.onRemoveQuery}
  212. onAddQuery={this.onAddQuery}
  213. onMoveQuery={this.onMoveQuery}
  214. inMixedMode={currentDS.meta.mixed}
  215. />
  216. ))}
  217. </div>
  218. <PanelOptionsGroup>
  219. <QueryOptions panel={panel} datasource={currentDS} />
  220. </PanelOptionsGroup>
  221. </>
  222. </EditorTabBody>
  223. );
  224. }
  225. }