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
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { DatasourceSrvMock, MockDataSourceApi } from 'test/mocks/datasource_srv';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
||||
import { LoadingState, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { MixedDatasource } from './module';
|
||||
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 { MixedDatasource } from './module';
|
||||
|
||||
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' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user