diff --git a/public/app/core/utils/richHistory.ts b/public/app/core/utils/richHistory.ts index a51ef1c71bd..6feb06819b7 100644 --- a/public/app/core/utils/richHistory.ts +++ b/public/app/core/utils/richHistory.ts @@ -234,12 +234,9 @@ export function createQueryHeading(query: RichHistoryQuery, sortOrder: SortOrder return heading; } -export function createQueryText(query: DataQuery, queryDsInstance: DataSourceApi | undefined) { - /* query DatasourceInstance is necessary because we use its getQueryDisplayText method - * to format query text - */ - if (queryDsInstance?.getQueryDisplayText) { - return queryDsInstance.getQueryDisplayText(query); +export function createQueryText(query: DataQuery, dsApi?: DataSourceApi) { + if (dsApi?.getQueryDisplayText) { + return dsApi.getQueryDisplayText(query); } return getQueryDisplayText(query); @@ -270,7 +267,6 @@ export function createDatasourcesList() { return { name: dsSettings.name, uid: dsSettings.uid, - imgUrl: dsSettings.meta.info.logos.small, }; }); } diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx index 15b1f4ba4ff..9b3011ff153 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx @@ -1,11 +1,10 @@ -import { render, screen, fireEvent, getByText } from '@testing-library/react'; +import { render, screen, fireEvent, getByText, waitFor } from '@testing-library/react'; import React from 'react'; -import { DataSourceApi } from '@grafana/data'; -import { DataQuery } from '@grafana/schema'; +import { DataSourceApi, DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data'; +import { DataQuery, DataSourceRef } from '@grafana/schema'; import appEvents from 'app/core/app_events'; -import { mockDataSource } from 'app/features/alerting/unified/mocks'; -import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; +import { MixedDatasource } from 'app/plugins/datasource/mixed/MixedDataSource'; import { ShowConfirmModalEvent } from 'app/types/events'; import { ExploreId, RichHistoryQuery } from 'app/types/explore'; @@ -14,10 +13,54 @@ import { RichHistoryCard, Props } from './RichHistoryCard'; const starRichHistoryMock = jest.fn(); const deleteRichHistoryMock = jest.fn(); -const mockDS = mockDataSource({ - name: 'CloudManager', - type: DataSourceType.Alertmanager, -}); +class MockDatasourceApi implements DataSourceApi { + name: string; + id: number; + type: string; + uid: string; + meta: DataSourcePluginMeta<{}>; + + constructor(name: string, id: number, type: string, uid: string, others?: Partial) { + this.name = name; + this.id = id; + this.type = type; + this.uid = uid; + this.meta = { + info: { + logos: { + small: `${type}.png`, + }, + }, + } as DataSourcePluginMeta; + + Object.assign(this, others); + } + + query(): ReturnType { + throw new Error('Method not implemented.'); + } + testDatasource(): ReturnType { + throw new Error('Method not implemented.'); + } + getRef(): DataSourceRef { + throw new Error('Method not implemented.'); + } +} + +const dsStore: Record = { + alertmanager: new MockDatasourceApi('Alertmanager', 3, 'alertmanager', 'alertmanager'), + loki: new MockDatasourceApi('Loki', 2, 'loki', 'loki'), + prometheus: new MockDatasourceApi('Prometheus', 1, 'prometheus', 'prometheus', { + getQueryDisplayText: (query: MockQuery) => query.queryText || 'Unknwon query', + }), + mixed: new MixedDatasource({ + id: 4, + name: 'Mixed', + type: 'mixed', + uid: 'mixed', + meta: { info: { logos: { small: 'mixed.png' } }, mixed: true }, + } as DataSourceInstanceSettings), +}; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -27,19 +70,33 @@ jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => { return { getDataSourceSrv: () => ({ - get: () => Promise.resolve(mockDS), - getList: () => [mockDS], - getInstanceSettings: () => mockDS, + get: (ref: DataSourceRef | string) => { + const uid = typeof ref === 'string' ? ref : ref.uid; + if (!uid) { + return Promise.reject(); + } + if (dsStore[uid]) { + return Promise.resolve(dsStore[uid]); + } + return Promise.reject(); + }, }), }; }); +const copyStringToClipboard = jest.fn(); +jest.mock('app/core/utils/explore', () => ({ + ...jest.requireActual('app/core/utils/explore'), + copyStringToClipboard: (str: string) => copyStringToClipboard(str), +})); + jest.mock('app/core/app_events', () => ({ publish: jest.fn(), })); interface MockQuery extends DataQuery { query: string; + queryText?: string; } const setup = (propOverrides?: Partial>) => { @@ -47,8 +104,8 @@ const setup = (propOverrides?: Partial>) => { query: { id: '1', createdAt: 1, - datasourceUid: 'Test datasource uid', - datasourceName: 'Test datasource', + datasourceUid: 'loki', + datasourceName: 'Loki', starred: false, comment: '', queries: [ @@ -57,15 +114,13 @@ const setup = (propOverrides?: Partial>) => { { query: 'query3', refId: 'C' }, ], }, - dsImg: '/app/img', - isRemoved: false, changeDatasource: jest.fn(), starHistoryItem: starRichHistoryMock, deleteHistoryItem: deleteRichHistoryMock, commentHistoryItem: jest.fn(), setQueries: jest.fn(), exploreId: ExploreId.left, - datasourceInstance: { name: 'Datasource' } as DataSourceApi, + datasourceInstance: dsStore.loki, }; Object.assign(props, propOverrides); @@ -107,12 +162,226 @@ describe('RichHistoryCard', () => { expect(datasourceIcon).toBeInTheDocument(); expect(datasourceName).toBeInTheDocument(); }); + it('should render "Data source does not exist anymore" if removed data source', async () => { - setup({ isRemoved: true }); + setup({ + query: { + id: '2', + createdAt: 1, + datasourceUid: 'non-existent DS', + datasourceName: 'Test datasource', + starred: false, + comment: '', + queries: [ + { query: 'query1', refId: 'A' }, + { query: 'query2', refId: 'B' }, + { query: 'query3', refId: 'C' }, + ], + }, + }); const datasourceName = await screen.findByLabelText('Data source name'); expect(datasourceName).toHaveTextContent('Data source does not exist anymore'); }); + describe('copy queries to clipboard', () => { + it('should copy query model to clipboard when copying a query from a non existent datasource', async () => { + setup({ + query: { + id: '2', + createdAt: 1, + datasourceUid: 'non-existent DS', + datasourceName: 'Test datasource', + starred: false, + comment: '', + queries: [{ query: 'query1', refId: 'A' }], + }, + }); + const copyQueriesButton = await screen.findByRole('button', { name: 'Copy query to clipboard' }); + expect(copyQueriesButton).toBeInTheDocument(); + fireEvent.click(copyQueriesButton); + await waitFor(() => { + expect(copyStringToClipboard).toHaveBeenCalledTimes(1); + }); + expect(copyStringToClipboard).toHaveBeenCalledWith(JSON.stringify({ query: 'query1' })); + }); + + it('should copy query model to clipboard when copying a query from a datasource that does not implement getQueryDisplayText', async () => { + setup({ + query: { + id: '2', + createdAt: 1, + datasourceUid: 'loki', + datasourceName: 'Test datasource', + starred: false, + comment: '', + queries: [{ query: 'query1', refId: 'A' }], + }, + }); + const copyQueriesButton = await screen.findByRole('button', { name: 'Copy query to clipboard' }); + expect(copyQueriesButton).toBeInTheDocument(); + fireEvent.click(copyQueriesButton); + await waitFor(() => { + expect(copyStringToClipboard).toHaveBeenCalledTimes(1); + }); + expect(copyStringToClipboard).toHaveBeenCalledWith(JSON.stringify({ query: 'query1' })); + }); + + it('should copy query text to clipboard when copying a query from a datasource that implements getQueryDisplayText', async () => { + setup({ + query: { + id: '2', + createdAt: 1, + datasourceUid: 'prometheus', + datasourceName: 'Test datasource', + starred: false, + comment: '', + queries: [{ query: 'query1', refId: 'A', queryText: 'query1' }], + }, + }); + const copyQueriesButton = await screen.findByRole('button', { name: 'Copy query to clipboard' }); + expect(copyQueriesButton).toBeInTheDocument(); + fireEvent.click(copyQueriesButton); + await waitFor(() => { + expect(copyStringToClipboard).toHaveBeenCalledTimes(1); + }); + expect(copyStringToClipboard).toHaveBeenCalledWith('query1'); + }); + + it('should use each datasource getQueryDisplayText when copying queries', async () => { + setup({ + query: { + id: '2', + createdAt: 1, + datasourceUid: 'mixed', + datasourceName: 'Mixed', + starred: false, + comment: '', + queries: [ + { query: 'query1', refId: 'A', queryText: 'query1', datasource: { uid: 'prometheus' } }, + { query: 'query2', refId: 'B', datasource: { uid: 'loki' } }, + ], + }, + }); + const copyQueriesButton = await screen.findByRole('button', { name: 'Copy query to clipboard' }); + expect(copyQueriesButton).toBeInTheDocument(); + fireEvent.click(copyQueriesButton); + await waitFor(() => { + expect(copyStringToClipboard).toHaveBeenCalledTimes(1); + }); + expect(copyStringToClipboard).toHaveBeenCalledWith(`query1\n${JSON.stringify({ query: 'query2' })}`); + }); + }); + + describe('run queries', () => { + it('should be disabled if at least one query datasource is missing when using mixed', async () => { + const setQueries = jest.fn(); + const changeDatasource = jest.fn(); + const queries: MockQuery[] = [ + { query: 'query1', refId: 'A', datasource: { uid: 'nonexistent-ds' } }, + { query: 'query2', refId: 'B', datasource: { uid: 'loki' } }, + ]; + setup({ + setQueries, + changeDatasource, + query: { + id: '2', + createdAt: 1, + datasourceUid: 'mixed', + datasourceName: 'Mixed', + starred: false, + comment: '', + queries, + }, + }); + const runQueryButton = await screen.findByRole('button', { name: /run query/i }); + + expect(runQueryButton).toBeDisabled(); + }); + + it('should be disabled if at datasource is missing', async () => { + const setQueries = jest.fn(); + const changeDatasource = jest.fn(); + const queries: MockQuery[] = [ + { query: 'query1', refId: 'A' }, + { query: 'query2', refId: 'B' }, + ]; + setup({ + setQueries, + changeDatasource, + query: { + id: '2', + createdAt: 1, + datasourceUid: 'nonexistent-ds', + datasourceName: 'nonexistent-ds', + starred: false, + comment: '', + queries, + }, + }); + const runQueryButton = await screen.findByRole('button', { name: /run query/i }); + + expect(runQueryButton).toBeDisabled(); + }); + + it('should only set new queries when running queries from the same datasource', async () => { + const setQueries = jest.fn(); + const changeDatasource = jest.fn(); + const queries: MockQuery[] = [ + { query: 'query1', refId: 'A' }, + { query: 'query2', refId: 'B' }, + ]; + setup({ + setQueries, + changeDatasource, + query: { + id: '2', + createdAt: 1, + datasourceUid: 'loki', + datasourceName: 'Loki', + starred: false, + comment: '', + queries, + }, + }); + + const runQueryButton = await screen.findByRole('button', { name: /run query/i }); + fireEvent.click(runQueryButton); + + expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries); + expect(changeDatasource).not.toHaveBeenCalled(); + }); + + it('should change datasource to mixed and set new queries when running queries from mixed datasource', async () => { + const setQueries = jest.fn(); + const changeDatasource = jest.fn(); + const queries: MockQuery[] = [ + { query: 'query1', refId: 'A', datasource: { type: 'loki', uid: 'loki' } }, + { query: 'query2', refId: 'B', datasource: { type: 'prometheus', uid: 'prometheus' } }, + ]; + setup({ + setQueries, + changeDatasource, + query: { + id: '2', + createdAt: 1, + datasourceUid: 'mixed', + datasourceName: 'Mixed', + starred: false, + comment: '', + queries, + }, + }); + + const runQueryButton = await screen.findByRole('button', { name: /run query/i }); + fireEvent.click(runQueryButton); + + await waitFor(() => { + expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries); + expect(changeDatasource).toHaveBeenCalledWith(expect.any(String), 'mixed'); + }); + }); + }); + describe('commenting', () => { it('should render comment, if comment present', async () => { setup({ query: starredQueryWithComment }); diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.tsx index ccf5e9eee6d..c21c423f942 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.tsx @@ -1,26 +1,26 @@ import { css, cx } from '@emotion/css'; -import React, { useState, useEffect } from 'react'; +import React, { useCallback, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import { useAsync } from 'react-use'; -import { DataSourceApi, GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, DataSourceApi } from '@grafana/data'; import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; -import { TextArea, Button, IconButton, useStyles2 } from '@grafana/ui'; +import { TextArea, Button, IconButton, useStyles2, LoadingPlaceholder } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; import appEvents from 'app/core/app_events'; import { createSuccessNotification } from 'app/core/copy/appNotification'; import { copyStringToClipboard } from 'app/core/utils/explore'; import { createUrlFromRichHistory, createQueryText } from 'app/core/utils/richHistory'; import { createAndCopyShortLink } from 'app/core/utils/shortLinks'; +import { changeDatasource } from 'app/features/explore/state/datasource'; +import { starHistoryItem, commentHistoryItem, deleteHistoryItem } from 'app/features/explore/state/history'; +import { setQueries } from 'app/features/explore/state/query'; import { dispatch } from 'app/store/store'; import { StoreState } from 'app/types'; +import { ShowConfirmModalEvent } from 'app/types/events'; import { RichHistoryQuery, ExploreId } from 'app/types/explore'; -import { ShowConfirmModalEvent } from '../../../types/events'; -import { changeDatasource } from '../state/datasource'; -import { starHistoryItem, commentHistoryItem, deleteHistoryItem } from '../state/history'; -import { setQueries } from '../state/query'; - function mapStateToProps(state: StoreState, { exploreId }: { exploreId: ExploreId }) { const explore = state.explore; const { datasourceInstance } = explore[exploreId]!; @@ -42,8 +42,6 @@ const connector = connect(mapStateToProps, mapDispatchToProps); interface OwnProps { query: RichHistoryQuery; - dsImg: string; - isRemoved: boolean; } export type Props = ConnectedProps & OwnProps; @@ -58,6 +56,7 @@ const getStyles = (theme: GrafanaTheme2) => { return { queryCard: css` + position: relative; display: flex; flex-direction: column; border: 1px solid ${theme.colors.border.weak}; @@ -84,12 +83,6 @@ const getStyles = (theme: GrafanaTheme2) => { margin-right: ${theme.spacing(1)}; } `, - datasourceContainer: css` - display: flex; - align-items: center; - font-size: ${theme.typography.bodySmall.fontSize}; - font-weight: ${theme.typography.fontWeightMedium}; - `, queryActionButtons: css` max-width: ${rightColumnContentWidth}; display: flex; @@ -103,15 +96,6 @@ const getStyles = (theme: GrafanaTheme2) => { font-weight: ${theme.typography.fontWeightMedium}; width: calc(100% - ${rightColumnWidth}); `, - queryRow: css` - border-top: 1px solid ${theme.colors.border.weak}; - word-break: break-all; - padding: 4px 2px; - :first-child { - border-top: none; - padding: 0 0 4px 0; - } - `, updateCommentContainer: css` width: calc(100% + ${rightColumnWidth}); margin-top: ${theme.spacing(1)}; @@ -143,14 +127,21 @@ const getStyles = (theme: GrafanaTheme2) => { } } `, + loader: css` + position: absolute; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: ${theme.colors.background.secondary}; + `, }; }; export function RichHistoryCard(props: Props) { const { query, - dsImg, - isRemoved, commentHistoryItem, starHistoryItem, deleteHistoryItem, @@ -161,16 +152,33 @@ export function RichHistoryCard(props: Props) { } = props; const [activeUpdateComment, setActiveUpdateComment] = useState(false); const [comment, setComment] = useState(query.comment); - const [queryDsInstance, setQueryDsInstance] = useState(undefined); + const { value, loading } = useAsync(async () => { + let dsInstance: DataSourceApi | undefined; + try { + dsInstance = await getDataSourceSrv().get(query.datasourceUid); + } catch (e) {} - useEffect(() => { - const getQueryDsInstance = async () => { - const ds = await getDataSourceSrv().get(query.datasourceUid); - setQueryDsInstance(ds); + return { + dsInstance, + queries: await Promise.all( + query.queries.map(async (query) => { + let datasource; + if (dsInstance?.meta.mixed) { + try { + datasource = await getDataSourceSrv().get(query.datasource); + } catch (e) {} + } else { + datasource = dsInstance; + } + + return { + query, + datasource, + }; + }) + ), }; - - getQueryDsInstance(); - }, [query.datasourceUid]); + }, [query.datasourceUid, query.queries]); const styles = useStyles2(getStyles); @@ -178,25 +186,34 @@ export function RichHistoryCard(props: Props) { const queriesToRun = query.queries; const differentDataSource = query.datasourceUid !== datasourceInstance?.uid; if (differentDataSource) { - await changeDatasource(exploreId, query.datasourceUid, { importQueries: true }); - setQueries(exploreId, queriesToRun); - } else { - setQueries(exploreId, queriesToRun); + await changeDatasource(exploreId, query.datasourceUid); } + setQueries(exploreId, queriesToRun); + reportInteraction('grafana_explore_query_history_run', { queryHistoryEnabled: config.queryHistoryEnabled, differentDataSource, }); }; - const onCopyQuery = () => { + const onCopyQuery = async () => { const datasources = [...query.queries.map((q) => q.datasource?.type || 'unknown')]; reportInteraction('grafana_explore_query_history_copy_query', { datasources, - mixed: Boolean(queryDsInstance?.meta.mixed), + mixed: Boolean(value?.dsInstance?.meta.mixed), }); - const queriesToCopy = query.queries.map((q) => createQueryText(q, queryDsInstance)).join('\n'); - copyStringToClipboard(queriesToCopy); + + if (loading || !value) { + return; + } + + const queriesText = value.queries + .map((q) => { + return createQueryText(q.query, q.datasource); + }) + .join('\n'); + + copyStringToClipboard(queriesText); dispatch(notifyApp(createSuccessNotification('Query copied to clipboard'))); }; @@ -273,9 +290,7 @@ export function RichHistoryCard(props: Props) { className={styles.textArea} />
- + @@ -291,7 +306,7 @@ export function RichHistoryCard(props: Props) { title={query.comment?.length > 0 ? 'Edit comment' : 'Add comment'} /> - {!isRemoved && ( + {value?.dsInstance && ( )} @@ -307,23 +322,14 @@ export function RichHistoryCard(props: Props) { return (
-
- -
- {isRemoved ? 'Data source does not exist anymore' : query.datasourceName} -
-
+ + {queryActionButtons}
- {query.queries.map((q, i) => { - const queryText = createQueryText(q, queryDsInstance); - return ( -
- {queryText} -
- ); + {value?.queries.map((q, i) => { + return ; })} {!activeUpdateComment && query.comment && (
@@ -334,12 +340,89 @@ export function RichHistoryCard(props: Props) {
{!activeUpdateComment && (
-
)}
+ {loading && } +
+ ); +} + +const getQueryStyles = (theme: GrafanaTheme2) => ({ + queryRow: css` + border-top: 1px solid ${theme.colors.border.weak}; + display: flex; + flex-direction: row; + padding: 4px 0px; + gap: 4px; + :first-child { + border-top: none; + } + `, + dsInfoContainer: css` + display: flex; + align-items: center; + `, + queryText: css` + word-break: break-all; + `, +}); + +interface QueryProps { + query: { + query: DataQuery; + datasource?: DataSourceApi; + }; + /** Show datasource info (icon+name) alongside the query text */ + showDsInfo?: boolean; +} + +const Query = ({ query, showDsInfo = false }: QueryProps) => { + const styles = useStyles2(getQueryStyles); + + return ( +
+ {showDsInfo && ( +
+ + {': '} +
+ )} + + {createQueryText(query.query, query.datasource)} + +
+ ); +}; + +const getDsInfoStyles = (size: 'sm' | 'md') => (theme: GrafanaTheme2) => + css` + display: flex; + align-items: center; + font-size: ${theme.typography[size === 'sm' ? 'bodySmall' : 'body'].fontSize}; + font-weight: ${theme.typography.fontWeightMedium}; + white-space: nowrap; + `; + +function DatasourceInfo({ dsApi, size }: { dsApi?: DataSourceApi; size: 'sm' | 'md' }) { + const getStyles = useCallback((theme: GrafanaTheme2) => getDsInfoStyles(size)(theme), [size]); + const styles = useStyles2(getStyles); + + return ( +
+ {dsApi?.type +
{dsApi?.name || 'Data source does not exist anymore'}
); } diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx index 6b0fd24b10e..c3f5a990bf9 100644 --- a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx @@ -240,16 +240,7 @@ export function RichHistoryQueriesTab(props: Props) {
{mappedQueriesToHeadings[heading].map((q: RichHistoryQuery) => { - const idx = listOfDatasources.findIndex((d) => d.uid === q.datasourceUid); - return ( - - ); + return ; })}
); diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx index 8b3a2d20576..dd6c9451a38 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx @@ -153,16 +153,7 @@ export function RichHistoryStarredTab(props: Props) { {loading && Loading results...} {!loading && queries.map((q) => { - const idx = listOfDatasources.findIndex((d) => d.uid === q.datasourceUid); - return ( - - ); + return ; })} {queries.length && queries.length !== totalQueries ? (
diff --git a/public/app/features/explore/spec/helper/interactions.ts b/public/app/features/explore/spec/helper/interactions.ts index 5c22b4305d1..4599ed101f1 100644 --- a/public/app/features/explore/spec/helper/interactions.ts +++ b/public/app/features/explore/spec/helper/interactions.ts @@ -71,7 +71,7 @@ export const commentQueryHistory = async ( const input = withinExplore(exploreId).getByPlaceholderText('An optional description of what the query does.'); await userEvent.clear(input); await userEvent.type(input, comment); - await invokeAction(queryIndex, 'Submit button', exploreId); + await invokeAction(queryIndex, 'Save comment', exploreId); }; export const deleteQueryHistory = async (queryIndex: number, exploreId: ExploreId = ExploreId.left) => { diff --git a/public/app/features/explore/state/datasource.ts b/public/app/features/explore/state/datasource.ts index 90a7808bd80..5aed7e998ed 100644 --- a/public/app/features/explore/state/datasource.ts +++ b/public/app/features/explore/state/datasource.ts @@ -41,7 +41,7 @@ export function changeDatasource( exploreId: ExploreId, datasourceUid: string, options?: { importQueries: boolean } -): ThunkResult { +): ThunkResult> { return async (dispatch, getState) => { const orgId = getState().user.orgId; const { history, instance } = await loadAndInitDatasource(orgId, { uid: datasourceUid }); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 531ebeb5374..bf3b3bbea04 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -305,7 +305,7 @@ export const importQueries = ( sourceDataSource: DataSourceApi | undefined | null, targetDataSource: DataSourceApi, singleQueryChangeRef?: string // when changing one query DS to another in a mixed environment, we do not want to change all queries, just the one being changed -): ThunkResult => { +): ThunkResult> => { return async (dispatch) => { if (!sourceDataSource) { // explore not initialized diff --git a/public/app/plugins/datasource/mixed/MixedDataSource.ts b/public/app/plugins/datasource/mixed/MixedDataSource.ts index b9d3761c273..a266215149c 100644 --- a/public/app/plugins/datasource/mixed/MixedDataSource.ts +++ b/public/app/plugins/datasource/mixed/MixedDataSource.ts @@ -1,4 +1,4 @@ -import { cloneDeep, groupBy, omit } from 'lodash'; +import { cloneDeep, groupBy } from 'lodash'; import { forkJoin, from, Observable, of, OperatorFunction } from 'rxjs'; import { catchError, map, mergeAll, mergeMap, reduce, toArray } from 'rxjs/operators'; @@ -98,13 +98,6 @@ export class MixedDatasource extends DataSourceApi { return Promise.resolve({}); } - getQueryDisplayText(query: DataQuery) { - const strippedQuery = omit(query, ['key', 'refId', 'datasource']); - const strippedQueryJSON = JSON.stringify(strippedQuery); - const prefix = query.datasource?.type ? `${query.datasource?.type}: ` : ''; - return `${prefix}${strippedQueryJSON}`; - } - private isQueryable(query: BatchedQueries): boolean { return query && Array.isArray(query.targets) && query.targets.length > 0; }