@ -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 < T extends DataQuery > implements DataSourceApi < T > {
name : string ;
id : number ;
type : string ;
uid : string ;
meta : DataSourcePluginMeta < { } > ;
constructor ( name : string , id : number , type : string , uid : string , others? : Partial < DataSourceApi > ) {
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 < DataSourceApi [ ' query ' ] > {
throw new Error ( 'Method not implemented.' ) ;
}
testDatasource ( ) : ReturnType < DataSourceApi [ ' testDatasource ' ] > {
throw new Error ( 'Method not implemented.' ) ;
}
getRef ( ) : DataSourceRef {
throw new Error ( 'Method not implemented.' ) ;
}
}
const dsStore : Record < string , DataSourceApi > = {
alertmanager : new MockDatasourceApi ( 'Alertmanager' , 3 , 'alertmanager' , 'alertmanager' ) ,
loki : new MockDatasourceApi ( 'Loki' , 2 , 'loki' , 'loki' ) ,
prometheus : new MockDatasourceApi < MockQuery > ( '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 < Props < MockQuery > > ) = > {
@ -47,8 +104,8 @@ const setup = (propOverrides?: Partial<Props<MockQuery>>) => {
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<Props<MockQuery>>) => {
{ 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 DataSourceAp i,
datasourceInstance : dsStore.lok i ,
} ;
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 } ) ;