2021-04-21 15:38:00 +08:00
import { map } from 'lodash' ;
2021-01-11 13:47:51 +08:00
import { Observable , of , throwError } from 'rxjs' ;
2022-04-22 21:33:13 +08:00
2020-07-01 15:45:21 +08:00
import {
CoreApp ,
2021-07-30 02:42:20 +08:00
DataLink ,
2020-07-01 15:45:21 +08:00
DataQueryRequest ,
2022-09-06 20:35:54 +08:00
DataQueryResponse ,
2020-07-01 15:45:21 +08:00
DataSourceInstanceSettings ,
2020-12-04 22:29:40 +08:00
DateTime ,
2020-07-01 15:45:21 +08:00
dateTime ,
Field ,
2022-11-28 15:57:56 +08:00
FieldType ,
2020-07-01 15:45:21 +08:00
MutableDataFrame ,
2023-01-26 23:06:10 +08:00
SupplementaryQueryType ,
2020-12-04 22:29:40 +08:00
TimeRange ,
2020-07-01 15:45:21 +08:00
toUtc ,
} from '@grafana/data' ;
2023-05-04 00:09:18 +08:00
import { BackendSrvRequest , FetchResponse , reportInteraction , config } from '@grafana/runtime' ;
2021-01-11 13:47:51 +08:00
2023-05-17 20:28:32 +08:00
import { enhanceDataFrame } from './LegacyQueryRunner' ;
import { ElasticDatasource } from './datasource' ;
2022-09-06 20:35:54 +08:00
import { createElasticDatasource } from './mocks' ;
2023-02-04 01:04:12 +08:00
import { Filters , ElasticsearchOptions , ElasticsearchQuery } from './types' ;
2018-07-04 16:43:36 +08:00
2020-06-09 23:32:47 +08:00
const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local' ;
2023-05-04 00:09:18 +08:00
const originalConsoleError = console . error ;
2024-02-09 20:11:08 +08:00
const backendSrv = {
fetch : jest.fn ( ) ,
} ;
2020-01-21 17:08:07 +08:00
jest . mock ( '@grafana/runtime' , ( ) = > ( {
2023-02-14 23:46:42 +08:00
. . . jest . requireActual ( '@grafana/runtime' ) ,
2020-01-21 17:08:07 +08:00
getBackendSrv : ( ) = > backendSrv ,
2022-12-08 18:28:40 +08:00
reportInteraction : jest.fn ( ) ,
2020-12-02 02:10:23 +08:00
getDataSourceSrv : ( ) = > {
return {
getInstanceSettings : ( ) = > {
return { name : 'elastic25' } ;
} ,
} ;
} ,
2020-01-21 17:08:07 +08:00
} ) ) ;
2024-02-09 20:11:08 +08:00
const createTimeRange = ( from : DateTime , to : DateTime ) : TimeRange = > ( {
from ,
to ,
raw : {
from ,
to ,
} ,
} ) ;
2023-10-24 16:25:23 +08:00
const TIME_START = [ 2022 , 8 , 21 , 6 , 10 , 10 ] ;
const TIME_END = [ 2022 , 8 , 24 , 6 , 10 , 21 ] ;
2022-09-06 20:35:54 +08:00
const DATAQUERY_BASE = {
requestId : '1' ,
interval : '' ,
intervalMs : 0 ,
scopedVars : {
test : { text : '' , value : '' } ,
} ,
timezone : '' ,
app : 'test' ,
startTime : 0 ,
2024-02-09 20:11:08 +08:00
range : createTimeRange ( toUtc ( TIME_START ) , toUtc ( TIME_END ) ) ,
2022-09-06 20:35:54 +08:00
} ;
2022-08-25 15:29:15 +08:00
2024-02-09 20:11:08 +08:00
function createFetchResponse < T > ( data : T ) : FetchResponse < T > {
return {
data ,
status : 200 ,
url : 'http://localhost:3000/api/ds/query' ,
config : { url : 'http://localhost:3000/api/ds/query' } ,
type : 'basic' ,
statusText : 'Ok' ,
redirected : false ,
headers : { } as unknown as Headers ,
ok : true ,
} ;
}
2020-12-04 22:29:40 +08:00
2022-09-06 20:35:54 +08:00
interface TestContext {
data? : Data ;
jsonData? : Partial < ElasticsearchOptions > ;
2021-01-11 13:47:51 +08:00
database? : string ;
2022-09-06 20:35:54 +08:00
fetchMockImplementation ? : ( options : BackendSrvRequest ) = > Observable < FetchResponse > ;
}
interface Data {
[ key : string ] : undefined | string | string [ ] | number | Data | Data [ ] ;
2021-01-11 13:47:51 +08:00
}
2020-01-21 17:08:07 +08:00
2023-10-24 16:25:23 +08:00
function getTestContext ( { data = { responses : [ ] } , jsonData , fetchMockImplementation } : TestContext = { } ) {
2021-01-11 13:47:51 +08:00
const defaultMock = ( options : BackendSrvRequest ) = > of ( createFetchResponse ( data ) ) ;
const fetchMock = jest . spyOn ( backendSrv , 'fetch' ) ;
2022-09-06 20:35:54 +08:00
fetchMock . mockImplementation ( fetchMockImplementation ? ? defaultMock ) ;
2018-07-03 16:12:07 +08:00
2022-09-06 20:35:54 +08:00
const settings : Partial < DataSourceInstanceSettings < ElasticsearchOptions > > = { url : ELASTICSEARCH_MOCK_URL } ;
settings . jsonData = jsonData as ElasticsearchOptions ;
2020-03-02 17:45:31 +08:00
2023-10-24 16:25:23 +08:00
const ds = createElasticDatasource ( settings ) ;
2020-03-02 17:45:31 +08:00
2023-10-24 16:25:23 +08:00
const timeRange = createTimeRange ( toUtc ( TIME_START ) , toUtc ( TIME_END ) ) ;
2020-03-02 17:45:31 +08:00
2023-10-24 16:25:23 +08:00
return { ds , fetchMock , timeRange } ;
2021-01-11 13:47:51 +08:00
}
2015-09-28 22:28:19 +08:00
2022-09-06 20:35:54 +08:00
describe ( 'ElasticDatasource' , ( ) = > {
beforeEach ( ( ) = > {
jest . clearAllMocks ( ) ;
} ) ;
2022-08-25 15:29:15 +08:00
describe ( 'When calling getTagValues' , ( ) = > {
it ( 'should respect the currently selected time range' , ( ) = > {
const data = {
responses : [
{
aggregations : {
'1' : {
buckets : [
{
doc_count : 10 ,
key : 'val1' ,
} ,
{
doc_count : 20 ,
key : 'val2' ,
} ,
{
doc_count : 30 ,
key : 'val3' ,
} ,
] ,
} ,
} ,
} ,
] ,
} ;
2023-10-24 16:25:23 +08:00
const { ds , fetchMock , timeRange } = getTestContext ( {
2022-08-25 15:29:15 +08:00
data ,
2023-03-28 22:04:56 +08:00
jsonData : { interval : 'Daily' , timeField : '@timestamp' } ,
2022-08-25 15:29:15 +08:00
} ) ;
2023-10-24 16:25:23 +08:00
ds . getTagValues ( { key : 'test' , timeRange : timeRange , filters : [ ] } ) ;
2022-08-25 15:29:15 +08:00
expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
const obj = JSON . parse ( fetchMock . mock . calls [ 0 ] [ 0 ] . data . split ( '\n' ) [ 1 ] ) ;
const { lte , gte } = obj . query . bool . filter [ 0 ] . range [ '@timestamp' ] ;
expect ( gte ) . toBe ( '1663740610000' ) ; // 2022-09-21T06:10:10Z
expect ( lte ) . toBe ( '1663999821000' ) ; // 2022-09-24T06:10:21Z
} ) ;
} ) ;
2018-09-04 21:55:41 +08:00
describe ( 'When testing datasource with index pattern' , ( ) = > {
2023-03-28 14:59:39 +08:00
it ( 'should translate index pattern to current day' , async ( ) = > {
2023-03-28 22:04:56 +08:00
const { ds , fetchMock } = getTestContext ( { jsonData : { interval : 'Daily' } } ) ;
2015-09-28 22:28:19 +08:00
2023-03-28 14:59:39 +08:00
await ds . testDatasource ( ) ;
2015-09-28 22:28:19 +08:00
2019-05-08 19:51:44 +08:00
const today = toUtc ( ) . format ( 'YYYY.MM.DD' ) ;
2023-03-28 14:59:39 +08:00
const lastCall = fetchMock . mock . calls [ fetchMock . mock . calls . length - 1 ] ;
expect ( lastCall [ 0 ] . url ) . toBe ( ` ${ ELASTICSEARCH_MOCK_URL } /test- ${ today } /_mapping ` ) ;
2015-09-28 22:28:19 +08:00
} ) ;
2024-01-23 19:41:13 +08:00
it ( 'should call `/_mapping` with an empty index' , async ( ) = > {
const { ds , fetchMock } = getTestContext ( { jsonData : { index : '' } } ) ;
await ds . testDatasource ( ) ;
const lastCall = fetchMock . mock . calls [ fetchMock . mock . calls . length - 1 ] ;
expect ( lastCall [ 0 ] . url ) . toBe ( ` ${ ELASTICSEARCH_MOCK_URL } /_mapping ` ) ;
} ) ;
2015-09-28 22:28:19 +08:00
} ) ;
2018-09-04 21:55:41 +08:00
describe ( 'When issuing metric query with interval pattern' , ( ) = > {
2021-01-11 13:47:51 +08:00
async function runScenario() {
2022-09-06 20:35:54 +08:00
const range = { from : toUtc ( [ 2015 , 4 , 30 , 10 ] ) , to : toUtc ( [ 2015 , 5 , 1 , 10 ] ) , raw : { from : '' , to : '' } } ;
const targets : ElasticsearchQuery [ ] = [
2021-01-11 13:47:51 +08:00
{
2022-09-06 20:35:54 +08:00
refId : 'test' ,
2021-01-11 13:47:51 +08:00
alias : '$varAlias' ,
bucketAggs : [ { type : 'date_histogram' , field : '@timestamp' , id : '1' } ] ,
metrics : [ { type : 'count' , id : '1' } ] ,
query : 'escape\\:test' ,
2015-09-28 22:28:19 +08:00
} ,
2021-01-11 13:47:51 +08:00
] ;
2022-09-06 20:35:54 +08:00
const query = { . . . DATAQUERY_BASE , range , targets } ;
2021-01-11 13:47:51 +08:00
const data = {
responses : [
2017-12-19 23:06:54 +08:00
{
2021-01-11 13:47:51 +08:00
aggregations : {
'1' : {
buckets : [
{
doc_count : 10 ,
key : 1000 ,
} ,
] ,
} ,
} ,
2017-12-20 19:33:33 +08:00
} ,
] ,
2019-01-09 14:46:20 +08:00
} ;
2023-03-28 22:04:56 +08:00
const { ds , fetchMock } = getTestContext ( { jsonData : { interval : 'Daily' } , data } ) ;
2021-01-11 13:47:51 +08:00
2022-09-06 20:35:54 +08:00
let result : DataQueryResponse = { data : [ ] } ;
2021-01-20 14:59:48 +08:00
await expect ( ds . query ( query ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toEqual ( {
data : [
{
2022-11-28 15:57:56 +08:00
name : 'resolvedVariable' ,
2022-09-06 20:35:54 +08:00
refId : 'test' ,
2022-11-28 15:57:56 +08:00
fields : [
{
name : 'Time' ,
type : FieldType . time ,
config : { } ,
2023-04-14 22:03:45 +08:00
values : [ 1000 ] ,
2022-11-28 15:57:56 +08:00
} ,
{
name : 'Value' ,
type : FieldType . number ,
config : { } ,
2023-04-14 22:03:45 +08:00
values : [ 10 ] ,
2022-11-28 15:57:56 +08:00
} ,
] ,
length : 1 ,
2021-01-11 13:47:51 +08:00
} ,
] ,
} ) ;
result = received [ 0 ] ;
} ) ;
2019-01-09 14:46:20 +08:00
2021-01-11 13:47:51 +08:00
expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
const requestOptions = fetchMock . mock . calls [ 0 ] [ 0 ] ;
const parts = requestOptions . data . split ( '\n' ) ;
const header = JSON . parse ( parts [ 0 ] ) ;
const body = JSON . parse ( parts [ 1 ] ) ;
2015-09-28 22:28:19 +08:00
2021-01-11 13:47:51 +08:00
return { result , body , header , query } ;
}
2015-10-23 04:23:21 +08:00
2021-01-11 13:47:51 +08:00
it ( 'should translate index pattern to current day' , async ( ) = > {
const { header } = await runScenario ( ) ;
2022-09-06 20:35:54 +08:00
expect ( header . index ) . toEqual ( [ 'test-2015.05.30' , 'test-2015.05.31' , 'test-2015.06.01' ] ) ;
2015-09-28 22:28:19 +08:00
} ) ;
2015-10-23 04:23:21 +08:00
2021-01-11 13:47:51 +08:00
it ( 'should not resolve the variable in the original alias field in the query' , async ( ) = > {
const { query } = await runScenario ( ) ;
2019-03-26 23:15:23 +08:00
expect ( query . targets [ 0 ] . alias ) . toEqual ( '$varAlias' ) ;
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should resolve the alias variable for the alias/target in the result' , async ( ) = > {
const { result } = await runScenario ( ) ;
2022-11-28 15:57:56 +08:00
expect ( result . data [ 0 ] . name ) . toEqual ( 'resolvedVariable' ) ;
2019-01-09 14:46:20 +08:00
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should json escape lucene query' , async ( ) = > {
const { body } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( body . query . bool . filter [ 1 ] . query_string . query ) . toBe ( 'escape\\:test' ) ;
2015-10-23 04:23:21 +08:00
} ) ;
2022-12-08 18:28:40 +08:00
it ( 'should report query interaction' , async ( ) = > {
await runScenario ( ) ;
expect ( reportInteraction ) . toHaveBeenCalledWith (
'grafana_elasticsearch_query_executed' ,
expect . objectContaining ( {
alias : '$varAlias' ,
app : 'test' ,
has_data : true ,
has_error : false ,
line_limit : undefined ,
query_type : 'metric' ,
simultaneously_sent_query_count : 1 ,
with_lucene_query : true ,
} )
) ;
} ) ;
2015-09-28 22:28:19 +08:00
} ) ;
2015-11-05 15:36:51 +08:00
2019-06-25 04:15:03 +08:00
describe ( 'When issuing logs query with interval pattern' , ( ) = > {
2019-12-12 00:40:56 +08:00
async function setupDataSource ( jsonData? : Partial < ElasticsearchOptions > ) {
2021-01-11 13:47:51 +08:00
jsonData = {
interval : 'Daily' ,
timeField : '@timestamp' ,
. . . ( jsonData || { } ) ,
} ;
const { ds } = getTestContext ( {
jsonData ,
data : logsResponse.data ,
2019-06-25 04:15:03 +08:00
database : 'mock-index' ,
} ) ;
2023-02-02 16:01:02 +08:00
const query = {
2020-12-04 22:29:40 +08:00
range : createTimeRange ( toUtc ( [ 2015 , 4 , 30 , 10 ] ) , toUtc ( [ 2019 , 7 , 1 , 10 ] ) ) ,
2019-06-25 04:15:03 +08:00
targets : [
{
alias : '$varAlias' ,
refId : 'A' ,
2021-01-11 13:47:51 +08:00
bucketAggs : [
{
type : 'date_histogram' ,
settings : { interval : 'auto' } ,
id : '2' ,
} ,
] ,
2021-03-02 17:10:41 +08:00
metrics : [ { type : 'logs' , id : '1' } ] ,
2019-06-25 04:15:03 +08:00
query : 'escape\\:test' ,
timeField : '@timestamp' ,
} ,
] ,
2023-02-02 16:01:02 +08:00
} as unknown as DataQueryRequest < ElasticsearchQuery > ;
2019-06-25 04:15:03 +08:00
2021-01-11 13:47:51 +08:00
const queryBuilderSpy = jest . spyOn ( ds . queryBuilder , 'getLogsQuery' ) ;
2022-09-06 20:35:54 +08:00
let response : DataQueryResponse = { data : [ ] } ;
2021-01-11 13:47:51 +08:00
2021-01-20 14:59:48 +08:00
await expect ( ds . query ( query ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
response = received [ 0 ] ;
} ) ;
2019-12-12 00:40:56 +08:00
return { queryBuilderSpy , response } ;
}
2019-06-25 04:15:03 +08:00
2019-12-12 00:40:56 +08:00
it ( 'should call getLogsQuery()' , async ( ) = > {
const { queryBuilderSpy } = await setupDataSource ( ) ;
2019-06-25 04:15:03 +08:00
expect ( queryBuilderSpy ) . toHaveBeenCalled ( ) ;
} ) ;
2019-12-12 00:40:56 +08:00
it ( 'should enhance fields with links' , async ( ) = > {
const { response } = await setupDataSource ( {
dataLinks : [
{
field : 'host' ,
url : 'http://localhost:3000/${__value.raw}' ,
2021-07-30 02:42:20 +08:00
urlDisplayLabel : 'Custom Label' ,
2019-12-12 00:40:56 +08:00
} ,
] ,
} ) ;
2020-07-09 22:14:55 +08:00
expect ( response . data . length ) . toBe ( 1 ) ;
2021-07-30 02:42:20 +08:00
const links : DataLink [ ] = response . data [ 0 ] . fields . find ( ( field : Field ) = > field . name === 'host' ) . config . links ;
2019-12-12 00:40:56 +08:00
expect ( links . length ) . toBe ( 1 ) ;
expect ( links [ 0 ] . url ) . toBe ( 'http://localhost:3000/${__value.raw}' ) ;
2021-07-30 02:42:20 +08:00
expect ( links [ 0 ] . title ) . toBe ( 'Custom Label' ) ;
2019-12-12 00:40:56 +08:00
} ) ;
2022-12-08 18:28:40 +08:00
it ( 'should report query interaction' , async ( ) = > {
await setupDataSource ( ) ;
expect ( reportInteraction ) . toHaveBeenCalledWith (
'grafana_elasticsearch_query_executed' ,
expect . objectContaining ( {
alias : '$varAlias' ,
app : undefined ,
has_data : true ,
has_error : false ,
line_limit : undefined ,
query_type : 'logs' ,
simultaneously_sent_query_count : 1 ,
with_lucene_query : true ,
} )
) ;
} ) ;
2019-06-25 04:15:03 +08:00
} ) ;
2018-09-04 21:55:41 +08:00
describe ( 'When issuing document query' , ( ) = > {
2021-01-11 13:47:51 +08:00
async function runScenario() {
const range = createTimeRange ( dateTime ( [ 2015 , 4 , 30 , 10 ] ) , dateTime ( [ 2015 , 5 , 1 , 10 ] ) ) ;
2022-09-06 20:35:54 +08:00
const targets : ElasticsearchQuery [ ] = [
{ refId : 'A' , metrics : [ { type : 'raw_document' , id : '1' } ] , query : 'test' } ,
] ;
const query = { . . . DATAQUERY_BASE , range , targets } ;
2021-01-11 13:47:51 +08:00
const data = { responses : [ ] } ;
2015-11-05 15:36:51 +08:00
2023-03-28 22:04:56 +08:00
const { ds , fetchMock } = getTestContext ( { data , database : 'test' } ) ;
2020-12-04 22:29:40 +08:00
2021-01-20 14:59:48 +08:00
await expect ( ds . query ( query ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toEqual ( { data : [ ] } ) ;
} ) ;
2015-11-05 15:36:51 +08:00
2021-01-11 13:47:51 +08:00
expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
const requestOptions = fetchMock . mock . calls [ 0 ] [ 0 ] ;
const parts = requestOptions . data . split ( '\n' ) ;
const header = JSON . parse ( parts [ 0 ] ) ;
const body = JSON . parse ( parts [ 1 ] ) ;
2015-11-05 15:36:51 +08:00
2021-01-11 13:47:51 +08:00
return { body , header } ;
}
it ( 'should set search type to query_then_fetch' , async ( ) = > {
const { header } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( header . search_type ) . toEqual ( 'query_then_fetch' ) ;
2015-11-05 15:36:51 +08:00
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should set size' , async ( ) = > {
const { body } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( body . size ) . toBe ( 500 ) ;
2015-11-05 15:36:51 +08:00
} ) ;
2022-12-08 18:28:40 +08:00
it ( 'should report query interaction' , async ( ) = > {
await runScenario ( ) ;
expect ( reportInteraction ) . toHaveBeenCalledWith (
'grafana_elasticsearch_query_executed' ,
expect . objectContaining ( {
alias : undefined ,
app : 'test' ,
has_data : false ,
has_error : false ,
line_limit : undefined ,
query_type : 'raw_document' ,
simultaneously_sent_query_count : 1 ,
with_lucene_query : true ,
} )
) ;
} ) ;
2015-11-05 15:36:51 +08:00
} ) ;
2020-06-09 23:32:47 +08:00
describe ( 'When getting an error on response' , ( ) = > {
2020-12-04 22:29:40 +08:00
const query : DataQueryRequest < ElasticsearchQuery > = {
range : createTimeRange ( toUtc ( [ 2020 , 1 , 1 , 10 ] ) , toUtc ( [ 2020 , 2 , 1 , 10 ] ) ) ,
2020-06-09 23:32:47 +08:00
targets : [
{
2020-12-04 22:29:40 +08:00
refId : 'A' ,
2020-06-09 23:32:47 +08:00
alias : '$varAlias' ,
bucketAggs : [ { type : 'date_histogram' , field : '@timestamp' , id : '1' } ] ,
metrics : [ { type : 'count' , id : '1' } ] ,
query : 'escape\\:test' ,
} ,
] ,
2020-12-04 22:29:40 +08:00
} as DataQueryRequest < ElasticsearchQuery > ;
2020-06-09 23:32:47 +08:00
it ( 'should process it properly' , async ( ) = > {
2021-01-11 13:47:51 +08:00
const { ds } = getTestContext ( {
2023-03-28 22:04:56 +08:00
jsonData : { interval : 'Daily' } ,
2021-01-11 13:47:51 +08:00
data : {
took : 1 ,
responses : [
{
error : {
reason : 'all shards failed' ,
2020-06-09 23:32:47 +08:00
} ,
2021-01-11 13:47:51 +08:00
status : 400 ,
} ,
] ,
} ,
2020-06-09 23:32:47 +08:00
} ) ;
const errObject = {
data : '{\n "reason": "all shards failed"\n}' ,
message : 'all shards failed' ,
2021-01-11 13:47:51 +08:00
config : {
2022-06-01 19:05:15 +08:00
url : 'http://localhost:3000/api/ds/query' ,
2021-01-11 13:47:51 +08:00
} ,
2020-06-09 23:32:47 +08:00
} ;
2021-01-20 14:59:48 +08:00
await expect ( ds . query ( query ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toEqual ( errObject ) ;
} ) ;
2020-06-09 23:32:47 +08:00
} ) ;
2021-02-02 20:21:24 +08:00
it ( 'should properly throw an error with just a message' , async ( ) = > {
const response : FetchResponse = {
data : {
error : 'Bad Request' ,
message : 'Authentication to data source failed' ,
} ,
status : 400 ,
2022-06-01 19:05:15 +08:00
url : 'http://localhost:3000/api/ds/query' ,
config : { url : 'http://localhost:3000/api/ds/query' } ,
2021-02-02 20:21:24 +08:00
type : 'basic' ,
statusText : 'Bad Request' ,
redirected : false ,
2022-02-02 20:02:32 +08:00
headers : { } as unknown as Headers ,
2021-02-02 20:21:24 +08:00
ok : false ,
} ;
const { ds } = getTestContext ( {
2022-09-06 20:35:54 +08:00
fetchMockImplementation : ( ) = > throwError ( response ) ,
2021-02-02 20:21:24 +08:00
} ) ;
const errObject = {
error : 'Bad Request' ,
2023-06-16 18:56:34 +08:00
message : 'Authentication to data source failed' ,
2021-02-02 20:21:24 +08:00
} ;
await expect ( ds . query ( query ) ) . toEmitValuesWith ( ( received ) = > {
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toEqual ( errObject ) ;
} ) ;
} ) ;
2020-06-09 23:32:47 +08:00
it ( 'should properly throw an unknown error' , async ( ) = > {
2021-01-11 13:47:51 +08:00
const { ds } = getTestContext ( {
2023-03-28 22:04:56 +08:00
jsonData : { interval : 'Daily' } ,
2021-01-11 13:47:51 +08:00
data : {
took : 1 ,
responses : [
{
error : { } ,
status : 400 ,
} ,
] ,
} ,
2020-06-09 23:32:47 +08:00
} ) ;
const errObject = {
data : '{}' ,
message : 'Unknown elastic error response' ,
2021-01-11 13:47:51 +08:00
config : {
2022-06-01 19:05:15 +08:00
url : 'http://localhost:3000/api/ds/query' ,
2021-01-11 13:47:51 +08:00
} ,
2020-06-09 23:32:47 +08:00
} ;
2021-01-20 14:59:48 +08:00
await expect ( ds . query ( query ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toEqual ( errObject ) ;
} ) ;
2020-06-09 23:32:47 +08:00
} ) ;
} ) ;
2020-03-02 17:45:31 +08:00
describe ( 'When getting field mappings on indices with gaps' , ( ) = > {
const basicResponse = {
2021-01-11 13:47:51 +08:00
metricbeat : {
mappings : {
metricsets : {
_all : { } ,
properties : {
'@timestamp' : { type : 'date' } ,
beat : {
properties : {
hostname : { type : 'string' } ,
2020-03-02 17:45:31 +08:00
} ,
} ,
} ,
} ,
} ,
} ,
} ;
it ( 'should not retry when ES is down' , async ( ) = > {
2021-01-20 14:59:48 +08:00
const twoDaysBefore = toUtc ( ) . subtract ( 2 , 'day' ) . format ( 'YYYY.MM.DD' ) ;
2020-03-02 17:45:31 +08:00
2023-10-24 16:25:23 +08:00
const { ds , fetchMock , timeRange } = getTestContext ( {
2023-03-28 22:04:56 +08:00
jsonData : { interval : 'Daily' } ,
2022-09-06 20:35:54 +08:00
fetchMockImplementation : ( options ) = > {
2021-01-11 13:47:51 +08:00
if ( options . url === ` ${ ELASTICSEARCH_MOCK_URL } /asd- ${ twoDaysBefore } /_mapping ` ) {
return of ( createFetchResponse ( basicResponse ) ) ;
}
return throwError ( { status : 500 } ) ;
} ,
2020-03-02 17:45:31 +08:00
} ) ;
2023-10-24 16:25:23 +08:00
await expect ( ds . getFields ( undefined , timeRange ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toStrictEqual ( { status : 500 } ) ;
expect ( fetchMock ) . toBeCalledTimes ( 1 ) ;
} ) ;
2020-03-02 17:45:31 +08:00
} ) ;
it ( 'should not retry more than 7 indices' , async ( ) = > {
2023-10-24 16:25:23 +08:00
const { ds , fetchMock } = getTestContext ( {
2023-03-28 22:04:56 +08:00
jsonData : { interval : 'Daily' } ,
2022-09-06 20:35:54 +08:00
fetchMockImplementation : ( options ) = > {
2021-01-11 13:47:51 +08:00
return throwError ( { status : 404 } ) ;
} ,
2020-03-02 17:45:31 +08:00
} ) ;
2023-10-24 16:25:23 +08:00
const timeRange = createTimeRange ( dateTime ( ) . subtract ( 2 , 'week' ) , dateTime ( ) ) ;
await expect ( ds . getFields ( undefined , timeRange ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toStrictEqual ( 'Could not find an available index for this time range.' ) ;
expect ( fetchMock ) . toBeCalledTimes ( 7 ) ;
} ) ;
2020-03-02 17:45:31 +08:00
} ) ;
} ) ;
2019-04-25 15:41:13 +08:00
describe ( 'When getting fields from ES 7.0' , ( ) = > {
2021-01-11 13:47:51 +08:00
const data = {
'genuine.es7._mapping.response' : {
mappings : {
properties : {
'@timestamp_millis' : {
type : 'date' ,
format : 'epoch_millis' ,
} ,
classification_terms : {
type : 'keyword' ,
} ,
domains : {
type : 'keyword' ,
} ,
ip_address : {
type : 'ip' ,
} ,
justification_blob : {
properties : {
criterion : {
type : 'text' ,
fields : {
keyword : {
type : 'keyword' ,
ignore_above : 256 ,
} ,
2019-04-25 15:41:13 +08:00
} ,
2021-01-11 13:47:51 +08:00
} ,
overall_vote_score : {
type : 'float' ,
} ,
shallow : {
properties : {
jsi : {
properties : {
sdb : {
properties : {
dsel2 : {
properties : {
'bootlegged-gille' : {
properties : {
botness : {
type : 'float' ,
} ,
general_algorithm_score : {
type : 'float' ,
} ,
} ,
} ,
'uncombed-boris' : {
properties : {
botness : {
type : 'float' ,
} ,
general_algorithm_score : {
type : 'float' ,
2019-04-25 15:41:13 +08:00
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2021-01-11 13:47:51 +08:00
overall_vote_score : {
type : 'float' ,
} ,
ua_terms_long : {
type : 'keyword' ,
} ,
ua_terms_short : {
type : 'keyword' ,
} ,
2019-04-25 15:41:13 +08:00
} ,
2021-01-11 13:47:51 +08:00
} ,
} ,
} ;
2019-04-25 15:41:13 +08:00
2021-06-04 18:07:59 +08:00
const dateFields = [ '@timestamp_millis' ] ;
const numberFields = [
'justification_blob.overall_vote_score' ,
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score' ,
'overall_vote_score' ,
] ;
2019-04-25 15:41:13 +08:00
it ( 'should return nested fields' , async ( ) = > {
2022-05-05 22:16:34 +08:00
const { ds } = getTestContext ( {
data ,
database : 'genuine.es7._mapping.response' ,
} ) ;
2021-01-11 13:47:51 +08:00
2021-01-20 14:59:48 +08:00
await expect ( ds . getFields ( ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
const fieldObjects = received [ 0 ] ;
2021-04-21 15:38:00 +08:00
const fields = map ( fieldObjects , 'text' ) ;
2021-01-11 13:47:51 +08:00
expect ( fields ) . toEqual ( [
'@timestamp_millis' ,
'classification_terms' ,
'domains' ,
'ip_address' ,
'justification_blob.criterion.keyword' ,
'justification_blob.criterion' ,
'justification_blob.overall_vote_score' ,
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score' ,
'overall_vote_score' ,
'ua_terms_long' ,
'ua_terms_short' ,
] ) ;
} ) ;
2019-04-25 15:41:13 +08:00
} ) ;
it ( 'should return number fields' , async ( ) = > {
2022-05-05 22:16:34 +08:00
const { ds } = getTestContext ( {
data ,
database : 'genuine.es7._mapping.response' ,
} ) ;
2021-01-11 13:47:51 +08:00
2021-06-04 18:07:59 +08:00
await expect ( ds . getFields ( [ 'number' ] ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
const fieldObjects = received [ 0 ] ;
2021-04-21 15:38:00 +08:00
const fields = map ( fieldObjects , 'text' ) ;
2021-06-04 18:07:59 +08:00
expect ( fields ) . toEqual ( numberFields ) ;
2021-01-11 13:47:51 +08:00
} ) ;
2019-04-25 15:41:13 +08:00
} ) ;
it ( 'should return date fields' , async ( ) = > {
2022-05-05 22:16:34 +08:00
const { ds } = getTestContext ( {
data ,
database : 'genuine.es7._mapping.response' ,
} ) ;
2019-04-25 15:41:13 +08:00
2021-06-04 18:07:59 +08:00
await expect ( ds . getFields ( [ 'date' ] ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
2020-12-04 22:29:40 +08:00
2021-01-11 13:47:51 +08:00
const fieldObjects = received [ 0 ] ;
2021-04-21 15:38:00 +08:00
const fields = map ( fieldObjects , 'text' ) ;
2021-06-04 18:07:59 +08:00
expect ( fields ) . toEqual ( dateFields ) ;
2021-01-11 13:47:51 +08:00
} ) ;
2016-09-15 13:30:08 +08:00
} ) ;
} ) ;
2016-09-20 02:10:45 +08:00
2018-09-04 21:55:41 +08:00
describe ( 'When issuing aggregation query on es5.x' , ( ) = > {
2021-01-11 13:47:51 +08:00
async function runScenario() {
const range = createTimeRange ( dateTime ( [ 2015 , 4 , 30 , 10 ] ) , dateTime ( [ 2015 , 5 , 1 , 10 ] ) ) ;
2022-09-06 20:35:54 +08:00
const targets : ElasticsearchQuery [ ] = [
2021-01-11 13:47:51 +08:00
{
refId : 'A' ,
bucketAggs : [ { type : 'date_histogram' , field : '@timestamp' , id : '2' } ] ,
metrics : [ { type : 'count' , id : '1' } ] ,
query : 'test' ,
} ,
] ;
2022-09-06 20:35:54 +08:00
const query = { . . . DATAQUERY_BASE , range , targets } ;
2021-01-11 13:47:51 +08:00
const data = { responses : [ ] } ;
2016-06-20 06:40:16 +08:00
2023-03-28 22:04:56 +08:00
const { ds , fetchMock } = getTestContext ( { data , database : 'test' } ) ;
2020-12-04 22:29:40 +08:00
2021-01-20 14:59:48 +08:00
await expect ( ds . query ( query ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toEqual ( { data : [ ] } ) ;
} ) ;
2016-06-20 06:40:16 +08:00
2021-01-11 13:47:51 +08:00
expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
const requestOptions = fetchMock . mock . calls [ 0 ] [ 0 ] ;
const parts = requestOptions . data . split ( '\n' ) ;
const header = JSON . parse ( parts [ 0 ] ) ;
const body = JSON . parse ( parts [ 1 ] ) ;
2016-06-20 06:40:16 +08:00
2021-01-11 13:47:51 +08:00
return { body , header } ;
}
it ( 'should not set search type to count' , async ( ) = > {
const { header } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( header . search_type ) . not . toEqual ( 'count' ) ;
2016-06-20 06:40:16 +08:00
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should set size to 0' , async ( ) = > {
const { body } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( body . size ) . toBe ( 0 ) ;
2016-06-20 06:40:16 +08:00
} ) ;
} ) ;
2018-09-04 21:55:41 +08:00
describe ( 'When issuing metricFind query on es5.x' , ( ) = > {
2021-01-11 13:47:51 +08:00
async function runScenario() {
const data = {
responses : [
{
aggregations : {
'1' : {
buckets : [
{ doc_count : 1 , key : 'test' } ,
{
doc_count : 2 ,
key : 'test2' ,
key_as_string : 'test2_as_string' ,
2017-12-20 19:33:33 +08:00
} ,
2021-01-11 13:47:51 +08:00
] ,
2017-12-20 19:33:33 +08:00
} ,
2021-01-11 13:47:51 +08:00
} ,
2017-12-20 19:33:33 +08:00
} ,
2021-01-11 13:47:51 +08:00
] ,
} ;
2016-06-20 06:40:16 +08:00
2023-03-28 22:04:56 +08:00
const { ds , fetchMock } = getTestContext ( { data , database : 'test' } ) ;
2017-07-12 13:43:12 +08:00
2021-01-11 13:47:51 +08:00
const results = await ds . metricFindQuery ( '{"find": "terms", "field": "test"}' ) ;
expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
const requestOptions = fetchMock . mock . calls [ 0 ] [ 0 ] ;
const parts = requestOptions . data . split ( '\n' ) ;
const header = JSON . parse ( parts [ 0 ] ) ;
const body = JSON . parse ( parts [ 1 ] ) ;
2016-06-20 06:40:16 +08:00
2021-01-11 13:47:51 +08:00
return { results , body , header } ;
}
it ( 'should get results' , async ( ) = > {
const { results } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( results . length ) . toEqual ( 2 ) ;
2017-07-12 13:43:12 +08:00
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should use key or key_as_string' , async ( ) = > {
const { results } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( results [ 0 ] . text ) . toEqual ( 'test' ) ;
expect ( results [ 1 ] . text ) . toEqual ( 'test2_as_string' ) ;
2017-07-12 13:43:12 +08:00
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should not set search type to count' , async ( ) = > {
const { header } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( header . search_type ) . not . toEqual ( 'count' ) ;
2016-06-20 06:40:16 +08:00
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should set size to 0' , async ( ) = > {
const { body } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( body . size ) . toBe ( 0 ) ;
2016-06-20 06:40:16 +08:00
} ) ;
2021-01-11 13:47:51 +08:00
it ( 'should not set terms aggregation size to 0' , async ( ) = > {
const { body } = await runScenario ( ) ;
2018-07-03 16:12:07 +08:00
expect ( body [ 'aggs' ] [ '1' ] [ 'terms' ] . size ) . not . toBe ( 0 ) ;
2016-10-02 22:59:25 +08:00
} ) ;
2016-06-20 06:40:16 +08:00
} ) ;
2020-02-13 23:00:01 +08:00
describe ( 'query' , ( ) = > {
2021-01-11 13:47:51 +08:00
it ( 'should replace range as integer not string' , async ( ) = > {
2023-03-28 22:04:56 +08:00
const { ds } = getTestContext ( { jsonData : { interval : 'Daily' , timeField : '@time' } } ) ;
2023-05-02 15:52:02 +08:00
const postMock = jest . fn ( ( method : string , url : string , data , header : object ) = >
of ( createFetchResponse ( { responses : [ ] } ) )
) ;
ds . legacyQueryRunner [ 'request' ] = postMock ;
2021-01-11 13:47:51 +08:00
2021-01-20 14:59:48 +08:00
await expect ( ds . query ( createElasticQuery ( ) ) ) . toEmitValuesWith ( ( received ) = > {
2021-01-11 13:47:51 +08:00
expect ( postMock ) . toHaveBeenCalledTimes ( 1 ) ;
2023-05-02 15:52:02 +08:00
const query = postMock . mock . calls [ 0 ] [ 2 ] ;
2021-01-11 13:47:51 +08:00
expect ( typeof JSON . parse ( query . split ( '\n' ) [ 1 ] ) . query . bool . filter [ 0 ] . range [ '@time' ] . gte ) . toBe ( 'number' ) ;
} ) ;
2020-02-13 23:00:01 +08:00
} ) ;
} ) ;
2020-11-11 20:56:43 +08:00
it ( 'should correctly interpolate variables in query' , ( ) = > {
2021-01-11 13:47:51 +08:00
const { ds } = getTestContext ( ) ;
2020-12-04 22:29:40 +08:00
const query : ElasticsearchQuery = {
refId : 'A' ,
2020-11-11 20:56:43 +08:00
bucketAggs : [ { type : 'filters' , settings : { filters : [ { query : '$var' , label : '' } ] } , id : '1' } ] ,
metrics : [ { type : 'count' , id : '1' } ] ,
query : '$var' ,
} ;
2021-01-11 13:47:51 +08:00
const interpolatedQuery = ds . interpolateVariablesInQueries ( [ query ] , { } ) [ 0 ] ;
2020-11-11 20:56:43 +08:00
expect ( interpolatedQuery . query ) . toBe ( 'resolvedVariable' ) ;
2020-12-04 22:29:40 +08:00
expect ( ( interpolatedQuery . bucketAggs ! [ 0 ] as Filters ) . settings ! . filters ! [ 0 ] . query ) . toBe ( 'resolvedVariable' ) ;
2020-11-11 20:56:43 +08:00
} ) ;
2022-12-22 23:06:30 +08:00
it ( 'should correctly add ad hoc filters when interpolating variables in query' , ( ) = > {
2023-10-24 16:25:23 +08:00
const adHocFilters = [ { key : 'bar' , operator : '=' , value : 'test' } ] ;
const { ds } = getTestContext ( ) ;
2022-12-22 23:06:30 +08:00
const query : ElasticsearchQuery = {
refId : 'A' ,
bucketAggs : [ { type : 'filters' , settings : { filters : [ { query : '$var' , label : '' } ] } , id : '1' } ] ,
metrics : [ { type : 'count' , id : '1' } ] ,
query : 'foo:"bar"' ,
} ;
2023-10-24 16:25:23 +08:00
const interpolatedQuery = ds . interpolateVariablesInQueries ( [ query ] , { } , adHocFilters ) [ 0 ] ;
2022-12-22 23:06:30 +08:00
expect ( interpolatedQuery . query ) . toBe ( 'foo:"bar" AND bar:"test"' ) ;
} ) ;
2021-11-24 22:09:10 +08:00
it ( 'should correctly handle empty query strings in filters bucket aggregation' , ( ) = > {
2021-01-11 13:47:51 +08:00
const { ds } = getTestContext ( ) ;
2020-12-04 22:29:40 +08:00
const query : ElasticsearchQuery = {
refId : 'A' ,
2020-11-11 20:56:43 +08:00
bucketAggs : [ { type : 'filters' , settings : { filters : [ { query : '' , label : '' } ] } , id : '1' } ] ,
metrics : [ { type : 'count' , id : '1' } ] ,
query : '' ,
} ;
2021-01-11 13:47:51 +08:00
const interpolatedQuery = ds . interpolateVariablesInQueries ( [ query ] , { } ) [ 0 ] ;
2020-11-11 20:56:43 +08:00
2020-12-04 22:29:40 +08:00
expect ( ( interpolatedQuery . bucketAggs ! [ 0 ] as Filters ) . settings ! . filters ! [ 0 ] . query ) . toBe ( '*' ) ;
2020-11-11 20:56:43 +08:00
} ) ;
2023-01-26 23:06:10 +08:00
describe ( 'getSupplementaryQuery' , ( ) = > {
let ds : ElasticDatasource ;
beforeEach ( ( ) = > {
ds = getTestContext ( ) . ds ;
} ) ;
it ( 'does not return logs volume query for metric query' , ( ) = > {
expect (
2023-04-19 23:04:47 +08:00
ds . getSupplementaryQuery (
{ type : SupplementaryQueryType . LogsVolume } ,
{
refId : 'A' ,
metrics : [ { type : 'count' , id : '1' } ] ,
bucketAggs : [ { type : 'filters' , settings : { filters : [ { query : 'foo' , label : '' } ] } , id : '1' } ] ,
query : 'foo="bar"' ,
}
)
2023-01-26 23:06:10 +08:00
) . toEqual ( undefined ) ;
} ) ;
it ( 'returns logs volume query for log query' , ( ) = > {
expect (
2023-04-19 23:04:47 +08:00
ds . getSupplementaryQuery (
{ type : SupplementaryQueryType . LogsVolume } ,
{
refId : 'A' ,
metrics : [ { type : 'logs' , id : '1' } ] ,
query : 'foo="bar"' ,
}
)
2023-01-26 23:06:10 +08:00
) . toEqual ( {
bucketAggs : [
{
field : '' ,
id : '3' ,
settings : {
interval : 'auto' ,
min_doc_count : '0' ,
trimEdges : '0' ,
} ,
type : 'date_histogram' ,
} ,
] ,
metrics : [
{
id : '1' ,
type : 'count' ,
} ,
] ,
query : 'foo="bar"' ,
refId : 'log-volume-A' ,
timeField : '' ,
} ) ;
} ) ;
2023-06-22 19:53:05 +08:00
it ( 'does not return logs samples for non time series queries' , ( ) = > {
expect (
ds . getSupplementaryQuery (
{ type : SupplementaryQueryType . LogsSample , limit : 100 } ,
{
refId : 'A' ,
bucketAggs : [ { type : 'filters' , id : '1' } ] ,
query : '' ,
}
)
) . toEqual ( undefined ) ;
} ) ;
it ( 'returns logs samples for time series queries' , ( ) = > {
expect (
ds . getSupplementaryQuery (
{ type : SupplementaryQueryType . LogsSample , limit : 100 } ,
{
refId : 'A' ,
query : '' ,
bucketAggs : [ { type : 'date_histogram' , id : '1' } ] ,
}
)
) . toEqual ( {
refId : ` log-sample-A ` ,
query : '' ,
metrics : [ { type : 'logs' , id : '1' , settings : { limit : '100' } } ] ,
} ) ;
} ) ;
} ) ;
describe ( 'getDataProvider' , ( ) = > {
let ds : ElasticDatasource ;
beforeEach ( ( ) = > {
ds = getTestContext ( ) . ds ;
} ) ;
it ( 'does not create a logs sample provider for non time series query' , ( ) = > {
2024-02-09 20:11:08 +08:00
const options : DataQueryRequest < ElasticsearchQuery > = {
. . . DATAQUERY_BASE ,
2023-06-22 19:53:05 +08:00
targets : [
{
refId : 'A' ,
metrics : [ { type : 'logs' , id : '1' , settings : { limit : '100' } } ] ,
} ,
] ,
2024-02-09 20:11:08 +08:00
} ;
2023-06-22 19:53:05 +08:00
2024-01-30 18:36:20 +08:00
expect ( ds . getSupplementaryRequest ( SupplementaryQueryType . LogsSample , options ) ) . not . toBeDefined ( ) ;
2023-06-22 19:53:05 +08:00
} ) ;
it ( 'does create a logs sample provider for time series query' , ( ) = > {
2024-02-09 20:11:08 +08:00
const options : DataQueryRequest < ElasticsearchQuery > = {
. . . DATAQUERY_BASE ,
2023-06-22 19:53:05 +08:00
targets : [
{
refId : 'A' ,
bucketAggs : [ { type : 'date_histogram' , id : '1' } ] ,
} ,
] ,
2024-02-09 20:11:08 +08:00
} ;
2023-06-22 19:53:05 +08:00
2024-01-30 18:36:20 +08:00
expect ( ds . getSupplementaryRequest ( SupplementaryQueryType . LogsSample , options ) ) . toBeDefined ( ) ;
2023-06-22 19:53:05 +08:00
} ) ;
} ) ;
describe ( 'getLogsSampleDataProvider' , ( ) = > {
let ds : ElasticDatasource ;
beforeEach ( ( ) = > {
ds = getTestContext ( ) . ds ;
} ) ;
it ( "doesn't return a logs sample provider given a non time series query" , ( ) = > {
2024-02-09 20:11:08 +08:00
const request : DataQueryRequest < ElasticsearchQuery > = {
. . . DATAQUERY_BASE ,
2023-06-22 19:53:05 +08:00
targets : [
{
refId : 'A' ,
metrics : [ { type : 'logs' , id : '1' , settings : { limit : '100' } } ] ,
} ,
] ,
2024-02-09 20:11:08 +08:00
} ;
2023-06-22 19:53:05 +08:00
2024-01-30 18:36:20 +08:00
expect ( ds . getSupplementaryRequest ( SupplementaryQueryType . LogsSample , request ) ) . not . toBeDefined ( ) ;
2023-06-22 19:53:05 +08:00
} ) ;
it ( 'returns a logs sample provider given a time series query' , ( ) = > {
2024-02-09 20:11:08 +08:00
const request : DataQueryRequest < ElasticsearchQuery > = {
. . . DATAQUERY_BASE ,
2023-06-22 19:53:05 +08:00
targets : [
{
refId : 'A' ,
bucketAggs : [ { type : 'date_histogram' , id : '1' } ] ,
} ,
] ,
2024-02-09 20:11:08 +08:00
} ;
2023-06-22 19:53:05 +08:00
2024-01-30 18:36:20 +08:00
expect ( ds . getSupplementaryRequest ( SupplementaryQueryType . LogsSample , request ) ) . toBeDefined ( ) ;
2023-06-22 19:53:05 +08:00
} ) ;
2023-01-26 23:06:10 +08:00
} ) ;
2015-09-28 22:28:19 +08:00
} ) ;
2019-12-12 00:40:56 +08:00
2021-07-15 21:52:02 +08:00
describe ( 'getMultiSearchUrl' , ( ) = > {
2023-03-28 22:04:56 +08:00
it ( 'Should add correct params to URL if "includeFrozen" is enabled' , ( ) = > {
2024-03-20 16:59:26 +08:00
const { ds } = getTestContext ( { jsonData : { includeFrozen : true } } ) ;
2021-07-15 21:52:02 +08:00
2023-03-28 22:04:56 +08:00
expect ( ds . getMultiSearchUrl ( ) ) . toMatch ( /ignore_throttled=false/ ) ;
} ) ;
2021-07-15 21:52:02 +08:00
2023-03-28 22:04:56 +08:00
it ( 'Should NOT add ignore_throttled if "includeFrozen" is disabled' , ( ) = > {
2024-03-20 16:59:26 +08:00
const { ds } = getTestContext ( { jsonData : { includeFrozen : false } } ) ;
2021-07-15 21:52:02 +08:00
2023-03-28 22:04:56 +08:00
expect ( ds . getMultiSearchUrl ( ) ) . not . toMatch ( /ignore_throttled=false/ ) ;
2021-07-15 21:52:02 +08:00
} ) ;
} ) ;
2020-07-01 15:45:21 +08:00
describe ( 'enhanceDataFrame' , ( ) = > {
it ( 'adds links to dataframe' , ( ) = > {
const df = new MutableDataFrame ( {
fields : [
{
name : 'urlField' ,
2023-04-14 22:03:45 +08:00
values : [ ] ,
2020-07-01 15:45:21 +08:00
} ,
{
name : 'traceField' ,
2023-04-14 22:03:45 +08:00
values : [ ] ,
2020-07-01 15:45:21 +08:00
} ,
] ,
} ) ;
Chore: Fix all Typescript strict null errors (#26204)
* Chore: Fix typescript strict null errors
* Added new limit
* Fixed ts issue
* fixed tests
* trying to fix type inference
* Fixing more ts errors
* Revert tsconfig option
* Fix
* Fixed code
* More fixes
* fix tests
* Updated snapshot
* Chore: More ts strict null fixes
* More fixes in some really messed up azure config components
* More fixes, current count: 441
* 419
* More fixes
* Fixed invalid initial state in explore
* Fixing tests
* Fixed tests
* Explore fix
* More fixes
* Progress
* Sub 300
* Now at 218
* Progress
* Update
* Progress
* Updated tests
* at 159
* fixed tests
* Progress
* YAy blow 100! at 94
* 10,9,8,7,6,5,4,3,2,1... lift off
* Fixed tests
* Fixed more type errors
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2020-07-10 18:46:59 +08:00
2020-07-01 15:45:21 +08:00
enhanceDataFrame ( df , [
{
field : 'urlField' ,
url : 'someUrl' ,
} ,
2022-01-14 22:05:23 +08:00
{
field : 'urlField' ,
url : 'someOtherUrl' ,
} ,
2020-07-01 15:45:21 +08:00
{
field : 'traceField' ,
url : 'query' ,
2022-01-14 22:05:23 +08:00
datasourceUid : 'ds1' ,
} ,
{
field : 'traceField' ,
url : 'otherQuery' ,
datasourceUid : 'ds2' ,
2020-07-01 15:45:21 +08:00
} ,
] ) ;
2022-01-14 22:05:23 +08:00
expect ( df . fields [ 0 ] . config . links ) . toHaveLength ( 2 ) ;
expect ( df . fields [ 0 ] . config . links ) . toContainEqual ( {
2020-07-01 15:45:21 +08:00
title : '' ,
url : 'someUrl' ,
} ) ;
2022-01-14 22:05:23 +08:00
expect ( df . fields [ 0 ] . config . links ) . toContainEqual ( {
2020-07-01 15:45:21 +08:00
title : '' ,
2022-01-14 22:05:23 +08:00
url : 'someOtherUrl' ,
} ) ;
expect ( df . fields [ 1 ] . config . links ) . toHaveLength ( 2 ) ;
expect ( df . fields [ 1 ] . config . links ) . toContainEqual (
expect . objectContaining ( {
title : '' ,
url : '' ,
internal : expect.objectContaining ( {
query : { query : 'query' } ,
datasourceUid : 'ds1' ,
} ) ,
} )
) ;
expect ( df . fields [ 1 ] . config . links ) . toContainEqual (
expect . objectContaining ( {
title : '' ,
url : '' ,
internal : expect.objectContaining ( {
query : { query : 'otherQuery' } ,
datasourceUid : 'ds2' ,
} ) ,
} )
) ;
2020-07-01 15:45:21 +08:00
} ) ;
2021-05-19 16:07:17 +08:00
it ( 'adds limit to dataframe' , ( ) = > {
const df = new MutableDataFrame ( {
fields : [
{
name : 'someField' ,
2023-04-14 22:03:45 +08:00
values : [ ] ,
2021-05-19 16:07:17 +08:00
} ,
] ,
} ) ;
enhanceDataFrame ( df , [ ] , 10 ) ;
expect ( df . meta ? . limit ) . toBe ( 10 ) ;
} ) ;
2020-07-01 15:45:21 +08:00
} ) ;
2022-07-15 20:37:53 +08:00
describe ( 'modifyQuery' , ( ) = > {
let ds : ElasticDatasource ;
2023-11-16 17:48:10 +08:00
let query : ElasticsearchQuery ;
2022-07-15 20:37:53 +08:00
beforeEach ( ( ) = > {
ds = getTestContext ( ) . ds ;
} ) ;
describe ( 'with empty query' , ( ) = > {
2023-11-16 17:48:10 +08:00
describe ( 'ADD_FILTER and ADD_FITER_OUT' , ( ) = > {
beforeEach ( ( ) = > {
query = { query : '' , refId : 'A' } ;
} ) ;
it ( 'should add the filter' , ( ) = > {
expect ( ds . modifyQuery ( query , { type : 'ADD_FILTER' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'foo:"bar"'
) ;
} ) ;
it ( 'should add the negative filter' , ( ) = > {
expect ( ds . modifyQuery ( query , { type : 'ADD_FILTER_OUT' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'-foo:"bar"'
) ;
} ) ;
it ( 'should do nothing on unknown type' , ( ) = > {
expect ( ds . modifyQuery ( query , { type : 'unknown' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
query . query
) ;
} ) ;
} ) ;
describe ( 'with non-empty query' , ( ) = > {
let query : ElasticsearchQuery ;
beforeEach ( ( ) = > {
query = { query : 'test:"value"' , refId : 'A' } ;
} ) ;
it ( 'should add the filter' , ( ) = > {
expect ( ds . modifyQuery ( query , { type : 'ADD_FILTER' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'test:"value" AND foo:"bar"'
) ;
} ) ;
it ( 'should add the negative filter' , ( ) = > {
expect ( ds . modifyQuery ( query , { type : 'ADD_FILTER_OUT' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'test:"value" AND -foo:"bar"'
) ;
} ) ;
it ( 'should do nothing on unknown type' , ( ) = > {
expect ( ds . modifyQuery ( query , { type : 'unknown' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
query . query
) ;
} ) ;
} ) ;
} ) ;
describe ( 'ADD_STRING_FILTER and ADD_STRING_FILTER_OUT' , ( ) = > {
2022-07-15 20:37:53 +08:00
beforeEach ( ( ) = > {
query = { query : '' , refId : 'A' } ;
} ) ;
it ( 'should add the filter' , ( ) = > {
2023-11-16 17:48:10 +08:00
expect ( ds . modifyQuery ( query , { type : 'ADD_STRING_FILTER' , options : { value : 'bar' } } ) . query ) . toBe ( '"bar"' ) ;
2022-07-15 20:37:53 +08:00
} ) ;
it ( 'should add the negative filter' , ( ) = > {
2023-11-16 17:48:10 +08:00
expect ( ds . modifyQuery ( query , { type : 'ADD_STRING_FILTER_OUT' , options : { value : 'bar' } } ) . query ) . toBe (
'NOT "bar"'
2022-07-18 20:13:34 +08:00
) ;
2022-07-15 20:37:53 +08:00
} ) ;
} ) ;
describe ( 'with non-empty query' , ( ) = > {
let query : ElasticsearchQuery ;
beforeEach ( ( ) = > {
query = { query : 'test:"value"' , refId : 'A' } ;
} ) ;
it ( 'should add the filter' , ( ) = > {
2023-11-16 17:48:10 +08:00
expect ( ds . modifyQuery ( query , { type : 'ADD_STRING_FILTER' , options : { value : 'bar' } } ) . query ) . toBe (
'test:"value" AND "bar"'
2022-07-15 20:37:53 +08:00
) ;
} ) ;
it ( 'should add the negative filter' , ( ) = > {
2023-11-16 17:48:10 +08:00
expect ( ds . modifyQuery ( query , { type : 'ADD_STRING_FILTER_OUT' , options : { value : 'bar' } } ) . query ) . toBe (
'test:"value" NOT "bar"'
2022-07-15 20:37:53 +08:00
) ;
} ) ;
} ) ;
2023-07-24 16:22:47 +08:00
} ) ;
2023-06-27 16:38:20 +08:00
2023-07-24 16:22:47 +08:00
describe ( 'toggleQueryFilter' , ( ) = > {
let ds : ElasticDatasource ;
beforeEach ( ( ) = > {
ds = getTestContext ( ) . ds ;
} ) ;
describe ( 'with empty query' , ( ) = > {
let query : ElasticsearchQuery ;
2023-06-27 16:38:20 +08:00
beforeEach ( ( ) = > {
2023-07-24 16:22:47 +08:00
query = { query : '' , refId : 'A' } ;
2023-06-27 16:38:20 +08:00
} ) ;
2023-07-24 16:22:47 +08:00
it ( 'should add the filter' , ( ) = > {
expect ( ds . toggleQueryFilter ( query , { type : 'FILTER_FOR' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'foo:"bar"'
) ;
} ) ;
it ( 'should toggle the filter' , ( ) = > {
query . query = 'foo:"bar"' ;
expect ( ds . toggleQueryFilter ( query , { type : 'FILTER_FOR' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe ( '' ) ;
2023-06-27 16:38:20 +08:00
} ) ;
2023-07-24 16:22:47 +08:00
it ( 'should add the negative filter' , ( ) = > {
expect ( ds . toggleQueryFilter ( query , { type : 'FILTER_OUT' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'-foo:"bar"'
) ;
} ) ;
it ( 'should remove a positive filter to add a negative filter' , ( ) = > {
query . query = 'foo:"bar"' ;
expect ( ds . toggleQueryFilter ( query , { type : 'FILTER_OUT' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'-foo:"bar"'
) ;
} ) ;
} ) ;
describe ( 'with non-empty query' , ( ) = > {
let query : ElasticsearchQuery ;
beforeEach ( ( ) = > {
query = { query : 'test:"value"' , refId : 'A' } ;
} ) ;
it ( 'should add the filter' , ( ) = > {
expect ( ds . toggleQueryFilter ( query , { type : 'FILTER_FOR' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'test:"value" AND foo:"bar"'
) ;
} ) ;
it ( 'should add the negative filter' , ( ) = > {
expect ( ds . toggleQueryFilter ( query , { type : 'FILTER_OUT' , options : { key : 'foo' , value : 'bar' } } ) . query ) . toBe (
'test:"value" AND -foo:"bar"'
) ;
} ) ;
} ) ;
} ) ;
describe ( 'queryHasFilter()' , ( ) = > {
let ds : ElasticDatasource ;
beforeEach ( ( ) = > {
ds = getTestContext ( ) . ds ;
} ) ;
it ( 'inspects queries for filter presence' , ( ) = > {
const query = { refId : 'A' , query : 'grafana:"awesome"' } ;
expect (
ds . queryHasFilter ( query , {
key : 'grafana' ,
value : 'awesome' ,
} )
) . toBe ( true ) ;
2023-06-27 16:38:20 +08:00
} ) ;
2022-07-15 20:37:53 +08:00
} ) ;
2023-07-24 16:22:47 +08:00
describe ( 'addAdhocFilters' , ( ) = > {
2022-12-22 23:06:30 +08:00
describe ( 'with invalid filters' , ( ) = > {
2023-10-24 16:25:23 +08:00
let ds : ElasticDatasource ;
2023-10-19 16:16:47 +08:00
beforeEach ( ( ) = > {
const context = getTestContext ( ) ;
ds = context . ds ;
} ) ;
2022-12-22 23:06:30 +08:00
it ( 'should filter out ad hoc filter without key' , ( ) = > {
2023-10-24 16:25:23 +08:00
const query = ds . addAdHocFilters ( 'foo:"bar"' , [ { key : '' , operator : '=' , value : 'a' , condition : '' } ] ) ;
2022-12-22 23:06:30 +08:00
expect ( query ) . toBe ( 'foo:"bar"' ) ;
} ) ;
it ( 'should filter out ad hoc filter without value' , ( ) = > {
2023-10-24 16:25:23 +08:00
const query = ds . addAdHocFilters ( 'foo:"bar"' , [ { key : 'a' , operator : '=' , value : '' , condition : '' } ] ) ;
2022-12-22 23:06:30 +08:00
expect ( query ) . toBe ( 'foo:"bar"' ) ;
} ) ;
it ( 'should filter out filter ad hoc filter with invalid operator' , ( ) = > {
2023-10-24 16:25:23 +08:00
const query = ds . addAdHocFilters ( 'foo:"bar"' , [ { key : 'a' , operator : 'A' , value : '' , condition : '' } ] ) ;
2022-12-22 23:06:30 +08:00
expect ( query ) . toBe ( 'foo:"bar"' ) ;
} ) ;
} ) ;
describe ( 'with 1 ad hoc filter' , ( ) = > {
2023-10-24 16:25:23 +08:00
let ds : ElasticDatasource ;
2023-06-29 22:40:55 +08:00
beforeEach ( ( ) = > {
2023-10-24 16:25:23 +08:00
const { ds : datasource } = getTestContext ( ) ;
2023-06-29 22:40:55 +08:00
ds = datasource ;
} ) ;
2022-12-22 23:06:30 +08:00
it ( 'should correctly add 1 ad hoc filter when query is not empty' , ( ) = > {
2023-10-24 16:25:23 +08:00
const filters = [ { key : 'test' , operator : '=' , value : 'test1' , condition : '' } ] ;
const query = ds . addAdHocFilters ( 'foo:"bar"' , filters ) ;
2022-12-22 23:06:30 +08:00
expect ( query ) . toBe ( 'foo:"bar" AND test:"test1"' ) ;
} ) ;
it ( 'should correctly add 1 ad hoc filter when query is empty' , ( ) = > {
2023-10-24 16:25:23 +08:00
const filters = [ { key : 'test' , operator : '=' , value : 'test1' , condition : '' } ] ;
expect ( ds . addAdHocFilters ( '' , filters ) ) . toBe ( 'test:"test1"' ) ;
expect ( ds . addAdHocFilters ( ' ' , filters ) ) . toBe ( 'test:"test1"' ) ;
expect ( ds . addAdHocFilters ( ' ' , filters ) ) . toBe ( 'test:"test1"' ) ;
2022-12-22 23:06:30 +08:00
} ) ;
2023-06-29 22:40:55 +08:00
2023-10-19 16:16:47 +08:00
it ( 'should not fail if the filter value is a number' , ( ) = > {
2023-10-24 16:25:23 +08:00
// @ts-expect-error
expect ( ds . addAdHocFilters ( '' , [ { key : 'key' , operator : '=' , value : 1 , condition : '' } ] ) ) . toBe ( 'key:"1"' ) ;
2023-10-19 16:16:47 +08:00
} ) ;
it . each ( [ '=' , '!=' , '=~' , '!~' , '>' , '<' , '' , '' ] ) (
` should properly build queries with '%s' filters ` ,
( operator : string ) = > {
2023-10-24 16:25:23 +08:00
const filters = [ { key : 'key' , operator , value : 'value' , condition : '' } ] ;
const query = ds . addAdHocFilters ( 'foo:"bar"' , filters ) ;
2023-10-19 16:16:47 +08:00
switch ( operator ) {
case '=' :
expect ( query ) . toBe ( 'foo:"bar" AND key:"value"' ) ;
break ;
case '!=' :
expect ( query ) . toBe ( 'foo:"bar" AND -key:"value"' ) ;
break ;
case '=~' :
expect ( query ) . toBe ( 'foo:"bar" AND key:/value/' ) ;
break ;
case '!~' :
expect ( query ) . toBe ( 'foo:"bar" AND -key:/value/' ) ;
break ;
case '>' :
expect ( query ) . toBe ( 'foo:"bar" AND key:>value' ) ;
break ;
case '<' :
expect ( query ) . toBe ( 'foo:"bar" AND key:<value' ) ;
break ;
}
}
) ;
2023-06-29 22:40:55 +08:00
it ( 'should escape characters in filter keys' , ( ) = > {
2023-10-24 16:25:23 +08:00
const filters = [ { key : 'field:name' , operator : '=' , value : 'field:value' , condition : '' } ] ;
const query = ds . addAdHocFilters ( '' , filters ) ;
2023-06-29 22:40:55 +08:00
expect ( query ) . toBe ( 'field\\:name:"field:value"' ) ;
} ) ;
2023-07-28 20:49:02 +08:00
it ( 'should escape characters in filter values' , ( ) = > {
2023-10-24 16:25:23 +08:00
const filters = [ { key : 'field:name' , operator : '=' , value : 'field "value"' , condition : '' } ] ;
const query = ds . addAdHocFilters ( '' , filters ) ;
2023-07-28 20:49:02 +08:00
expect ( query ) . toBe ( 'field\\:name:"field \\"value\\""' ) ;
} ) ;
2022-12-22 23:06:30 +08:00
} ) ;
describe ( 'with multiple ad hoc filters' , ( ) = > {
2023-06-29 22:40:55 +08:00
let ds : ElasticDatasource ;
2023-10-24 16:25:23 +08:00
const filters = [
{ key : 'bar' , operator : '=' , value : 'baz' , condition : '' } ,
{ key : 'job' , operator : '!=' , value : 'grafana' , condition : '' } ,
{ key : 'service' , operator : '=~' , value : 'service' , condition : '' } ,
{ key : 'count' , operator : '>' , value : '1' , condition : '' } ,
] ;
2023-06-29 22:40:55 +08:00
beforeEach ( ( ) = > {
2023-10-24 16:25:23 +08:00
const { ds : datasource } = getTestContext ( ) ;
2023-06-29 22:40:55 +08:00
ds = datasource ;
} ) ;
2022-12-22 23:06:30 +08:00
it ( 'should correctly add ad hoc filters when query is not empty' , ( ) = > {
2023-10-24 16:25:23 +08:00
const query = ds . addAdHocFilters ( 'foo:"bar" AND test:"test1"' , filters ) ;
2022-12-22 23:06:30 +08:00
expect ( query ) . toBe (
'foo:"bar" AND test:"test1" AND bar:"baz" AND -job:"grafana" AND service:/service/ AND count:>1'
) ;
} ) ;
it ( 'should correctly add ad hoc filters when query is empty' , ( ) = > {
2023-10-24 16:25:23 +08:00
const query = ds . addAdHocFilters ( '' , filters ) ;
2022-12-22 23:06:30 +08:00
expect ( query ) . toBe ( 'bar:"baz" AND -job:"grafana" AND service:/service/ AND count:>1' ) ;
} ) ;
} ) ;
} ) ;
2020-02-13 23:00:01 +08:00
const createElasticQuery = ( ) : DataQueryRequest < ElasticsearchQuery > = > {
return {
requestId : '' ,
interval : '' ,
panelId : 0 ,
2020-07-09 21:16:35 +08:00
intervalMs : 1 ,
2020-02-13 23:00:01 +08:00
scopedVars : { } ,
timezone : '' ,
app : CoreApp.Dashboard ,
startTime : 0 ,
range : {
from : dateTime ( [ 2015 , 4 , 30 , 10 ] ) ,
to : dateTime ( [ 2015 , 5 , 1 , 10 ] ) ,
2022-09-06 20:35:54 +08:00
raw : {
from : '' ,
to : '' ,
} ,
} ,
2020-02-13 23:00:01 +08:00
targets : [
{
refId : '' ,
bucketAggs : [ { type : 'date_histogram' , field : '@timestamp' , id : '2' } ] ,
metrics : [ { type : 'count' , id : '' } ] ,
query : 'test' ,
} ,
] ,
} ;
} ;
2019-12-12 00:40:56 +08:00
const logsResponse = {
data : {
responses : [
{
aggregations : {
'2' : {
buckets : [
{
doc_count : 10 ,
key : 1000 ,
} ,
{
doc_count : 15 ,
key : 2000 ,
} ,
] ,
} ,
} ,
hits : {
hits : [
{
'@timestamp' : [ '2019-06-24T09:51:19.765Z' ] ,
_id : 'fdsfs' ,
_type : '_doc' ,
_index : 'mock-index' ,
_source : {
'@timestamp' : '2019-06-24T09:51:19.765Z' ,
host : 'djisaodjsoad' ,
message : 'hello, i am a message' ,
} ,
fields : {
'@timestamp' : [ '2019-06-24T09:51:19.765Z' ] ,
} ,
} ,
{
'@timestamp' : [ '2019-06-24T09:52:19.765Z' ] ,
_id : 'kdospaidopa' ,
_type : '_doc' ,
_index : 'mock-index' ,
_source : {
'@timestamp' : '2019-06-24T09:52:19.765Z' ,
host : 'dsalkdakdop' ,
message : 'hello, i am also message' ,
} ,
fields : {
'@timestamp' : [ '2019-06-24T09:52:19.765Z' ] ,
} ,
} ,
] ,
} ,
} ,
] ,
} ,
} ;
2023-05-04 00:09:18 +08:00
2023-06-20 20:43:49 +08:00
describe ( 'targetContainsTemplate' , ( ) = > {
let ds : ElasticDatasource ;
let target : ElasticsearchQuery ;
beforeEach ( ( ) = > {
const context = getTestContext ( ) ;
ds = context . ds ;
target = {
refId : 'test' ,
bucketAggs : [ { type : 'date_histogram' , field : '@timestamp' , id : '1' } ] ,
metrics : [ { type : 'count' , id : '1' } ] ,
query : 'escape\\:test' ,
} ;
} ) ;
it ( 'returns false when there is no variable in the query' , ( ) = > {
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( false ) ;
} ) ;
it ( 'returns true when there are variables in the query alias' , ( ) = > {
target . alias = '$variable' ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
} ) ;
it ( 'returns true when there are variables in the query' , ( ) = > {
target . query = '$variable:something' ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
} ) ;
it ( 'returns true when there are variables in the bucket aggregation' , ( ) = > {
target . bucketAggs = [ { type : 'date_histogram' , field : '$field' , id : '1' } ] ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
target . bucketAggs = [ { type : 'date_histogram' , field : '@timestamp' , id : '1' , settings : { interval : '$interval' } } ] ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
} ) ;
it ( 'returns true when there are variables in the metric aggregation' , ( ) = > {
target . metrics = [ { type : 'moving_avg' , id : '1' , settings : { window : '$window' } } ] ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
target . metrics = [ { type : 'moving_avg' , id : '1' , field : '$field' } ] ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
target . metrics = [ { type : 'extended_stats' , id : '1' , meta : { something : '$something' } } ] ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
} ) ;
it ( 'returns true when there are variables in an array inside an object in metrics' , ( ) = > {
target . metrics = [
{
field : 'counter' ,
id : '1' ,
settings : { percents : [ '20' , '40' , '$qqq' ] } ,
type : 'percentiles' ,
} ,
] ;
expect ( ds . targetContainsTemplate ( target ) ) . toBe ( true ) ;
} ) ;
} ) ;
2023-05-04 00:09:18 +08:00
describe ( 'ElasticDatasource using backend' , ( ) = > {
beforeEach ( ( ) = > {
console . error = jest . fn ( ) ;
config . featureToggles . enableElasticsearchBackendQuerying = true ;
} ) ;
afterEach ( ( ) = > {
console . error = originalConsoleError ;
config . featureToggles . enableElasticsearchBackendQuerying = false ;
} ) ;
2023-05-10 15:30:57 +08:00
describe ( 'annotationQuery' , ( ) = > {
describe ( 'results processing' , ( ) = > {
it ( 'should return simple annotations using defaults' , async ( ) = > {
2023-10-24 16:25:23 +08:00
const { ds , timeRange } = getTestContext ( ) ;
2023-05-10 15:30:57 +08:00
ds . postResourceRequest = jest . fn ( ) . mockResolvedValue ( {
responses : [
{
hits : {
hits : [
{ _source : { '@timestamp' : 1 , '@test_tags' : 'foo' , text : 'abc' } } ,
{ _source : { '@timestamp' : 3 , '@test_tags' : 'bar' , text : 'def' } } ,
] ,
} ,
} ,
] ,
} ) ;
const annotations = await ds . annotationQuery ( {
annotation : { } ,
2024-02-08 18:47:18 +08:00
dashboard : {
getVariables : ( ) = > [ ] ,
} ,
2023-10-24 16:25:23 +08:00
range : timeRange ,
2023-05-10 15:30:57 +08:00
} ) ;
expect ( annotations ) . toHaveLength ( 2 ) ;
expect ( annotations [ 0 ] . time ) . toBe ( 1 ) ;
expect ( annotations [ 1 ] . time ) . toBe ( 3 ) ;
} ) ;
it ( 'should return annotation events using options' , async ( ) = > {
2023-10-24 16:25:23 +08:00
const { ds , timeRange } = getTestContext ( ) ;
2023-05-10 15:30:57 +08:00
ds . postResourceRequest = jest . fn ( ) . mockResolvedValue ( {
responses : [
{
hits : {
hits : [
{ _source : { '@test_time' : 1 , '@test_tags' : 'foo' , text : 'abc' } } ,
{ _source : { '@test_time' : 3 , '@test_tags' : 'bar' , text : 'def' } } ,
] ,
} ,
} ,
] ,
} ) ;
const annotations = await ds . annotationQuery ( {
annotation : {
timeField : '@test_time' ,
name : 'foo' ,
query : 'abc' ,
tagsField : '@test_tags' ,
textField : 'text' ,
} ,
2024-02-08 18:47:18 +08:00
dashboard : {
getVariables : ( ) = > [ ] ,
} ,
2023-10-24 16:25:23 +08:00
range : timeRange ,
2023-05-10 15:30:57 +08:00
} ) ;
expect ( annotations ) . toHaveLength ( 2 ) ;
expect ( annotations [ 0 ] . time ) . toBe ( 1 ) ;
expect ( annotations [ 0 ] . tags ? . [ 0 ] ) . toBe ( 'foo' ) ;
expect ( annotations [ 0 ] . text ) . toBe ( 'abc' ) ;
expect ( annotations [ 1 ] . time ) . toBe ( 3 ) ;
expect ( annotations [ 1 ] . tags ? . [ 0 ] ) . toBe ( 'bar' ) ;
expect ( annotations [ 1 ] . text ) . toBe ( 'def' ) ;
} ) ;
} ) ;
describe ( 'request processing' , ( ) = > {
it ( 'should process annotation request using options' , async ( ) = > {
const { ds } = getTestContext ( ) ;
const postResourceRequestMock = jest . spyOn ( ds , 'postResourceRequest' ) . mockResolvedValue ( {
responses : [
{
hits : {
hits : [
{ _source : { '@test_time' : 1 , '@test_tags' : 'foo' , text : 'abc' } } ,
{ _source : { '@test_time' : 3 , '@test_tags' : 'bar' , text : 'def' } } ,
] ,
} ,
} ,
] ,
} ) ;
await ds . annotationQuery ( {
annotation : {
timeField : '@test_time' ,
timeEndField : '@time_end_field' ,
name : 'foo' ,
query : 'abc' ,
tagsField : '@test_tags' ,
textField : 'text' ,
} ,
2024-02-08 18:47:18 +08:00
dashboard : {
getVariables : ( ) = > [ ] ,
} ,
2023-05-10 15:30:57 +08:00
range : {
from : dateTime ( 1683291160012 ) ,
to : dateTime ( 1683291460012 ) ,
} ,
} ) ;
expect ( postResourceRequestMock ) . toHaveBeenCalledWith (
'_msearch' ,
'{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc"}}]}},"size":10000}\n'
) ;
} ) ;
it ( 'should process annotation request using defaults' , async ( ) = > {
const { ds } = getTestContext ( ) ;
const postResourceRequestMock = jest . spyOn ( ds , 'postResourceRequest' ) . mockResolvedValue ( {
responses : [
{
hits : {
hits : [
{ _source : { '@test_time' : 1 , '@test_tags' : 'foo' , text : 'abc' } } ,
{ _source : { '@test_time' : 3 , '@test_tags' : 'bar' , text : 'def' } } ,
] ,
} ,
} ,
] ,
} ) ;
await ds . annotationQuery ( {
annotation : { } ,
2024-02-08 18:47:18 +08:00
dashboard : {
getVariables : ( ) = > [ ] ,
} ,
2023-05-10 15:30:57 +08:00
range : {
from : dateTime ( 1683291160012 ) ,
to : dateTime ( 1683291460012 ) ,
} ,
} ) ;
expect ( postResourceRequestMock ) . toHaveBeenCalledWith (
'_msearch' ,
'{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}}]}},"size":10000}\n'
) ;
} ) ;
2024-02-08 18:47:18 +08:00
it ( 'should process annotation request using dashboard adhoc variables' , async ( ) = > {
const { ds } = getTestContext ( ) ;
const postResourceRequestMock = jest . spyOn ( ds , 'postResourceRequest' ) . mockResolvedValue ( {
responses : [
{
hits : {
hits : [
{ _source : { '@test_time' : 1 , '@test_tags' : 'foo' , text : 'abc' } } ,
{ _source : { '@test_time' : 3 , '@test_tags' : 'bar' , text : 'def' } } ,
] ,
} ,
} ,
] ,
} ) ;
await ds . annotationQuery ( {
annotation : {
timeField : '@test_time' ,
timeEndField : '@time_end_field' ,
name : 'foo' ,
query : 'abc' ,
tagsField : '@test_tags' ,
textField : 'text' ,
datasource : {
type : 'elasticsearch' ,
uid : 'gdev-elasticsearch' ,
} ,
} ,
dashboard : {
getVariables : ( ) = > [
{
type : 'adhoc' ,
datasource : {
type : 'elasticsearch' ,
uid : 'gdev-elasticsearch' ,
} ,
filters : [
{
key : 'abc_key' ,
operator : '=' ,
value : 'abc_value' ,
} ,
] ,
} ,
] ,
} ,
range : {
from : dateTime ( 1683291160012 ) ,
to : dateTime ( 1683291460012 ) ,
} ,
} ) ;
expect ( postResourceRequestMock ) . toHaveBeenCalledWith (
'_msearch' ,
'{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc AND abc_key:\\"abc_value\\""}}]}},"size":10000}\n'
) ;
} ) ;
2023-05-10 15:30:57 +08:00
} ) ;
} ) ;
2023-05-04 00:09:18 +08:00
describe ( 'getDatabaseVersion' , ( ) = > {
it ( 'should correctly get db version' , async ( ) = > {
const { ds } = getTestContext ( ) ;
ds . getResource = jest . fn ( ) . mockResolvedValue ( { version : { number : '8.0.0' } } ) ;
const version = await ds . getDatabaseVersion ( ) ;
expect ( version ? . raw ) . toBe ( '8.0.0' ) ;
} ) ;
it ( 'should correctly return null if invalid numeric version' , async ( ) = > {
const { ds } = getTestContext ( ) ;
ds . getResource = jest . fn ( ) . mockResolvedValue ( { version : { number : 8 } } ) ;
const version = await ds . getDatabaseVersion ( ) ;
expect ( version ) . toBe ( null ) ;
} ) ;
it ( 'should correctly return null if rejected request' , async ( ) = > {
const { ds } = getTestContext ( ) ;
ds . getResource = jest . fn ( ) . mockRejectedValue ( { } ) ;
const version = await ds . getDatabaseVersion ( ) ;
expect ( version ) . toBe ( null ) ;
} ) ;
} ) ;
2023-05-05 17:35:30 +08:00
2023-05-06 16:00:43 +08:00
describe ( 'metricFindQuery' , ( ) = > {
async function runScenario() {
const data = {
responses : [
{
aggregations : {
'1' : {
buckets : [
{ doc_count : 1 , key : 'test' } ,
{
doc_count : 2 ,
key : 'test2' ,
key_as_string : 'test2_as_string' ,
} ,
] ,
} ,
} ,
} ,
] ,
} ;
const { ds } = getTestContext ( ) ;
const postResourceMock = jest . spyOn ( ds , 'postResource' ) ;
postResourceMock . mockResolvedValue ( data ) ;
const results = await ds . metricFindQuery ( '{"find": "terms", "field": "test"}' ) ;
expect ( ds . postResource ) . toHaveBeenCalledTimes ( 1 ) ;
const requestOptions = postResourceMock . mock . calls [ 0 ] [ 1 ] ;
const parts = requestOptions . split ( '\n' ) ;
const header = JSON . parse ( parts [ 0 ] ) ;
const body = JSON . parse ( parts [ 1 ] ) ;
return { results , body , header } ;
}
it ( 'should get results' , async ( ) = > {
const { results } = await runScenario ( ) ;
expect ( results . length ) . toEqual ( 2 ) ;
} ) ;
it ( 'should use key or key_as_string' , async ( ) = > {
const { results } = await runScenario ( ) ;
expect ( results [ 0 ] . text ) . toEqual ( 'test' ) ;
expect ( results [ 1 ] . text ) . toEqual ( 'test2_as_string' ) ;
} ) ;
it ( 'should not set search type to count' , async ( ) = > {
const { header } = await runScenario ( ) ;
expect ( header . search_type ) . not . toEqual ( 'count' ) ;
} ) ;
it ( 'should set size to 0' , async ( ) = > {
const { body } = await runScenario ( ) ;
expect ( body . size ) . toBe ( 0 ) ;
} ) ;
it ( 'should not set terms aggregation size to 0' , async ( ) = > {
const { body } = await runScenario ( ) ;
expect ( body [ 'aggs' ] [ '1' ] [ 'terms' ] . size ) . not . toBe ( 0 ) ;
} ) ;
} ) ;
2023-05-05 17:35:30 +08:00
describe ( 'getFields' , ( ) = > {
const getFieldsMockData = {
'[test-]YYYY.MM.DD' : {
mappings : {
properties : {
'@timestamp_millis' : {
type : 'date' ,
format : 'epoch_millis' ,
} ,
classification_terms : {
type : 'keyword' ,
} ,
ip_address : {
type : 'ip' ,
} ,
justification_blob : {
properties : {
criterion : {
type : 'text' ,
fields : {
keyword : {
type : 'keyword' ,
ignore_above : 256 ,
} ,
} ,
} ,
shallow : {
properties : {
jsi : {
properties : {
sdb : {
properties : {
dsel2 : {
properties : {
'bootlegged-gille' : {
properties : {
botness : {
type : 'float' ,
} ,
general_algorithm_score : {
type : 'float' ,
} ,
} ,
} ,
'uncombed-boris' : {
properties : {
botness : {
type : 'float' ,
} ,
general_algorithm_score : {
type : 'float' ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
overall_vote_score : {
type : 'float' ,
} ,
} ,
} ,
} ,
} ;
it ( 'should not retry when ES is down' , async ( ) = > {
const twoDaysBefore = toUtc ( ) . subtract ( 2 , 'day' ) . format ( 'YYYY.MM.DD' ) ;
2023-10-24 16:25:23 +08:00
const { ds , timeRange } = getTestContext ( {
2023-05-05 17:35:30 +08:00
jsonData : { interval : 'Daily' } ,
} ) ;
ds . getResource = jest . fn ( ) . mockImplementation ( ( options ) = > {
if ( options . url === ` test- ${ twoDaysBefore } /_mapping ` ) {
return of ( {
data : { } ,
} ) ;
}
return throwError ( { status : 500 } ) ;
} ) ;
2023-10-24 16:25:23 +08:00
await expect ( ds . getFields ( undefined , timeRange ) ) . toEmitValuesWith ( ( received ) = > {
2023-05-05 17:35:30 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toStrictEqual ( { status : 500 } ) ;
expect ( ds . getResource ) . toBeCalledTimes ( 1 ) ;
} ) ;
} ) ;
it ( 'should not retry more than 7 indices' , async ( ) = > {
2023-10-24 16:25:23 +08:00
const { ds } = getTestContext ( {
2023-05-05 17:35:30 +08:00
jsonData : { interval : 'Daily' } ,
} ) ;
ds . getResource = jest . fn ( ) . mockImplementation ( ( ) = > {
return throwError ( { status : 404 } ) ;
} ) ;
2023-10-24 16:25:23 +08:00
const timeRange = createTimeRange ( dateTime ( ) . subtract ( 2 , 'week' ) , dateTime ( ) ) ;
await expect ( ds . getFields ( undefined , timeRange ) ) . toEmitValuesWith ( ( received ) = > {
2023-05-05 17:35:30 +08:00
expect ( received . length ) . toBe ( 1 ) ;
expect ( received [ 0 ] ) . toStrictEqual ( 'Could not find an available index for this time range.' ) ;
expect ( ds . getResource ) . toBeCalledTimes ( 7 ) ;
} ) ;
} ) ;
it ( 'should return nested fields' , async ( ) = > {
const { ds } = getTestContext ( {
jsonData : { interval : 'Daily' } ,
} ) ;
ds . getResource = jest . fn ( ) . mockResolvedValue ( getFieldsMockData ) ;
await expect ( ds . getFields ( ) ) . toEmitValuesWith ( ( received ) = > {
expect ( received . length ) . toBe ( 1 ) ;
const fieldObjects = received [ 0 ] ;
const fields = map ( fieldObjects , 'text' ) ;
expect ( fields ) . toEqual ( [
'@timestamp_millis' ,
'classification_terms' ,
'ip_address' ,
'justification_blob.criterion.keyword' ,
'justification_blob.criterion' ,
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score' ,
'overall_vote_score' ,
] ) ;
} ) ;
} ) ;
it ( 'should return number fields' , async ( ) = > {
const { ds } = getTestContext ( { } ) ;
ds . getResource = jest . fn ( ) . mockResolvedValue ( getFieldsMockData ) ;
await expect ( ds . getFields ( [ 'number' ] ) ) . toEmitValuesWith ( ( received ) = > {
expect ( received . length ) . toBe ( 1 ) ;
const fieldObjects = received [ 0 ] ;
const fields = map ( fieldObjects , 'text' ) ;
expect ( fields ) . toEqual ( [
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness' ,
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score' ,
'overall_vote_score' ,
] ) ;
} ) ;
} ) ;
it ( 'should return date fields' , async ( ) = > {
const { ds } = getTestContext ( { } ) ;
ds . getResource = jest . fn ( ) . mockResolvedValue ( getFieldsMockData ) ;
await expect ( ds . getFields ( [ 'date' ] ) ) . toEmitValuesWith ( ( received ) = > {
expect ( received . length ) . toBe ( 1 ) ;
const fieldObjects = received [ 0 ] ;
const fields = map ( fieldObjects , 'text' ) ;
expect ( fields ) . toEqual ( [ '@timestamp_millis' ] ) ;
} ) ;
} ) ;
} ) ;
2023-05-04 00:09:18 +08:00
} ) ;