QueriesTab.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Libraries
  2. import React, { PureComponent } from 'react';
  3. import _ from 'lodash';
  4. import { css } from 'emotion';
  5. // Components
  6. import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
  7. import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
  8. import { QueryInspector } from './QueryInspector';
  9. import { QueryOptions } from './QueryOptions';
  10. import { PanelOptionsGroup, TransformationsEditor } from '@grafana/ui';
  11. import { QueryEditorRow } from './QueryEditorRow';
  12. // Services
  13. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  14. import { getBackendSrv } from 'app/core/services/backend_srv';
  15. import config from 'app/core/config';
  16. // Types
  17. import { PanelModel } from '../state/PanelModel';
  18. import { DashboardModel } from '../state/DashboardModel';
  19. import { DataQuery, DataSourceSelectItem, PanelData, AlphaNotice, PluginState } from '@grafana/ui';
  20. import { LoadingState, DataTransformerConfig } from '@grafana/data';
  21. import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
  22. import { Unsubscribable } from 'rxjs';
  23. import { isSharedDashboardQuery, DashboardQueryEditor } from 'app/plugins/datasource/dashboard';
  24. interface Props {
  25. panel: PanelModel;
  26. dashboard: DashboardModel;
  27. }
  28. interface State {
  29. currentDS: DataSourceSelectItem;
  30. helpContent: JSX.Element;
  31. isLoadingHelp: boolean;
  32. isPickerOpen: boolean;
  33. isAddingMixed: boolean;
  34. scrollTop: number;
  35. data: PanelData;
  36. }
  37. export class QueriesTab extends PureComponent<Props, State> {
  38. datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
  39. backendSrv = getBackendSrv();
  40. querySubscription: Unsubscribable;
  41. state: State = {
  42. isLoadingHelp: false,
  43. currentDS: this.findCurrentDataSource(),
  44. helpContent: null,
  45. isPickerOpen: false,
  46. isAddingMixed: false,
  47. scrollTop: 0,
  48. data: {
  49. state: LoadingState.NotStarted,
  50. series: [],
  51. },
  52. };
  53. componentDidMount() {
  54. const { panel } = this.props;
  55. const queryRunner = panel.getQueryRunner();
  56. this.querySubscription = queryRunner.getData(false).subscribe({
  57. next: (data: PanelData) => this.onPanelDataUpdate(data),
  58. });
  59. }
  60. componentWillUnmount() {
  61. if (this.querySubscription) {
  62. this.querySubscription.unsubscribe();
  63. this.querySubscription = null;
  64. }
  65. }
  66. onPanelDataUpdate(data: PanelData) {
  67. this.setState({ data });
  68. }
  69. findCurrentDataSource(): DataSourceSelectItem {
  70. const { panel } = this.props;
  71. return this.datasources.find(datasource => datasource.value === panel.datasource) || this.datasources[0];
  72. }
  73. onChangeDataSource = (datasource: any) => {
  74. const { panel } = this.props;
  75. const { currentDS } = this.state;
  76. // switching to mixed
  77. if (datasource.meta.mixed) {
  78. panel.targets.forEach(target => {
  79. target.datasource = panel.datasource;
  80. if (!target.datasource) {
  81. target.datasource = config.defaultDatasource;
  82. }
  83. });
  84. } else if (currentDS) {
  85. // if switching from mixed
  86. if (currentDS.meta.mixed) {
  87. for (const target of panel.targets) {
  88. delete target.datasource;
  89. }
  90. } else if (currentDS.meta.id !== datasource.meta.id) {
  91. // we are changing data source type, clear queries
  92. panel.targets = [{ refId: 'A' }];
  93. }
  94. }
  95. panel.datasource = datasource.value;
  96. panel.refresh();
  97. this.setState({
  98. currentDS: datasource,
  99. });
  100. };
  101. renderQueryInspector = () => {
  102. const { panel } = this.props;
  103. return <QueryInspector panel={panel} />;
  104. };
  105. renderHelp = () => {
  106. return <PluginHelp plugin={this.state.currentDS.meta} type="query_help" />;
  107. };
  108. onAddQuery = (query?: Partial<DataQuery>) => {
  109. this.props.panel.addQuery(query);
  110. this.setState({ scrollTop: this.state.scrollTop + 100000 });
  111. };
  112. onAddQueryClick = () => {
  113. if (this.state.currentDS.meta.mixed) {
  114. this.setState({ isAddingMixed: true });
  115. return;
  116. }
  117. this.onAddQuery();
  118. };
  119. onRemoveQuery = (query: DataQuery) => {
  120. const { panel } = this.props;
  121. const index = _.indexOf(panel.targets, query);
  122. panel.targets.splice(index, 1);
  123. panel.refresh();
  124. this.forceUpdate();
  125. };
  126. onMoveQuery = (query: DataQuery, direction: number) => {
  127. const { panel } = this.props;
  128. const index = _.indexOf(panel.targets, query);
  129. // @ts-ignore
  130. _.move(panel.targets, index, index + direction);
  131. this.forceUpdate();
  132. };
  133. renderToolbar = () => {
  134. const { currentDS, isAddingMixed } = this.state;
  135. const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name));
  136. return (
  137. <>
  138. <DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
  139. <div className="flex-grow-1" />
  140. {showAddButton && (
  141. <button className="btn navbar-button" onClick={this.onAddQueryClick}>
  142. Add Query
  143. </button>
  144. )}
  145. {isAddingMixed && this.renderMixedPicker()}
  146. </>
  147. );
  148. };
  149. renderMixedPicker = () => {
  150. return (
  151. <DataSourcePicker
  152. datasources={this.datasources}
  153. onChange={this.onAddMixedQuery}
  154. current={null}
  155. autoFocus={true}
  156. onBlur={this.onMixedPickerBlur}
  157. openMenuOnFocus={true}
  158. />
  159. );
  160. };
  161. onAddMixedQuery = (datasource: any) => {
  162. this.onAddQuery({ datasource: datasource.name });
  163. this.setState({ isAddingMixed: false, scrollTop: this.state.scrollTop + 10000 });
  164. };
  165. onMixedPickerBlur = () => {
  166. this.setState({ isAddingMixed: false });
  167. };
  168. onQueryChange = (query: DataQuery, index: number) => {
  169. this.props.panel.changeQuery(query, index);
  170. this.forceUpdate();
  171. };
  172. onTransformersChange = (transformers: DataTransformerConfig[]) => {
  173. this.props.panel.setTransformations(transformers);
  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. const enableTransformations = config.featureToggles.transformations;
  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. {isSharedDashboardQuery(currentDS.name) ? (
  203. <DashboardQueryEditor panel={panel} panelData={data} onChange={query => this.onQueryChange(query, 0)} />
  204. ) : (
  205. <>
  206. <div className="query-editor-rows">
  207. {panel.targets.map((query, index) => (
  208. <QueryEditorRow
  209. dataSourceValue={query.datasource || panel.datasource}
  210. key={query.refId}
  211. panel={panel}
  212. dashboard={dashboard}
  213. data={data}
  214. query={query}
  215. onChange={query => this.onQueryChange(query, index)}
  216. onRemoveQuery={this.onRemoveQuery}
  217. onAddQuery={this.onAddQuery}
  218. onMoveQuery={this.onMoveQuery}
  219. inMixedMode={currentDS.meta.mixed}
  220. />
  221. ))}
  222. </div>
  223. <PanelOptionsGroup>
  224. <QueryOptions panel={panel} datasource={currentDS} />
  225. </PanelOptionsGroup>
  226. </>
  227. )}
  228. {enableTransformations && (
  229. <PanelOptionsGroup
  230. title={
  231. <>
  232. Query results
  233. <AlphaNotice
  234. state={PluginState.alpha}
  235. className={css`
  236. margin-left: 16px;
  237. `}
  238. />
  239. </>
  240. }
  241. >
  242. {this.state.data.state !== LoadingState.NotStarted && (
  243. <TransformationsEditor
  244. transformations={this.props.panel.transformations || []}
  245. onChange={this.onTransformersChange}
  246. dataFrames={data.series}
  247. />
  248. )}
  249. </PanelOptionsGroup>
  250. )}
  251. </>
  252. </EditorTabBody>
  253. );
  254. }
  255. }