From 085258c0351cb77126a87eae2421597145535f8e Mon Sep 17 00:00:00 2001 From: Brendan O'Handley Date: Wed, 17 Aug 2022 13:20:46 -0400 Subject: [PATCH] Influxdb Datasource: Remove angular dependencies for Influxdb influxql annotations (#52546) * migrate influxQL annotations to react and build new annotation editor * use es-lint ignore for any type in migration * changes for PR comments * handle annotation editor on load error without query * correct label for ann editor query * add null coalesce operator and remove comment * fix tooltip --- .betterer.results | 14 ++-- .../influxdb/components/AnnotationEditor.tsx | 83 +++++++++++++++++++ .../plugins/datasource/influxdb/datasource.ts | 47 ++++++++--- .../plugins/datasource/influxdb/migrations.ts | 31 +++++++ .../app/plugins/datasource/influxdb/module.ts | 5 -- .../influxdb/partials/annotations.editor.html | 28 ------- .../datasource/influxdb/response_parser.ts | 12 +-- .../influxdb/specs/response_parser.test.ts | 17 ++-- .../app/plugins/datasource/influxdb/types.ts | 9 ++ 9 files changed, 184 insertions(+), 62 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/components/AnnotationEditor.tsx create mode 100644 public/app/plugins/datasource/influxdb/migrations.ts delete mode 100644 public/app/plugins/datasource/influxdb/partials/annotations.editor.html diff --git a/.betterer.results b/.betterer.results index dfcd2fb49dd..6ca4cada3d3 100644 --- a/.betterer.results +++ b/.betterer.results @@ -7116,13 +7116,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "14"], [0, 0, 0, "Unexpected any. Specify a different type.", "15"], [0, 0, 0, "Unexpected any. Specify a different type.", "16"], - [0, 0, 0, "Unexpected any. Specify a different type.", "17"], - [0, 0, 0, "Do not use any type assertions.", "18"], + [0, 0, 0, "Do not use any type assertions.", "17"], + [0, 0, 0, "Unexpected any. Specify a different type.", "18"], [0, 0, 0, "Unexpected any. Specify a different type.", "19"], [0, 0, 0, "Unexpected any. Specify a different type.", "20"], [0, 0, 0, "Unexpected any. Specify a different type.", "21"], - [0, 0, 0, "Unexpected any. Specify a different type.", "22"], - [0, 0, 0, "Do not use any type assertions.", "23"], + [0, 0, 0, "Do not use any type assertions.", "22"], + [0, 0, 0, "Unexpected any. Specify a different type.", "23"], [0, 0, 0, "Unexpected any. Specify a different type.", "24"], [0, 0, 0, "Unexpected any. Specify a different type.", "25"], [0, 0, 0, "Unexpected any. Specify a different type.", "26"], @@ -7132,8 +7132,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "30"], [0, 0, 0, "Unexpected any. Specify a different type.", "31"], [0, 0, 0, "Unexpected any. Specify a different type.", "32"], - [0, 0, 0, "Unexpected any. Specify a different type.", "33"], - [0, 0, 0, "Unexpected any. Specify a different type.", "34"] + [0, 0, 0, "Unexpected any. Specify a different type.", "33"] ], "public/app/plugins/datasource/influxdb/influx_query_model.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -7179,6 +7178,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "17"], [0, 0, 0, "Unexpected any. Specify a different type.", "18"] ], + "public/app/plugins/datasource/influxdb/migrations.ts:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + ], "public/app/plugins/datasource/influxdb/query_builder.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/public/app/plugins/datasource/influxdb/components/AnnotationEditor.tsx b/public/app/plugins/datasource/influxdb/components/AnnotationEditor.tsx new file mode 100644 index 00000000000..a401e24f1c0 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/components/AnnotationEditor.tsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; + +import { QueryEditorProps } from '@grafana/data'; +import { InlineFormLabel, Input } from '@grafana/ui'; + +import { InfluxQuery, InfluxOptions } from '../types'; + +import InfluxDatasource from './../datasource'; + +export const AnnotationEditor = (props: QueryEditorProps) => { + const { query, onChange } = props; + const [eventQuery, setEventQuery] = useState(query.query ?? ''); + + const [textColumn, setTextColumn] = useState(query.textColumn ?? ''); + const [tagsColumn, setTagsColumn] = useState(query.tagsColumn ?? ''); + const [timeEndColumn, setTimeEndColumn] = useState(query?.timeEndColumn ?? ''); + const [titleColumn] = useState(query?.titleColumn ?? ''); + const updateValue = (key: K, val: V) => { + onChange({ + ...query, + [key]: val, + fromAnnotations: true, + textEditor: true, + }); + }; + return ( +
+
+ InfluxQL Query + setEventQuery(e.currentTarget.value ?? '')} + onBlur={() => updateValue('query', eventQuery)} + placeholder="select text from events where $timeFilter limit 1000" + /> +
+ + If your influxdb query returns more than one field you need to specify the column names below. An annotation + event is composed of a title, tags, and an additional text field. Optionally you can map the timeEnd column + for region annotation usage. +
+ } + > + Field mappings + +
+
+
+ Text + setTextColumn(e.currentTarget.value ?? '')} + onBlur={() => updateValue('textColumn', textColumn)} + /> +
+
+ Tags + setTagsColumn(e.currentTarget.value ?? '')} + onBlur={() => updateValue('tagsColumn', tagsColumn)} + /> +
+
+ TimeEnd + setTimeEndColumn(e.currentTarget.value ?? '')} + onBlur={() => updateValue('timeEndColumn', timeEndColumn)} + /> +
+
+ Title + +
+
+
+ + ); +}; diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 1be5585058e..f54bfcacf3e 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -1,10 +1,9 @@ import { cloneDeep, extend, groupBy, has, isString, map as _map, omit, pick, reduce } from 'lodash'; -import { lastValueFrom, Observable, of, throwError } from 'rxjs'; +import { lastValueFrom, merge, Observable, of, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { AnnotationEvent, - AnnotationQueryRequest, ArrayVector, DataFrame, DataQueryError, @@ -19,6 +18,7 @@ import { TIME_SERIES_TIME_FIELD_NAME, TIME_SERIES_VALUE_FIELD_NAME, TimeSeries, + toDataFrame, } from '@grafana/data'; import { BackendDataSourceResponse, @@ -30,10 +30,12 @@ import { import config from 'app/core/config'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; +import { AnnotationEditor } from './components/AnnotationEditor'; import { FluxQueryEditor } from './components/FluxQueryEditor'; import { BROWSER_MODE_DISABLED_MESSAGE } from './constants'; import InfluxQueryModel from './influx_query_model'; import InfluxSeries from './influx_series'; +import { prepareAnnotation } from './migrations'; import { buildRawQuery } from './queryUtils'; import { InfluxQueryBuilder } from './query_builder'; import ResponseParser from './response_parser'; @@ -156,6 +158,11 @@ export default class InfluxDatasource extends DataSourceWithBackend { + // migrate annotations + if (options.targets.some((target: InfluxQuery) => target.fromAnnotations)) { + const streams: Array> = []; + + for (const target of options.targets) { + if (target.query) { + streams.push( + new Observable((subscriber) => { + this.annotationEvents(options, target) + .then((events) => subscriber.next({ data: [toDataFrame(events)] })) + .catch((ex) => subscriber.error(new Error(ex))) + .finally(() => subscriber.complete()); + }) + ); + } + } + + return merge(...streams); + } + let timeFilter = this.getTimeFilter(options); const scopedVars = options.scopedVars; const targets = cloneDeep(options.targets); @@ -353,7 +380,7 @@ export default class InfluxDatasource extends DataSourceWithBackend): Promise { + async annotationEvents(options: DataQueryRequest, annotation: InfluxQuery): Promise { if (this.isFlux) { return Promise.reject({ message: 'Flux requires the standard annotation query', @@ -361,7 +388,7 @@ export default class InfluxDatasource extends DataSourceWithBackend) => - await this.responseParser.transformAnnotationResponse(options, res, target) + await this.responseParser.transformAnnotationResponse(annotation, res, target) ) ) ); } - const timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw, timezone: options.dashboard.timezone }); - let query = options.annotation.query.replace('$timeFilter', timeFilter); + const timeFilter = this.getTimeFilter({ rangeRaw: options.range.raw, timezone: options.timezone }); + let query = annotation.query.replace('$timeFilter', timeFilter); query = this.templateSrv.replace(query, undefined, 'regex'); return lastValueFrom(this._seriesQuery(query, options)).then((data: any) => { @@ -407,7 +434,7 @@ export default class InfluxDatasource extends DataSourceWithBackend { + return { + query: json.query ?? '', + queryType: 'tags', + fromAnnotations: true, + tagsColumn: json.tagsColumn ?? '', + textColumn: json.textColumn ?? '', + timeEndColumn: json.timeEndColumn ?? '', + titleColumn: json.titleColumn ?? '', + name: json.name ?? '', + }; +}; + +// eslint-ignore-next-line +export const prepareAnnotation = (json: any) => { + json.target = json.target ?? migrateLegacyAnnotation(json); + + return json; +}; diff --git a/public/app/plugins/datasource/influxdb/module.ts b/public/app/plugins/datasource/influxdb/module.ts index 24d9e767648..dc22d3b3fb0 100644 --- a/public/app/plugins/datasource/influxdb/module.ts +++ b/public/app/plugins/datasource/influxdb/module.ts @@ -6,13 +6,8 @@ import { QueryEditor } from './components/QueryEditor'; import VariableQueryEditor from './components/VariableQueryEditor'; import InfluxDatasource from './datasource'; -class InfluxAnnotationsQueryCtrl { - static templateUrl = 'partials/annotations.editor.html'; -} - export const plugin = new DataSourcePlugin(InfluxDatasource) .setConfigEditor(ConfigEditor) .setQueryEditor(QueryEditor) - .setAnnotationQueryCtrl(InfluxAnnotationsQueryCtrl) .setVariableQueryEditor(VariableQueryEditor) .setQueryEditorHelp(InfluxStartPage); diff --git a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html deleted file mode 100644 index f6b6b5894c0..00000000000 --- a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html +++ /dev/null @@ -1,28 +0,0 @@ - -
-
- -
-
- -
Field mappings If your influxdb query returns more than one field you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field. Optionally you can map the timeEnd column for region annotation usage.
-
-
-
- Text - -
-
- Tags - -
-
- TimeEnd - -
-
- Title (deprecated) - -
-
-
diff --git a/public/app/plugins/datasource/influxdb/response_parser.ts b/public/app/plugins/datasource/influxdb/response_parser.ts index 037e0f60ff7..a156e98be56 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.ts @@ -88,7 +88,7 @@ export default class ResponseParser { return table; } - async transformAnnotationResponse(options: any, data: any, target: InfluxQuery): Promise { + async transformAnnotationResponse(annotation: any, data: any, target: InfluxQuery): Promise { const rsp = toDataQueryResponse(data, [target] as DataQuery[]); if (rsp) { @@ -105,19 +105,19 @@ export default class ResponseParser { timeCol = index; return; } - if (column.text === options.annotation.titleColumn) { + if (column.text === annotation.titleColumn) { titleCol = index; return; } - if (colContainsTag(column.text, options.annotation.tagsColumn)) { + if (colContainsTag(column.text, annotation.tagsColumn)) { tagsCol.push(index); return; } - if (column.text.includes(options.annotation.textColumn)) { + if (column.text.includes(annotation.textColumn)) { textCol = index; return; } - if (column.text === options.annotation.timeEndColumn) { + if (column.text === annotation.timeEndColumn) { timeEndCol = index; return; } @@ -129,7 +129,7 @@ export default class ResponseParser { each(table.rows, (value) => { const data = { - annotation: options.annotation, + annotation: annotation, time: +new Date(value[timeCol]), title: value[titleCol], timeEnd: value[timeEndCol], diff --git a/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts b/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts index 4ce76c6fa05..c908fff33ca 100644 --- a/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts +++ b/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts @@ -306,13 +306,16 @@ describe('influxdb response parser', () => { const fetchMock = jest.spyOn(backendSrv, 'fetch'); + const annotation = { + fromAnnotations: true, + name: 'Anno', + query: 'select * from logs where time >= now() - 15m and time <= now()', + textColumn: 'textColumn', + tagsColumn: 'host,path', + }; + const queryOptions: any = { - annotation: { - name: 'Anno', - query: 'select * from logs where time >= now() - 15m and time <= now()', - textColumn: 'textColumn', - tagsColumn: 'host,path', - }, + targets: [annotation], range: { from: '2018-01-01T00:00:00Z', to: '2018-01-02T00:00:00Z', @@ -424,7 +427,7 @@ describe('influxdb response parser', () => { ctx.ds = new InfluxDatasource(ctx.instanceSettings, templateSrv); ctx.ds.access = 'proxy'; config.featureToggles.influxdbBackendMigration = true; - response = await ctx.ds.annotationQuery(queryOptions); + response = await ctx.ds.annotationEvents(queryOptions, annotation); }); it('should return annotation list', () => { diff --git a/public/app/plugins/datasource/influxdb/types.ts b/public/app/plugins/datasource/influxdb/types.ts index 8eb1ef59408..a3eb732c568 100644 --- a/public/app/plugins/datasource/influxdb/types.ts +++ b/public/app/plugins/datasource/influxdb/types.ts @@ -61,4 +61,13 @@ export interface InfluxQuery extends DataQuery { rawQuery?: boolean; query?: string; alias?: string; + // for migrated InfluxQL annotations + queryType?: string; + fromAnnotations?: boolean; + tagsColumn?: string; + textColumn?: string; + timeEndColumn?: string; + titleColumn?: string; + name?: string; + textEditor?: boolean; }