From 4cc6b53a6dd46c00b5752369210371a8e23286df Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Tue, 30 Apr 2024 15:59:42 +0200 Subject: [PATCH] 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 --- .betterer.results | 6 - .../influxdb/__mocks__/datasource.ts | 89 +++ .../datasource/influxdb/__mocks__/query.ts | 32 + .../datasource/influxdb/__mocks__/request.ts | 99 +++ .../{mocks.ts => __mocks__/response.ts} | 278 +++----- .../visual/VisualInfluxQLEditor.test.tsx | 2 +- .../datasource/influxdb/datasource.test.ts | 615 +++++++++++------- .../plugins/datasource/influxdb/datasource.ts | 2 +- .../influxdb/datasource_backend_mode.test.ts | 443 ------------- .../influxdb/datasource_sql.test.ts | 10 +- .../fsql/datasource.flightsql.test.ts | 3 +- .../influxdb/influxql_metadata_query.test.ts | 2 +- .../influxdb/influxql_query_builder.test.ts | 2 +- .../influxdb/response_parser.test.ts | 2 +- 14 files changed, 687 insertions(+), 898 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/__mocks__/datasource.ts create mode 100644 public/app/plugins/datasource/influxdb/__mocks__/query.ts create mode 100644 public/app/plugins/datasource/influxdb/__mocks__/request.ts rename public/app/plugins/datasource/influxdb/{mocks.ts => __mocks__/response.ts} (56%) delete mode 100644 public/app/plugins/datasource/influxdb/datasource_backend_mode.test.ts diff --git a/.betterer.results b/.betterer.results index 2c05174492b..8509dde739a 100644 --- a/.betterer.results +++ b/.betterer.results @@ -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"], diff --git a/public/app/plugins/datasource/influxdb/__mocks__/datasource.ts b/public/app/plugins/datasource/influxdb/__mocks__/datasource.ts new file mode 100644 index 00000000000..6630252bd64 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/__mocks__/datasource.ts @@ -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 = getMockDSInstanceSettings(), + templateSrv: TemplateSrv = templateSrvStub +): InfluxDatasource { + return new InfluxDatasource(instanceSettings, templateSrv); +} + +export function getMockDSInstanceSettings( + overrideJsonData?: Partial +): DataSourceInstanceSettings { + 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 : {}), + }, + }; +} diff --git a/public/app/plugins/datasource/influxdb/__mocks__/query.ts b/public/app/plugins/datasource/influxdb/__mocks__/query.ts new file mode 100644 index 00000000000..0d74ecf113e --- /dev/null +++ b/public/app/plugins/datasource/influxdb/__mocks__/query.ts @@ -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 = { + 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 }, + }, +}; diff --git a/public/app/plugins/datasource/influxdb/__mocks__/request.ts b/public/app/plugins/datasource/influxdb/__mocks__/request.ts new file mode 100644 index 00000000000..986c780922d --- /dev/null +++ b/public/app/plugins/datasource/influxdb/__mocks__/request.ts @@ -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 => { + 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, +}); diff --git a/public/app/plugins/datasource/influxdb/mocks.ts b/public/app/plugins/datasource/influxdb/__mocks__/response.ts similarity index 56% rename from public/app/plugins/datasource/influxdb/mocks.ts rename to public/app/plugins/datasource/influxdb/__mocks__/response.ts index 8aee8e79b5c..23b6cef2595 100644 --- a/public/app/plugins/datasource/influxdb/mocks.ts +++ b/public/app/plugins/datasource/influxdb/__mocks__/response.ts @@ -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 = getMockDSInstanceSettings(), - templateSrv: TemplateSrv = templateSrvStub -): InfluxDatasource { - return new InfluxDatasource(instanceSettings, templateSrv); -} - -export function getMockDSInstanceSettings( - overrideJsonData?: Partial -): DataSourceInstanceSettings { - 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> @@ -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 => { - 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 = { config: { url: 'mock-response-url', @@ -433,3 +263,51 @@ export const mockInfluxSQLVariableFetchResponse: FetchResponse { +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 = { - $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); + }); +}); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index e54bde8734d..e1f514efa84 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -174,7 +174,7 @@ export default class InfluxDatasource extends DataSourceWithBackend { - const text = 'interpolationText'; - const text2 = 'interpolationText2'; - const textWithoutFormatRegex = 'interpolationText,interpolationText2'; - const textWithFormatRegex = 'interpolationText|interpolationText2'; - const variableMap: Record = { - $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; - 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 = { - $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']], - }, - }, - ], - }, - }, - }, -}; diff --git a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts index 705b370a264..bc516995873 100644 --- a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts +++ b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts @@ -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; diff --git a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts index 30b0855893a..bea4f7bfc98 100644 --- a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts +++ b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts @@ -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'; diff --git a/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts b/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts index e27429beb93..897eff27c05 100644 --- a/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts +++ b/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts @@ -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', () => { diff --git a/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts b/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts index d5c70c4c572..4e86911da25 100644 --- a/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts +++ b/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts @@ -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', () => { diff --git a/public/app/plugins/datasource/influxdb/response_parser.test.ts b/public/app/plugins/datasource/influxdb/response_parser.test.ts index 4a606d605ea..e206dca77a8 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.test.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.test.ts @@ -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';