SQLDS: Use builtin annotation editor

Plus strict rule fixes
This commit is contained in:
Zoltán Bedi 2022-07-05 20:42:42 +02:00
parent 035862bade
commit fee2eb3716
6 changed files with 88 additions and 101 deletions

View File

@ -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"],

View File

@ -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',
];

View File

@ -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<SQLQuery, SQLO
name: string;
interval: string;
db: DB;
annotations = {};
constructor(
instanceSettings: DataSourceInstanceSettings<SQLOptions>,
@ -121,78 +123,56 @@ export abstract class SqlDatasource extends DataSourceWithBackend<SQLQuery, SQLO
return value.replace(/''/g, "'");
}
// eslint-ignore @typescript-eslint/no-explicit-any
async annotationQuery(options: any): Promise<AnnotationEvent[]> {
if (!options.annotation.rawQuery) {
return Promise.reject({
message: 'Query missing in annotation definition',
});
}
async metricFindQuery(query: string, optionalOptions?: MetricFindQueryOptions): Promise<MetricFindValue[]> {
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<T>(query: string, options?: MetricFindQueryOptions) {
const frame = await this.runMetaQuery({ rawSql: query, format: QueryFormat.Table }, options);
return new DataFrameView<T>(frame);
}
private runMetaQuery(request: Partial<SQLQuery>, options?: MetricFindQueryOptions): Promise<DataFrame> {
const range = getTimeSrv().timeRange();
const refId = request.refId || 'meta';
const queries: DataQuery[] = [{ ...request, datasource: request.datasource || this.getRef(), refId }];
return lastValueFrom(
getBackendSrv()
.fetch<BackendDataSourceResponse>({
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<BackendDataSourceResponse>) =>
await this.getResponseParser().transformAnnotationResponse(options, res.data)
)
map((res: FetchResponse<BackendDataSourceResponse>) => {
const rsp = toDataQueryResponse(res, queries);
return rsp.data[0];
})
)
);
}
async metricFindQuery(query: string, optionalOptions: any): Promise<MetricFindValue[]> {
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<T = any>(query: string) {
const frame = await this.runQuery({ rawSql: query, format: QueryFormat.Table }, {});
return new DataFrameView<T>(frame);
}
private runQuery(request: Partial<SQLQuery>, options?: any): Promise<DataFrame> {
return new Promise((resolve) => {
const req = {
targets: [{ ...request, refId: String(Math.random()) }],
range: options?.range,
} as DataQueryRequest<SQLQuery>;
this.query(req).subscribe((res: DataQueryResponse) => {
resolve(res.data[0] || { fields: [] });
});
});
}
testDatasource(): Promise<any> {
testDatasource(): Promise<{ status: string; message: string }> {
return lastValueFrom(
getBackendSrv()
.fetch({
@ -223,7 +203,15 @@ export abstract class SqlDatasource extends DataSourceWithBackend<SQLQuery, SQLO
);
}
targetContainsTemplate(target: any) {
return this.templateSrv.containsTemplate(target.rawSql);
targetContainsTemplate(target: SQLQuery) {
let queryWithoutMacros = target.rawSql;
MACRO_NAMES.forEach((value) => {
queryWithoutMacros = queryWithoutMacros?.replace(value, '') || '';
});
return this.templateSrv.containsTemplate(queryWithoutMacros);
}
}
interface MetricFindQueryOptions extends SearchFilterOptions {
range?: TimeRange;
}

View File

@ -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,

View File

@ -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<AnnotationEvent[]>;
transformMetricFindResponse: (frame: DataFrame) => MetricFindValue[];
}

View File

@ -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)) {