mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
8f4e50f439
commit
7abf0506b1
@ -1,10 +1,12 @@
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.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 { status: 'error', message: err.data.message };
|
return of({ 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) {
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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++) {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
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', () => ({
|
|||||||
|
|
||||||
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', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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', () => {
|
|||||||
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', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user