diff --git a/.betterer.results b/.betterer.results
index 01c46062c5a..5e7202416bd 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -4162,14 +4162,14 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "5"],
- [0, 0, 0, "Do not use any type assertions.", "6"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "7"],
+ [0, 0, 0, "Do not use any type assertions.", "5"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "6"],
+ [0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
- [0, 0, 0, "Do not use any type assertions.", "11"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "12"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "11"],
+ [0, 0, 0, "Do not use any type assertions.", "12"],
[0, 0, 0, "Unexpected any. Specify a different type.", "13"],
[0, 0, 0, "Unexpected any. Specify a different type.", "14"],
[0, 0, 0, "Unexpected any. Specify a different type.", "15"],
@@ -4184,7 +4184,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "24"],
[0, 0, 0, "Unexpected any. Specify a different type.", "25"],
[0, 0, 0, "Unexpected any. Specify a different type.", "26"],
- [0, 0, 0, "Do not use any type assertions.", "27"]
+ [0, 0, 0, "Unexpected any. Specify a different type.", "27"],
+ [0, 0, 0, "Do not use any type assertions.", "28"]
],
"public/app/plugins/datasource/influxdb/influx_query_model.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@@ -4229,14 +4230,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "17"],
[0, 0, 0, "Unexpected any. Specify a different type.", "18"]
],
- "public/app/plugins/datasource/influxdb/influxql_query_builder.ts:5381": [
- [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "2"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "3"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "4"],
- [0, 0, 0, "Do not use any type assertions.", "5"]
- ],
"public/app/plugins/datasource/influxdb/migrations.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
@@ -4294,6 +4287,12 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "15"],
[0, 0, 0, "Unexpected any. Specify a different type.", "16"]
],
+ "public/app/plugins/datasource/influxdb/specs/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, "Unexpected any. Specify a different type.", "2"],
+ [0, 0, 0, "Do not use any type assertions.", "3"]
+ ],
"public/app/plugins/datasource/influxdb/specs/response_parser.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx
index e3c0c590b5a..7a2ad49cd3f 100644
--- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx
+++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx
@@ -139,7 +139,7 @@ describe('InfluxDB InfluxQL Visual Editor field-filtering', () => {
// verify `getTagValues` was called once, and in the tags-param we did not receive `field1`
expect(mockedMeta.getTagValues).toHaveBeenCalledTimes(1);
- expect((mockedMeta.getTagValues as jest.Mock).mock.calls[0][3]).toStrictEqual(ONLY_TAGS);
+ expect((mockedMeta.getTagValues as jest.Mock).mock.calls[0][1]).toStrictEqual(ONLY_TAGS);
// now we click on the FROM/cpudata button
await userEvent.click(screen.getByRole('button', { name: 'cpudata' }));
diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx
index c763668811d..d3cc6f80ab4 100644
--- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx
+++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx
@@ -2,6 +2,7 @@ import { render, waitFor } from '@testing-library/react';
import React from 'react';
import InfluxDatasource from '../../datasource';
+import { getMockDS, getMockDSInstanceSettings } from '../../specs/mocks';
import { InfluxQuery } from '../../types';
import { Editor } from './Editor';
@@ -38,10 +39,8 @@ jest.mock('./Seg', () => {
async function assertEditor(query: InfluxQuery, textContent: string) {
const onChange = jest.fn();
const onRunQuery = jest.fn();
- const datasource: InfluxDatasource = {
- retentionPolicies: [],
- metricFindQuery: () => Promise.resolve([]),
- } as unknown as InfluxDatasource;
+ const datasource: InfluxDatasource = getMockDS(getMockDSInstanceSettings());
+ datasource.metricFindQuery = () => Promise.resolve([]);
const { container } = render(
);
diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx
index e085e4d088a..da71ca6ff9b 100644
--- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx
+++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx
@@ -92,11 +92,11 @@ export const Editor = (props: Props): JSX.Element => {
const retentionPolicies = !!policyData.error ? [] : policyData.value ?? [];
const allTagKeys = useMemo(async () => {
- const tagKeys = (await getTagKeysForMeasurementAndTags(measurement, policy, [], datasource)).map(
+ const tagKeys = (await getTagKeysForMeasurementAndTags(datasource, [], measurement, policy)).map(
(tag) => `${tag}::tag`
);
- const fieldKeys = (await getFieldKeysForMeasurement(measurement || '', policy, datasource)).map(
+ const fieldKeys = (await getFieldKeysForMeasurement(datasource, measurement || '', policy)).map(
(field) => `${field}::field`
);
@@ -109,7 +109,7 @@ export const Editor = (props: Props): JSX.Element => {
'field_0',
() => {
return measurement !== undefined
- ? getFieldKeysForMeasurement(measurement, policy, datasource)
+ ? getFieldKeysForMeasurement(datasource, measurement, policy)
: Promise.resolve([]);
},
],
@@ -162,7 +162,7 @@ export const Editor = (props: Props): JSX.Element => {
measurement={measurement}
getPolicyOptions={() =>
withTemplateVariableOptions(
- allTagKeys.then((keys) => getAllPolicies(datasource)),
+ allTagKeys.then(() => getAllPolicies(datasource)),
wrapPure
)
}
@@ -170,9 +170,9 @@ export const Editor = (props: Props): JSX.Element => {
withTemplateVariableOptions(
allTagKeys.then((keys) =>
getAllMeasurementsForTags(
- filter === '' ? undefined : filter,
+ datasource,
filterTags(query.tags ?? [], keys),
- datasource
+ filter === '' ? undefined : filter
)
),
wrapRegex,
@@ -188,11 +188,9 @@ export const Editor = (props: Props): JSX.Element => {
tags={query.tags ?? []}
onChange={handleTagsSectionChange}
getTagKeyOptions={getTagKeys}
- getTagValueOptions={(key: string) =>
+ getTagValueOptions={(key) =>
withTemplateVariableOptions(
- allTagKeys.then((keys) =>
- getTagValues(key, measurement, policy, filterTags(query.tags ?? [], keys), datasource)
- ),
+ allTagKeys.then((keys) => getTagValues(datasource, filterTags(query.tags ?? [], keys), key)),
wrapRegex
)
}
diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts
index 349e1e01d41..b8deb8b2639 100644
--- a/public/app/plugins/datasource/influxdb/datasource.ts
+++ b/public/app/plugins/datasource/influxdb/datasource.ts
@@ -35,7 +35,7 @@ import { BROWSER_MODE_DISABLED_MESSAGE } from './constants';
import InfluxQueryModel from './influx_query_model';
import InfluxSeries from './influx_series';
import { getAllPolicies } from './influxql_metadata_query';
-import { InfluxQueryBuilder } from './influxql_query_builder';
+import { buildMetadataQuery } from './influxql_query_builder';
import { prepareAnnotation } from './migrations';
import { buildRawQuery, replaceHardCodedRetentionPolicy } from './queryUtils';
import ResponseParser from './response_parser';
@@ -59,7 +59,7 @@ export default class InfluxDatasource extends DataSourceWithBackend,
- private readonly templateSrv: TemplateSrv = getTemplateSrv()
+ readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings);
@@ -310,6 +310,19 @@ export default class InfluxDatasource extends DataSourceWithBackend {
+ return lastValueFrom(
+ super.query({
+ targets: [target],
+ } as DataQueryRequest)
+ ).then((rsp) => {
+ if (rsp.data?.length) {
+ return frameToMetricFindValue(rsp.data[0]);
+ }
+ return [];
+ });
+ }
+
async metricFindQuery(query: string, options?: any): Promise {
if (this.isFlux || this.isMigrationToggleOnAndIsAccessProxy()) {
const target: InfluxQuery = {
@@ -348,14 +361,25 @@ export default class InfluxDatasource extends DataSourceWithBackend> => {
- const builder = new InfluxQueryBuilder(target, datasource.database);
- const q = builder.buildExploreQuery(type, withKey, withMeasurementFilter);
- const options = { policy: replaceHardCodedRetentionPolicy(target.policy, datasource.retentionPolicies) };
- return datasource.metricFindQuery(q, options);
+import InfluxDatasource from './datasource';
+import { buildMetadataQuery } from './influxql_query_builder';
+import { replaceHardCodedRetentionPolicy } from './queryUtils';
+import { InfluxQuery, InfluxQueryTag, MetadataQueryType } from './types';
+
+type MetadataQueryOptions = {
+ type: MetadataQueryType;
+ datasource: InfluxDatasource;
+ scopedVars?: ScopedVars;
+ measurement?: string;
+ retentionPolicy?: string;
+ tags?: InfluxQueryTag[];
+ withKey?: string;
+ withMeasurementFilter?: string;
+};
+
+const runExploreQuery = async (options: MetadataQueryOptions): Promise> => {
+ const { type, datasource, scopedVars, measurement, retentionPolicy, tags, withKey, withMeasurementFilter } = options;
+ const query = buildMetadataQuery({
+ type,
+ scopedVars,
+ measurement,
+ retentionPolicy,
+ tags,
+ withKey,
+ withMeasurementFilter,
+ templateService: datasource.templateSrv,
+ database: datasource.database,
+ });
+ const policy = retentionPolicy ? datasource.templateSrv.replace(retentionPolicy, {}, 'regex') : '';
+ const target: InfluxQuery = {
+ query,
+ rawQuery: true,
+ refId: 'metadataQuery',
+ policy: replaceHardCodedRetentionPolicy(policy, datasource.retentionPolicies),
+ };
+ if (config.featureToggles.influxdbBackendMigration) {
+ return datasource.runMetadataQuery(target);
+ } else {
+ const options = { policy: target.policy };
+ return datasource.metricFindQuery(query, options);
+ }
};
export async function getAllPolicies(datasource: InfluxDatasource): Promise {
- const target = { tags: [], measurement: undefined, policy: undefined };
- const data = await runExploreQuery('RETENTION POLICIES', undefined, undefined, target, datasource);
+ const data = await runExploreQuery({ type: 'RETENTION_POLICIES', datasource });
return data.map((item) => item.text);
}
export async function getAllMeasurementsForTags(
- measurementFilter: string | undefined,
+ datasource: InfluxDatasource,
tags: InfluxQueryTag[],
- datasource: InfluxDatasource
+ withMeasurementFilter?: string
): Promise {
- const target = { tags, measurement: undefined, policy: undefined };
- const data = await runExploreQuery('MEASUREMENTS', undefined, measurementFilter, target, datasource);
+ const data = await runExploreQuery({ type: 'MEASUREMENTS', datasource, tags, withMeasurementFilter });
return data.map((item) => item.text);
}
export async function getTagKeysForMeasurementAndTags(
- measurement: string | undefined,
- policy: string | undefined,
+ datasource: InfluxDatasource,
tags: InfluxQueryTag[],
- datasource: InfluxDatasource
+ measurement?: string,
+ retentionPolicy?: string
): Promise {
- const target = { tags, measurement, policy };
- const data = await runExploreQuery('TAG_KEYS', undefined, undefined, target, datasource);
+ const data = await runExploreQuery({ type: 'TAG_KEYS', datasource, measurement, retentionPolicy });
return data.map((item) => item.text);
}
export async function getTagValues(
- tagKey: string,
- measurement: string | undefined,
- policy: string | undefined,
+ datasource: InfluxDatasource,
tags: InfluxQueryTag[],
- datasource: InfluxDatasource
+ tagKey: string,
+ measurement?: string,
+ retentionPolicy?: string
): Promise {
- const target = { tags, measurement, policy };
-
if (tagKey.endsWith('::field')) {
return [];
}
-
- const data = await runExploreQuery('TAG_VALUES', tagKey, undefined, target, datasource);
+ const data = await runExploreQuery({
+ type: 'TAG_VALUES',
+ withKey: tagKey,
+ datasource,
+ measurement,
+ retentionPolicy,
+ });
return data.map((item) => item.text);
}
export async function getFieldKeysForMeasurement(
+ datasource: InfluxDatasource,
measurement: string,
- policy: string | undefined,
- datasource: InfluxDatasource
+ retentionPolicy?: string
): Promise {
- const target = { tags: [], measurement, policy };
- const data = await runExploreQuery('FIELDS', undefined, undefined, target, datasource);
+ const data = await runExploreQuery({ type: 'FIELDS', datasource, measurement, retentionPolicy });
return data.map((item) => item.text);
}
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 3d99d2a7080..c24dd26e419 100644
--- a/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts
+++ b/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts
@@ -1,225 +1,281 @@
-import { InfluxQueryBuilder } from './influxql_query_builder';
+import { buildMetadataQuery } from './influxql_query_builder';
+import { templateSrvStub as templateService } from './specs/mocks';
-describe('InfluxQueryBuilder', () => {
- describe('when building explore queries', () => {
- it('should only have measurement condition in tag keys query given query with measurement', () => {
- const builder = new InfluxQueryBuilder({ measurement: 'cpu', tags: [] });
- const query = builder.buildExploreQuery('TAG_KEYS');
- expect(query).toBe('SHOW TAG KEYS FROM "cpu"');
- });
-
- it('should handle regex measurement in tag keys query', () => {
- const builder = new InfluxQueryBuilder({
- measurement: '/.*/',
- tags: [],
+describe('influx-query-builder', () => {
+ describe('RETENTION_POLICIES', () => {
+ it('should build retention policies query', () => {
+ const query = buildMetadataQuery({
+ type: 'RETENTION_POLICIES',
+ templateService,
+ database: 'site',
});
- const query = builder.buildExploreQuery('TAG_KEYS');
- expect(query).toBe('SHOW TAG KEYS FROM /.*/');
- });
-
- it('should have no conditions in tags keys query given query with no measurement or tag', () => {
- const builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
- const query = builder.buildExploreQuery('TAG_KEYS');
- expect(query).toBe('SHOW TAG KEYS');
- });
-
- it('should have where condition in tag keys query with tags', () => {
- const builder = new InfluxQueryBuilder({
- measurement: '',
- tags: [{ key: 'host', value: 'se1' }],
- });
- const query = builder.buildExploreQuery('TAG_KEYS');
- expect(query).toBe('SHOW TAG KEYS WHERE "host" = \'se1\'');
- });
-
- it('should ignore condition if operator is a value operator', () => {
- const builder = new InfluxQueryBuilder({
- measurement: '',
- tags: [{ key: 'value', value: '10', operator: '>' }],
- });
- const query = builder.buildExploreQuery('TAG_KEYS');
- expect(query).toBe('SHOW TAG KEYS');
- });
-
- it('should have no conditions in measurement query for query with no tags', () => {
- const builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
- const query = builder.buildExploreQuery('MEASUREMENTS');
- expect(query).toBe('SHOW MEASUREMENTS LIMIT 100');
- });
-
- it('should have no conditions in measurement query for query with no tags and empty query', () => {
- const builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
- const query = builder.buildExploreQuery('MEASUREMENTS', undefined, '');
- expect(query).toBe('SHOW MEASUREMENTS LIMIT 100');
- });
-
- it('should have WITH MEASUREMENT in measurement query for non-empty query with no tags', () => {
- const builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
- const query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'something');
- expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /(?i)something/ LIMIT 100');
- });
-
- it('should escape the regex value in measurement query', () => {
- const builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
- const query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'abc/edf/');
- expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /(?i)abc\\/edf\\// LIMIT 100');
- });
-
- it('should have WITH MEASUREMENT WHERE in measurement query for non-empty query with tags', () => {
- const builder = new InfluxQueryBuilder({
- measurement: '',
- tags: [{ key: 'app', value: 'email' }],
- });
- const query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'something');
- expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /(?i)something/ WHERE "app" = \'email\' LIMIT 100');
- });
-
- it('should have where condition in measurement query for query with tags', () => {
- const builder = new InfluxQueryBuilder({
- measurement: '',
- tags: [{ key: 'app', value: 'email' }],
- });
- const query = builder.buildExploreQuery('MEASUREMENTS');
- expect(query).toBe('SHOW MEASUREMENTS WHERE "app" = \'email\' LIMIT 100');
- });
-
- it('should have where tag name IN filter in tag values query for query with one tag', () => {
- const builder = new InfluxQueryBuilder({
- measurement: '',
- tags: [{ key: 'app', value: 'asdsadsad' }],
- });
- const query = builder.buildExploreQuery('TAG_VALUES', 'app');
- expect(query).toBe('SHOW TAG VALUES WITH KEY = "app"');
- });
-
- it('should have measurement tag condition and tag name IN filter in tag values query', () => {
- const builder = new InfluxQueryBuilder({
- measurement: 'cpu',
- tags: [
- { key: 'app', value: 'email' },
- { key: 'host', value: 'server1' },
- ],
- });
- const query = builder.buildExploreQuery('TAG_VALUES', 'app');
- expect(query).toBe('SHOW TAG VALUES FROM "cpu" WITH KEY = "app" WHERE "host" = \'server1\'');
- });
-
- it('should select from policy correctly if policy is specified', () => {
- const builder = new InfluxQueryBuilder({
- measurement: 'cpu',
- policy: 'one_week',
- tags: [
- { key: 'app', value: 'email' },
- { key: 'host', value: 'server1' },
- ],
- });
- const query = builder.buildExploreQuery('TAG_VALUES', 'app');
- expect(query).toBe('SHOW TAG VALUES FROM "one_week"."cpu" WITH KEY = "app" WHERE "host" = \'server1\'');
- });
-
- it('should not include policy when policy is default', () => {
- const builder = new InfluxQueryBuilder({
- measurement: 'cpu',
- policy: 'default',
- tags: [],
- });
- const query = builder.buildExploreQuery('TAG_VALUES', 'app');
- expect(query).toBe('SHOW TAG VALUES FROM "cpu" WITH KEY = "app"');
- });
-
- it('should switch to regex operator in tag condition', () => {
- const builder = new InfluxQueryBuilder({
- measurement: 'cpu',
- tags: [{ key: 'host', value: '/server.*/' }],
- });
- const query = builder.buildExploreQuery('TAG_VALUES', 'app');
- expect(query).toBe('SHOW TAG VALUES FROM "cpu" WITH KEY = "app" WHERE "host" =~ /server.*/');
+ expect(query).toBe('SHOW RETENTION POLICIES on "site"');
});
+ });
+ describe('FIELDS', () => {
it('should build show field query', () => {
- const builder = new InfluxQueryBuilder({
+ const query = buildMetadataQuery({
+ type: 'FIELDS',
+ templateService,
measurement: 'cpu',
tags: [{ key: 'app', value: 'email' }],
});
- const query = builder.buildExploreQuery('FIELDS');
expect(query).toBe('SHOW FIELD KEYS FROM "cpu"');
});
it('should build show field query with regexp', () => {
- const builder = new InfluxQueryBuilder({
+ const query = buildMetadataQuery({
+ type: 'FIELDS',
+ templateService,
measurement: '/$var/',
tags: [{ key: 'app', value: 'email' }],
});
- const query = builder.buildExploreQuery('FIELDS');
expect(query).toBe('SHOW FIELD KEYS FROM /$var/');
});
+ });
- it('should build show retention policies query', () => {
- const builder = new InfluxQueryBuilder({ measurement: 'cpu', tags: [] }, 'site');
- const query = builder.buildExploreQuery('RETENTION POLICIES');
- expect(query).toBe('SHOW RETENTION POLICIES on "site"');
+ describe('TAG_KEYS', () => {
+ it('should only have measurement condition in tag keys query given query with measurement', () => {
+ const query = buildMetadataQuery({ type: 'TAG_KEYS', templateService, measurement: 'cpu', tags: [] });
+ expect(query).toBe('SHOW TAG KEYS FROM "cpu"');
});
- it('should handle tag-value=number-ish when getting measurements', () => {
- const builder = new InfluxQueryBuilder(
- { measurement: undefined, tags: [{ key: 'app', value: '42', operator: '==' }] },
- undefined
- );
- const query = builder.buildExploreQuery('MEASUREMENTS');
- expect(query).toBe(`SHOW MEASUREMENTS WHERE "app" == '42' LIMIT 100`);
+ it('should handle regex measurement in tag keys query', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_KEYS',
+ templateService,
+ measurement: '/.*/',
+ tags: [],
+ });
+ expect(query).toBe('SHOW TAG KEYS FROM /.*/');
});
+ it('should have no conditions in tags keys query given query with no measurement or tag', () => {
+ const query = buildMetadataQuery({ type: 'TAG_KEYS', templateService, measurement: '', tags: [] });
+ expect(query).toBe('SHOW TAG KEYS');
+ });
+
+ it('should have where condition in tag keys query with tags', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_KEYS',
+ templateService,
+ measurement: '',
+ tags: [{ key: 'host', value: 'se1' }],
+ });
+ expect(query).toBe('SHOW TAG KEYS WHERE "host" = \'se1\'');
+ });
+
+ it('should ignore condition if operator is a value operator', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_KEYS',
+ templateService,
+ measurement: '',
+ tags: [{ key: 'value', value: '10', operator: '>' }],
+ });
+ expect(query).toBe('SHOW TAG KEYS');
+ });
it('should handle tag-value=number-ish getting tag-keys', () => {
- const builder = new InfluxQueryBuilder(
- { measurement: undefined, tags: [{ key: 'app', value: '42', operator: '==' }] },
- undefined
- );
- const query = builder.buildExploreQuery('TAG_KEYS');
+ const query = buildMetadataQuery({
+ type: 'TAG_KEYS',
+ templateService,
+ measurement: undefined,
+ tags: [{ key: 'app', value: '42', operator: '==' }],
+ database: undefined,
+ });
expect(query).toBe(`SHOW TAG KEYS WHERE "app" == '42'`);
});
it('should handle tag-value-contains-backslash-character getting tag-keys', () => {
- const builder = new InfluxQueryBuilder(
- { measurement: undefined, tags: [{ key: 'app', value: 'lab\\el', operator: '==' }] },
- undefined
- );
- const query = builder.buildExploreQuery('TAG_KEYS');
+ const query = buildMetadataQuery({
+ type: 'TAG_KEYS',
+ templateService,
+ measurement: undefined,
+ tags: [{ key: 'app', value: 'lab\\el', operator: '==' }],
+ database: undefined,
+ });
expect(query).toBe(`SHOW TAG KEYS WHERE "app" == 'lab\\\\el'`);
});
it('should handle tag-value-contains-single-quote-character getting tag-keys', () => {
- const builder = new InfluxQueryBuilder(
- { measurement: undefined, tags: [{ key: 'app', value: "lab'el", operator: '==' }] },
- undefined
- );
- const query = builder.buildExploreQuery('TAG_KEYS');
+ const query = buildMetadataQuery({
+ type: 'TAG_KEYS',
+ templateService,
+ measurement: undefined,
+ tags: [{ key: 'app', value: "lab'el", operator: '==' }],
+ database: undefined,
+ });
expect(query).toBe(`SHOW TAG KEYS WHERE "app" == 'lab\\'el'`);
});
- it('should handle tag-value=emptry-string when getting measurements', () => {
- const builder = new InfluxQueryBuilder(
- { measurement: undefined, tags: [{ key: 'app', value: '', operator: '==' }] },
- undefined
- );
- const query = builder.buildExploreQuery('MEASUREMENTS');
- expect(query).toBe(`SHOW MEASUREMENTS WHERE "app" == '' LIMIT 100`);
- });
-
it('should handle tag-value=emptry-string when getting tag-keys', () => {
- const builder = new InfluxQueryBuilder(
- { measurement: undefined, tags: [{ key: 'app', value: '', operator: '==' }] },
- undefined
- );
- const query = builder.buildExploreQuery('TAG_KEYS');
+ const query = buildMetadataQuery({
+ type: 'TAG_KEYS',
+ templateService,
+ measurement: undefined,
+ tags: [{ key: 'app', value: '', operator: '==' }],
+ database: undefined,
+ });
expect(query).toBe(`SHOW TAG KEYS WHERE "app" == ''`);
});
+ });
- it('should not add FROM statement if the measurement empty', () => {
- const builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
- let query = builder.buildExploreQuery('TAG_KEYS');
- expect(query).toBe('SHOW TAG KEYS');
- query = builder.buildExploreQuery('FIELDS');
- expect(query).toBe('SHOW FIELD KEYS');
+ describe('TAG_VALUES', () => {
+ it('should have where tag name IN filter in tag values query for query with one tag', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_VALUES',
+ templateService,
+ withKey: 'app',
+ measurement: '',
+ tags: [{ key: 'app', value: 'asdsadsad' }],
+ });
+ expect(query).toBe('SHOW TAG VALUES WITH KEY = "app"');
+ });
+
+ it('should have measurement tag condition and tag name IN filter in tag values query', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_VALUES',
+ templateService,
+ withKey: 'app',
+ measurement: 'cpu',
+ tags: [
+ { key: 'app', value: 'email' },
+ { key: 'host', value: 'server1' },
+ ],
+ });
+ expect(query).toBe('SHOW TAG VALUES FROM "cpu" WITH KEY = "app" WHERE "host" = \'server1\'');
+ });
+
+ it('should select from policy correctly if policy is specified', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_VALUES',
+ templateService,
+ withKey: 'app',
+ measurement: 'cpu',
+ retentionPolicy: 'one_week',
+ tags: [
+ { key: 'app', value: 'email' },
+ { key: 'host', value: 'server1' },
+ ],
+ });
+ expect(query).toBe('SHOW TAG VALUES FROM "one_week"."cpu" WITH KEY = "app" WHERE "host" = \'server1\'');
+ });
+
+ it('should not include policy when policy is default', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_VALUES',
+ templateService,
+ withKey: 'app',
+ measurement: 'cpu',
+ retentionPolicy: 'default',
+ tags: [],
+ });
+ expect(query).toBe('SHOW TAG VALUES FROM "cpu" WITH KEY = "app"');
+ });
+
+ it('should switch to regex operator in tag condition', () => {
+ const query = buildMetadataQuery({
+ type: 'TAG_VALUES',
+ templateService,
+ withKey: 'app',
+ measurement: 'cpu',
+ tags: [{ key: 'host', value: '/server.*/' }],
+ });
+ expect(query).toBe('SHOW TAG VALUES FROM "cpu" WITH KEY = "app" WHERE "host" =~ /server.*/');
});
});
+
+ describe('MEASUREMENTS', () => {
+ it('should have no conditions in measurement query for query with no tags', () => {
+ const query = buildMetadataQuery({ type: 'MEASUREMENTS', templateService, measurement: '', tags: [] });
+ expect(query).toBe('SHOW MEASUREMENTS LIMIT 100');
+ });
+
+ it('should have no conditions in measurement query for query with no tags and empty query', () => {
+ const query = buildMetadataQuery({
+ type: 'MEASUREMENTS',
+ templateService,
+ withKey: undefined,
+ withMeasurementFilter: '',
+ measurement: '',
+ tags: [],
+ });
+ expect(query).toBe('SHOW MEASUREMENTS LIMIT 100');
+ });
+
+ it('should have WITH MEASUREMENT in measurement query for non-empty query with no tags', () => {
+ const query = buildMetadataQuery({
+ type: 'MEASUREMENTS',
+ templateService,
+ withKey: undefined,
+ withMeasurementFilter: 'something',
+ measurement: '',
+ tags: [],
+ });
+ expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /(?i)something/ LIMIT 100');
+ });
+
+ it('should escape the regex value in measurement query', () => {
+ const query = buildMetadataQuery({
+ type: 'MEASUREMENTS',
+ templateService,
+ withKey: undefined,
+ withMeasurementFilter: 'abc/edf/',
+ measurement: '',
+ tags: [],
+ });
+ expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /(?i)abc\\/edf\\// LIMIT 100');
+ });
+
+ it('should have WITH MEASUREMENT WHERE in measurement query for non-empty query with tags', () => {
+ const query = buildMetadataQuery({
+ type: 'MEASUREMENTS',
+ templateService,
+ withKey: undefined,
+ withMeasurementFilter: 'something',
+ measurement: '',
+ tags: [{ key: 'app', value: 'email' }],
+ });
+ expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /(?i)something/ WHERE "app" = \'email\' LIMIT 100');
+ });
+
+ it('should have where condition in measurement query for query with tags', () => {
+ const query = buildMetadataQuery({
+ type: 'MEASUREMENTS',
+ templateService,
+ measurement: '',
+ tags: [{ key: 'app', value: 'email' }],
+ });
+ expect(query).toBe('SHOW MEASUREMENTS WHERE "app" = \'email\' LIMIT 100');
+ });
+
+ it('should handle tag-value=number-ish when getting measurements', () => {
+ const query = buildMetadataQuery({
+ type: 'MEASUREMENTS',
+ templateService,
+ database: undefined,
+ measurement: undefined,
+ tags: [{ key: 'app', value: '42', operator: '==' }],
+ });
+ expect(query).toBe(`SHOW MEASUREMENTS WHERE "app" == '42' LIMIT 100`);
+ });
+
+ it('should handle tag-value=emptry-string when getting measurements', () => {
+ const query = buildMetadataQuery({
+ type: 'MEASUREMENTS',
+ templateService,
+ database: undefined,
+ measurement: undefined,
+ tags: [{ key: 'app', value: '', operator: '==' }],
+ });
+ expect(query).toBe(`SHOW MEASUREMENTS WHERE "app" == '' LIMIT 100`);
+ });
+ });
+
+ it('should not add FROM statement if the measurement empty', () => {
+ let query = buildMetadataQuery({ type: 'TAG_KEYS', templateService, measurement: '', tags: [] });
+ expect(query).toBe('SHOW TAG KEYS');
+ query = buildMetadataQuery({ type: 'FIELDS', templateService });
+ expect(query).toBe('SHOW FIELD KEYS');
+ });
});
diff --git a/public/app/plugins/datasource/influxdb/influxql_query_builder.ts b/public/app/plugins/datasource/influxdb/influxql_query_builder.ts
index 60d190602da..97ad4f07d6f 100644
--- a/public/app/plugins/datasource/influxdb/influxql_query_builder.ts
+++ b/public/app/plugins/datasource/influxdb/influxql_query_builder.ts
@@ -1,9 +1,138 @@
import { reduce } from 'lodash';
-import { escapeRegex } from '@grafana/data';
+import { escapeRegex, ScopedVars } from '@grafana/data/src';
-function renderTagCondition(tag: { operator: any; value: string; condition: any; key: string }, index: number) {
- // FIXME: merge this function with influx_query_model/renderTagCondition
+import { TemplateSrv } from '../../../features/templating/template_srv';
+
+import { InfluxQueryTag, MetadataQueryType } from './types';
+
+export const buildMetadataQuery = (params: {
+ type: MetadataQueryType;
+ templateService: TemplateSrv;
+ scopedVars?: ScopedVars;
+ database?: string;
+ measurement?: string;
+ retentionPolicy?: string;
+ tags?: InfluxQueryTag[];
+ withKey?: string;
+ withMeasurementFilter?: string;
+}): string => {
+ let query = '';
+ let {
+ type,
+ templateService,
+ scopedVars,
+ database,
+ measurement,
+ retentionPolicy,
+ tags,
+ withKey,
+ withMeasurementFilter,
+ } = params;
+
+ switch (type) {
+ case 'RETENTION_POLICIES':
+ return 'SHOW RETENTION POLICIES on "' + database + '"';
+ case 'FIELDS':
+ if (!measurement || measurement === '') {
+ return 'SHOW FIELD KEYS';
+ }
+
+ // If there is a measurement and it is not empty string
+ if (measurement && !measurement.match(/^\/.*\/|^$/)) {
+ measurement = '"' + measurement + '"';
+
+ if (retentionPolicy && retentionPolicy !== 'default') {
+ retentionPolicy = '"' + retentionPolicy + '"';
+ measurement = retentionPolicy + '.' + measurement;
+ }
+ }
+
+ return 'SHOW FIELD KEYS FROM ' + measurement;
+ case 'TAG_KEYS':
+ query = 'SHOW TAG KEYS';
+ break;
+ case 'TAG_VALUES':
+ query = 'SHOW TAG VALUES';
+ break;
+ case 'MEASUREMENTS':
+ query = 'SHOW MEASUREMENTS';
+ if (withMeasurementFilter) {
+ // we do a case-insensitive regex-based lookup
+ query += ' WITH MEASUREMENT =~ /(?i)' + escapeRegex(withMeasurementFilter) + '/';
+ }
+ break;
+ default:
+ return query;
+ }
+ if (measurement) {
+ if (!measurement.match('^/.*/') && !measurement.match(/^merge\(.*\)/)) {
+ measurement = '"' + measurement + '"';
+ }
+
+ if (retentionPolicy && retentionPolicy !== 'default') {
+ retentionPolicy = '"' + retentionPolicy + '"';
+ measurement = retentionPolicy + '.' + measurement;
+ }
+
+ if (measurement !== '') {
+ query += ' FROM ' + measurement;
+ }
+ }
+
+ if (withKey) {
+ let keyIdentifier = withKey;
+
+ if (keyIdentifier.endsWith('::tag')) {
+ keyIdentifier = keyIdentifier.slice(0, -5);
+ }
+
+ query += ' WITH KEY = "' + keyIdentifier + '"';
+ }
+
+ if (tags && tags.length > 0) {
+ const whereConditions = reduce(
+ tags,
+ (memo, tag) => {
+ // do not add a condition for the key we want to explore for
+ if (tag.key && tag.key === withKey) {
+ return memo;
+ }
+
+ // value operators not supported in these types of queries
+ if (tag.operator === '>' || tag.operator === '<') {
+ return memo;
+ }
+
+ memo.push(renderTagCondition(tag, memo.length, templateService, scopedVars, true));
+ return memo;
+ },
+ []
+ );
+
+ if (whereConditions.length > 0) {
+ query += ' WHERE ' + whereConditions.join(' ');
+ }
+ }
+
+ if (type === 'MEASUREMENTS') {
+ query += ' LIMIT 100';
+ //Solve issue #2524 by limiting the number of measurements returned
+ //LIMIT must be after WITH MEASUREMENT and WHERE clauses
+ //This also could be used for TAG KEYS and TAG VALUES, if desired
+ }
+
+ return query;
+};
+
+// A merge of query_builder/renderTagCondition and influx_query_model/renderTagCondition
+export function renderTagCondition(
+ tag: InfluxQueryTag,
+ index: number,
+ templateSrv: TemplateSrv,
+ scopedVars?: ScopedVars,
+ interpolate?: boolean
+) {
let str = '';
let operator = tag.operator;
let value = tag.value;
@@ -25,6 +154,17 @@ function renderTagCondition(tag: { operator: any; value: string; condition: any;
value = "'" + value.replace(/\\/g, '\\\\').replace(/\'/g, "\\'") + "'";
}
+ // quote value unless regex
+ if (operator !== '=~' && operator !== '!~') {
+ if (interpolate) {
+ value = templateSrv.replace(value, scopedVars);
+ } else if (operator !== '>' && operator !== '<') {
+ value = "'" + value.replace(/\\/g, '\\\\').replace(/\'/g, "\\'") + "'";
+ }
+ } else if (interpolate) {
+ value = templateSrv.replace(value, scopedVars, 'regex');
+ }
+
let escapedKey = `"${tag.key}"`;
if (tag.key.endsWith('::tag')) {
@@ -37,110 +177,3 @@ function renderTagCondition(tag: { operator: any; value: string; condition: any;
return str + escapedKey + ' ' + operator + ' ' + value;
}
-
-export class InfluxQueryBuilder {
- constructor(private target: { measurement: any; tags: any; policy?: any }, private database?: string) {}
-
- buildExploreQuery(type: string, withKey?: string, withMeasurementFilter?: string): string {
- let query = '';
- let measurement;
- let policy;
-
- if (type === 'TAG_KEYS') {
- query = 'SHOW TAG KEYS';
- measurement = this.target.measurement;
- policy = this.target.policy;
- } else if (type === 'TAG_VALUES') {
- query = 'SHOW TAG VALUES';
- measurement = this.target.measurement;
- policy = this.target.policy;
- } else if (type === 'MEASUREMENTS') {
- query = 'SHOW MEASUREMENTS';
- if (withMeasurementFilter) {
- // we do a case-insensitive regex-based lookup
- query += ' WITH MEASUREMENT =~ /(?i)' + escapeRegex(withMeasurementFilter) + '/';
- }
- } else if (type === 'FIELDS') {
- measurement = this.target.measurement;
- policy = this.target.policy;
-
- // If there is a measurement and it is not empty string
- if (!measurement.match(/^\/.*\/|^$/)) {
- measurement = '"' + measurement + '"';
-
- if (policy && policy !== 'default') {
- policy = '"' + policy + '"';
- measurement = policy + '.' + measurement;
- }
- }
-
- if (measurement === '') {
- return 'SHOW FIELD KEYS';
- }
-
- return 'SHOW FIELD KEYS FROM ' + measurement;
- } else if (type === 'RETENTION POLICIES') {
- query = 'SHOW RETENTION POLICIES on "' + this.database + '"';
- return query;
- }
-
- if (measurement) {
- if (!measurement.match('^/.*/') && !measurement.match(/^merge\(.*\)/)) {
- measurement = '"' + measurement + '"';
- }
-
- if (policy && policy !== 'default') {
- policy = '"' + policy + '"';
- measurement = policy + '.' + measurement;
- }
-
- if (measurement !== '') {
- query += ' FROM ' + measurement;
- }
- }
-
- if (withKey) {
- let keyIdentifier = withKey;
-
- if (keyIdentifier.endsWith('::tag')) {
- keyIdentifier = keyIdentifier.slice(0, -5);
- }
-
- query += ' WITH KEY = "' + keyIdentifier + '"';
- }
-
- if (this.target.tags && this.target.tags.length > 0) {
- const whereConditions = reduce(
- this.target.tags,
- (memo, tag) => {
- // do not add a condition for the key we want to explore for
- if (tag.key === withKey) {
- return memo;
- }
-
- // value operators not supported in these types of queries
- if (tag.operator === '>' || tag.operator === '<') {
- return memo;
- }
-
- memo.push(renderTagCondition(tag, memo.length));
- return memo;
- },
- [] as string[]
- );
-
- if (whereConditions.length > 0) {
- query += ' WHERE ' + whereConditions.join(' ');
- }
- }
-
- if (type === 'MEASUREMENTS') {
- query += ' LIMIT 100';
- //Solve issue #2524 by limiting the number of measurements returned
- //LIMIT must be after WITH MEASUREMENT and WHERE clauses
- //This also could be used for TAG KEYS and TAG VALUES, if desired
- }
-
- return query;
- }
-}
diff --git a/public/app/plugins/datasource/influxdb/specs/mocks.ts b/public/app/plugins/datasource/influxdb/specs/mocks.ts
new file mode 100644
index 00000000000..9402f08aa44
--- /dev/null
+++ b/public/app/plugins/datasource/influxdb/specs/mocks.ts
@@ -0,0 +1,63 @@
+import { of } from 'rxjs';
+
+import { DataSourceInstanceSettings, PluginType } from '@grafana/data/src';
+import { FetchResponse, getBackendSrv, setBackendSrv } from '@grafana/runtime/src';
+
+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 mockBackendService(response: any) {
+ const fetchMock = jest.fn().mockReturnValue(of(response as FetchResponse));
+ const origBackendSrv = getBackendSrv();
+ setBackendSrv({
+ ...origBackendSrv,
+ fetch: fetchMock,
+ });
+}
+
+export function getMockDS(instanceSettings: DataSourceInstanceSettings): InfluxDatasource {
+ return new InfluxDatasource(instanceSettings, templateSrvStub);
+}
+
+export function getMockDSInstanceSettings(): 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' },
+ };
+}
diff --git a/public/app/plugins/datasource/influxdb/types.ts b/public/app/plugins/datasource/influxdb/types.ts
index ea588d9a223..9de94c0c8a0 100644
--- a/public/app/plugins/datasource/influxdb/types.ts
+++ b/public/app/plugins/datasource/influxdb/types.ts
@@ -77,3 +77,5 @@ export interface InfluxQuery extends DataQuery {
textEditor?: boolean;
adhocFilters?: AdHocVariableFilter[];
}
+
+export type MetadataQueryType = 'TAG_KEYS' | 'TAG_VALUES' | 'MEASUREMENTS' | 'FIELDS' | 'RETENTION_POLICIES';