mirror of https://github.com/grafana/grafana.git
Panels: Skip re-rendering panel/visualisation in loading state (#19518)
* Loading states and partial rendering, set loading state in mixed data source, and do not render loading states for react panels * Updated mixed data source tests
This commit is contained in:
parent
f2ef49f950
commit
0ec8303878
|
|
@ -115,27 +115,34 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
let { errorMessage, isFirstLoad } = this.state;
|
||||
let { isFirstLoad } = this.state;
|
||||
let errorMessage: string | null = null;
|
||||
|
||||
if (data.state === LoadingState.Error) {
|
||||
const { error } = data;
|
||||
if (error) {
|
||||
if (errorMessage !== error.message) {
|
||||
errorMessage = error.message;
|
||||
switch (data.state) {
|
||||
case LoadingState.Loading:
|
||||
// Skip updating state data if it is already in loading state
|
||||
// This is to avoid rendering partial loading responses
|
||||
if (this.state.data.state === LoadingState.Loading) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorMessage = null;
|
||||
}
|
||||
|
||||
if (data.state === LoadingState.Done) {
|
||||
// If we are doing a snapshot save data in panel model
|
||||
if (this.props.dashboard.snapshot) {
|
||||
this.props.panel.snapshotData = data.series.map(frame => toDataFrameDTO(frame));
|
||||
}
|
||||
if (isFirstLoad) {
|
||||
isFirstLoad = false;
|
||||
}
|
||||
break;
|
||||
case LoadingState.Error:
|
||||
const { error } = data;
|
||||
if (error) {
|
||||
if (errorMessage !== error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LoadingState.Done:
|
||||
// If we are doing a snapshot save data in panel model
|
||||
if (this.props.dashboard.snapshot) {
|
||||
this.props.panel.snapshotData = data.series.map(frame => toDataFrameDTO(frame));
|
||||
}
|
||||
if (isFirstLoad) {
|
||||
isFirstLoad = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({ isFirstLoad, errorMessage, data });
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { DatasourceSrvMock, MockDataSourceApi } from 'test/mocks/datasource_srv'
|
|||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
||||
import { DataSourceInstanceSettings } from '@grafana/ui';
|
||||
import { LoadingState } from '@grafana/data';
|
||||
import { MixedDatasource } from './module';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
|
|
@ -29,17 +30,23 @@ describe('MixedDatasource', () => {
|
|||
});
|
||||
const results: any[] = [];
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(async done => {
|
||||
const ds = await getDataSourceSrv().get('-- Mixed --');
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { from, of, Observable, merge } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
import { LoadingState } from '@grafana/data';
|
||||
import { DataSourceApi, DataQuery, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/ui';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { mergeMap, map, filter } from 'rxjs/operators';
|
||||
import { mergeMap, map } from 'rxjs/operators';
|
||||
|
||||
export const MIXED_DATASOURCE_NAME = '-- Mixed --';
|
||||
|
||||
|
|
@ -25,13 +27,14 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
|
|||
|
||||
const sets: { [key: string]: DataQuery[] } = groupBy(queries, 'datasource');
|
||||
const observables: Array<Observable<DataQueryResponse>> = [];
|
||||
let runningSubRequests = 0;
|
||||
|
||||
for (const key in sets) {
|
||||
const targets = sets[key];
|
||||
const dsName = targets[0].datasource;
|
||||
|
||||
const observable = from(getDataSourceSrv().get(dsName)).pipe(
|
||||
map((dataSourceApi: DataSourceApi) => {
|
||||
mergeMap((dataSourceApi: DataSourceApi) => {
|
||||
const datasourceRequest = cloneDeep(request);
|
||||
|
||||
// Remove any unused hidden queries
|
||||
|
|
@ -42,28 +45,41 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
|
|||
|
||||
datasourceRequest.targets = newTargets;
|
||||
datasourceRequest.requestId = `${dsName}${datasourceRequest.requestId || ''}`;
|
||||
return {
|
||||
dataSourceApi,
|
||||
datasourceRequest,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const noTargets = observable.pipe(
|
||||
filter(({ datasourceRequest }) => datasourceRequest.targets.length === 0),
|
||||
mergeMap(() => {
|
||||
return of({ data: [] } as DataQueryResponse);
|
||||
})
|
||||
);
|
||||
// all queries hidden return empty result for for this requestId
|
||||
if (datasourceRequest.targets.length === 0) {
|
||||
return of({ data: [], key: datasourceRequest.requestId });
|
||||
}
|
||||
|
||||
runningSubRequests++;
|
||||
let hasCountedAsDone = false;
|
||||
|
||||
const hasTargets = observable.pipe(
|
||||
filter(({ datasourceRequest }) => datasourceRequest.targets.length > 0),
|
||||
mergeMap(({ dataSourceApi, datasourceRequest }) => {
|
||||
return from(dataSourceApi.query(datasourceRequest)).pipe(
|
||||
tap(
|
||||
(response: DataQueryResponse) => {
|
||||
if (
|
||||
hasCountedAsDone ||
|
||||
response.state === LoadingState.Streaming ||
|
||||
response.state === LoadingState.Loading
|
||||
) {
|
||||
return;
|
||||
}
|
||||
runningSubRequests--;
|
||||
hasCountedAsDone = true;
|
||||
},
|
||||
() => {
|
||||
if (hasCountedAsDone) {
|
||||
return;
|
||||
}
|
||||
hasCountedAsDone = true;
|
||||
runningSubRequests--;
|
||||
}
|
||||
),
|
||||
map((response: DataQueryResponse) => {
|
||||
return {
|
||||
...response,
|
||||
data: response.data || [],
|
||||
state: runningSubRequests === 0 ? LoadingState.Done : LoadingState.Loading,
|
||||
key: `${dsName}${response.key || ''}`,
|
||||
} as DataQueryResponse;
|
||||
})
|
||||
|
|
@ -71,7 +87,7 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
|
|||
})
|
||||
);
|
||||
|
||||
observables.push(merge(noTargets, hasTargets));
|
||||
observables.push(observable);
|
||||
}
|
||||
|
||||
return merge(...observables);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@ export class MockDataSourceApi extends DataSourceApi {
|
|||
if (this.queryResolver) {
|
||||
return this.queryResolver;
|
||||
}
|
||||
return Promise.resolve(this.result);
|
||||
return new Promise(resolver => {
|
||||
setTimeout(() => {
|
||||
resolver(this.result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue