2021-04-21 02:38:00 -05:00
|
|
|
import { cloneDeep, groupBy } from 'lodash';
|
2020-08-19 01:52:40 -05:00
|
|
|
import { forkJoin, from, Observable, of } from 'rxjs';
|
|
|
|
import { catchError, map, mergeAll, mergeMap } from 'rxjs/operators';
|
2019-09-09 04:29:37 -05:00
|
|
|
|
2019-10-31 04:48:05 -05:00
|
|
|
import {
|
|
|
|
DataQuery,
|
|
|
|
DataQueryRequest,
|
|
|
|
DataQueryResponse,
|
2020-08-19 01:52:40 -05:00
|
|
|
DataSourceApi,
|
2019-10-31 04:48:05 -05:00
|
|
|
DataSourceInstanceSettings,
|
2020-08-19 01:52:40 -05:00
|
|
|
LoadingState,
|
2019-10-31 04:48:05 -05:00
|
|
|
} from '@grafana/data';
|
2020-08-19 01:52:40 -05:00
|
|
|
import { getDataSourceSrv, toDataQueryError } from '@grafana/runtime';
|
2019-09-09 04:29:37 -05:00
|
|
|
|
|
|
|
export const MIXED_DATASOURCE_NAME = '-- Mixed --';
|
|
|
|
|
2019-11-07 00:18:27 -06:00
|
|
|
export interface BatchedQueries {
|
|
|
|
datasource: Promise<DataSourceApi>;
|
|
|
|
targets: DataQuery[];
|
|
|
|
}
|
|
|
|
|
2019-09-09 04:29:37 -05:00
|
|
|
export class MixedDatasource extends DataSourceApi<DataQuery> {
|
|
|
|
constructor(instanceSettings: DataSourceInstanceSettings) {
|
|
|
|
super(instanceSettings);
|
|
|
|
}
|
|
|
|
|
2019-09-12 10:28:46 -05:00
|
|
|
query(request: DataQueryRequest<DataQuery>): Observable<DataQueryResponse> {
|
2019-09-09 04:29:37 -05:00
|
|
|
// Remove any invalid queries
|
2021-01-20 00:59:48 -06:00
|
|
|
const queries = request.targets.filter((t) => {
|
2019-09-09 04:29:37 -05:00
|
|
|
return t.datasource !== MIXED_DATASOURCE_NAME;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!queries.length) {
|
2019-09-12 10:28:46 -05:00
|
|
|
return of({ data: [] } as DataQueryResponse); // nothing
|
2019-09-09 04:29:37 -05:00
|
|
|
}
|
|
|
|
|
2019-11-07 00:18:27 -06:00
|
|
|
// Build groups of queries to run in parallel
|
2019-09-12 10:28:46 -05:00
|
|
|
const sets: { [key: string]: DataQuery[] } = groupBy(queries, 'datasource');
|
2019-11-07 00:18:27 -06:00
|
|
|
const mixed: BatchedQueries[] = [];
|
2020-07-06 14:16:27 -05:00
|
|
|
|
2019-09-12 10:28:46 -05:00
|
|
|
for (const key in sets) {
|
|
|
|
const targets = sets[key];
|
2020-07-06 14:16:27 -05:00
|
|
|
const dsName = targets[0].datasource;
|
|
|
|
|
2019-11-07 00:18:27 -06:00
|
|
|
mixed.push({
|
2020-07-03 10:49:29 -05:00
|
|
|
datasource: getDataSourceSrv().get(dsName, request.scopedVars),
|
2019-11-07 00:18:27 -06:00
|
|
|
targets,
|
|
|
|
});
|
|
|
|
}
|
2020-07-06 14:16:27 -05:00
|
|
|
|
2019-11-07 00:18:27 -06:00
|
|
|
return this.batchQueries(mixed, request);
|
|
|
|
}
|
2019-09-12 10:28:46 -05:00
|
|
|
|
2019-11-07 00:18:27 -06:00
|
|
|
batchQueries(mixed: BatchedQueries[], request: DataQueryRequest<DataQuery>): Observable<DataQueryResponse> {
|
2020-03-12 12:16:32 -05:00
|
|
|
const runningQueries = mixed.filter(this.isQueryable).map((query, i) =>
|
|
|
|
from(query.datasource).pipe(
|
|
|
|
mergeMap((api: DataSourceApi) => {
|
|
|
|
const dsRequest = cloneDeep(request);
|
|
|
|
dsRequest.requestId = `mixed-${i}-${dsRequest.requestId || ''}`;
|
|
|
|
dsRequest.targets = query.targets;
|
2019-11-07 00:18:27 -06:00
|
|
|
|
2020-03-12 12:16:32 -05:00
|
|
|
return from(api.query(dsRequest)).pipe(
|
2021-01-20 00:59:48 -06:00
|
|
|
map((response) => {
|
2019-09-12 10:28:46 -05:00
|
|
|
return {
|
|
|
|
...response,
|
|
|
|
data: response.data || [],
|
2020-03-12 12:16:32 -05:00
|
|
|
state: LoadingState.Loading,
|
2019-11-07 00:18:27 -06:00
|
|
|
key: `mixed-${i}-${response.key || ''}`,
|
2019-09-12 10:28:46 -05:00
|
|
|
} as DataQueryResponse;
|
2020-08-19 01:52:40 -05:00
|
|
|
}),
|
2021-01-20 00:59:48 -06:00
|
|
|
catchError((err) => {
|
2020-08-19 01:52:40 -05:00
|
|
|
err = toDataQueryError(err);
|
|
|
|
|
|
|
|
err.message = `${api.name}: ${err.message}`;
|
|
|
|
|
|
|
|
return of({
|
|
|
|
data: [],
|
|
|
|
state: LoadingState.Error,
|
|
|
|
error: err,
|
|
|
|
key: `mixed-${i}-${dsRequest.requestId || ''}`,
|
|
|
|
});
|
2019-09-12 10:28:46 -05:00
|
|
|
})
|
|
|
|
);
|
|
|
|
})
|
2020-03-12 12:16:32 -05:00
|
|
|
)
|
|
|
|
);
|
2019-09-12 10:28:46 -05:00
|
|
|
|
2020-08-19 01:52:40 -05:00
|
|
|
return forkJoin(runningQueries).pipe(map(this.finalizeResponses), mergeAll());
|
2019-09-09 04:29:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
testDatasource() {
|
|
|
|
return Promise.resolve({});
|
|
|
|
}
|
2020-03-12 12:16:32 -05:00
|
|
|
|
|
|
|
private isQueryable(query: BatchedQueries): boolean {
|
|
|
|
return query && Array.isArray(query.targets) && query.targets.length > 0;
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:52:40 -05:00
|
|
|
private finalizeResponses(responses: DataQueryResponse[]): DataQueryResponse[] {
|
2020-03-12 12:16:32 -05:00
|
|
|
const { length } = responses;
|
|
|
|
|
|
|
|
if (length === 0) {
|
|
|
|
return responses;
|
|
|
|
}
|
|
|
|
|
2021-01-20 00:59:48 -06:00
|
|
|
const error = responses.find((response) => response.state === LoadingState.Error);
|
2020-08-19 01:52:40 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-03-12 12:16:32 -05:00
|
|
|
return responses;
|
|
|
|
}
|
2019-09-09 04:29:37 -05:00
|
|
|
}
|