Explore: Add Mixed Datasource (#51605)

* Toggle on the mixed mode option

* Ensure switching to mixed gives existing query prev datasource

* WIP - Populate datasource when switching between mixed and not

* WIP - handle change from mixed

* Remove preimport filter, refine filter to work for queries

* WIP debugging datasource transition

* Ensure creating a new query gets target data source if switching with no matches between

* Add mixed datasource to rich history display

* Cleanup console logs, add relevant comments

* Add feature toggle for mixed datasource

* Fix Wrapper tests

* Fix tests!

* Fix test types and add feature tracking

* Remove unnecessary default, remove explore/mixed workarounds for D2E

* Move display text logic to mixed datasource file

* Add in the default query parameters to a generated empty query

* Condense some code

* Apply suggestions from code review

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

Co-authored-by: Giordano Ricci <me@giordanoricci.com>
This commit is contained in:
Kristina
2022-07-27 09:40:59 -05:00
committed by GitHub
co-authored by Giordano Ricci
parent a98920f910
commit e2258120e7
18 changed files with 194 additions and 87 deletions
+51 -21
View File
@@ -7,6 +7,7 @@ import {
DataQuery,
DataQueryRequest,
DataSourceApi,
DataSourceRef,
dateMath,
DateTime,
DefaultTimeZone,
@@ -24,7 +25,7 @@ import {
toUtc,
urlUtil,
} from '@grafana/data';
import { DataSourceSrv } from '@grafana/runtime';
import { DataSourceSrv, getDataSourceSrv } from '@grafana/runtime';
import { RefreshPicker } from '@grafana/ui';
import store from 'app/core/store';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
@@ -70,19 +71,6 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise<strin
let exploreTargets: DataQuery[] = panel.targets.map((t) => omit(t, 'legendFormat'));
let url: string | undefined;
// Mixed datasources need to choose only one datasource
if (exploreDatasource.meta?.id === 'mixed' && exploreTargets) {
// Find first explore datasource among targets
for (const t of exploreTargets) {
const datasource = await datasourceSrv.get(t.datasource || undefined);
if (datasource) {
exploreDatasource = datasource;
exploreTargets = panel.targets.filter((t) => t.datasource === datasource.name);
break;
}
}
}
if (exploreDatasource) {
const range = timeSrv.timeRangeForUrl();
let state: Partial<ExploreUrlState> = { range };
@@ -99,7 +87,7 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise<strin
...state,
datasource: exploreDatasource.name,
context: 'explore',
queries: exploreTargets.map((t) => ({ ...t, datasource: exploreDatasource.getRef() })),
queries: exploreTargets,
};
}
@@ -254,8 +242,34 @@ export function generateKey(index = 0): string {
return `Q-${uuidv4()}-${index}`;
}
export function generateEmptyQuery(queries: DataQuery[], index = 0): DataQuery {
return { refId: getNextRefIdChar(queries), key: generateKey(index) };
export async function generateEmptyQuery(
queries: DataQuery[],
index = 0,
dataSourceOverride?: DataSourceRef
): Promise<DataQuery> {
let datasourceInstance: DataSourceApi | undefined;
let datasourceRef: DataSourceRef | null | undefined;
let defaultQuery: Partial<DataQuery> | undefined;
// datasource override is if we have switched datasources with no carry-over - we want to create a new query with a datasource we define
if (dataSourceOverride) {
datasourceRef = dataSourceOverride;
} else if (queries.length > 0 && queries[queries.length - 1].datasource) {
// otherwise use last queries' datasource
datasourceRef = queries[queries.length - 1].datasource;
} else {
// if neither exists, use the default datasource
datasourceInstance = await getDataSourceSrv().get();
defaultQuery = datasourceInstance.getDefaultQuery?.(CoreApp.Explore);
datasourceRef = datasourceInstance.getRef();
}
if (!datasourceInstance) {
datasourceInstance = await getDataSourceSrv().get(datasourceRef);
defaultQuery = datasourceInstance.getDefaultQuery?.(CoreApp.Explore);
}
return { refId: getNextRefIdChar(queries), key: generateKey(index), datasource: datasourceRef, ...defaultQuery };
}
export const generateNewKeyAndAddRefIdIfMissing = (target: DataQuery, queries: DataQuery[], index = 0): DataQuery => {
@@ -267,8 +281,13 @@ export const generateNewKeyAndAddRefIdIfMissing = (target: DataQuery, queries: D
/**
* Ensure at least one target exists and that targets have the necessary keys
*
* This will return an empty array if there are no datasources, as Explore is not usable in that state
*/
export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
export async function ensureQueries(
queries?: DataQuery[],
newQueryDataSourceOverride?: DataSourceRef
): Promise<DataQuery[]> {
if (queries && typeof queries === 'object' && queries.length > 0) {
const allQueries = [];
for (let index = 0; index < queries.length; index++) {
@@ -287,7 +306,18 @@ export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
}
return allQueries;
}
return [{ ...generateEmptyQuery(queries ?? []) }];
try {
// if a datasourse override get its ref, otherwise get the default datasource
const emptyQueryRef = newQueryDataSourceOverride ?? (await getDataSourceSrv().get()).getRef();
const emptyQuery = await generateEmptyQuery(queries ?? [], undefined, emptyQueryRef);
return [emptyQuery];
} catch {
// if there are no datasources, return an empty array because we will not allow use of explore
// this will occur on init of explore with no datasources defined
return [];
}
}
/**
@@ -344,9 +374,9 @@ export function clearHistory(datasourceId: string) {
store.delete(historyKey);
}
export const getQueryKeys = (queries: DataQuery[], datasourceInstance?: DataSourceApi | null): string[] => {
export const getQueryKeys = (queries: DataQuery[]): string[] => {
const queryKeys = queries.reduce<string[]>((newQueryKeys, query, index) => {
const primaryKey = datasourceInstance && datasourceInstance.name ? datasourceInstance.name : query.key;
const primaryKey = query.datasource?.uid || query.key;
return newQueryKeys.concat(`${primaryKey}-${index}`);
}, []);