Commit 07553902 by Hugo Häggmark Committed by GitHub

MixedDatasource: Shows retrieved data even if a data source fails (#27024)

* MixedDatasource: Shows data for working data sources and error for erroneous

* Tests: remove funky import

* Tests: fixes types
parent 3d18c4d6
import { DatasourceSrvMock, MockDataSourceApi } from 'test/mocks/datasource_srv';
import { from } from 'rxjs';
import { getDataSourceSrv } from '@grafana/runtime';
import { DataSourceInstanceSettings, LoadingState } from '@grafana/data';
import { DatasourceSrvMock, MockDataSourceApi } from 'test/mocks/datasource_srv';
import { getQueryOptions } from 'test/helpers/getQueryOptions';
import { LoadingState, DataSourceInstanceSettings } from '@grafana/data';
import { MixedDatasource } from './module';
import { from } from 'rxjs';
const defaultDS = new MockDataSourceApi('DefaultDS', { data: ['DDD'] });
const datasourceSrv = new DatasourceSrvMock(defaultDS, {
......@@ -11,42 +12,89 @@ const datasourceSrv = new DatasourceSrvMock(defaultDS, {
A: new MockDataSourceApi('DSA', { data: ['AAAA'] }),
B: new MockDataSourceApi('DSB', { data: ['BBBB'] }),
C: new MockDataSourceApi('DSC', { data: ['CCCC'] }),
D: new MockDataSourceApi('DSD', { data: [] }, {}, 'syntax error near FROM'),
E: new MockDataSourceApi('DSE', { data: [] }, {}, 'syntax error near WHERE'),
});
jest.mock('@grafana/runtime', () => ({
...((jest.requireActual('@grafana/runtime') as unknown) as object),
getDataSourceSrv: () => {
return datasourceSrv;
},
}));
describe('MixedDatasource', () => {
const requestMixed = getQueryOptions({
targets: [
{ refId: 'QA', datasource: 'A' }, // 1
{ refId: 'QB', datasource: 'B' }, // 2
{ refId: 'QC', datasource: 'C' }, // 3
],
});
const results: any[] = [];
describe('with no errors', () => {
const requestMixed = getQueryOptions({
targets: [
{ refId: 'QA', datasource: 'A' }, // 1
{ refId: 'QB', datasource: 'B' }, // 2
{ refId: 'QC', datasource: 'C' }, // 3
],
});
const results: any[] = [];
beforeEach(async done => {
const ds = await getDataSourceSrv().get('-- Mixed --');
beforeEach(async done => {
const ds = await getDataSourceSrv().get('-- Mixed --');
from(ds.query(requestMixed)).subscribe(result => {
results.push(result);
if (result.state === LoadingState.Done) {
done();
}
from(ds.query(requestMixed)).subscribe(result => {
results.push(result);
if (result.state === LoadingState.Done) {
done();
}
});
});
it('direct query should return results', async () => {
expect(results.length).toBe(3);
expect(results[0].data).toEqual(['AAAA']);
expect(results[0].state).toEqual(LoadingState.Loading);
expect(results[1].data).toEqual(['BBBB']);
expect(results[2].data).toEqual(['CCCC']);
expect(results[2].state).toEqual(LoadingState.Done);
expect(results.length).toBe(3);
});
});
it('direct query should return results', async () => {
expect(results.length).toBe(3);
expect(results[0].data).toEqual(['AAAA']);
expect(results[0].state).toEqual(LoadingState.Loading);
expect(results[1].data).toEqual(['BBBB']);
expect(results[2].data).toEqual(['CCCC']);
expect(results[2].state).toEqual(LoadingState.Done);
expect(results.length).toBe(3);
describe('with errors', () => {
const requestMixed = getQueryOptions({
targets: [
{ refId: 'QA', datasource: 'A' }, // 1
{ refId: 'QD', datasource: 'D' }, // 2
{ refId: 'QB', datasource: 'B' }, // 3
{ refId: 'QE', datasource: 'E' }, // 4
{ refId: 'QC', datasource: 'C' }, // 5
],
});
const results: any[] = [];
beforeEach(async done => {
const ds = await getDataSourceSrv().get('-- Mixed --');
from(ds.query(requestMixed)).subscribe(result => {
results.push(result);
if (results.length === 5) {
done();
}
});
});
it('direct query should return results', async () => {
expect(results[0].data).toEqual(['AAAA']);
expect(results[0].state).toEqual(LoadingState.Loading);
expect(results[1].data).toEqual([]);
expect(results[1].state).toEqual(LoadingState.Error);
expect(results[1].error).toEqual({ message: 'DSD: syntax error near FROM' });
expect(results[2].data).toEqual(['BBBB']);
expect(results[2].state).toEqual(LoadingState.Loading);
expect(results[3].data).toEqual([]);
expect(results[3].state).toEqual(LoadingState.Error);
expect(results[3].error).toEqual({ message: 'DSE: syntax error near WHERE' });
expect(results[4].data).toEqual(['CCCC']);
expect(results[4].state).toEqual(LoadingState.Loading);
expect(results[5].data).toEqual([]);
expect(results[5].state).toEqual(LoadingState.Error);
expect(results[5].error).toEqual({ message: 'DSD: syntax error near FROM' });
});
});
});
import cloneDeep from 'lodash/cloneDeep';
import groupBy from 'lodash/groupBy';
import { from, of, Observable, forkJoin } from 'rxjs';
import { map, mergeMap, mergeAll } from 'rxjs/operators';
import { forkJoin, from, Observable, of } from 'rxjs';
import { catchError, map, mergeAll, mergeMap } from 'rxjs/operators';
import {
LoadingState,
DataSourceApi,
DataQuery,
DataQueryRequest,
DataQueryResponse,
DataSourceApi,
DataSourceInstanceSettings,
LoadingState,
} from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { getDataSourceSrv, toDataQueryError } from '@grafana/runtime';
export const MIXED_DATASOURCE_NAME = '-- Mixed --';
......@@ -68,13 +68,25 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
state: LoadingState.Loading,
key: `mixed-${i}-${response.key || ''}`,
} as DataQueryResponse;
}),
catchError(err => {
err = toDataQueryError(err);
err.message = `${api.name}: ${err.message}`;
return of({
data: [],
state: LoadingState.Error,
error: err,
key: `mixed-${i}-${dsRequest.requestId || ''}`,
});
})
);
})
)
);
return forkJoin(runningQueries).pipe(map(this.markAsDone), mergeAll());
return forkJoin(runningQueries).pipe(map(this.finalizeResponses), mergeAll());
}
testDatasource() {
......@@ -85,14 +97,20 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
return query && Array.isArray(query.targets) && query.targets.length > 0;
}
private markAsDone(responses: DataQueryResponse[]): DataQueryResponse[] {
private finalizeResponses(responses: DataQueryResponse[]): DataQueryResponse[] {
const { length } = responses;
if (length === 0) {
return responses;
}
responses[length - 1].state = LoadingState.Done;
const error = responses.find(response => response.state === LoadingState.Error);
if (error) {
responses.push(error); // adds the first found error entry so error shows up in the panel
} else {
responses[length - 1].state = LoadingState.Done;
}
return responses;
}
}
import {
DataSourceApi,
DataQueryRequest,
DataQueryResponse,
DataSourceApi,
DataSourceInstanceSettings,
DataSourcePluginMeta,
} from '@grafana/data';
......@@ -27,7 +27,7 @@ export class MockDataSourceApi extends DataSourceApi {
result: DataQueryResponse = { data: [] };
queryResolver: Promise<DataQueryResponse>;
constructor(name?: string, result?: DataQueryResponse, meta?: any) {
constructor(name?: string, result?: DataQueryResponse, meta?: any, private error: string | null = null) {
super({ name: name ? name : 'MockDataSourceApi' } as DataSourceInstanceSettings);
if (result) {
this.result = result;
......@@ -40,6 +40,11 @@ export class MockDataSourceApi extends DataSourceApi {
if (this.queryResolver) {
return this.queryResolver;
}
if (this.error) {
return Promise.reject(this.error);
}
return new Promise(resolver => {
setTimeout(() => {
resolver(this.result);
......
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