QueriesTab.tsx 7.9 KB

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