From fee2eb3716b501b769b45e137e4171652a43e970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Bedi?= Date: Tue, 5 Jul 2022 20:42:42 +0200 Subject: [PATCH] SQLDS: Use builtin annotation editor Plus strict rule fixes --- .betterer.results | 17 --- public/app/features/plugins/sql/constants.ts | 30 +++-- .../plugins/sql/datasource/SqlDatasource.ts | 112 ++++++++---------- public/app/features/plugins/sql/defaults.ts | 15 +-- public/app/features/plugins/sql/types.ts | 9 +- public/app/features/variables/utils.ts | 6 +- 6 files changed, 88 insertions(+), 101 deletions(-) diff --git a/.betterer.results b/.betterer.results index e1450844209..e18424ab5a8 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5573,15 +5573,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], - "public/app/features/plugins/sql/datasource/SqlDatasource.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] - ], "public/app/features/plugins/tests/datasource_srv.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -7765,14 +7756,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"] ], - "public/app/plugins/datasource/mssql/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] - ], "public/app/plugins/datasource/mysql/datasource.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/features/plugins/sql/constants.ts b/public/app/features/plugins/sql/constants.ts index 38eaff650ee..7518715682e 100644 --- a/public/app/features/plugins/sql/constants.ts +++ b/public/app/features/plugins/sql/constants.ts @@ -9,7 +9,7 @@ export const AGGREGATE_FNS = [ expression ) [OVER (...)] - + Returns the average of non-NULL input values, or NaN if the input contains a NaN.`, }, { @@ -17,13 +17,13 @@ export const AGGREGATE_FNS = [ name: 'COUNT', description: `COUNT(*) [OVER (...)] Returns the number of rows in the input. - + COUNT( [DISTINCT] expression ) [OVER (...)] - + Returns the number of rows with expression evaluated to any value other than NULL. `, }, @@ -34,7 +34,7 @@ export const AGGREGATE_FNS = [ expression ) [OVER (...)] - + Returns the maximum value of non-NULL expressions. Returns NULL if there are zero input rows or expression evaluates to NULL for all rows. Returns NaN if the input contains a NaN. `, }, @@ -45,7 +45,7 @@ export const AGGREGATE_FNS = [ expression ) [OVER (...)] - + Returns the minimum value of non-NULL expressions. Returns NULL if there are zero input rows or expression evaluates to NULL for all rows. Returns NaN if the input contains a NaN. `, }, @@ -57,9 +57,9 @@ export const AGGREGATE_FNS = [ expression ) [OVER (...)] - + Returns the sum of non-null values. - + If the expression is a floating point value, the sum is non-deterministic, which means you might receive a different result each time you use this function. `, }, @@ -110,3 +110,19 @@ export const OPERATORS = [ { type: OperatorType.Logical, id: 'AND', operator: 'AND' }, { type: OperatorType.Logical, id: 'OR', operator: 'OR' }, ]; + +export const MACRO_NAMES = [ + '$__time', + '$__timeEpoch', + '$__timeFilter', + '$__timeFrom', + '$__timeTo', + '$__timeGroup', + '$__timeGroupAlias', + '$__unixEpochFilter', + '$__unixEpochNanoFilter', + '$__unixEpochNanoFrom', + '$__unixEpochNanoTo', + '$__unixEpochGroup', + '$__unixEpochGroupAlias', +]; diff --git a/public/app/features/plugins/sql/datasource/SqlDatasource.ts b/public/app/features/plugins/sql/datasource/SqlDatasource.ts index 73e18ba4b18..8039583dad2 100644 --- a/public/app/features/plugins/sql/datasource/SqlDatasource.ts +++ b/public/app/features/plugins/sql/datasource/SqlDatasource.ts @@ -2,15 +2,14 @@ import { lastValueFrom, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { - AnnotationEvent, DataFrame, DataFrameView, - DataQueryRequest, - DataQueryResponse, + DataQuery, DataSourceInstanceSettings, DataSourceRef, MetricFindValue, ScopedVars, + TimeRange, } from '@grafana/data'; import { BackendDataSourceResponse, @@ -20,10 +19,12 @@ import { getTemplateSrv, TemplateSrv, } from '@grafana/runtime'; -import { toTestingStatus } from '@grafana/runtime/src/utils/queryResponse'; +import { toDataQueryResponse, toTestingStatus } from '@grafana/runtime/src/utils/queryResponse'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { VariableWithMultiSupport } from '../../../variables/types'; -import { getSearchFilterScopedVar } from '../../../variables/utils'; +import { getSearchFilterScopedVar, SearchFilterOptions } from '../../../variables/utils'; +import { MACRO_NAMES } from '../constants'; import { DB, SQLQuery, @@ -39,6 +40,7 @@ export abstract class SqlDatasource extends DataSourceWithBackend, @@ -121,78 +123,56 @@ export abstract class SqlDatasource extends DataSourceWithBackend { - if (!options.annotation.rawQuery) { - return Promise.reject({ - message: 'Query missing in annotation definition', - }); - } + async metricFindQuery(query: string, optionalOptions?: MetricFindQueryOptions): Promise { + const rawSql = this.templateSrv.replace( + query, + getSearchFilterScopedVar({ query, wildcardChar: '%', options: optionalOptions }), + this.interpolateVariable + ); - const query = { - refId: options.annotation.name, + const interpolatedQuery: SQLQuery = { + refId: 'tempvar', datasource: this.getRef(), - rawSql: this.templateSrv.replace(options.annotation.rawQuery, options.scopedVars, this.interpolateVariable), - format: 'table', + rawSql, + format: QueryFormat.Table, }; + const response = await this.runMetaQuery(interpolatedQuery, optionalOptions); + return this.getResponseParser().transformMetricFindResponse(response); + } + + async runSql(query: string, options?: MetricFindQueryOptions) { + const frame = await this.runMetaQuery({ rawSql: query, format: QueryFormat.Table }, options); + return new DataFrameView(frame); + } + + private runMetaQuery(request: Partial, options?: MetricFindQueryOptions): Promise { + const range = getTimeSrv().timeRange(); + const refId = request.refId || 'meta'; + const queries: DataQuery[] = [{ ...request, datasource: request.datasource || this.getRef(), refId }]; + return lastValueFrom( getBackendSrv() .fetch({ url: '/api/ds/query', method: 'POST', data: { - from: options.range.from.valueOf().toString(), - to: options.range.to.valueOf().toString(), - queries: [query], + from: options?.range?.from.valueOf().toString() || range.from.valueOf().toString(), + to: options?.range?.to.valueOf().toString() || range.to.valueOf().toString(), + queries, }, - requestId: options.annotation.name, + requestId: refId, }) .pipe( - map( - async (res: FetchResponse) => - await this.getResponseParser().transformAnnotationResponse(options, res.data) - ) + map((res: FetchResponse) => { + const rsp = toDataQueryResponse(res, queries); + return rsp.data[0]; + }) ) ); } - async metricFindQuery(query: string, optionalOptions: any): Promise { - const rawSql = this.templateSrv.replace( - query, - getSearchFilterScopedVar({ query, wildcardChar: '%', options: optionalOptions }), - this.interpolateVariable - ); - - const interpolatedQuery = { - datasourceId: this.id, - datasource: this.getRef(), - rawSql, - format: QueryFormat.Table, - }; - - const response = await this.runQuery(interpolatedQuery, optionalOptions); - return this.getResponseParser().transformMetricFindResponse(response); - } - - async runSql(query: string) { - const frame = await this.runQuery({ rawSql: query, format: QueryFormat.Table }, {}); - return new DataFrameView(frame); - } - - private runQuery(request: Partial, options?: any): Promise { - return new Promise((resolve) => { - const req = { - targets: [{ ...request, refId: String(Math.random()) }], - range: options?.range, - } as DataQueryRequest; - this.query(req).subscribe((res: DataQueryResponse) => { - resolve(res.data[0] || { fields: [] }); - }); - }); - } - - testDatasource(): Promise { + testDatasource(): Promise<{ status: string; message: string }> { return lastValueFrom( getBackendSrv() .fetch({ @@ -223,7 +203,15 @@ export abstract class SqlDatasource extends DataSourceWithBackend { + queryWithoutMacros = queryWithoutMacros?.replace(value, '') || ''; + }); + return this.templateSrv.containsTemplate(queryWithoutMacros); } } + +interface MetricFindQueryOptions extends SearchFilterOptions { + range?: TimeRange; +} diff --git a/public/app/features/plugins/sql/defaults.ts b/public/app/features/plugins/sql/defaults.ts index 6093df7adc0..e67a4128acb 100644 --- a/public/app/features/plugins/sql/defaults.ts +++ b/public/app/features/plugins/sql/defaults.ts @@ -3,20 +3,21 @@ import { EditorMode } from '@grafana/experimental'; import { QueryFormat, SQLQuery } from './types'; import { createFunctionField, setGroupByField } from './utils/sql.utils'; -export function applyQueryDefaults(q: SQLQuery): SQLQuery { - let editorMode = q.editorMode || EditorMode.Builder; +export function applyQueryDefaults(q?: SQLQuery): SQLQuery { + let editorMode = q?.editorMode || EditorMode.Builder; // Switching to code editor if the query was created before visual query builder was introduced. - if (q.editorMode === undefined && q.rawSql !== undefined) { + if (q?.editorMode === undefined && q?.rawSql !== undefined) { editorMode = EditorMode.Code; } - const result = { + const result: SQLQuery = { ...q, - format: q.format !== undefined ? q.format : QueryFormat.Table, - rawSql: q.rawSql || '', + refId: q?.refId || 'A', + format: q?.format !== undefined ? q.format : QueryFormat.Table, + rawSql: q?.rawSql || '', editorMode, - sql: q.sql || { + sql: q?.sql || { columns: [createFunctionField()], groupBy: [setGroupByField()], limit: 50, diff --git a/public/app/features/plugins/sql/types.ts b/public/app/features/plugins/sql/types.ts index 29ef49e4f22..b5509dbba2e 100644 --- a/public/app/features/plugins/sql/types.ts +++ b/public/app/features/plugins/sql/types.ts @@ -1,7 +1,6 @@ import { JsonTree } from 'react-awesome-query-builder'; import { - AnnotationEvent, DataFrame, DataQuery, DataSourceJsonData, @@ -11,7 +10,6 @@ import { toOption as toOptionFromData, } from '@grafana/data'; import { CompletionItemKind, EditorMode, LanguageCompletionProvider } from '@grafana/experimental'; -import { BackendDataSourceResponse } from '@grafana/runtime'; import { QueryWithDefaults } from './defaults'; import { @@ -23,7 +21,7 @@ import { export interface SqlQueryForInterpolation { dataset?: string; alias?: string; - format?: ResultFormat; + format?: QueryFormat; rawSql?: string; refId: string; hide?: boolean; @@ -34,8 +32,6 @@ export interface SQLOptions extends DataSourceJsonData { database: string; } -export type ResultFormat = 'time_series' | 'table'; - export enum QueryFormat { Timeseries = 'time_series', Table = 'table', @@ -43,7 +39,7 @@ export enum QueryFormat { export interface SQLQuery extends DataQuery { alias?: string; - format?: ResultFormat | QueryFormat | string | undefined; + format?: QueryFormat; rawSql?: string; dataset?: string; table?: string; @@ -150,7 +146,6 @@ export interface SqlQueryModel { } export interface ResponseParser { - transformAnnotationResponse: (options: object, data: BackendDataSourceResponse) => Promise; transformMetricFindResponse: (frame: DataFrame) => MetricFindValue[]; } diff --git a/public/app/features/variables/utils.ts b/public/app/features/variables/utils.ts index 06984535888..fc5e9dd861d 100644 --- a/public/app/features/variables/utils.ts +++ b/public/app/features/variables/utils.ts @@ -33,10 +33,14 @@ export const SEARCH_FILTER_VARIABLE = '__searchFilter'; export const containsSearchFilter = (query: string | unknown): boolean => query && typeof query === 'string' ? query.indexOf(SEARCH_FILTER_VARIABLE) !== -1 : false; +export interface SearchFilterOptions { + searchFilter?: string; +} + export const getSearchFilterScopedVar = (args: { query: string; wildcardChar: string; - options: { searchFilter?: string }; + options?: SearchFilterOptions; }): ScopedVars => { const { query, wildcardChar } = args; if (!containsSearchFilter(query)) {