mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 06:51:49 +08:00
173 lines
5.4 KiB
TypeScript
173 lines
5.4 KiB
TypeScript
import { Observable, debounce, debounceTime, defer, finalize, first, interval, map, of } from 'rxjs';
|
|
|
|
import {
|
|
DataSourceApi,
|
|
DataQueryRequest,
|
|
DataQueryResponse,
|
|
DataSourceInstanceSettings,
|
|
TestDataSourceResponse,
|
|
ScopedVar,
|
|
DataTopic,
|
|
PanelData,
|
|
DataFrame,
|
|
LoadingState,
|
|
Field,
|
|
} from '@grafana/data';
|
|
import { SceneDataProvider, SceneDataTransformer, SceneObject } from '@grafana/scenes';
|
|
import {
|
|
activateSceneObjectAndParentTree,
|
|
findOriginalVizPanelByKey,
|
|
getVizPanelKeyForPanelId,
|
|
} from 'app/features/dashboard-scene/utils/utils';
|
|
|
|
import { MIXED_REQUEST_PREFIX } from '../mixed/MixedDataSource';
|
|
|
|
import { DashboardQuery } from './types';
|
|
|
|
/**
|
|
* This should not really be called
|
|
*/
|
|
export class DashboardDatasource extends DataSourceApi<DashboardQuery> {
|
|
constructor(instanceSettings: DataSourceInstanceSettings) {
|
|
super(instanceSettings);
|
|
}
|
|
|
|
getCollapsedText(query: DashboardQuery) {
|
|
return `Dashboard Reference: ${query.panelId}`;
|
|
}
|
|
|
|
query(options: DataQueryRequest<DashboardQuery>): Observable<DataQueryResponse> {
|
|
const sceneScopedVar: ScopedVar | undefined = options.scopedVars?.__sceneObject;
|
|
let scene: SceneObject | undefined = sceneScopedVar ? (sceneScopedVar.value.valueOf() as SceneObject) : undefined;
|
|
|
|
if (!scene) {
|
|
throw new Error('Can only be called from a scene');
|
|
}
|
|
|
|
const query = options.targets[0];
|
|
if (!query) {
|
|
return of({ data: [] });
|
|
}
|
|
|
|
const panelId = query.panelId;
|
|
|
|
if (!panelId) {
|
|
return of({ data: [] });
|
|
}
|
|
|
|
let sourcePanel = this.findSourcePanel(scene, panelId);
|
|
|
|
if (!sourcePanel) {
|
|
return of({ data: [], error: { message: 'Could not find source panel' } });
|
|
}
|
|
|
|
let sourceDataProvider: SceneDataProvider | undefined = sourcePanel.state.$data;
|
|
|
|
if (!query.withTransforms && sourceDataProvider instanceof SceneDataTransformer) {
|
|
sourceDataProvider = sourceDataProvider.state.$data;
|
|
}
|
|
|
|
if (!sourceDataProvider || !sourceDataProvider.getResultsStream) {
|
|
return of({ data: [] });
|
|
}
|
|
|
|
return defer(() => {
|
|
if (!sourceDataProvider!.isActive && sourceDataProvider?.setContainerWidth) {
|
|
sourceDataProvider?.setContainerWidth(500);
|
|
}
|
|
|
|
const cleanUp = activateSceneObjectAndParentTree(sourceDataProvider!);
|
|
|
|
return sourceDataProvider!.getResultsStream!().pipe(
|
|
debounceTime(50),
|
|
map((result) => {
|
|
return {
|
|
data: this.getDataFramesForQueryTopic(result.data, query),
|
|
state: result.data.state,
|
|
errors: result.data.errors,
|
|
error: result.data.error,
|
|
key: 'source-ds-provider',
|
|
};
|
|
}),
|
|
this.emitFirstLoadedDataIfMixedDS(options.requestId),
|
|
finalize(() => cleanUp?.())
|
|
);
|
|
});
|
|
}
|
|
|
|
private getDataFramesForQueryTopic(data: PanelData, query: DashboardQuery): DataFrame[] {
|
|
const annotations = data.annotations ?? [];
|
|
if (query.topic === DataTopic.Annotations) {
|
|
return annotations.map((frame) => ({
|
|
...frame,
|
|
meta: {
|
|
...frame.meta,
|
|
dataTopic: DataTopic.Series,
|
|
},
|
|
}));
|
|
} else {
|
|
const series = data.series.map((s) => {
|
|
return {
|
|
...s,
|
|
fields: s.fields.map((field: Field) => ({
|
|
...field,
|
|
state: {
|
|
...field.state,
|
|
},
|
|
})),
|
|
};
|
|
});
|
|
|
|
return [...series, ...annotations];
|
|
}
|
|
}
|
|
|
|
private findSourcePanel(scene: SceneObject, panelId: number) {
|
|
// We're trying to find the original panel, not a cloned one, since `panelId` alone cannot resolve clones
|
|
return findOriginalVizPanelByKey(scene, getVizPanelKeyForPanelId(panelId));
|
|
}
|
|
|
|
private emitFirstLoadedDataIfMixedDS(
|
|
requestId: string
|
|
): (source: Observable<DataQueryResponse>) => Observable<DataQueryResponse> {
|
|
return (source: Observable<DataQueryResponse>) => {
|
|
if (requestId.includes(MIXED_REQUEST_PREFIX)) {
|
|
let count = 0;
|
|
|
|
return source.pipe(
|
|
/*
|
|
* We can have the following piped values scenarios:
|
|
* Loading -> Done - initial load
|
|
* Done -> Loading -> Done - refresh
|
|
* Done - adding another query in editor
|
|
*
|
|
* When we see Done as a first element this is because of ReplaySubject in SceneQueryRunner
|
|
*
|
|
* we use first(...) below to emit correct result which is last value with Done/Error states
|
|
*
|
|
* to avoid emitting first Done/Error (due to ReplaySubject) we selectively debounce only first value with such states
|
|
*/
|
|
debounce((val) => {
|
|
if ([LoadingState.Done, LoadingState.Error].includes(val.state!) && count === 0) {
|
|
count++;
|
|
// in the refresh scenario we need to debounce first Done/Error until Loading arrives
|
|
// 400ms here is a magic number that was sufficient enough with the 20x cpu throttle
|
|
// this still might affect slower machines but the issue affects only panel view/edit modes
|
|
return interval(400);
|
|
}
|
|
count++;
|
|
return interval(0);
|
|
}),
|
|
first((val) => val.state === LoadingState.Done || val.state === LoadingState.Error)
|
|
);
|
|
}
|
|
|
|
return source;
|
|
};
|
|
}
|
|
|
|
testDatasource(): Promise<TestDataSourceResponse> {
|
|
return Promise.resolve({ message: '', status: '' });
|
|
}
|
|
}
|