@ -1,12 +1,11 @@
import { act , waitFor } from '@testing-library/react' ;
import { renderHook } from '@testing-library/react-hooks' ;
import { act , waitFor , renderHook } from '@testing-library/react' ;
import { createMemoryHistory } from 'history' ;
import { stringify } from 'querystring' ;
import { ReactNode } from 'react' ;
import { TestProvider } from 'test/helpers/TestProvider' ;
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock' ;
import { DataSourceApi , UrlQueryMap } from '@grafana/data' ;
import { DataQuery , Data SourceApi , UrlQueryMap } from '@grafana/data' ;
import { HistoryWrapper , setDataSourceSrv , DataSourceSrv } from '@grafana/runtime' ;
import { setLastUsedDatasourceUID } from 'app/core/utils/explore' ;
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource' ;
@ -108,7 +107,7 @@ function setup({ queryParams = {}, datasourceGetter = defaultDsGetter }: SetupPa
) ;
return {
. . . renderHook < { params : UrlQueryMap ; children : ReactNode } , void > ( ( { params } ) = > useStateSync ( params ) , {
. . . renderHook ( ( { params } ) = > useStateSync ( params ) , {
wrapper ,
initialProps : {
children : null ,
@ -122,20 +121,19 @@ function setup({ queryParams = {}, datasourceGetter = defaultDsGetter }: SetupPa
describe ( 'useStateSync' , ( ) = > {
it ( 'does not push a new entry to history on first render' , async ( ) = > {
const { location , waitForNextUpdate } = setup ( { } ) ;
const { location } = setup ( { } ) ;
const initialHistoryLength = location . getHistory ( ) . length ;
await waitForNextUpdate ( ) ;
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
await waitFor ( ( ) = > {
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
} ) ;
} ) ;
it ( 'correctly inits an explore pane for each key in the panes search object' , async ( ) = > {
const { location , waitForNextUpdate , store } = setup ( {
const { location , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : {
@ -180,30 +178,31 @@ describe('useStateSync', () => {
const initialHistoryLength = location . getHistory ( ) . length ;
await waitForNextUpdate ( ) ;
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
const panes = location . getSearch ( ) . get ( 'panes' ) ;
expect ( panes ) . not . toBeNull ( ) ;
if ( panes ) {
// check if the URL is properly encoded when finishing rendering the hook. (this would be '1 2' otherwise)
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 0 ] . refId ) . toBe ( '1+2' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 0 ] . datasource ? . uid ) . toBe ( 'loki-uid' ) ;
// we expect panes in the state to be in the same order as the ones in the URL
expect ( Object . keys ( store . getState ( ) . explore . panes ) ) . toStrictEqual ( Object . keys ( JSON . parse ( panes ) ) ) ;
// check that the datasources for the queries resolved correctly when set to a name or uid
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 1 ] . refId ) . toBe ( '3' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 1 ] . datasource ? . uid ) . toBe ( 'loki-uid' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 2 ] . refId ) . toBe ( '4' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 2 ] . datasource ? . uid ) . toBe ( 'loki-uid' ) ;
}
// await waitForNextUpdate();
await waitFor ( ( ) = > {
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
const panes = location . getSearch ( ) . get ( 'panes' ) ;
expect ( panes ) . not . toBeNull ( ) ;
if ( panes ) {
// check if the URL is properly encoded when finishing rendering the hook. (this would be '1 2' otherwise)
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 0 ] . refId ) . toBe ( '1+2' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 0 ] . datasource ? . uid ) . toBe ( 'loki-uid' ) ;
// we expect panes in the state to be in the same order as the ones in the URL
expect ( Object . keys ( store . getState ( ) . explore . panes ) ) . toStrictEqual ( Object . keys ( JSON . parse ( panes ) ) ) ;
// check that the datasources for the queries resolved correctly when set to a name or uid
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 1 ] . refId ) . toBe ( '3' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 1 ] . datasource ? . uid ) . toBe ( 'loki-uid' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 2 ] . refId ) . toBe ( '4' ) ;
expect ( JSON . parse ( panes ) [ 'one' ] . queries [ 2 ] . datasource ? . uid ) . toBe ( 'loki-uid' ) ;
}
} ) ;
} ) ;
it ( 'inits with a default query from the root level datasource when there are no valid queries in the URL' , async ( ) = > {
const { location , waitForNextUpdate , store } = setup ( {
const { location , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : { datasource : 'loki-uid' , queries : [ { datasource : { name : 'UNKNOWN' , uid : 'UNKNOWN-DS' } } ] } ,
@ -214,21 +213,21 @@ describe('useStateSync', () => {
const initialHistoryLength = location . getHistory ( ) . length ;
await waitForNextUpdate ( ) ;
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
await waitFor ( ( ) = > {
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
const queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 1 ) ;
const queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 1 ) ;
expect ( queries ? . [ 0 ] . datasource ? . uid ) . toBe ( 'loki-uid' ) ;
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 ( {
const { location , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : {
@ -244,23 +243,23 @@ describe('useStateSync', () => {
const initialHistoryLength = location . getHistory ( ) . length ;
await waitForNextUpdate ( ) ;
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
await waitFor ( ( ) = > {
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
const paneState = store . getState ( ) . explore . panes [ 'one' ] ;
expect ( paneState ? . datasourceInstance ? . name ) . toBe ( MIXED_DATASOURCE_NAME ) ;
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' ) ;
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 ( {
const { location , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : {
@ -276,44 +275,44 @@ describe('useStateSync', () => {
const initialHistoryLength = location . getHistory ( ) . length ;
await waitForNextUpdate ( ) ;
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
await waitFor ( ( ) = > {
expect ( location . getHistory ( ) . length ) . toBe ( initialHistoryLength ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
const search = location . getSearchObject ( ) ;
expect ( search . panes ) . toBeDefined ( ) ;
const paneState = store . getState ( ) . explore . panes [ 'one' ] ;
expect ( paneState ? . datasourceInstance ? . getRef ( ) . uid ) . toBe ( 'loki-uid' ) ;
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' ) ;
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 ( {
const { store } = setup ( {
queryParams : { } ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( Object . values ( store . getState ( ) . explore . panes ) [ 0 ] ? . datasourceInstance ? . uid ) . toBe ( 'elastic-uid' ) ;
await waitFor ( ( ) = > {
expect ( Object . values ( store . getState ( ) . explore . panes ) [ 0 ] ? . datasourceInstance ? . uid ) . toBe ( 'elastic-uid' ) ;
} ) ;
} ) ;
it ( 'inits with the default datasource if the last used in localStorage does not exits' , async ( ) = > {
setLastUsedDatasourceUID ( 1 , 'unknown-ds-uid' ) ;
const { waitForNextUpdate , store } = setup ( {
const { store } = setup ( {
queryParams : { } ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( Object . values ( store . getState ( ) . explore . panes ) [ 0 ] ? . datasourceInstance ? . uid ) . toBe ( 'loki-uid' ) ;
await waitFor ( ( ) = > {
expect ( Object . values ( store . getState ( ) . explore . panes ) [ 0 ] ? . datasourceInstance ? . uid ) . toBe ( 'loki-uid' ) ;
} ) ;
} ) ;
it ( 'updates the state with correct queries from URL' , async ( ) = > {
const { waitForNextUpdate , rerender , store } = setup ( {
const { rerender , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : { datasource : 'loki-uid' , queries : [ { expr : 'a' } ] } ,
@ -322,11 +321,12 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
let queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 1 ) ;
expect ( queries ? . [ 0 ] ) . toMatchObject ( { expr : 'a' } ) ;
let queries : DataQuery [ ] | undefined ;
await waitFor ( ( ) = > {
queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 1 ) ;
expect ( queries ? . [ 0 ] ) . toMatchObject ( { expr : 'a' } ) ;
} ) ;
rerender ( {
children : null ,
@ -338,12 +338,12 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 2 ) ;
expect ( queries ? . [ 0 ] ) . toMatchObject ( { expr : 'a ' } ) ;
expect ( queries ? . [ 1 ] ) . toMatchObject ( { expr : 'b' } ) ;
await waitFor ( ( ) = > {
queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 2 ) ;
expect ( queries ? . [ 0 ] ) . toMatchObject ( { expr : 'a' } ) ;
expect ( queries ? . [ 1 ] ) . toMatchObject ( { expr : 'b ' } ) ;
} ) ;
rerender ( {
children : null ,
@ -355,15 +355,15 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 1 ) ;
expect ( queries ? . [ 0 ] ) . toMatchObject ( { expr : 'a' } ) ;
await waitFor ( ( ) = > {
queries = store . getState ( ) . explore . panes [ 'one' ] ? . queries ;
expect ( queries ) . toHaveLength ( 1 ) ;
expect ( queries ? . [ 0 ] ) . toMatchObject ( { expr : 'a' } ) ;
} ) ;
} ) ;
it ( 'Opens and closes the split pane if an a new pane is added or removed in the URL' , async ( ) = > {
const { waitForNextUpdate , rerender , store } = setup ( {
const { rerender , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : { datasource : 'loki-uid' , queries : [ { expr : 'a' } ] } ,
@ -372,10 +372,10 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
let panes = Object . keys ( store . getState ( ) . explore . panes ) ;
expect ( panes ) . toHaveLength ( 1 ) ;
await waitFor ( ( ) = > {
let panes = Object . keys ( store . getState ( ) . explore . panes ) ;
expect ( panes ) . toHaveLength ( 1 ) ;
} ) ;
rerender ( {
children : null ,
@ -388,9 +388,9 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( Object . keys ( store . getState ( ) . explore . panes ) ) . toHaveLength ( 2 ) ;
await waitFor ( ( ) = > {
expect ( Object . keys ( store . getState ( ) . explore . panes ) ) . toHaveLength ( 2 ) ;
} ) ;
rerender ( {
children : null ,
@ -402,15 +402,13 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
await waitFor ( ( ) = > {
expect ( Object . keys ( store . getState ( ) . explore . panes ) ) . toHaveLength ( 1 ) ;
} ) ;
} ) ;
it ( 'Changes datasource when the datasource in the URL is updated' , async ( ) = > {
const { waitForNextUpdate , rerender , store } = setup ( {
const { rerender , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : { datasource : 'loki-uid' , queries : [ { expr : 'a' } ] } ,
@ -419,11 +417,11 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . getRef ( ) ) . toMatchObject ( {
type : 'logs ',
uid : 'loki-uid' ,
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . getRef ( ) ) . toMatchObject ( {
type : 'logs' ,
uid : 'loki-uid ' ,
} ) ;
} ) ;
rerender ( {
@ -436,16 +434,16 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . getRef ( ) ) . toMatchObject ( {
type : 'logs ',
uid : 'elastic-uid' ,
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . getRef ( ) ) . toMatchObject ( {
type : 'logs' ,
uid : 'elastic-uid ' ,
} ) ;
} ) ;
} ) ;
it ( 'Changes time rage when the range in the URL is updated' , async ( ) = > {
const { waitForNextUpdate , rerender , store } = setup ( {
const { rerender , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : { datasource : 'loki-uid' , queries : [ { expr : 'a' } ] , range : { from : 'now-1h' , to : 'now' } } ,
@ -454,9 +452,9 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw ) . toMatchObject ( { from : 'now-1h' , to : 'now' } ) ;
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw ) . toMatchObject ( { from : 'now-1h' , to : 'now' } ) ;
} ) ;
rerender ( {
children : null ,
@ -468,13 +466,13 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw ) . toMatchObject ( { from : 'now-6h' , to : 'now' } ) ;
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw ) . toMatchObject ( { from : 'now-6h' , to : 'now' } ) ;
} ) ;
} ) ;
it ( 'Changes time range when the range in the URL is updated to absolute range' , async ( ) = > {
const { waitForNextUpdate , rerender , store } = setup ( {
const { rerender , store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : { datasource : 'loki-uid' , queries : [ { expr : 'a' } ] , range : { from : 'now-1h' , to : 'now' } } ,
@ -483,9 +481,9 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw ) . toMatchObject ( { from : 'now-1h' , to : 'now' } ) ;
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw ) . toMatchObject ( { from : 'now-1h' , to : 'now' } ) ;
} ) ;
rerender ( {
children : null ,
@ -501,14 +499,14 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw . from . valueOf ( ) . toString ( ) ) . toEqual ( '1500000000 000' ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw . to . valueOf ( ) . toString ( ) ) . toEqual ( '1500000001000' ) ;
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw . from . valueOf ( ) . toString ( ) ) . toEqual ( '1500000000000' ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . range . raw . to . valueOf ( ) . toString ( ) ) . toEqual ( '1500000001 000' ) ;
} ) ;
} ) ;
it ( 'uses the first query datasource if no root datasource is specified in the URL' , async ( ) = > {
const { waitForNextUpdate , store } = setup ( {
const { store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : {
@ -519,16 +517,16 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . getRef ( ) ) . toMatchObject ( {
uid : 'loki-uid ',
type : 'logs' ,
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . getRef ( ) ) . toMatchObject ( {
uid : 'loki-uid' ,
type : 'logs ' ,
} ) ;
} ) ;
} ) ;
it ( 'updates the URL opening and closing a pane datasource changes' , async ( ) = > {
const { waitForNextUpdate , store , location } = setup ( {
const { store , location } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : {
@ -540,22 +538,20 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( location . getHistory ( ) . length ) . toBe ( 1 ) ;
await waitFor ( ( ) = > {
expect ( location . getHistory ( ) . length ) . toBe ( 1 ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . uid ) . toBe ( 'loki-uid' ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . datasourceInstance ? . uid ) . toBe ( 'loki-uid' ) ;
} ) ;
act ( ( ) = > {
store . dispatch ( splitOpen ( ) ) ;
} ) ;
await waitForNextUpdate ( ) ;
await waitFor ( async ( ) = > {
await waitFor ( ( ) = > {
expect ( location . getHistory ( ) . length ) . toBe ( 2 ) ;
expect ( Object . keys ( store . getState ( ) . explore . panes ) ) . toHaveLength ( 2 ) ;
} ) ;
expect ( Object . keys ( store . getState ( ) . explore . panes ) ) . toHaveLength ( 2 ) ;
act ( ( ) = > {
store . dispatch ( splitClose ( 'one' ) ) ;
@ -567,7 +563,7 @@ describe('useStateSync', () => {
} ) ;
it ( 'filters out queries from the URL that do not have a datasource' , async ( ) = > {
const { waitForNextUpdate , store } = setup ( {
const { store } = setup ( {
queryParams : {
panes : JSON.stringify ( {
one : {
@ -582,9 +578,9 @@ describe('useStateSync', () => {
} ,
} ) ;
await waitForNextUpdate ( ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . queries . length ) . toBe ( 1 ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . queries [ 0 ] ) . toMatchObject ( { expr : 'b' , refId : 'B' } ) ;
await waitFor ( ( ) = > {
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . queries . length ) . toBe ( 1 ) ;
expect ( store . getState ( ) . explore . panes [ 'one' ] ? . queries [ 0 ] ) . toMatchObject ( { expr : 'b' , refId : 'B' } ) ;
} ) ;
} ) ;
} ) ;