StandardAnnotationQueryEditor.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import { css, cx } from '@emotion/css';
  2. import React, { PureComponent } from 'react';
  3. import { lastValueFrom } from 'rxjs';
  4. import { AnnotationEventMappings, AnnotationQuery, DataQuery, DataSourceApi, LoadingState } from '@grafana/data';
  5. import { Button, Icon, IconName, Spinner } from '@grafana/ui';
  6. import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
  7. import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
  8. import { PanelModel } from 'app/features/dashboard/state';
  9. import { executeAnnotationQuery } from '../executeAnnotationQuery';
  10. import { shouldUseLegacyRunner, shouldUseMappingUI, standardAnnotationSupport } from '../standardAnnotationSupport';
  11. import { AnnotationQueryResponse } from '../types';
  12. import { AnnotationFieldMapper } from './AnnotationResultMapper';
  13. interface Props {
  14. datasource: DataSourceApi;
  15. annotation: AnnotationQuery<DataQuery>;
  16. onChange: (annotation: AnnotationQuery<DataQuery>) => void;
  17. }
  18. interface State {
  19. running?: boolean;
  20. response?: AnnotationQueryResponse;
  21. }
  22. export default class StandardAnnotationQueryEditor extends PureComponent<Props, State> {
  23. state = {} as State;
  24. componentDidMount() {
  25. this.verifyDataSource();
  26. }
  27. componentDidUpdate(oldProps: Props) {
  28. if (this.props.annotation !== oldProps.annotation && !shouldUseLegacyRunner(this.props.datasource)) {
  29. this.verifyDataSource();
  30. }
  31. }
  32. verifyDataSource() {
  33. const { datasource, annotation } = this.props;
  34. // Handle any migration issues
  35. const processor = {
  36. ...standardAnnotationSupport,
  37. ...datasource.annotations,
  38. };
  39. const fixed = processor.prepareAnnotation!(annotation);
  40. if (fixed !== annotation) {
  41. this.props.onChange(fixed);
  42. } else {
  43. this.onRunQuery();
  44. }
  45. }
  46. onRunQuery = async () => {
  47. const { datasource, annotation } = this.props;
  48. if (shouldUseLegacyRunner(datasource)) {
  49. // In the new UI the running of query is done so the data can be mapped. In the legacy annotations this does
  50. // not exist as the annotationQuery already returns annotation events which cannot be mapped. This means that
  51. // right now running a query for data source with legacy runner does not make much sense.
  52. return;
  53. }
  54. const dashboard = getDashboardSrv().getCurrent();
  55. if (!dashboard) {
  56. return;
  57. }
  58. this.setState({
  59. running: true,
  60. });
  61. const response = await lastValueFrom(
  62. executeAnnotationQuery(
  63. {
  64. range: getTimeSrv().timeRange(),
  65. panel: new PanelModel({}),
  66. dashboard,
  67. },
  68. datasource,
  69. annotation
  70. )
  71. );
  72. this.setState({
  73. running: false,
  74. response,
  75. });
  76. };
  77. onQueryChange = (target: DataQuery) => {
  78. this.props.onChange({
  79. ...this.props.annotation,
  80. target,
  81. });
  82. };
  83. onMappingChange = (mappings?: AnnotationEventMappings) => {
  84. this.props.onChange({
  85. ...this.props.annotation,
  86. mappings,
  87. });
  88. };
  89. renderStatus() {
  90. const { response, running } = this.state;
  91. let rowStyle = 'alert-info';
  92. let text = '...';
  93. let icon: IconName | undefined = undefined;
  94. if (running || response?.panelData?.state === LoadingState.Loading || !response) {
  95. text = 'loading...';
  96. } else {
  97. const { events, panelData } = response;
  98. if (panelData?.error) {
  99. rowStyle = 'alert-error';
  100. icon = 'exclamation-triangle';
  101. text = panelData.error.message ?? 'error';
  102. } else if (!events?.length) {
  103. rowStyle = 'alert-warning';
  104. icon = 'exclamation-triangle';
  105. text = 'No events found';
  106. } else {
  107. const frame = panelData?.series[0];
  108. text = `${events.length} events (from ${frame?.fields.length} fields)`;
  109. }
  110. }
  111. return (
  112. <div
  113. className={cx(
  114. rowStyle,
  115. css`
  116. margin: 4px 0px;
  117. padding: 4px;
  118. display: flex;
  119. justify-content: space-between;
  120. align-items: center;
  121. `
  122. )}
  123. >
  124. <div>
  125. {icon && (
  126. <>
  127. <Icon name={icon} />
  128. &nbsp;
  129. </>
  130. )}
  131. {text}
  132. </div>
  133. <div>
  134. {running ? (
  135. <Spinner />
  136. ) : (
  137. <Button variant="secondary" size="xs" onClick={this.onRunQuery}>
  138. TEST
  139. </Button>
  140. )}
  141. </div>
  142. </div>
  143. );
  144. }
  145. onAnnotationChange = (annotation: AnnotationQuery) => {
  146. this.props.onChange(annotation);
  147. };
  148. render() {
  149. const { datasource, annotation } = this.props;
  150. const { response } = this.state;
  151. // Find the annotation runner
  152. let QueryEditor = datasource.annotations?.QueryEditor || datasource.components?.QueryEditor;
  153. if (!QueryEditor) {
  154. return <div>Annotations are not supported. This datasource needs to export a QueryEditor</div>;
  155. }
  156. const query = annotation.target ?? { refId: 'Anno' };
  157. return (
  158. <>
  159. <QueryEditor
  160. key={datasource?.name}
  161. query={query}
  162. datasource={datasource}
  163. onChange={this.onQueryChange}
  164. onRunQuery={this.onRunQuery}
  165. data={response?.panelData}
  166. range={getTimeSrv().timeRange()}
  167. annotation={annotation}
  168. onAnnotationChange={this.onAnnotationChange}
  169. />
  170. {shouldUseMappingUI(datasource) && (
  171. <>
  172. {this.renderStatus()}
  173. <AnnotationFieldMapper response={response} mappings={annotation.mappings} change={this.onMappingChange} />
  174. </>
  175. )}
  176. </>
  177. );
  178. }
  179. }