diff --git a/public/app/features/explore/hooks/useStateSync/index.test.tsx b/public/app/features/explore/hooks/useStateSync/index.test.tsx index 1a9daf675c1..19a8741c0ed 100644 --- a/public/app/features/explore/hooks/useStateSync/index.test.tsx +++ b/public/app/features/explore/hooks/useStateSync/index.test.tsx @@ -213,6 +213,69 @@ describe('useStateSync', () => { expect(queries?.[0].datasource?.uid).toBe('loki-uid'); }); + it('inits with mixed datasource if there are multiple datasources in queries and no root level datasource is defined', async () => { + const { location, waitForNextUpdate, store } = setup({ + queryParams: { + panes: JSON.stringify({ + one: { + queries: [ + { datasource: { name: 'loki', uid: 'loki-uid' } }, + { datasource: { name: 'elastic', uid: 'elastic-uid' } }, + ], + }, + }), + schemaVersion: 1, + }, + }); + + const initialHistoryLength = location.getHistory().length; + + await waitForNextUpdate(); + + expect(location.getHistory().length).toBe(initialHistoryLength); + + const search = location.getSearchObject(); + expect(search.panes).toBeDefined(); + + const paneState = store.getState().explore.panes['one']; + expect(paneState?.datasourceInstance?.name).toBe(MIXED_DATASOURCE_NAME); + + expect(paneState?.queries).toHaveLength(2); + expect(paneState?.queries?.[0].datasource?.uid).toBe('loki-uid'); + expect(paneState?.queries?.[1].datasource?.uid).toBe('elastic-uid'); + }); + + it("inits with a query's datasource if there are multiple datasources in queries, no root level datasource, and only one query has a valid datsource", async () => { + const { location, waitForNextUpdate, store } = setup({ + queryParams: { + panes: JSON.stringify({ + one: { + queries: [ + { datasource: { name: 'loki', uid: 'loki-uid' } }, + { datasource: { name: 'UNKNOWN', uid: 'UNKNOWN-UID' } }, + ], + }, + }), + schemaVersion: 1, + }, + }); + + const initialHistoryLength = location.getHistory().length; + + await waitForNextUpdate(); + + expect(location.getHistory().length).toBe(initialHistoryLength); + + const search = location.getSearchObject(); + expect(search.panes).toBeDefined(); + + const paneState = store.getState().explore.panes['one']; + expect(paneState?.datasourceInstance?.getRef().uid).toBe('loki-uid'); + + expect(paneState?.queries).toHaveLength(1); + expect(paneState?.queries?.[0].datasource?.uid).toBe('loki-uid'); + }); + it('inits with the last used datasource from localStorage', async () => { setLastUsedDatasourceUID(1, 'elastic-uid'); const { waitForNextUpdate, store } = setup({ diff --git a/public/app/features/explore/hooks/useStateSync/index.ts b/public/app/features/explore/hooks/useStateSync/index.ts index e40b57d55f5..bb237aaa467 100644 --- a/public/app/features/explore/hooks/useStateSync/index.ts +++ b/public/app/features/explore/hooks/useStateSync/index.ts @@ -1,7 +1,7 @@ import { identity, isEmpty, isEqual, isObject, mapValues, omitBy } from 'lodash'; import { useEffect, useRef } from 'react'; -import { CoreApp, ExploreUrlState, DataSourceApi, toURLRange, EventBusSrv } from '@grafana/data'; +import { CoreApp, ExploreUrlState, DataSourceApi, toURLRange, EventBusSrv, isTruthy } from '@grafana/data'; import { DataQuery, DataSourceRef } from '@grafana/schema'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { useAppNotification } from 'app/core/copy/appNotification'; @@ -374,21 +374,38 @@ async function getPaneDatasource( } catch (_) {} } - // TODO: if queries have multiple datasources we should return mixed datasource - // Else we try to find a datasource in the queries, returning the first one that exists - const queriesWithDS = queries.filter((q) => q.datasource); - for (const query of queriesWithDS) { - try { - return await getDatasourceSrv().get(query.datasource); - } catch (_) {} - } + // Else we try to find a datasource in the queries + const queriesDatasources = [ + ...new Set( + queries + .map((q) => q.datasource) + .filter(isTruthy) + .map((ds) => (typeof ds === 'string' ? ds : ds.uid)) + ), + ]; - // If none of the queries specify a avalid datasource, we use the last used one - const lastUsedDSUID = getLastUsedDatasourceUID(orgId); + try { + if (queriesDatasources.length >= 1) { + const datasources = (await Promise.allSettled(queriesDatasources.map((ds) => getDatasourceSrv().get(ds)))).filter( + isFulfilled + ); + // if queries have multiple (valid) datasources, we return the mixed datasource + if (datasources.length > 1) { + return await getDatasourceSrv().get(MIXED_DATASOURCE_NAME); + } + + // otherwise we return the first datasource. + if (datasources.length === 1) { + return await getDatasourceSrv().get(queriesDatasources[0]); + } + } + } catch (_) {} + + // If none of the queries specify a valid datasource, we use the last used one return ( getDatasourceSrv() - .get(lastUsedDSUID) + .get(getLastUsedDatasourceUID(orgId)) // Or the default one .catch(() => getDatasourceSrv().get()) .catch(() => undefined)