Commit 7abf0506 by Hugo Häggmark Committed by GitHub

MSSQL: Support request cancellation properly (Uses new backendSrv.fetch…

MSSQL: Support request cancellation properly (Uses new backendSrv.fetch Observable request API) (#28809)

* MSSQL: Support request cancellation properly (Uses new backendSrv.fetch Observable request API)

* Tests: fix test typings
parent 8f4e50f4
import _ from 'lodash'; import _ from 'lodash';
import ResponseParser from './response_parser'; import { Observable, of } from 'rxjs';
import { catchError, map, mapTo } from 'rxjs/operators';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { ScopedVars } from '@grafana/data'; import { ScopedVars } from '@grafana/data';
import ResponseParser, { MssqlResponse } from './response_parser';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
//Types
import { MssqlQueryForInterpolation } from './types'; import { MssqlQueryForInterpolation } from './types';
export class MssqlDatasource { export class MssqlDatasource {
...@@ -66,7 +68,7 @@ export class MssqlDatasource { ...@@ -66,7 +68,7 @@ export class MssqlDatasource {
return expandedQueries; return expandedQueries;
} }
query(options: any) { query(options: any): Observable<MssqlResponse> {
const queries = _.filter(options.targets, item => { const queries = _.filter(options.targets, item => {
return item.hide !== true; return item.hide !== true;
}).map(item => { }).map(item => {
...@@ -81,11 +83,11 @@ export class MssqlDatasource { ...@@ -81,11 +83,11 @@ export class MssqlDatasource {
}); });
if (queries.length === 0) { if (queries.length === 0) {
return Promise.resolve({ data: [] }); return of({ data: [] });
} }
return getBackendSrv() return getBackendSrv()
.datasourceRequest({ .fetch({
url: '/api/tsdb/query', url: '/api/tsdb/query',
method: 'POST', method: 'POST',
data: { data: {
...@@ -94,7 +96,7 @@ export class MssqlDatasource { ...@@ -94,7 +96,7 @@ export class MssqlDatasource {
queries: queries, queries: queries,
}, },
}) })
.then(this.responseParser.processQueryResult); .pipe(map(this.responseParser.processQueryResult));
} }
annotationQuery(options: any) { annotationQuery(options: any) {
...@@ -110,7 +112,7 @@ export class MssqlDatasource { ...@@ -110,7 +112,7 @@ export class MssqlDatasource {
}; };
return getBackendSrv() return getBackendSrv()
.datasourceRequest({ .fetch({
url: '/api/tsdb/query', url: '/api/tsdb/query',
method: 'POST', method: 'POST',
data: { data: {
...@@ -119,7 +121,8 @@ export class MssqlDatasource { ...@@ -119,7 +121,8 @@ export class MssqlDatasource {
queries: [query], queries: [query],
}, },
}) })
.then((data: any) => this.responseParser.transformAnnotationResponse(options, data)); .pipe(map((data: any) => this.responseParser.transformAnnotationResponse(options, data)))
.toPromise();
} }
metricFindQuery(query: string, optionalOptions: { variable: { name: string } }) { metricFindQuery(query: string, optionalOptions: { variable: { name: string } }) {
...@@ -143,17 +146,18 @@ export class MssqlDatasource { ...@@ -143,17 +146,18 @@ export class MssqlDatasource {
}; };
return getBackendSrv() return getBackendSrv()
.datasourceRequest({ .fetch({
url: '/api/tsdb/query', url: '/api/tsdb/query',
method: 'POST', method: 'POST',
data: data, data: data,
}) })
.then((data: any) => this.responseParser.parseMetricFindQueryResult(refId, data)); .pipe(map((data: any) => this.responseParser.parseMetricFindQueryResult(refId, data)))
.toPromise();
} }
testDatasource() { testDatasource() {
return getBackendSrv() return getBackendSrv()
.datasourceRequest({ .fetch({
url: '/api/tsdb/query', url: '/api/tsdb/query',
method: 'POST', method: 'POST',
data: { data: {
...@@ -171,17 +175,18 @@ export class MssqlDatasource { ...@@ -171,17 +175,18 @@ export class MssqlDatasource {
], ],
}, },
}) })
.then((res: any) => { .pipe(
return { status: 'success', message: 'Database Connection OK' }; mapTo({ status: 'success', message: 'Database Connection OK' }),
}) catchError(err => {
.catch((err: any) => { console.error(err);
console.error(err); if (err.data && err.data.message) {
if (err.data && err.data.message) { return of({ status: 'error', message: err.data.message });
return { status: 'error', message: err.data.message }; }
} else {
return { status: 'error', message: err.status }; return of({ status: 'error', message: err.status });
} })
}); )
.toPromise();
} }
targetContainsTemplate(target: any) { targetContainsTemplate(target: any) {
......
import _ from 'lodash'; import _ from 'lodash';
import { MetricFindValue } from '@grafana/data';
interface TableResponse extends Record<string, any> {
type: string;
refId: string;
meta: any;
}
interface SeriesResponse extends Record<string, any> {
target: string;
refId: string;
meta: any;
datapoints: [any[]];
}
export interface MssqlResponse {
data: Array<TableResponse | SeriesResponse>;
}
export default class ResponseParser { export default class ResponseParser {
processQueryResult(res: any) { processQueryResult(res: any): MssqlResponse {
const data: any[] = []; const data: any[] = [];
if (!res.data.results) { if (!res.data.results) {
...@@ -35,7 +53,7 @@ export default class ResponseParser { ...@@ -35,7 +53,7 @@ export default class ResponseParser {
return { data: data }; return { data: data };
} }
parseMetricFindQueryResult(refId: string, results: any) { parseMetricFindQueryResult(refId: string, results: any): MetricFindValue[] {
if (!results || results.data.length === 0 || results.data.results[refId].meta.rowCount === 0) { if (!results || results.data.length === 0 || results.data.results[refId].meta.rowCount === 0) {
return []; return [];
} }
...@@ -52,7 +70,7 @@ export default class ResponseParser { ...@@ -52,7 +70,7 @@ export default class ResponseParser {
return this.transformToSimpleList(rows); return this.transformToSimpleList(rows);
} }
transformToKeyValueList(rows: any, textColIndex: number, valueColIndex: number) { transformToKeyValueList(rows: any, textColIndex: number, valueColIndex: number): MetricFindValue[] {
const res = []; const res = [];
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
...@@ -64,7 +82,7 @@ export default class ResponseParser { ...@@ -64,7 +82,7 @@ export default class ResponseParser {
return res; return res;
} }
transformToSimpleList(rows: any) { transformToSimpleList(rows: any): MetricFindValue[] {
const res = []; const res = [];
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
......
import { of } from 'rxjs';
import { dateTime } from '@grafana/data';
import { MssqlDatasource } from '../datasource'; import { MssqlDatasource } from '../datasource';
import { TimeSrvStub } from 'test/specs/helpers'; import { TimeSrvStub } from 'test/specs/helpers';
import { dateTime } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
import { backendSrv } from 'app/core/services/backend_srv'; import { backendSrv } from 'app/core/services/backend_srv';
import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer'; // will use the version in __mocks__ import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer';
import { FetchResponse } from '@grafana/runtime'; // will use the version in __mocks__
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...((jest.requireActual('@grafana/runtime') as unknown) as object), ...((jest.requireActual('@grafana/runtime') as unknown) as object),
...@@ -13,7 +15,7 @@ jest.mock('@grafana/runtime', () => ({ ...@@ -13,7 +15,7 @@ jest.mock('@grafana/runtime', () => ({
describe('MSSQLDatasource', () => { describe('MSSQLDatasource', () => {
const templateSrv: TemplateSrv = new TemplateSrv(); const templateSrv: TemplateSrv = new TemplateSrv();
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest'); const fetchMock = jest.spyOn(backendSrv, 'fetch');
const ctx: any = { const ctx: any = {
timeSrv: new TimeSrvStub(), timeSrv: new TimeSrvStub(),
...@@ -61,7 +63,7 @@ describe('MSSQLDatasource', () => { ...@@ -61,7 +63,7 @@ describe('MSSQLDatasource', () => {
}; };
beforeEach(() => { beforeEach(() => {
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 })); fetchMock.mockImplementation(() => of(createFetchResponse(response)));
return ctx.ds.annotationQuery(options).then((data: any) => { return ctx.ds.annotationQuery(options).then((data: any) => {
results = data; results = data;
...@@ -107,7 +109,7 @@ describe('MSSQLDatasource', () => { ...@@ -107,7 +109,7 @@ describe('MSSQLDatasource', () => {
}; };
beforeEach(() => { beforeEach(() => {
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 })); fetchMock.mockImplementation(() => of(createFetchResponse(response)));
return ctx.ds.metricFindQuery(query).then((data: any) => { return ctx.ds.metricFindQuery(query).then((data: any) => {
results = data; results = data;
...@@ -146,7 +148,7 @@ describe('MSSQLDatasource', () => { ...@@ -146,7 +148,7 @@ describe('MSSQLDatasource', () => {
}; };
beforeEach(() => { beforeEach(() => {
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 })); fetchMock.mockImplementation(() => of(createFetchResponse(response)));
return ctx.ds.metricFindQuery(query).then((data: any) => { return ctx.ds.metricFindQuery(query).then((data: any) => {
results = data; results = data;
...@@ -187,7 +189,7 @@ describe('MSSQLDatasource', () => { ...@@ -187,7 +189,7 @@ describe('MSSQLDatasource', () => {
}; };
beforeEach(() => { beforeEach(() => {
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 })); fetchMock.mockImplementation(() => of(createFetchResponse(response)));
return ctx.ds.metricFindQuery(query).then((data: any) => { return ctx.ds.metricFindQuery(query).then((data: any) => {
results = data; results = data;
}); });
...@@ -201,7 +203,6 @@ describe('MSSQLDatasource', () => { ...@@ -201,7 +203,6 @@ describe('MSSQLDatasource', () => {
}); });
describe('When performing metricFindQuery', () => { describe('When performing metricFindQuery', () => {
let results: any;
const query = 'select * from atable'; const query = 'select * from atable';
const response = { const response = {
results: { results: {
...@@ -227,19 +228,17 @@ describe('MSSQLDatasource', () => { ...@@ -227,19 +228,17 @@ describe('MSSQLDatasource', () => {
beforeEach(() => { beforeEach(() => {
ctx.timeSrv.setTime(time); ctx.timeSrv.setTime(time);
datasourceRequestMock.mockImplementation((options: any) => { fetchMock.mockImplementation(() => of(createFetchResponse(response)));
results = options.data;
return Promise.resolve({ data: response, status: 200 });
});
return ctx.ds.metricFindQuery(query); return ctx.ds.metricFindQuery(query);
}); });
it('should pass timerange to datasourceRequest', () => { it('should pass timerange to datasourceRequest', () => {
expect(results.from).toBe(time.from.valueOf().toString()); expect(fetchMock).toBeCalledTimes(1);
expect(results.to).toBe(time.to.valueOf().toString()); expect(fetchMock.mock.calls[0][0].data.from).toBe(time.from.valueOf().toString());
expect(results.queries.length).toBe(1); expect(fetchMock.mock.calls[0][0].data.to).toBe(time.to.valueOf().toString());
expect(results.queries[0].rawSql).toBe(query); expect(fetchMock.mock.calls[0][0].data.queries.length).toBe(1);
expect(fetchMock.mock.calls[0][0].data.queries[0].rawSql).toBe(query);
}); });
}); });
...@@ -335,3 +334,17 @@ describe('MSSQLDatasource', () => { ...@@ -335,3 +334,17 @@ describe('MSSQLDatasource', () => {
}); });
}); });
}); });
function createFetchResponse<T>(data: T): FetchResponse<T> {
return {
data,
status: 200,
url: 'http://localhost:3000/api/query',
config: { url: 'http://localhost:3000/api/query' },
type: 'basic',
statusText: 'Ok',
redirected: false,
headers: ({} as unknown) as Headers,
ok: true,
};
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment