@ -1,37 +1,30 @@
import { lastValueFrom , of , throwError } from 'rxjs' ;
import { of } from 'rxjs' ;
import { take } from 'rxjs/operators' ;
import { createFetchResponse } from 'test/helpers/createFetchResponse' ;
import { getQueryOptions } from 'test/helpers/getQueryOptions' ;
import {
AbstractLabelOperator ,
AnnotationQueryRequest ,
CoreApp ,
ArrayVector ,
DataFrame ,
dataFrameToJSON ,
DataQueryResponse ,
dateTime ,
FieldCache ,
FieldType ,
LogRowModel ,
MutableDataFrame ,
toUtc ,
} from '@grafana/data' ;
import { BackendSrvRequest , FetchResponse } from '@grafana/runtime' ;
import { backendSrv } from 'app/core/services/backend_srv' ;
import { BackendSrvRequest , FetchResponse , setBackendSrv , getBackendSrv , BackendSrv } from '@grafana/runtime' ;
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv' ;
import { TemplateSrv } from 'app/features/templating/template_srv' ;
import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer' ;
import { CustomVariableModel } from '../../../features/variables/types' ;
import { isMetricsQuery , LokiDatasource , RangeQueryOptions } from './datasource' ;
import { isMetricsQuery , LokiDatasource } from './datasource' ;
import { makeMockLokiDatasource } from './mocks' ;
import { LokiQuery , LokiResponse , LokiResultType } from './types' ;
jest . mock ( '@grafana/runtime' , ( ) = > ( {
// @ts-ignore
. . . jest . requireActual ( '@grafana/runtime' ) ,
getBackendSrv : ( ) = > backendSrv ,
} ) ) ;
import { LokiQuery } from './types' ;
const rawRange = {
from : toUtc ( '2018-04-25 10:00' ) ,
@ -51,48 +44,67 @@ const templateSrvStub = {
replace : jest.fn ( ( a : string , . . . rest : any ) = > a ) ,
} ;
const testLogsResponse : FetchResponse < LokiResponse > = {
data : {
data : {
resultType : LokiResultType.Stream ,
result : [
const testFrame : DataFrame = {
refId : 'A' ,
fields : [
{
name : 'Time' ,
type : FieldType . time ,
config : { } ,
values : new ArrayVector ( [ 1 , 2 ] ) ,
} ,
{
name : 'Line' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ 'line1' , 'line2' ] ) ,
} ,
{
name : 'labels' ,
type : FieldType . other ,
config : { } ,
values : new ArrayVector ( [
{
label : 'value' ,
label2 : 'value ' ,
} ,
{
stream : { } ,
values : [ [ '1573646419522934000' , 'hello' ] ] ,
label : '' ,
label2 : 'value2' ,
label3 : ' ' ,
} ,
] ,
] ) ,
} ,
status : 'success' ,
} ,
ok : true ,
headers : { } as unknown as Headers ,
redirected : false ,
status : 200 ,
statusText : 'Success' ,
type : 'default' ,
url : '' ,
config : { } as unknown as BackendSrvRequest ,
{
name : 'tsNs' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ '1000000' , '2000000' ] ) ,
} ,
{
name : 'id' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ 'id1' , 'id2' ] ) ,
} ,
] ,
length : 2 ,
} ;
const testMetricsResponse : FetchResponse < LokiResponse > = {
const testLogsResponse : FetchResponse = {
data : {
data : {
resultType : LokiResultType.Matrix ,
result : [
{
metric : { } ,
values : [ [ 1605715380 , '1.1' ] ] ,
} ,
] ,
results : {
A : {
frames : [ dataFrameToJSON ( testFrame ) ] ,
} ,
} ,
status : 'success' ,
} ,
ok : true ,
headers : { } as unknown as Headers ,
redirected : false ,
status : 200 ,
statusText : 'OK ' ,
type : 'basic ' ,
statusText : 'Success ' ,
type : 'default ' ,
url : '' ,
config : { } as unknown as BackendSrvRequest ,
} ;
@ -105,113 +117,22 @@ interface AdHocFilter {
}
describe ( 'LokiDatasource' , ( ) = > {
const fetchMock = jest . spyOn ( backendSrv , 'fetch' ) ;
let origBackendSrv : BackendSrv ;
beforeEach ( ( ) = > {
jest . clearAllMocks ( ) ;
fetchMock . mockImplementation ( ( ) = > of ( createFetchResponse ( { } ) ) ) ;
origBackendSrv = getBackendSrv ( ) ;
} ) ;
describe ( 'when creating range query' , ( ) = > {
let ds : LokiDatasource ;
let adjustIntervalSpy : jest.SpyInstance ;
beforeEach ( ( ) = > {
ds = createLokiDSForTests ( ) ;
adjustIntervalSpy = jest . spyOn ( ds , 'adjustInterval' ) ;
} ) ;
it ( 'should use default intervalMs if one is not provided' , ( ) = > {
const target = { expr : '{job="grafana"}' , refId : 'B' } ;
const raw = { from : 'now' , to : 'now-1h' } ;
const range = { from : dateTime ( ) , to : dateTime ( ) , raw : raw } ;
const options = {
range ,
} ;
const req = ds . createRangeQuery ( target , options as any , 1000 ) ;
expect ( req . start ) . toBeDefined ( ) ;
expect ( req . end ) . toBeDefined ( ) ;
expect ( adjustIntervalSpy ) . toHaveBeenCalledWith ( 1000 , 1 , expect . anything ( ) ) ;
} ) ;
it ( 'should use provided intervalMs' , ( ) = > {
const target = { expr : '{job="grafana"}' , refId : 'B' } ;
const raw = { from : 'now' , to : 'now-1h' } ;
const range = { from : dateTime ( ) , to : dateTime ( ) , raw : raw } ;
const options = {
range ,
intervalMs : 2000 ,
} ;
const req = ds . createRangeQuery ( target , options as any , 1000 ) ;
expect ( req . start ) . toBeDefined ( ) ;
expect ( req . end ) . toBeDefined ( ) ;
expect ( adjustIntervalSpy ) . toHaveBeenCalledWith ( 2000 , 1 , expect . anything ( ) ) ;
} ) ;
it ( 'should set the minimal step to 1ms' , ( ) = > {
const target = { expr : '{job="grafana"}' , refId : 'B' } ;
const raw = { from : 'now' , to : 'now-1h' } ;
const range = { from : dateTime ( '2020-10-14T00:00:00' ) , to : dateTime ( '2020-10-14T00:00:01' ) , raw : raw } ;
const options = {
range ,
intervalMs : 0.0005 ,
} ;
const req = ds . createRangeQuery ( target , options as any , 1000 ) ;
expect ( req . start ) . toBeDefined ( ) ;
expect ( req . end ) . toBeDefined ( ) ;
expect ( adjustIntervalSpy ) . toHaveBeenCalledWith ( 0.0005 , expect . anything ( ) , 1000 ) ;
// Step is in seconds (1 ms === 0.001 s)
expect ( req . step ) . toEqual ( 0.001 ) ;
} ) ;
describe ( 'log volume hint' , ( ) = > {
let options : RangeQueryOptions ;
beforeEach ( ( ) = > {
const raw = { from : 'now' , to : 'now-1h' } ;
const range = { from : dateTime ( ) , to : dateTime ( ) , raw : raw } ;
options = {
range ,
} as unknown as RangeQueryOptions ;
} ) ;
it ( 'should add volume hint param for log volume queries' , ( ) = > {
const target = { expr : '{job="grafana"}' , refId : 'B' , volumeQuery : true } ;
ds . runRangeQuery ( target , options ) ;
expect ( backendSrv . fetch ) . toBeCalledWith (
expect . objectContaining ( {
headers : {
'X-Query-Tags' : 'Source=logvolhist' ,
} ,
} )
) ;
} ) ;
it ( 'should not add volume hint param for regular queries' , ( ) = > {
const target = { expr : '{job="grafana"}' , refId : 'B' , volumeQuery : false } ;
ds . runRangeQuery ( target , options ) ;
expect ( backendSrv . fetch ) . not . toBeCalledWith (
expect . objectContaining ( {
headers : {
'X-Query-Tags' : 'Source=logvolhist' ,
} ,
} )
) ;
} ) ;
} ) ;
afterEach ( ( ) = > {
setBackendSrv ( origBackendSrv ) ;
} ) ;
describe ( 'when doing logs queries with limits' , ( ) = > {
const runLimitTest = async ( {
maxDataPoints = 123 ,
queryMaxLines ,
dsMaxLines = 456 ,
expectedLimit ,
expr = '{label="val"}' ,
} : any ) = > {
const runTest = async (
queryMaxLines : number | undefined ,
dsMaxLines : number | undefined ,
expectedMaxLines : number
) = > {
let settings : any = {
url : 'myloggingurl' ,
jsonData : {
@ -226,264 +147,85 @@ describe('LokiDatasource', () => {
const ds = new LokiDatasource ( settings , templateSrvMock , timeSrvStub as any ) ;
const options = getQueryOptions < LokiQuery > ( { targets : [ { expr , refId : 'B' , maxLines : queryMaxLines } ] } ) ;
options . maxDataPoints = maxDataPoints ;
// we need to check the final query before it is sent out,
// and applyTemplateVariables is a convenient place to do that.
const spy = jest . spyOn ( ds , 'applyTemplateVariables' ) ;
fetchMock . mockImplementation ( ( ) = > of ( testLogsResponse ) ) ;
const options = getQueryOptions < LokiQuery > ( {
targets : [ { expr : '{a="b"}' , refId : 'B' , maxLines : queryMaxLines } ] ,
} ) ;
const fetchMock = jest . fn ( ) . mockReturnValue ( of ( { data : testLogsResponse } ) ) ;
setBackendSrv ( { . . . origBackendSrv , fetch : fetchMock } ) ;
await expect ( ds . query ( options ) . pipe ( take ( 1 ) ) ) . toEmitValuesWith ( ( ) = > {
expect ( fetchMock . mock . calls . length ) . toBe ( 1 ) ;
expect ( fetchMock . mock . calls [ 0 ] [ 0 ] . url ) . toContain ( ` limit= ${ expectedLimit } ` ) ;
expect ( spy . mock . calls [ 0 ] [ 0 ] . maxLines ) . toBe ( expectedMaxLines ) ;
} ) ;
} ;
it ( 'should use datasource max lines when no limit given and it is log query' , async ( ) = > {
await runLimitTest ( { expectedLimit : 456 } ) ;
} ) ;
it ( 'should use custom max lines from query if set and it is logs query' , async ( ) = > {
await runLimitTest ( { queryMaxLines : 20 , expectedLimit : 20 } ) ;
} ) ;
it ( 'should use custom max lines from query if set and it is logs query even if it is higher than data source limit' , async ( ) = > {
await runLimitTest ( { queryMaxLines : 500 , expectedLimit : 500 } ) ;
it ( 'should use datasource max lines when no query max lines' , async ( ) = > {
await runTest ( undefined , 40 , 40 ) ;
} ) ;
it ( 'should use maxDataPoints if it is metrics query ' , async ( ) = > {
await runLimitTest ( { expr : 'rate({label="val"}[10m])' , expectedLimit : 123 } ) ;
it ( 'should use query max lines, if exists' , async ( ) = > {
await runTest ( 80 , undefined , 80 ) ;
} ) ;
it ( 'should use maxDataPoints if it is metrics query and using search ' , async ( ) = > {
await runLimitTest ( { expr : 'rate({label="val"}[10m])' , expectedLimit : 123 } ) ;
it ( 'should use query max lines, if both exist, even if it is higher than ds max lines' , async ( ) = > {
await runTest ( 80 , 40 , 80 ) ;
} ) ;
} ) ;
describe ( 'when querying' , ( ) = > {
function setup ( expr : string , app : CoreApp , instant? : boolean , range? : boolean ) {
const ds = createLokiDSForTests ( ) ;
const options = getQueryOptions < LokiQuery > ( {
targets : [ { expr , refId : 'B' , instant , range } ] ,
app ,
} ) ;
ds . runInstantQuery = jest . fn ( ( ) = > of ( { data : [ ] } ) ) ;
ds . runRangeQuery = jest . fn ( ( ) = > of ( { data : [ ] } ) ) ;
return { ds , options } ;
}
const metricsQuery = 'rate({job="grafana"}[10m])' ;
const logsQuery = '{job="grafana"} |= "foo"' ;
it ( 'should run logs instant if only instant is selected' , async ( ) = > {
const { ds , options } = setup ( logsQuery , CoreApp . Explore , true , false ) ;
await lastValueFrom ( ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . not . toBeCalled ( ) ;
} ) ;
it ( 'should run metrics instant if only instant is selected' , async ( ) = > {
const { ds , options } = setup ( metricsQuery , CoreApp . Explore , true , false ) ;
lastValueFrom ( await ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . not . toBeCalled ( ) ;
} ) ;
it ( 'should run only logs range query if only range is selected' , async ( ) = > {
const { ds , options } = setup ( logsQuery , CoreApp . Explore , false , true ) ;
lastValueFrom ( await ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . not . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . toBeCalled ( ) ;
} ) ;
it ( 'should run only metrics range query if only range is selected' , async ( ) = > {
const { ds , options } = setup ( metricsQuery , CoreApp . Explore , false , true ) ;
lastValueFrom ( await ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . not . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . toBeCalled ( ) ;
} ) ;
describe ( 'When using adhoc filters' , ( ) = > {
const DEFAULT_EXPR = 'rate({bar="baz", job="foo"} |= "bar" [5m])' ;
const query : LokiQuery = { expr : DEFAULT_EXPR , refId : 'A' } ;
const originalAdhocFiltersMock = templateSrvStub . getAdhocFilters ( ) ;
const ds = new LokiDatasource ( { } as any , templateSrvStub as any , timeSrvStub as any ) ;
it ( 'should run only logs range query if no query type is selected in Explore' , async ( ) = > {
const { ds , options } = setup ( logsQuery , CoreApp . Explore ) ;
lastValueFrom ( await ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . not . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . toBeCalled ( ) ;
afterAll ( ( ) = > {
templateSrvStub . getAdhocFilters . mockReturnValue ( originalAdhocFiltersMock ) ;
} ) ;
it ( 'should run only metrics range query if no query type is selected in Explore' , async ( ) = > {
const { ds , options } = setup ( metricsQuery , CoreApp . Explore ) ;
lastValueFrom ( await ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . not . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . toBeCalled ( ) ;
} ) ;
it ( 'should run only logs range query in Dashboard' , async ( ) = > {
const { ds , options } = setup ( logsQuery , CoreApp . Dashboard ) ;
lastValueFrom ( await ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . not . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . toBeCalled ( ) ;
} ) ;
it ( 'should run only metrics range query in Dashboard' , async ( ) = > {
const { ds , options } = setup ( metricsQuery , CoreApp . Dashboard ) ;
lastValueFrom ( await ds . query ( options ) ) ;
expect ( ds . runInstantQuery ) . not . toBeCalled ( ) ;
expect ( ds . runRangeQuery ) . toBeCalled ( ) ;
} ) ;
it ( 'should return dataframe data for metrics range queries' , async ( ) = > {
const ds = createLokiDSForTests ( ) ;
const options = getQueryOptions < LokiQuery > ( {
targets : [ { expr : metricsQuery , refId : 'B' , range : true } ] ,
app : CoreApp.Explore ,
} ) ;
fetchMock . mockImplementation ( ( ) = > of ( testMetricsResponse ) ) ;
await expect ( ds . query ( options ) ) . toEmitValuesWith ( ( received ) = > {
const result = received [ 0 ] ;
const frame = result . data [ 0 ] as DataFrame ;
expect ( frame . meta ? . preferredVisualisationType ) . toBe ( 'graph' ) ;
expect ( frame . refId ) . toBe ( 'B' ) ;
frame . fields . forEach ( ( field ) = > {
const value = field . values . get ( 0 ) ;
if ( field . type === FieldType . time ) {
expect ( value ) . toBe ( 1605715380000 ) ;
} else {
expect ( value ) . toBe ( 1.1 ) ;
}
} ) ;
} ) ;
} ) ;
it ( 'should return series data for logs range query' , async ( ) = > {
const ds = createLokiDSForTests ( ) ;
const options = getQueryOptions < LokiQuery > ( {
targets : [ { expr : logsQuery , refId : 'B' } ] ,
} ) ;
fetchMock . mockImplementation ( ( ) = > of ( testLogsResponse ) ) ;
await expect ( ds . query ( options ) ) . toEmitValuesWith ( ( received ) = > {
const result = received [ 0 ] ;
const dataFrame = result . data [ 0 ] as DataFrame ;
const fieldCache = new FieldCache ( dataFrame ) ;
expect ( fieldCache . getFieldByName ( 'Line' ) ? . values . get ( 0 ) ) . toBe ( 'hello' ) ;
expect ( dataFrame . meta ? . limit ) . toBe ( 20 ) ;
expect ( dataFrame . meta ? . searchWords ) . toEqual ( [ 'foo' ] ) ;
} ) ;
it ( 'should not modify expression with no filters' , async ( ) = > {
expect ( ds . applyTemplateVariables ( query , { } ) . expr ) . toBe ( DEFAULT_EXPR ) ;
} ) ;
it ( 'should return custom error message when Loki returns escaping error' , async ( ) = > {
const ds = createLokiDSForTests ( ) ;
const options = getQueryOptions < LokiQuery > ( {
targets : [ { expr : '{job="gra\\fana"}' , refId : 'B' } ] ,
} ) ;
it ( 'should add filters to expression' , async ( ) = > {
templateSrvStub . getAdhocFilters . mockReturnValue ( [
{
key : 'k1' ,
operator : '=' ,
value : 'v1' ,
} ,
{
key : 'k2' ,
operator : '!=' ,
value : 'v2' ,
} ,
] ) ;
fetchMock . mockImplementation ( ( ) = >
throwError ( {
data : {
message : 'parse error at line 1, col 6: invalid char escape' ,
} ,
status : 400 ,
statusText : 'Bad Request' ,
} )
expect ( ds . applyTemplateVariables ( query , { } ) . expr ) . toBe (
'rate({bar="baz",job="foo",k1="v1",k2!="v2"} |= "bar" [5m])'
) ;
await expect ( ds . query ( options ) ) . toEmitValuesWith ( ( received ) = > {
const err : any = received [ 0 ] ;
expect ( err . data . message ) . toBe (
'Error: parse error at line 1, col 6: invalid char escape. Make sure that all special characters are escaped with \\. For more information on escaping of special characters visit LogQL documentation at https://grafana.com/docs/loki/latest/logql/.'
) ;
} ) ;
} ) ;
describe ( 'When using adhoc filters' , ( ) = > {
const DEFAULT_EXPR = 'rate({bar="baz", job="foo"} |= "bar" [5m])' ;
const options = {
targets : [ { expr : DEFAULT_EXPR } ] ,
} ;
const originalAdhocFiltersMock = templateSrvStub . getAdhocFilters ( ) ;
const ds = new LokiDatasource ( { } as any , templateSrvStub as any , timeSrvStub as any ) ;
ds . runRangeQuery = jest . fn ( ( ) = > of ( { data : [ ] } ) ) ;
afterAll ( ( ) = > {
templateSrvStub . getAdhocFilters . mockReturnValue ( originalAdhocFiltersMock ) ;
} ) ;
it ( 'should not modify expression with no filters' , async ( ) = > {
await lastValueFrom ( ds . query ( options as any ) ) ;
expect ( ds . runRangeQuery ) . toBeCalledWith ( { expr : DEFAULT_EXPR } , expect . anything ( ) ) ;
} ) ;
it ( 'should add filters to expression' , async ( ) = > {
templateSrvStub . getAdhocFilters . mockReturnValue ( [
{
key : 'k1' ,
operator : '=' ,
value : 'v1' ,
} ,
{
key : 'k2' ,
operator : '!=' ,
value : 'v2' ,
} ,
] ) ;
await lastValueFrom ( ds . query ( options as any ) ) ;
expect ( ds . runRangeQuery ) . toBeCalledWith (
{ expr : 'rate({bar="baz",job="foo",k1="v1",k2!="v2"} |= "bar" [5m])' } ,
expect . anything ( )
) ;
} ) ;
it ( 'should add escaping if needed to regex filter expressions' , async ( ) = > {
templateSrvStub . getAdhocFilters . mockReturnValue ( [
{
key : 'k1' ,
operator : '=~' ,
value : 'v.*' ,
} ,
{
key : 'k2' ,
operator : '=~' ,
value : ` v'.* ` ,
} ,
] ) ;
await lastValueFrom ( ds . query ( options as any ) ) ;
expect ( ds . runRangeQuery ) . toBeCalledWith (
{ expr : 'rate({bar="baz",job="foo",k1=~"v\\\\.\\\\*",k2=~"v\'\\\\.\\\\*"} |= "bar" [5m])' } ,
expect . anything ( )
) ;
} ) ;
} ) ;
describe ( '__range, __range_s and __range_ms variables' , ( ) = > {
const options = {
targets : [ { expr : 'rate(process_cpu_seconds_total[$__range])' , refId : 'A' , stepInterval : '2s' } ] ,
range : {
from : rawRange . from ,
to : rawRange.to ,
raw : rawRange ,
it ( 'should add escaping if needed to regex filter expressions' , async ( ) = > {
templateSrvStub . getAdhocFilters . mockReturnValue ( [
{
key : 'k1' ,
operator : '=~' ,
value : 'v.*' ,
} ,
} ;
const ds = new LokiDatasource ( { } as any , templateSrvStub as any , timeSrvStub as any ) ;
beforeEach ( ( ) = > {
templateSrvStub . replace . mockClear ( ) ;
} ) ;
it ( 'should be correctly interpolated' , ( ) = > {
ds . query ( options as any ) ;
const range = templateSrvStub . replace . mock . calls [ 0 ] [ 1 ] . __range ;
const rangeMs = templateSrvStub . replace . mock . calls [ 0 ] [ 1 ] . __range_ms ;
const rangeS = templateSrvStub . replace . mock . calls [ 0 ] [ 1 ] . __range_s ;
expect ( range ) . toEqual ( { text : '3600s' , value : '3600s' } ) ;
expect ( rangeMs ) . toEqual ( { text : 3600000 , value : 3600000 } ) ;
expect ( rangeS ) . toEqual ( { text : 3600 , value : 3600 } ) ;
} ) ;
{
key : 'k2' ,
operator : '=~' ,
value : ` v'.* ` ,
} ,
] ) ;
expect ( ds . applyTemplateVariables ( query , { } ) . expr ) . toBe (
'rate({bar="baz",job="foo",k1=~"v\\\\.\\\\*",k2=~"v\'\\\\.\\\\*"} |= "bar" [5m])'
) ;
} ) ;
} ) ;
@ -589,45 +331,65 @@ describe('LokiDatasource', () => {
} ) ;
describe ( 'when calling annotationQuery' , ( ) = > {
const getTestContext = ( response : any , options : any = [ ] ) = > {
const getTestContext = ( frame : DataFrame , options : any = [ ] ) = > {
const query = makeAnnotationQueryRequest ( options ) ;
fetchMock . mockImplementation ( ( ) = > of ( response ) ) ;
const ds = createLokiDSForTests ( ) ;
const promise = ds . annotationQuery ( query ) ;
return { promise } ;
const response : DataQueryResponse = {
data : [ frame ] ,
} ;
ds . query = ( ) = > of ( response ) ;
return ds . annotationQuery ( query ) ;
} ;
it ( 'should transform the loki data to annotation response' , async ( ) = > {
const response : FetchResponse = {
data : {
data : {
resultType : LokiResultType.Stream ,
result : [
const testFrame : DataFrame = {
refId : 'A' ,
fields : [
{
name : 'Time' ,
type : FieldType . time ,
config : { } ,
values : new ArrayVector ( [ 1 , 2 ] ) ,
} ,
{
name : 'Line' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ 'hello' , 'hello 2' ] ) ,
} ,
{
name : 'labels' ,
type : FieldType . other ,
config : { } ,
values : new ArrayVector ( [
{
stream : {
label : 'value' ,
label2 : 'value ' ,
} ,
values : [ [ '1549016857498000000' , 'hello' ] ] ,
label : 'value' ,
label2 : 'value ' ,
} ,
{
stream : {
label : '' , // empty value gets filtered
label2 : 'value2' ,
label3 : ' ' , // whitespace value gets trimmed then filtered
} ,
values : [ [ '1549024057498000000' , 'hello 2' ] ] ,
label : '' ,
label2 : 'value2' ,
label3 : ' ' ,
} ,
] ,
] ) ,
} ,
status : 'success' ,
} ,
} as unknown as FetchResponse ;
const { promise } = getTestContext ( response , { stepInterval : '15s' } ) ;
const res = await promise ;
{
name : 'tsNs' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ '1000000' , '2000000' ] ) ,
} ,
{
name : 'id' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ 'id1' , 'id2' ] ) ,
} ,
] ,
length : 2 ,
} ;
const res = await getTestContext ( testFrame , { stepInterval : '15s' } ) ;
expect ( res . length ) . toBe ( 2 ) ;
expect ( res [ 0 ] . text ) . toBe ( 'hello' ) ;
@ -636,30 +398,53 @@ describe('LokiDatasource', () => {
expect ( res [ 1 ] . text ) . toBe ( 'hello 2' ) ;
expect ( res [ 1 ] . tags ) . toEqual ( [ 'value2' ] ) ;
} ) ;
describe ( 'Formatting' , ( ) = > {
const response : FetchResponse = {
data : {
data : {
resultType : LokiResultType.Stream ,
result : [
const testFrame : DataFrame = {
refId : 'A' ,
fields : [
{
name : 'Time' ,
type : FieldType . time ,
config : { } ,
values : new ArrayVector ( [ 1 ] ) ,
} ,
{
name : 'Line' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ 'hello' ] ) ,
} ,
{
name : 'labels' ,
type : FieldType . other ,
config : { } ,
values : new ArrayVector ( [
{
stream : {
label : 'value' ,
label2 : 'value2' ,
label3 : 'value3' ,
} ,
values : [ [ '1549016857498000000' , 'hello' ] ] ,
label : 'value' ,
label2 : 'value2' ,
label3 : 'value3' ,
} ,
] ,
] ) ,
} ,
status : 'success' ,
} ,
} as unknown as FetchResponse ;
{
name : 'tsNs' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ '1000000' ] ) ,
} ,
{
name : 'id' ,
type : FieldType . string ,
config : { } ,
values : new ArrayVector ( [ 'id1' ] ) ,
} ,
] ,
length : 1 ,
} ;
describe ( 'When tagKeys is set' , ( ) = > {
it ( 'should only include selected labels' , async ( ) = > {
const { promise } = getTestContext ( response , { tagKeys : 'label2,label3' , stepInterval : '15s' } ) ;
const res = await promise ;
const res = await getTestContext ( testFrame , { tagKeys : 'label2,label3' , stepInterval : '15s' } ) ;
expect ( res . length ) . toBe ( 1 ) ;
expect ( res [ 0 ] . text ) . toBe ( 'hello' ) ;
@ -668,9 +453,7 @@ describe('LokiDatasource', () => {
} ) ;
describe ( 'When textFormat is set' , ( ) = > {
it ( 'should fromat the text accordingly' , async ( ) = > {
const { promise } = getTestContext ( response , { textFormat : 'hello {{label2}}' , stepInterval : '15s' } ) ;
const res = await promise ;
const res = await getTestContext ( testFrame , { textFormat : 'hello {{label2}}' , stepInterval : '15s' } ) ;
expect ( res . length ) . toBe ( 1 ) ;
expect ( res [ 0 ] . text ) . toBe ( 'hello value2' ) ;
@ -678,9 +461,7 @@ describe('LokiDatasource', () => {
} ) ;
describe ( 'When titleFormat is set' , ( ) = > {
it ( 'should fromat the title accordingly' , async ( ) = > {
const { promise } = getTestContext ( response , { titleFormat : 'Title {{label2}}' , stepInterval : '15s' } ) ;
const res = await promise ;
const res = await getTestContext ( testFrame , { titleFormat : 'Title {{label2}}' , stepInterval : '15s' } ) ;
expect ( res . length ) . toBe ( 1 ) ;
expect ( res [ 0 ] . title ) . toBe ( 'Title value2' ) ;
@ -920,26 +701,6 @@ describe('LokiDatasource', () => {
} ) ;
} ) ;
describe ( 'adjustInterval' , ( ) = > {
const dynamicInterval = 15 ;
const range = 1642 ;
const resolution = 1 ;
const ds = createLokiDSForTests ( ) ;
it ( 'should return the interval as a factor of dynamicInterval and resolution' , ( ) = > {
let interval = ds . adjustInterval ( dynamicInterval , resolution , range ) ;
expect ( interval ) . toBe ( resolution * dynamicInterval ) ;
} ) ;
it ( 'should not return a value less than the safe interval' , ( ) = > {
let safeInterval = range / 11000 ;
if ( safeInterval > 1 ) {
safeInterval = Math . ceil ( safeInterval ) ;
}
const unsafeInterval = safeInterval - 0.01 ;
let interval = ds . adjustInterval ( unsafeInterval , resolution , range ) ;
expect ( interval ) . toBeGreaterThanOrEqual ( safeInterval ) ;
} ) ;
} ) ;
describe ( 'prepareLogRowContextQueryTarget' , ( ) = > {
const ds = createLokiDSForTests ( ) ;
it ( 'creates query with only labels from /labels API' , ( ) = > {
@ -1002,7 +763,7 @@ describe('LokiDatasource', () => {
describe ( 'importing queries' , ( ) = > {
it ( 'keeps all labels when no labels are loaded' , async ( ) = > {
const ds = createLokiDSForTests ( ) ;
fetchMock . mockImplementation ( ( ) = > of ( createFetchRespons e( { data : [ ] } ) ) ) ;
ds . getResource = ( ) = > Promise . resolv e( { data : [ ] } ) ;
const queries = await ds . importFromAbstractQueries ( [
{
refId : 'A' ,
@ -1017,7 +778,7 @@ describe('LokiDatasource', () => {
it ( 'filters out non existing labels' , async ( ) = > {
const ds = createLokiDSForTests ( ) ;
fetchMock . mockImplementation ( ( ) = > of ( createFetchRespons e( { data : [ 'foo' ] } ) ) ) ;
ds . getResource = ( ) = > Promise . resolv e( { data : [ 'foo' ] } ) ;
const queries = await ds . importFromAbstractQueries ( [
{
refId : 'A' ,