2023-09-29 11:28:52 +02:00
import { lastValueFrom , of } from 'rxjs' ;
import { ScopedVars } from '@grafana/data' ;
import { BackendSrvRequest } from '@grafana/runtime/' ;
import config from 'app/core/config' ;
import { TemplateSrv } from '../../../features/templating/template_srv' ;
import { BROWSER_MODE_DISABLED_MESSAGE } from './constants' ;
2023-11-14 15:59:34 +01:00
import InfluxDatasource , { influxSpecialRegexEscape } from './datasource' ;
2023-09-29 11:28:52 +02:00
import {
getMockDSInstanceSettings ,
getMockInfluxDS ,
mockBackendService ,
mockInfluxFetchResponse ,
mockInfluxQueryRequest ,
mockInfluxQueryWithTemplateVars ,
mockTemplateSrv ,
} from './mocks' ;
import { InfluxQuery , InfluxVersion } from './types' ;
// we want only frontend mode in this file
config . featureToggles . influxdbBackendMigration = false ;
const fetchMock = mockBackendService ( mockInfluxFetchResponse ( ) ) ;
describe ( 'InfluxDataSource Frontend Mode' , ( ) = > {
beforeEach ( ( ) = > {
jest . clearAllMocks ( ) ;
} ) ;
it ( 'should throw an error if there is 200 response with error' , async ( ) = > {
const ds = getMockInfluxDS ( ) ;
fetchMock . mockImplementation ( ( ) = > {
return of ( {
data : {
results : [
{
error : 'Query timeout' ,
} ,
] ,
} ,
} ) ;
} ) ;
try {
await lastValueFrom ( ds . query ( mockInfluxQueryRequest ( ) ) ) ;
} catch ( err ) {
if ( err instanceof Error ) {
expect ( err . message ) . toBe ( 'InfluxDB Error: Query timeout' ) ;
}
}
} ) ;
describe ( 'outdated browser mode' , ( ) = > {
it ( 'should throw an error when querying data' , async ( ) = > {
expect . assertions ( 1 ) ;
const instanceSettings = getMockDSInstanceSettings ( ) ;
instanceSettings . access = 'direct' ;
const ds = getMockInfluxDS ( instanceSettings ) ;
try {
await lastValueFrom ( ds . query ( mockInfluxQueryRequest ( ) ) ) ;
} catch ( err ) {
if ( err instanceof Error ) {
expect ( err . message ) . toBe ( BROWSER_MODE_DISABLED_MESSAGE ) ;
}
}
} ) ;
} ) ;
describe ( 'metricFindQuery with HTTP GET' , ( ) = > {
let ds : InfluxDatasource ;
const query = 'SELECT max(value) FROM measurement WHERE $timeFilter' ;
const queryOptions = {
range : {
from : '2018-01-01T00:00:00Z' ,
to : '2018-01-02T00:00:00Z' ,
} ,
} ;
let requestQuery : string ;
let requestMethod : string | undefined ;
let requestData : string | null ;
const fetchMockImpl = ( req : BackendSrvRequest ) = > {
requestMethod = req . method ;
requestQuery = req . params ? . q ;
requestData = req . data ;
return of ( {
data : {
status : 'success' ,
results : [
{
series : [
{
name : 'measurement' ,
columns : [ 'name' ] ,
values : [ [ 'cpu' ] ] ,
} ,
] ,
} ,
] ,
} ,
} ) ;
} ;
beforeEach ( async ( ) = > {
jest . clearAllMocks ( ) ;
fetchMock . mockImplementation ( fetchMockImpl ) ;
} ) ;
it ( 'should read the http method from jsonData' , async ( ) = > {
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'GET' } ) ) ;
await ds . metricFindQuery ( query , queryOptions ) ;
expect ( requestMethod ) . toBe ( 'GET' ) ;
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'POST' } ) ) ;
await ds . metricFindQuery ( query , queryOptions ) ;
expect ( requestMethod ) . toBe ( 'POST' ) ;
} ) ;
it ( 'should replace $timefilter' , async ( ) = > {
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'GET' } ) ) ;
await ds . metricFindQuery ( query , queryOptions ) ;
expect ( requestQuery ) . toMatch ( 'time >= 1514764800000ms and time <= 1514851200000ms' ) ;
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'POST' } ) ) ;
await ds . metricFindQuery ( query , queryOptions ) ;
expect ( requestQuery ) . toBeFalsy ( ) ;
expect ( requestData ) . toMatch ( 'time%20%3E%3D%201514764800000ms%20and%20time%20%3C%3D%201514851200000ms' ) ;
} ) ;
it ( 'should not have any data in request body if http mode is GET' , async ( ) = > {
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'GET' } ) ) ;
await ds . metricFindQuery ( query , queryOptions ) ;
expect ( requestData ) . toBeNull ( ) ;
} ) ;
it ( 'should have data in request body if http mode is POST' , async ( ) = > {
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'POST' } ) ) ;
await ds . metricFindQuery ( query , queryOptions ) ;
expect ( requestData ) . not . toBeNull ( ) ;
expect ( requestData ) . toMatch ( 'q=SELECT' ) ;
} ) ;
it ( 'parse response correctly' , async ( ) = > {
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'GET' } ) ) ;
let responseGet = await ds . metricFindQuery ( query , queryOptions ) ;
expect ( responseGet ) . toEqual ( [ { text : 'cpu' } ] ) ;
ds = getMockInfluxDS ( getMockDSInstanceSettings ( { httpMode : 'POST' } ) ) ;
let responsePost = await ds . metricFindQuery ( query , queryOptions ) ;
expect ( responsePost ) . toEqual ( [ { text : 'cpu' } ] ) ;
} ) ;
} ) ;
describe ( 'adhoc variables' , ( ) = > {
const adhocFilters = [
{
key : 'adhoc_key' ,
operator : '=' ,
value : 'adhoc_val' ,
condition : '' ,
} ,
] ;
const mockTemplateService = new TemplateSrv ( ) ;
mockTemplateService . getAdhocFilters = jest . fn ( ( _ : string ) = > adhocFilters ) ;
let ds = getMockInfluxDS ( getMockDSInstanceSettings ( ) , mockTemplateService ) ;
2023-10-25 15:56:10 +02:00
// const fetchMock = jest.fn().mockReturnValue(fetchResult);
2023-09-29 11:28:52 +02:00
it ( 'query should contain the ad-hoc variable' , ( ) = > {
ds . query ( mockInfluxQueryRequest ( ) ) ;
const expected = encodeURIComponent (
'SELECT mean("value") FROM "cpu" WHERE time >= 0ms and time <= 10ms AND "adhoc_key" = \'adhoc_val\' GROUP BY time($__interval) fill(null)'
) ;
expect ( fetchMock . mock . calls [ 0 ] [ 0 ] . data ) . toBe ( ` q= ${ expected } ` ) ;
} ) ;
2023-10-25 15:56:10 +02:00
it ( 'should make the fetch call for adhoc filter keys' , ( ) = > {
fetchMock . mockReturnValue (
of ( {
results : [
{
statement_id : 0 ,
series : [
{
name : 'cpu' ,
columns : [ 'tagKey' ] ,
values : [ [ 'datacenter' ] , [ 'geohash' ] , [ 'source' ] ] ,
} ,
] ,
} ,
] ,
} )
) ;
ds . getTagKeys ( ) ;
expect ( fetchMock ) . toHaveBeenCalled ( ) ;
const fetchReq = fetchMock . mock . calls [ 0 ] [ 0 ] ;
expect ( fetchReq ) . not . toBeNull ( ) ;
expect ( fetchReq . data ) . toMatch ( encodeURIComponent ( ` SHOW TAG KEYS ` ) ) ;
} ) ;
it ( 'should make the fetch call for adhoc filter values' , ( ) = > {
fetchMock . mockReturnValue (
of ( {
results : [
{
statement_id : 0 ,
series : [
{
name : 'mykey' ,
columns : [ 'key' , 'value' ] ,
values : [ [ 'mykey' , 'value' ] ] ,
} ,
] ,
} ,
] ,
} )
) ;
ds . getTagValues ( { key : 'mykey' , filters : [ ] } ) ;
expect ( fetchMock ) . toHaveBeenCalled ( ) ;
const fetchReq = fetchMock . mock . calls [ 0 ] [ 0 ] ;
expect ( fetchReq ) . not . toBeNull ( ) ;
expect ( fetchReq . data ) . toMatch ( encodeURIComponent ( ` SHOW TAG VALUES WITH KEY = "mykey" ` ) ) ;
} ) ;
2023-09-29 11:28:52 +02:00
} ) ;
describe ( 'datasource contract' , ( ) = > {
let ds : InfluxDatasource ;
const metricFindQueryMock = jest . fn ( ) ;
beforeEach ( ( ) = > {
jest . clearAllMocks ( ) ;
ds = getMockInfluxDS ( ) ;
ds . metricFindQuery = metricFindQueryMock ;
} ) ;
afterEach ( ( ) = > {
jest . clearAllMocks ( ) ;
} ) ;
it ( 'should check the datasource has "getTagKeys" function defined' , ( ) = > {
expect ( Object . getOwnPropertyNames ( Object . getPrototypeOf ( ds ) ) ) . toContain ( 'getTagKeys' ) ;
} ) ;
it ( 'should check the datasource has "getTagValues" function defined' , ( ) = > {
expect ( Object . getOwnPropertyNames ( Object . getPrototypeOf ( ds ) ) ) . toContain ( 'getTagValues' ) ;
} ) ;
it ( 'should be able to call getTagKeys without specifying any parameter' , ( ) = > {
ds . getTagKeys ( ) ;
expect ( metricFindQueryMock ) . toHaveBeenCalled ( ) ;
} ) ;
it ( 'should be able to call getTagValues without specifying anything but key' , ( ) = > {
ds . getTagValues ( { key : 'test' , filters : [ ] } ) ;
expect ( metricFindQueryMock ) . toHaveBeenCalled ( ) ;
} ) ;
} ) ;
describe ( 'variable interpolation' , ( ) = > {
const text = 'interpolationText' ;
const text2 = 'interpolationText2' ;
const textWithoutFormatRegex = 'interpolationText,interpolationText2' ;
2023-10-04 17:02:46 +02:00
const textWithFormatRegex = 'interpolationText,interpolationText2' ;
2023-09-29 11:28:52 +02:00
const variableMap : Record < string , string > = {
$interpolationVar : text ,
$interpolationVar2 : text2 ,
} ;
const adhocFilters = [
{
key : 'adhoc' ,
operator : '=' ,
value : 'val' ,
condition : '' ,
} ,
] ;
const templateSrv = mockTemplateSrv (
jest . fn ( ( _ : string ) = > adhocFilters ) ,
jest . fn ( ( target? : string , scopedVars? : ScopedVars , format? : string | Function ) : string = > {
if ( ! format ) {
return variableMap [ target ! ] || '' ;
}
if ( format === 'regex' ) {
return textWithFormatRegex ;
}
return textWithoutFormatRegex ;
} )
) ;
const ds = new InfluxDatasource ( getMockDSInstanceSettings ( ) , templateSrv ) ;
function influxChecks ( query : InfluxQuery ) {
2023-11-08 15:00:13 +01:00
expect ( templateSrv . replace ) . toBeCalledTimes ( 11 ) ;
2023-09-29 11:28:52 +02:00
expect ( query . alias ) . toBe ( text ) ;
expect ( query . measurement ) . toBe ( textWithFormatRegex ) ;
expect ( query . policy ) . toBe ( textWithFormatRegex ) ;
expect ( query . limit ) . toBe ( textWithFormatRegex ) ;
expect ( query . slimit ) . toBe ( textWithFormatRegex ) ;
expect ( query . tz ) . toBe ( text ) ;
expect ( query . tags ! [ 0 ] . value ) . toBe ( textWithFormatRegex ) ;
expect ( query . groupBy ! [ 0 ] . params ! [ 0 ] ) . toBe ( textWithFormatRegex ) ;
expect ( query . select ! [ 0 ] [ 0 ] . params ! [ 0 ] ) . toBe ( textWithFormatRegex ) ;
expect ( query . adhocFilters ? . [ 0 ] . key ) . toBe ( adhocFilters [ 0 ] . key ) ;
}
describe ( 'when interpolating query variables for dashboard->explore' , ( ) = > {
it ( 'should interpolate all variables with Flux mode' , ( ) = > {
ds . version = InfluxVersion . Flux ;
const fluxQuery = {
refId : 'x' ,
query : '$interpolationVar,$interpolationVar2' ,
} ;
const queries = ds . interpolateVariablesInQueries ( [ fluxQuery ] , {
interpolationVar : { text : text , value : text } ,
interpolationVar2 : { text : text2 , value : text2 } ,
} ) ;
expect ( templateSrv . replace ) . toBeCalledTimes ( 1 ) ;
expect ( queries [ 0 ] . query ) . toBe ( textWithFormatRegex ) ;
} ) ;
it ( 'should interpolate all variables with InfluxQL mode' , ( ) = > {
ds . version = InfluxVersion . InfluxQL ;
const queries = ds . interpolateVariablesInQueries ( [ mockInfluxQueryWithTemplateVars ( adhocFilters ) ] , {
interpolationVar : { text : text , value : text } ,
interpolationVar2 : { text : text2 , value : text2 } ,
} ) ;
influxChecks ( queries [ 0 ] ) ;
} ) ;
} ) ;
describe ( 'when interpolating template variables' , ( ) = > {
it ( 'should apply all template variables with Flux mode' , ( ) = > {
ds . version = InfluxVersion . Flux ;
const fluxQuery = {
refId : 'x' ,
query : '$interpolationVar' ,
} ;
const query = ds . applyTemplateVariables ( fluxQuery , {
interpolationVar : {
text : text ,
value : text ,
} ,
} ) ;
expect ( templateSrv . replace ) . toBeCalledTimes ( 1 ) ;
expect ( query . query ) . toBe ( text ) ;
} ) ;
2023-10-04 17:02:46 +02:00
} ) ;
2023-09-29 11:28:52 +02:00
2023-10-04 17:02:46 +02:00
describe ( 'variable interpolation with chained variables with frontend mode' , ( ) = > {
const mockTemplateService = new TemplateSrv ( ) ;
mockTemplateService . getAdhocFilters = jest . fn ( ( _ : string ) = > [ ] ) ;
let ds = getMockInfluxDS ( getMockDSInstanceSettings ( ) , mockTemplateService ) ;
const fetchMockImpl = ( ) = >
of ( {
data : {
status : 'success' ,
results : [
{
series : [
{
name : 'measurement' ,
columns : [ 'name' ] ,
values : [ [ 'cpu' ] ] ,
} ,
] ,
} ,
] ,
} ,
2023-09-29 11:28:52 +02:00
} ) ;
2023-10-04 17:02:46 +02:00
beforeEach ( ( ) = > {
jest . clearAllMocks ( ) ;
fetchMock . mockImplementation ( fetchMockImpl ) ;
2023-09-29 11:28:52 +02:00
} ) ;
2023-10-04 17:02:46 +02:00
it ( 'should render chained regex variables with floating point number' , ( ) = > {
ds . metricFindQuery ( ` SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $ maxSED ` , {
scopedVars : { maxSED : { text : '8.1' , value : '8.1' } } ,
} ) ;
const qe = ` SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 ` ;
const qData = decodeURIComponent ( fetchMock . mock . calls [ 0 ] [ 0 ] . data . substring ( 2 ) ) ;
expect ( qData ) . toBe ( qe ) ;
} ) ;
it ( 'should render chained regex variables with URL' , ( ) = > {
ds . metricFindQuery ( 'SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^$var1$/' , {
scopedVars : {
var1 : {
text : 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg' ,
value : 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg' ,
} ,
} ,
2023-09-29 11:28:52 +02:00
} ) ;
2023-10-04 17:02:46 +02:00
const qe = ` SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^https: \\ / \\ /aaaa-aa-aaa \\ .bbb \\ .ccc \\ .ddd:8443 \\ /ggggg $ / ` ;
const qData = decodeURIComponent ( fetchMock . mock . calls [ 0 ] [ 0 ] . data . substring ( 2 ) ) ;
expect ( qData ) . toBe ( qe ) ;
} ) ;
it ( 'should render chained regex variables with floating point number and url' , ( ) = > {
ds . metricFindQuery (
'SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED AND agent_url =~ /^$var1$/' ,
{
scopedVars : {
var1 : {
text : 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg' ,
value : 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg' ,
} ,
maxSED : { text : '8.1' , value : '8.1' } ,
} ,
}
) ;
const qe = ` SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 AND agent_url =~ /^https: \\ / \\ /aaaa-aa-aaa \\ .bbb \\ .ccc \\ .ddd:8443 \\ /ggggg $ / ` ;
const qData = decodeURIComponent ( fetchMock . mock . calls [ 0 ] [ 0 ] . data . substring ( 2 ) ) ;
expect ( qData ) . toBe ( qe ) ;
2023-09-29 11:28:52 +02:00
} ) ;
} ) ;
2023-11-14 15:59:34 +01:00
describe ( 'influxSpecialRegexEscape' , ( ) = > {
it ( 'should escape the dot properly' , ( ) = > {
const value = 'value.with-dot' ;
const expectation = ` value \ .with-dot ` ;
const result = influxSpecialRegexEscape ( value ) ;
expect ( result ) . toBe ( expectation ) ;
} ) ;
it ( 'should escape the url properly' , ( ) = > {
const value = 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/jolokia' ;
const expectation = ` https: \ / \ /aaaa-aa-aaa \ .bbb \ .ccc \ .ddd:8443 \ /jolokia ` ;
const result = influxSpecialRegexEscape ( value ) ;
expect ( result ) . toBe ( expectation ) ;
} ) ;
} ) ;
2023-09-29 11:28:52 +02:00
} ) ;
} ) ;