Chore: InfluxDB unit testing overhaul (#86586)

* move mocks into the __mocks__ folder

* refactor datasource.test.ts

* refactor datasource_backend_mode.test.ts

* add dbName tests

* prettier

* betterer
This commit is contained in:
ismail simsek
2024-04-30 09:59:42 -04:00
committed by GitHub
parent 3d80693456
commit 4cc6b53a6d
14 changed files with 687 additions and 898 deletions
-6
View File
@@ -5154,12 +5154,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/influxdb/migrations.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/datasource/influxdb/mocks.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
"public/app/plugins/datasource/influxdb/query_part.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@@ -0,0 +1,89 @@
import { of } from 'rxjs';
import { AdHocVariableFilter, DataSourceInstanceSettings, PluginType, ScopedVars } from '@grafana/data';
import { FetchResponse, getBackendSrv, setBackendSrv, VariableInterpolation } from '@grafana/runtime';
import { TemplateSrv } from '../../../../features/templating/template_srv';
import InfluxDatasource from '../datasource';
import { InfluxOptions, InfluxVersion } from '../types';
const getAdhocFiltersMock = jest.fn().mockImplementation(() => []);
const replaceMock = jest.fn().mockImplementation((a: string, ...rest: unknown[]) => a);
export const templateSrvStub = {
getAdhocFilters: getAdhocFiltersMock,
replace: replaceMock,
} as unknown as TemplateSrv;
export function mockTemplateSrv(
getAdhocFiltersMock: (datasourceName: string) => AdHocVariableFilter[],
replaceMock: (
target?: string,
scopedVars?: ScopedVars,
format?: string | Function | undefined,
interpolations?: VariableInterpolation[]
) => string
): TemplateSrv {
return {
getAdhocFilters: getAdhocFiltersMock,
replace: replaceMock,
} as unknown as TemplateSrv;
}
export function mockBackendService(response: FetchResponse) {
const fetchMock = jest.fn().mockReturnValue(of(response));
const origBackendSrv = getBackendSrv();
setBackendSrv({
...origBackendSrv,
fetch: fetchMock,
});
return fetchMock;
}
export function getMockInfluxDS(
instanceSettings: DataSourceInstanceSettings<InfluxOptions> = getMockDSInstanceSettings(),
templateSrv: TemplateSrv = templateSrvStub
): InfluxDatasource {
return new InfluxDatasource(instanceSettings, templateSrv);
}
export function getMockDSInstanceSettings(
overrideJsonData?: Partial<InfluxOptions>
): DataSourceInstanceSettings<InfluxOptions> {
return {
id: 123,
url: 'proxied',
access: 'proxy',
name: 'influxDb',
readOnly: false,
uid: 'influxdb-test',
type: 'influxdb',
meta: {
id: 'influxdb-meta',
type: PluginType.datasource,
name: 'influxdb-test',
info: {
author: {
name: 'observability-metrics',
},
version: 'v0.0.1',
description: 'test',
links: [],
logos: {
large: '',
small: '',
},
updated: '',
screenshots: [],
},
module: '',
baseUrl: '',
},
jsonData: {
version: InfluxVersion.InfluxQL,
httpMode: 'POST',
dbName: 'site',
...(overrideJsonData ? overrideJsonData : {}),
},
};
}
@@ -0,0 +1,32 @@
import { DataQueryRequest, dateTime } from '@grafana/data';
import { InfluxQuery } from '../types';
const now = dateTime('2023-09-16T21:26:00Z');
export const queryOptions: DataQueryRequest<InfluxQuery> = {
app: 'dashboard',
interval: '10',
intervalMs: 10,
requestId: 'A-testing',
startTime: 0,
range: {
from: dateTime(now).subtract(15, 'minutes'),
to: now,
raw: {
from: 'now-15m',
to: 'now',
},
},
rangeRaw: {
from: 'now-15m',
to: 'now',
},
targets: [],
timezone: 'UTC',
scopedVars: {
interval: { text: '1m', value: '1m' },
__interval: { text: '1m', value: '1m' },
__interval_ms: { text: 60000, value: 60000 },
},
};
@@ -0,0 +1,99 @@
import { AdHocVariableFilter, DataQueryRequest, dateTime } from '@grafana/data';
import { SQLQuery } from '@grafana/sql';
import { InfluxQuery } from '../types';
type QueryType = InfluxQuery & SQLQuery;
export const mockInfluxQueryRequest = (targets?: QueryType[]): DataQueryRequest<QueryType> => {
return {
app: 'explore',
interval: '1m',
intervalMs: 60000,
range: {
from: dateTime(0),
to: dateTime(10),
raw: { from: dateTime(0), to: dateTime(10) },
},
rangeRaw: {
from: dateTime(0),
to: dateTime(10),
},
requestId: '',
scopedVars: {},
startTime: 0,
targets: targets ?? mockTargets(),
timezone: '',
};
};
export const mockTargets = (): QueryType[] => {
return [
{
refId: 'A',
datasource: {
type: 'influxdb',
uid: 'vA4bkHenk',
},
policy: 'default',
resultFormat: 'time_series',
orderByTime: 'ASC',
tags: [],
groupBy: [
{
type: 'time',
params: ['$__interval'],
},
{
type: 'fill',
params: ['null'],
},
],
select: [
[
{
type: 'field',
params: ['value'],
},
{
type: 'mean',
params: [],
},
],
],
measurement: 'cpu',
},
];
};
export const mockInfluxQueryWithTemplateVars = (adhocFilters: AdHocVariableFilter[]): InfluxQuery => ({
refId: 'x',
alias: '$var1',
measurement: '$var1',
policy: '$var1',
limit: '$var1',
slimit: '$var1',
tz: '$var1',
tags: [
{
key: 'drive',
operator: '=~',
value: '/^$path$/',
},
],
groupBy: [
{
params: ['$var1'],
type: 'tag',
},
],
select: [
[
{
params: ['$var1'],
type: 'field',
},
],
],
adhocFilters,
});
@@ -1,108 +1,5 @@
import { of } from 'rxjs';
import {
AdHocVariableFilter,
DataQueryRequest,
DataSourceInstanceSettings,
dateTime,
FieldType,
PluginType,
ScopedVars,
} from '@grafana/data';
import {
BackendDataSourceResponse,
FetchResponse,
getBackendSrv,
setBackendSrv,
VariableInterpolation,
} from '@grafana/runtime';
import { SQLQuery } from '@grafana/sql';
import { TemplateSrv } from '../../../features/templating/template_srv';
import InfluxDatasource from './datasource';
import { InfluxOptions, InfluxQuery, InfluxVersion } from './types';
const getAdhocFiltersMock = jest.fn().mockImplementation(() => []);
const replaceMock = jest.fn().mockImplementation((a: string, ...rest: unknown[]) => a);
export const templateSrvStub = {
getAdhocFilters: getAdhocFiltersMock,
replace: replaceMock,
} as unknown as TemplateSrv;
export function mockTemplateSrv(
getAdhocFiltersMock: (datasourceName: string) => AdHocVariableFilter[],
replaceMock: (
target?: string,
scopedVars?: ScopedVars,
format?: string | Function | undefined,
interpolations?: VariableInterpolation[]
) => string
): TemplateSrv {
return {
getAdhocFilters: getAdhocFiltersMock,
replace: replaceMock,
} as unknown as TemplateSrv;
}
export function mockBackendService(response: FetchResponse) {
const fetchMock = jest.fn().mockReturnValue(of(response));
const origBackendSrv = getBackendSrv();
setBackendSrv({
...origBackendSrv,
fetch: fetchMock,
});
return fetchMock;
}
export function getMockInfluxDS(
instanceSettings: DataSourceInstanceSettings<InfluxOptions> = getMockDSInstanceSettings(),
templateSrv: TemplateSrv = templateSrvStub
): InfluxDatasource {
return new InfluxDatasource(instanceSettings, templateSrv);
}
export function getMockDSInstanceSettings(
overrideJsonData?: Partial<InfluxOptions>
): DataSourceInstanceSettings<InfluxOptions> {
return {
id: 123,
url: 'proxied',
access: 'proxy',
name: 'influxDb',
readOnly: false,
uid: 'influxdb-test',
type: 'influxdb',
meta: {
id: 'influxdb-meta',
type: PluginType.datasource,
name: 'influxdb-test',
info: {
author: {
name: 'observability-metrics',
},
version: 'v0.0.1',
description: 'test',
links: [],
logos: {
large: '',
small: '',
},
updated: '',
screenshots: [],
},
module: '',
baseUrl: '',
},
jsonData: {
version: InfluxVersion.InfluxQL,
httpMode: 'POST',
dbName: 'site',
...(overrideJsonData ? overrideJsonData : {}),
},
};
}
import { FieldType } from '@grafana/data';
import { BackendDataSourceResponse, FetchResponse } from '@grafana/runtime';
export const mockInfluxFetchResponse = (
overrides?: Partial<FetchResponse<BackendDataSourceResponse>>
@@ -133,6 +30,7 @@ export const mockInfluxFetchResponse = (
...overrides,
};
};
export const mockInfluxTSDBQueryResponse = [
{
schema: {
@@ -220,6 +118,33 @@ export const mockInfluxTSDBQueryResponse = [
},
];
export const metricFindQueryResponse = {
config: {
url: 'mock-response-url',
},
headers: new Headers(),
ok: false,
redirected: false,
status: 0,
statusText: '',
type: 'basic',
url: '',
data: {
status: 'success',
results: [
{
series: [
{
name: 'measurement',
columns: ['name'],
values: [['cpu']],
},
],
},
],
},
};
export const mockInfluxRetentionPolicyResponse = [
{
schema: {
@@ -230,101 +155,6 @@ export const mockInfluxRetentionPolicyResponse = [
},
];
type QueryType = InfluxQuery & SQLQuery;
export const mockInfluxQueryRequest = (targets?: QueryType[]): DataQueryRequest<QueryType> => {
return {
app: 'explore',
interval: '1m',
intervalMs: 60000,
range: {
from: dateTime(0),
to: dateTime(10),
raw: { from: dateTime(0), to: dateTime(10) },
},
rangeRaw: {
from: dateTime(0),
to: dateTime(10),
},
requestId: '',
scopedVars: {},
startTime: 0,
targets: targets ?? mockTargets(),
timezone: '',
};
};
export const mockTargets = (): QueryType[] => {
return [
{
refId: 'A',
datasource: {
type: 'influxdb',
uid: 'vA4bkHenk',
},
policy: 'default',
resultFormat: 'time_series',
orderByTime: 'ASC',
tags: [],
groupBy: [
{
type: 'time',
params: ['$__interval'],
},
{
type: 'fill',
params: ['null'],
},
],
select: [
[
{
type: 'field',
params: ['value'],
},
{
type: 'mean',
params: [],
},
],
],
measurement: 'cpu',
},
];
};
export const mockInfluxQueryWithTemplateVars = (adhocFilters: AdHocVariableFilter[]): InfluxQuery => ({
refId: 'x',
alias: '$interpolationVar',
measurement: '$interpolationVar',
policy: '$interpolationVar',
limit: '$interpolationVar',
slimit: '$interpolationVar',
tz: '$interpolationVar',
tags: [
{
key: 'cpu',
operator: '=~',
value: '/^$interpolationVar,$interpolationVar2$/',
},
],
groupBy: [
{
params: ['$interpolationVar'],
type: 'tag',
},
],
select: [
[
{
params: ['$interpolationVar'],
type: 'field',
},
],
],
adhocFilters,
});
export const mockInfluxSQLFetchResponse: FetchResponse<BackendDataSourceResponse> = {
config: {
url: 'mock-response-url',
@@ -433,3 +263,51 @@ export const mockInfluxSQLVariableFetchResponse: FetchResponse<BackendDataSource
},
},
};
export const mockMetricFindQueryResponse = {
data: {
results: {
metricFindQuery: {
status: 200,
frames: [
{
schema: {
name: 'NoneNone',
refId: 'metricFindQuery',
fields: [
{
name: 'Value',
type: 'string',
typeInfo: {
frame: 'string',
},
},
],
},
data: {
values: [['test-t2-1', 'test-t2-10']],
},
},
{
schema: {
name: 'some-other',
refId: 'metricFindQuery',
fields: [
{
name: 'Value',
type: 'string',
typeInfo: {
frame: 'string',
},
},
],
},
data: {
values: [['test-t2-1', 'test-t2-10', 'test-t2-2', 'test-t2-3', 'test-t2-4']],
},
},
],
},
},
},
};
@@ -1,8 +1,8 @@
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import { getMockInfluxDS, getMockDSInstanceSettings } from '../../../../../__mocks__/datasource';
import InfluxDatasource from '../../../../../datasource';
import { getMockInfluxDS, getMockDSInstanceSettings } from '../../../../../mocks';
import { DEFAULT_POLICY, InfluxQuery } from '../../../../../types';
import { VisualInfluxQLEditor } from './VisualInfluxQLEditor';
@@ -1,59 +1,63 @@
import { lastValueFrom, of } from 'rxjs';
import { ScopedVars } from '@grafana/data';
import { BackendSrvRequest } from '@grafana/runtime/';
import { BackendSrvRequest } from '@grafana/runtime';
import config from 'app/core/config';
import { TemplateSrv } from '../../../features/templating/template_srv';
import { queryBuilder } from '../../../features/variables/shared/testing/builders';
import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService } from './__mocks__/datasource';
import { queryOptions } from './__mocks__/query';
import { mockInfluxQueryRequest, mockInfluxQueryWithTemplateVars } from './__mocks__/request';
import { mockInfluxFetchResponse, mockMetricFindQueryResponse } from './__mocks__/response';
import { BROWSER_MODE_DISABLED_MESSAGE } from './constants';
import InfluxDatasource from './datasource';
import {
getMockDSInstanceSettings,
getMockInfluxDS,
mockBackendService,
mockInfluxFetchResponse,
mockInfluxQueryRequest,
mockInfluxQueryWithTemplateVars,
mockTemplateSrv,
} from './mocks';
import { InfluxQuery, InfluxVersion } from './types';
// we want only frontend mode in this file
config.featureToggles.influxdbBackendMigration = false;
const fetchMock = mockBackendService(mockInfluxFetchResponse());
describe('InfluxDataSource Frontend Mode', () => {
describe('datasource initialization', () => {
it('should read the http method from jsonData', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
expect(ds.httpMode).toBe('GET');
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
expect(ds.httpMode).toBe('POST');
});
});
// Remove this suite when influxdbBackendMigration feature toggle removed
describe('InfluxDataSource Frontend Mode [influxdbBackendMigration=false]', () => {
beforeEach(() => {
// we want only frontend mode in this suite
config.featureToggles.influxdbBackendMigration = false;
jest.clearAllMocks();
});
it('should throw an error if there is 200 response with error', async () => {
const ds = getMockInfluxDS();
fetchMock.mockImplementation(() => {
return of({
data: {
results: [
{
error: 'Query timeout',
},
],
},
describe('general checks', () => {
it('should throw an error if there is 200 response with error', async () => {
const ds = getMockInfluxDS();
fetchMock.mockImplementation(() => {
return of({
data: {
results: [
{
error: 'Query timeout',
},
],
},
});
});
try {
await lastValueFrom(ds.query(mockInfluxQueryRequest()));
} catch (err) {
if (err instanceof Error) {
expect(err.message).toBe('InfluxDB Error: Query timeout');
}
}
});
try {
await lastValueFrom(ds.query(mockInfluxQueryRequest()));
} catch (err) {
if (err instanceof Error) {
expect(err.message).toBe('InfluxDB Error: Query timeout');
}
}
});
describe('outdated browser mode', () => {
it('should throw an error when querying data', async () => {
it('should throw an error when querying data when deprecated access mode', async () => {
expect.assertions(1);
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.access = 'direct';
@@ -68,7 +72,7 @@ describe('InfluxDataSource Frontend Mode', () => {
});
});
describe('metricFindQuery with HTTP GET', () => {
describe('metricFindQuery', () => {
let ds: InfluxDatasource;
const query = 'SELECT max(value) FROM measurement WHERE $timeFilter';
const queryOptions = {
@@ -77,14 +81,7 @@ describe('InfluxDataSource Frontend Mode', () => {
to: '2018-01-02T00:00:00Z',
},
};
let requestQuery: string;
let requestMethod: string | undefined;
let requestData: string | null;
const fetchMockImpl = (req: BackendSrvRequest) => {
requestMethod = req.method;
requestQuery = req.params?.q;
requestData = req.data;
return of({
data: {
status: 'success',
@@ -108,36 +105,29 @@ describe('InfluxDataSource Frontend Mode', () => {
fetchMock.mockImplementation(fetchMockImpl);
});
it('should read the http method from jsonData', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
await ds.metricFindQuery(query, queryOptions);
expect(requestMethod).toBe('GET');
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
await ds.metricFindQuery(query, queryOptions);
expect(requestMethod).toBe('POST');
});
it('should replace $timefilter', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
await ds.metricFindQuery(query, queryOptions);
expect(requestQuery).toMatch('time >= 1514764800000ms and time <= 1514851200000ms');
expect(fetchMock.mock.lastCall[0].params?.q).toMatch('time >= 1514764800000ms and time <= 1514851200000ms');
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
await ds.metricFindQuery(query, queryOptions);
expect(requestQuery).toBeFalsy();
expect(requestData).toMatch('time%20%3E%3D%201514764800000ms%20and%20time%20%3C%3D%201514851200000ms');
expect(fetchMock.mock.lastCall[0].params?.q).toBeFalsy();
expect(fetchMock.mock.lastCall[0].data).toMatch(
'time%20%3E%3D%201514764800000ms%20and%20time%20%3C%3D%201514851200000ms'
);
});
it('should not have any data in request body if http mode is GET', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
await ds.metricFindQuery(query, queryOptions);
expect(requestData).toBeNull();
expect(fetchMock.mock.lastCall[0].data).toBeNull();
});
it('should have data in request body if http mode is POST', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
await ds.metricFindQuery(query, queryOptions);
expect(requestData).not.toBeNull();
expect(requestData).toMatch('q=SELECT');
expect(fetchMock.mock.lastCall[0].data).not.toBeNull();
expect(fetchMock.mock.lastCall[0].data).toMatch('q=SELECT');
});
it('parse response correctly', async () => {
@@ -150,6 +140,7 @@ describe('InfluxDataSource Frontend Mode', () => {
});
});
// Update this after starting to use TemplateSrv from @grafana/runtime package
describe('adhoc variables', () => {
const adhocFilters = [
{
@@ -163,8 +154,6 @@ describe('InfluxDataSource Frontend Mode', () => {
mockTemplateService.getAdhocFilters = jest.fn((_: string) => adhocFilters);
let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService);
// const fetchMock = jest.fn().mockReturnValue(fetchResult);
it('query should contain the ad-hoc variable', () => {
ds.query(mockInfluxQueryRequest());
const expected = encodeURIComponent(
@@ -252,105 +241,75 @@ describe('InfluxDataSource Frontend Mode', () => {
ds.getTagValues({ key: 'test', filters: [] });
expect(metricFindQueryMock).toHaveBeenCalled();
});
it('should use dbName instead of database', () => {
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.database = 'should_not_be_used';
ds = getMockInfluxDS(instanceSettings);
expect(ds.database).toBe('site');
});
it('should fallback to use use database is dbName is not exist', () => {
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.database = 'fallback';
instanceSettings.jsonData.dbName = undefined;
ds = getMockInfluxDS(instanceSettings);
expect(ds.database).toBe('fallback');
});
});
describe('variable interpolation', () => {
const text = 'interpolationText';
const text2 = 'interpolationText2';
const textWithoutFormatRegex = 'interpolationText,interpolationText2';
const textWithFormatRegex = 'interpolationText,interpolationText2';
const justText = 'interpolationText';
const variableMap: Record<string, string> = {
$interpolationVar: text,
$interpolationVar2: text2,
};
const adhocFilters = [
{
key: 'adhoc',
operator: '=',
value: 'val',
condition: '',
},
const variablesMock = [
queryBuilder().withId('var1').withName('var1').withCurrent('var1_value').build(),
queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(),
];
const templateSrv = mockTemplateSrv(
jest.fn((_: string) => adhocFilters),
jest.fn((target?: string, scopedVars?: ScopedVars, format?: string | Function): string => {
if (!format) {
return variableMap[target!] || '';
}
if (format === 'regex') {
return textWithFormatRegex;
}
return textWithoutFormatRegex;
})
);
const ds = new InfluxDatasource(getMockDSInstanceSettings(), templateSrv);
function influxChecks(query: InfluxQuery) {
expect(templateSrv.replace).toBeCalledTimes(12);
expect(query.alias).toBe(text);
expect(query.measurement).toBe(textWithFormatRegex);
expect(query.policy).toBe(justText);
expect(query.limit).toBe(justText);
expect(query.slimit).toBe(justText);
expect(query.tz).toBe(text);
expect(query.tags![0].value).toBe(textWithFormatRegex);
expect(query.groupBy![0].params![0]).toBe(justText);
expect(query.select![0][0].params![0]).toBe(justText);
expect(query.adhocFilters?.[0].key).toBe(adhocFilters[0].key);
}
const mockTemplateService = new TemplateSrv({
getVariables: () => variablesMock,
getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0],
getFilteredVariables: jest.fn(),
});
// Remove this after start using TemplateSrv from @grafana/runtime
mockTemplateService.getAdhocFilters = jest.fn();
describe('when interpolating query variables for dashboard->explore', () => {
it('should interpolate all variables with Flux mode', () => {
ds.version = InfluxVersion.Flux;
const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.Flux }), mockTemplateService);
const fluxQuery = {
refId: 'x',
query: '$interpolationVar,$interpolationVar2',
query: 'some query with $var1 and $path',
};
const queries = ds.interpolateVariablesInQueries([fluxQuery], {
interpolationVar: { text: text, value: text },
interpolationVar2: { text: text2, value: text2 },
});
expect(templateSrv.replace).toBeCalledTimes(1);
expect(queries[0].query).toBe(textWithFormatRegex);
const queries = ds.interpolateVariablesInQueries([fluxQuery], {});
expect(queries[0].query).toBe('some query with var1_value and /etc/hosts');
});
it('should interpolate all variables with InfluxQL mode', () => {
ds.version = InfluxVersion.InfluxQL;
const queries = ds.interpolateVariablesInQueries([mockInfluxQueryWithTemplateVars(adhocFilters)], {
interpolationVar: { text: text, value: text },
interpolationVar2: { text: text2, value: text2 },
});
influxChecks(queries[0]);
const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.InfluxQL }), mockTemplateService);
const [query] = ds.interpolateVariablesInQueries([mockInfluxQueryWithTemplateVars([])], {});
expect(query.alias).toBe('var1_value');
expect(query.measurement).toBe('var1_value');
expect(query.policy).toBe('var1_value');
expect(query.limit).toBe('var1_value');
expect(query.slimit).toBe('var1_value');
expect(query.tz).toBe('var1_value');
expect(query.tags![0].value).toBe(`/^\\/etc\\/hosts$/`);
expect(query.groupBy![0].params![0]).toBe('var1_value');
expect(query.select![0][0].params![0]).toBe('var1_value');
});
});
describe('when interpolating template variables', () => {
describe('applyTemplateVariables', () => {
it('should apply all template variables with Flux mode', () => {
ds.version = InfluxVersion.Flux;
const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.Flux }), mockTemplateService);
const fluxQuery = {
refId: 'x',
query: '$interpolationVar',
query: '$var1',
};
const query = ds.applyTemplateVariables(fluxQuery, {
interpolationVar: {
text: text,
value: text,
},
});
expect(templateSrv.replace).toBeCalledTimes(1);
expect(query.query).toBe(text);
const query = ds.applyTemplateVariables(fluxQuery, {});
expect(query.query).toBe('var1_value');
});
});
describe('variable interpolation with chained variables with frontend mode', () => {
const variablesMock = [queryBuilder().withId('var1').withName('var1').withCurrent('var1').build()];
const mockTemplateService = new TemplateSrv({
getVariables: () => variablesMock,
getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0],
getFilteredVariables: jest.fn(),
});
mockTemplateService.getAdhocFilters = jest.fn((_: string) => []);
let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService);
const fetchMockImpl = () =>
of({
@@ -416,114 +375,298 @@ describe('InfluxDataSource Frontend Mode', () => {
expect(qData).toBe(qe);
});
});
});
});
describe('interpolateQueryExpr', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings(), new TemplateSrv());
it('should return the value as it is', () => {
const value = 'normalValue';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, 'my query $tempVar');
const expectation = 'normalValue';
expect(result).toBe(expectation);
});
describe('InfluxDataSource Backend Mode [influxdbBackendMigration=true]', () => {
beforeEach(() => {
// we want only backend mode in this suite
config.featureToggles.influxdbBackendMigration = true;
jest.clearAllMocks();
});
it('should return the escaped value if the value wrapped in regex', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /$tempVar/');
const expectation = `\\/special\\/path`;
expect(result).toBe(expectation);
});
describe('metric find query', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings());
it('handles multiple frames', async () => {
const fetchMockImpl = () => {
return of(mockMetricFindQueryResponse);
};
it('should return the escaped value if the value wrapped in regex 2', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /^$tempVar$/');
const expectation = `\\/special\\/path`;
expect(result).toBe(expectation);
});
fetchMock.mockImplementation(fetchMockImpl);
const values = await ds.getTagValues({ key: 'test_id', filters: [] });
expect(fetchMock).toHaveBeenCalled();
expect(values.length).toBe(5);
expect(values[0].text).toBe('test-t2-1');
});
});
it('should return the escaped value if the value wrapped in regex 3', () => {
const value = ['env', 'env2', 'env3'];
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withMulti(false)
.withIncludeAll(true)
.build();
const result = ds.interpolateQueryExpr(value, variableMock, 'select from /^($tempVar)$/');
const expectation = `(env|env2|env3)`;
expect(result).toBe(expectation);
});
it('should **not** return the escaped value if the value **is not** wrapped in regex', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `/special/path`;
expect(result).toBe(expectation);
});
it('should **not** return the escaped value if the value **is not** wrapped in regex 2', () => {
const value = '12.2';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `12.2`;
expect(result).toBe(expectation);
});
it('should escape the value **always** if the variable is a multi-value variable', () => {
const value = [`/special/path`, `/some/other/path`];
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti().build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`;
expect(result).toBe(expectation);
});
it('should escape and join with the pipe even the variable is not multi-value', () => {
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withCurrent('All', '$__all')
.withMulti(false)
.withAllValue('')
.withIncludeAll()
.withOptions(
describe('variable interpolation with chained variables with backend mode', () => {
const variablesMock = [
queryBuilder().withId('var1').withName('var1').withCurrent('var1').build(),
queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(),
queryBuilder()
.withId('field_var')
.withName('field_var')
.withMulti(true)
.withOptions(
{
text: `field_1`,
value: `field_1`,
},
{
text: `field_2`,
value: `field_2`,
},
{
text: `field_3`,
value: `field_3`,
}
)
.withCurrent(['field_1', 'field_3'])
.build(),
];
const mockTemplateService = new TemplateSrv({
getVariables: () => variablesMock,
getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0],
getFilteredVariables: jest.fn(),
});
mockTemplateService.getAdhocFilters = jest.fn((_: string) => []);
let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService);
const fetchMockImpl = () =>
of({
data: {
status: 'success',
results: [
{
text: 'All',
value: '$__all',
series: [
{
name: 'measurement',
columns: ['name'],
values: [['cpu']],
},
],
},
],
},
});
beforeEach(() => {
jest.clearAllMocks();
fetchMock.mockImplementation(fetchMockImpl);
});
it('should render chained regex variables with floating point number', () => {
ds.metricFindQuery(`SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED`, {
...queryOptions,
scopedVars: { maxSED: { text: '8.1', value: '8.1' } },
});
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1`;
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should render chained regex variables with URL', () => {
ds.metricFindQuery('SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^$var1$/', {
...queryOptions,
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
},
});
const qe = `SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
expect(fetchMock).toHaveBeenCalled();
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should render chained regex variables with floating point number and url', () => {
ds.metricFindQuery(
'SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED AND agent_url =~ /^$var1$/',
{
...queryOptions,
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
maxSED: { text: '8.1', value: '8.1' },
},
}
);
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 AND agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should interpolate variable inside a regex pattern', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=~',
value: '/^.*-$var1$/',
},
],
};
const res = ds.applyVariables(query, {});
const expected = `/^.*-var1$/`;
expect(res.tags?.[0].value).toEqual(expected);
});
it('should remove regex wrappers when operator is not a regex operator', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=',
value: '/^$path$/',
},
],
};
const res = ds.applyVariables(query, {});
const expected = `/etc/hosts`;
expect(res.tags?.[0].value).toEqual(expected);
});
it('should interpolate field keys with given scopedVars', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=',
value: 'value',
},
],
select: [
[
{
type: 'field',
params: ['$field_var'],
},
{
text: `/special/path`,
value: `/special/path`,
type: 'mean',
params: [],
},
{
text: `/some/other/path`,
value: `/some/other/path`,
}
)
.build();
const value = [`/special/path`, `/some/other/path`];
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = /$tempVar/`);
const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`;
expect(result).toBe(expectation);
});
it('should return floating point number as it is', () => {
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withMulti(false)
.withOptions({
text: `1.0`,
value: `1.0`,
})
.build();
const value = `1.0`;
const result = ds.interpolateQueryExpr(value, variableMock, `select value / $tempVar from /^measurement$/`);
const expectation = `1.0`;
expect(result).toBe(expectation);
});
],
],
};
const res = ds.applyVariables(query, { field_var: { text: 'field_3', value: 'field_3' } });
const expected = `field_3`;
expect(res.select?.[0][0].params?.[0]).toEqual(expected);
});
});
});
describe('interpolateQueryExpr', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings(), new TemplateSrv());
it('should return the value as it is', () => {
const value = 'normalValue';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, 'my query $tempVar');
const expectation = 'normalValue';
expect(result).toBe(expectation);
});
it('should return the escaped value if the value wrapped in regex', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /$tempVar/');
const expectation = `\\/special\\/path`;
expect(result).toBe(expectation);
});
it('should return the escaped value if the value wrapped in regex 2', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /^$tempVar$/');
const expectation = `\\/special\\/path`;
expect(result).toBe(expectation);
});
it('should return the escaped value if the value wrapped in regex 3', () => {
const value = ['env', 'env2', 'env3'];
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withMulti(false)
.withIncludeAll(true)
.build();
const result = ds.interpolateQueryExpr(value, variableMock, 'select from /^($tempVar)$/');
const expectation = `(env|env2|env3)`;
expect(result).toBe(expectation);
});
it('should **not** return the escaped value if the value **is not** wrapped in regex', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `/special/path`;
expect(result).toBe(expectation);
});
it('should **not** return the escaped value if the value **is not** wrapped in regex 2', () => {
const value = '12.2';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `12.2`;
expect(result).toBe(expectation);
});
it('should escape the value **always** if the variable is a multi-value variable', () => {
const value = [`/special/path`, `/some/other/path`];
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti().build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`;
expect(result).toBe(expectation);
});
it('should escape and join with the pipe even the variable is not multi-value', () => {
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withCurrent('All', '$__all')
.withMulti(false)
.withAllValue('')
.withIncludeAll()
.withOptions(
{
text: 'All',
value: '$__all',
},
{
text: `/special/path`,
value: `/special/path`,
},
{
text: `/some/other/path`,
value: `/some/other/path`,
}
)
.build();
const value = [`/special/path`, `/some/other/path`];
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = /$tempVar/`);
const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`;
expect(result).toBe(expectation);
});
it('should return floating point number as it is', () => {
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withMulti(false)
.withOptions({
text: `1.0`,
value: `1.0`,
})
.build();
const value = `1.0`;
const result = ds.interpolateQueryExpr(value, variableMock, `select value / $tempVar from /^measurement$/`);
const expectation = `1.0`;
expect(result).toBe(expectation);
});
});
@@ -174,7 +174,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
const variables = scopedVars || {};
// We want to interpolate these variables on backend.
// The pre-calculated values are replaced withe the variable strings.
// The pre-calculated values are replaced with the variable strings.
variables.__interval = {
value: '$__interval',
};
@@ -1,443 +0,0 @@
import { of } from 'rxjs';
import { DataQueryRequest, dateTime, ScopedVars } from '@grafana/data/src';
import { FetchResponse } from '@grafana/runtime/src';
import config from 'app/core/config';
import { TemplateSrv } from '../../../features/templating/template_srv';
import { queryBuilder } from '../../../features/variables/shared/testing/builders';
import InfluxDatasource from './datasource';
import {
getMockDSInstanceSettings,
getMockInfluxDS,
mockBackendService,
mockInfluxFetchResponse,
mockInfluxQueryWithTemplateVars,
mockTemplateSrv,
} from './mocks';
import { InfluxQuery, InfluxVersion } from './types';
config.featureToggles.influxdbBackendMigration = true;
const fetchMock = mockBackendService(mockInfluxFetchResponse());
describe('InfluxDataSource Backend Mode', () => {
const text = 'interpolationText';
const text2 = 'interpolationText2';
const textWithoutFormatRegex = 'interpolationText,interpolationText2';
const textWithFormatRegex = 'interpolationText|interpolationText2';
const variableMap: Record<string, string> = {
$interpolationVar: text,
$interpolationVar2: text2,
};
const adhocFilters = [
{
key: 'adhoc',
operator: '=',
value: 'val',
condition: '',
},
];
const templateSrv = mockTemplateSrv(
jest.fn(() => {
return adhocFilters;
}),
jest.fn((target?: string, scopedVars?: ScopedVars, format?: string | Function): string => {
if (!format) {
return variableMap[target!] || '';
}
if (format === 'regex') {
return textWithFormatRegex;
}
return textWithoutFormatRegex;
})
);
let queryOptions: DataQueryRequest<InfluxQuery>;
let influxQuery: InfluxQuery;
const now = dateTime('2023-09-16T21:26:00Z');
beforeEach(() => {
queryOptions = {
app: 'dashboard',
interval: '10',
intervalMs: 10,
requestId: 'A-testing',
startTime: 0,
range: {
from: dateTime(now).subtract(15, 'minutes'),
to: now,
raw: {
from: 'now-15m',
to: 'now',
},
},
rangeRaw: {
from: 'now-15m',
to: 'now',
},
targets: [],
timezone: 'UTC',
scopedVars: {
interval: { text: '1m', value: '1m' },
__interval: { text: '1m', value: '1m' },
__interval_ms: { text: 60000, value: 60000 },
},
};
influxQuery = {
refId: 'x',
alias: '$interpolationVar',
measurement: '$interpolationVar',
policy: '$interpolationVar',
limit: '$interpolationVar',
slimit: '$interpolationVar',
tz: '$interpolationVar',
tags: [
{
key: 'cpu',
operator: '=~',
value: '/^$interpolationVar,$interpolationVar2$/',
},
],
groupBy: [
{
params: ['$interpolationVar'],
type: 'tag',
},
],
select: [
[
{
params: ['$interpolationVar'],
type: 'field',
},
],
],
};
});
describe('adhoc filters', () => {
let fetchReq: { queries: InfluxQuery[] };
const ctx = {
ds: getMockInfluxDS(getMockDSInstanceSettings(), templateSrv),
};
beforeEach(async () => {
fetchMock.mockImplementation((req) => {
fetchReq = req.data;
return of(mockInfluxFetchResponse() as FetchResponse);
});
const req = {
...queryOptions,
targets: [...queryOptions.targets, { ...influxQuery, adhocFilters }],
};
ctx.ds.query(req);
});
it('should add adhocFilters to the tags in the query', () => {
expect(fetchMock).toHaveBeenCalled();
expect(fetchReq).not.toBeNull();
expect(fetchReq.queries.length).toBe(1);
expect(fetchReq.queries[0].tags).toBeDefined();
expect(fetchReq.queries[0].tags?.length).toBe(2);
expect(fetchReq.queries[0].tags?.[1].key).toBe(adhocFilters[0].key);
expect(fetchReq.queries[0].tags?.[1].value).toBe(adhocFilters[0].value);
});
});
describe('when interpolating template variables', () => {
const text = 'interpolationText';
const text2 = 'interpolationText2';
const textWithoutFormatRegex = 'interpolationText,interpolationText2';
const textWithFormatRegex = 'interpolationText,interpolationText2';
const justText = 'interpolationText';
const variableMap: Record<string, string> = {
$interpolationVar: text,
$interpolationVar2: text2,
};
const adhocFilters = [
{
key: 'adhoc',
operator: '=',
value: 'val',
condition: '',
},
];
const templateSrv = mockTemplateSrv(
jest.fn((_: string) => adhocFilters),
jest.fn((target?: string, scopedVars?: ScopedVars, format?: string | Function): string => {
if (!format) {
return variableMap[target!] || '';
}
if (format === 'regex') {
return textWithFormatRegex;
}
return textWithoutFormatRegex;
})
);
const ds = new InfluxDatasource(getMockDSInstanceSettings(), templateSrv);
function influxChecks(query: InfluxQuery) {
expect(templateSrv.replace).toBeCalledTimes(12);
expect(query.alias).toBe(text);
expect(query.measurement).toBe(textWithFormatRegex);
expect(query.policy).toBe(justText);
expect(query.limit).toBe(justText);
expect(query.slimit).toBe(justText);
expect(query.tz).toBe(text);
expect(query.tags![0].value).toBe(textWithFormatRegex);
expect(query.groupBy![0].params![0]).toBe(justText);
expect(query.select![0][0].params![0]).toBe(justText);
expect(query.adhocFilters?.[0].key).toBe(adhocFilters[0].key);
}
it('should apply all template variables with InfluxQL mode', () => {
ds.version = ds.version = InfluxVersion.InfluxQL;
ds.access = 'proxy';
const query = ds.applyTemplateVariables(mockInfluxQueryWithTemplateVars(adhocFilters), {
interpolationVar: { text: text, value: text },
interpolationVar2: { text: 'interpolationText2', value: 'interpolationText2' },
});
influxChecks(query);
});
it('should apply all scopedVars to tags', () => {
ds.version = InfluxVersion.InfluxQL;
ds.access = 'proxy';
const query = ds.applyTemplateVariables(mockInfluxQueryWithTemplateVars(adhocFilters), {
interpolationVar: { text: text, value: text },
interpolationVar2: { text: 'interpolationText2', value: 'interpolationText2' },
});
if (!query.tags?.length) {
throw new Error('Tags are not defined');
}
const value = query.tags[0].value;
const scopedVars = 'interpolationText,interpolationText2';
expect(value).toBe(scopedVars);
});
});
describe('variable interpolation with chained variables with backend mode', () => {
const variablesMock = [
queryBuilder().withId('var1').withName('var1').withCurrent('var1').build(),
queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(),
queryBuilder()
.withId('field_var')
.withName('field_var')
.withMulti(true)
.withOptions(
{
text: `field_1`,
value: `field_1`,
},
{
text: `field_2`,
value: `field_2`,
},
{
text: `field_3`,
value: `field_3`,
}
)
.withCurrent(['field_1', 'field_3'])
.build(),
];
const mockTemplateService = new TemplateSrv({
getVariables: () => variablesMock,
getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0],
getFilteredVariables: jest.fn(),
});
mockTemplateService.getAdhocFilters = jest.fn((_: string) => []);
let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService);
const fetchMockImpl = () =>
of({
data: {
status: 'success',
results: [
{
series: [
{
name: 'measurement',
columns: ['name'],
values: [['cpu']],
},
],
},
],
},
});
beforeEach(() => {
jest.clearAllMocks();
fetchMock.mockImplementation(fetchMockImpl);
});
it('should render chained regex variables with floating point number', () => {
ds.metricFindQuery(`SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED`, {
...queryOptions,
scopedVars: { maxSED: { text: '8.1', value: '8.1' } },
});
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1`;
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should render chained regex variables with URL', () => {
ds.metricFindQuery('SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^$var1$/', {
...queryOptions,
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
},
});
const qe = `SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
expect(fetchMock).toHaveBeenCalled();
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should render chained regex variables with floating point number and url', () => {
ds.metricFindQuery(
'SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED AND agent_url =~ /^$var1$/',
{
...queryOptions,
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
maxSED: { text: '8.1', value: '8.1' },
},
}
);
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 AND agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should interpolate variable inside a regex pattern', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=~',
value: '/^.*-$var1$/',
},
],
};
const res = ds.applyVariables(query, {});
const expected = `/^.*-var1$/`;
expect(res.tags?.[0].value).toEqual(expected);
});
it('should remove regex wrappers when operator is not a regex operator', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=',
value: '/^$path$/',
},
],
};
const res = ds.applyVariables(query, {});
const expected = `/etc/hosts`;
expect(res.tags?.[0].value).toEqual(expected);
});
it('should interpolate field keys with given scopedVars', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=',
value: 'value',
},
],
select: [
[
{
type: 'field',
params: ['$field_var'],
},
{
type: 'mean',
params: [],
},
],
],
};
const res = ds.applyVariables(query, { field_var: { text: 'field_3', value: 'field_3' } });
const expected = `field_3`;
expect(res.select?.[0][0].params?.[0]).toEqual(expected);
});
});
describe('metric find query', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings());
it('handles multiple frames', async () => {
const fetchMockImpl = () => {
return of(mockMetricFindQueryResponse);
};
fetchMock.mockImplementation(fetchMockImpl);
const values = await ds.getTagValues({ key: 'test_id', filters: [] });
expect(fetchMock).toHaveBeenCalled();
expect(values.length).toBe(5);
expect(values[0].text).toBe('test-t2-1');
});
});
});
const mockMetricFindQueryResponse = {
data: {
results: {
metricFindQuery: {
status: 200,
frames: [
{
schema: {
name: 'NoneNone',
refId: 'metricFindQuery',
fields: [
{
name: 'Value',
type: 'string',
typeInfo: {
frame: 'string',
},
},
],
},
data: {
values: [['test-t2-1', 'test-t2-10']],
},
},
{
schema: {
name: 'some-other',
refId: 'metricFindQuery',
fields: [
{
name: 'Value',
type: 'string',
typeInfo: {
frame: 'string',
},
},
],
},
data: {
values: [['test-t2-1', 'test-t2-10', 'test-t2-2', 'test-t2-3', 'test-t2-4']],
},
},
],
},
},
},
};
@@ -3,14 +3,10 @@ import { lastValueFrom } from 'rxjs';
import { SQLQuery } from '@grafana/sql';
import config from 'app/core/config';
import { getMockDSInstanceSettings, mockBackendService, mockTemplateSrv } from './__mocks__/datasource';
import { mockInfluxQueryRequest } from './__mocks__/request';
import { mockInfluxSQLFetchResponse } from './__mocks__/response';
import InfluxDatasource from './datasource';
import {
getMockDSInstanceSettings,
mockBackendService,
mockInfluxQueryRequest,
mockInfluxSQLFetchResponse,
mockTemplateSrv,
} from './mocks';
import { InfluxVersion } from './types';
config.featureToggles.influxdbBackendMigration = true;
@@ -1,6 +1,7 @@
import { TemplateSrv } from '@grafana/runtime';
import { getMockDSInstanceSettings, mockBackendService, mockInfluxSQLVariableFetchResponse } from '../mocks';
import { getMockDSInstanceSettings, mockBackendService } from '../__mocks__/datasource';
import { mockInfluxSQLVariableFetchResponse } from '../__mocks__/response';
import { FlightSQLDatasource } from './datasource.flightsql';
@@ -1,7 +1,7 @@
import config from 'app/core/config';
import { getMockInfluxDS } from './__mocks__/datasource';
import { getAllMeasurements, getAllPolicies, getFieldKeys, getTagKeys, getTagValues } from './influxql_metadata_query';
import { getMockInfluxDS } from './mocks';
import { InfluxQuery } from './types';
describe('influx_metadata_query', () => {
@@ -1,5 +1,5 @@
import { templateSrvStub as templateService } from './__mocks__/datasource';
import { buildMetadataQuery } from './influxql_query_builder';
import { templateSrvStub as templateService } from './mocks';
import { DEFAULT_POLICY } from './types';
describe('influxql-query-builder', () => {
@@ -6,8 +6,8 @@ import { FetchResponse } from '@grafana/runtime';
import config from 'app/core/config';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { getMockDSInstanceSettings, getMockInfluxDS } from './__mocks__/datasource';
import InfluxQueryModel from './influx_query_model';
import { getMockDSInstanceSettings, getMockInfluxDS } from './mocks';
import ResponseParser, { getSelectedParams } from './response_parser';
import { InfluxQuery } from './types';