QueriesTab.tsx 7.3 KB

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