mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
InfluxDB: Refactor query_builder and metadata_query (#69550)
* Move useUniqueId to a general place * Use new built-in useId hook * Rename query builder and metadata query * Move and rename the query builder tests * Refactor query_builder and metadata_query * Fix test * Fix test
This commit is contained in:
parent
f1178e0b81
commit
ae0f94e616
@ -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"]
|
||||
],
|
||||
|
@ -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' }));
|
||||
|
@ -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(
|
||||
<Editor query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
|
||||
);
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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<InfluxQuery,
|
||||
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<InfluxOptions>,
|
||||
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||
readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||
) {
|
||||
super(instanceSettings);
|
||||
|
||||
@ -310,6 +310,19 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
};
|
||||
}
|
||||
|
||||
async runMetadataQuery(target: InfluxQuery): Promise<MetricFindValue[]> {
|
||||
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<MetricFindValue[]> {
|
||||
if (this.isFlux || this.isMigrationToggleOnAndIsAccessProxy()) {
|
||||
const target: InfluxQuery = {
|
||||
@ -348,14 +361,25 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
// By implementing getTagKeys and getTagValues we add ad-hoc filters functionality
|
||||
// Used in public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx::fetchFilterKeys
|
||||
getTagKeys(options: any = {}) {
|
||||
const queryBuilder = new InfluxQueryBuilder({ measurement: options.measurement || '', tags: [] }, this.database);
|
||||
const query = queryBuilder.buildExploreQuery('TAG_KEYS');
|
||||
const query = buildMetadataQuery({
|
||||
type: 'TAG_KEYS',
|
||||
templateService: this.templateSrv,
|
||||
database: this.database,
|
||||
measurement: options.measurement || '',
|
||||
tags: [],
|
||||
});
|
||||
return this.metricFindQuery(query, options);
|
||||
}
|
||||
|
||||
getTagValues(options: any = {}) {
|
||||
const queryBuilder = new InfluxQueryBuilder({ measurement: options.measurement || '', tags: [] }, this.database);
|
||||
const query = queryBuilder.buildExploreQuery('TAG_VALUES', options.key);
|
||||
const query = buildMetadataQuery({
|
||||
type: 'TAG_VALUES',
|
||||
templateService: this.templateSrv,
|
||||
database: this.database,
|
||||
withKey: options.key,
|
||||
measurement: options.measurement || '',
|
||||
tags: [],
|
||||
});
|
||||
return this.metricFindQuery(query, options);
|
||||
}
|
||||
|
||||
|
@ -1,71 +1,99 @@
|
||||
import InfluxDatasource from './datasource';
|
||||
import { InfluxQueryBuilder } from './influxql_query_builder';
|
||||
import { replaceHardCodedRetentionPolicy } from './queryUtils';
|
||||
import { InfluxQueryTag } from './types';
|
||||
import { ScopedVars } from '@grafana/data/src';
|
||||
import config from 'app/core/config';
|
||||
|
||||
const runExploreQuery = (
|
||||
type: string,
|
||||
withKey: string | undefined,
|
||||
withMeasurementFilter: string | undefined,
|
||||
target: { measurement: string | undefined; tags: InfluxQueryTag[]; policy: string | undefined },
|
||||
datasource: InfluxDatasource
|
||||
): Promise<Array<{ text: string }>> => {
|
||||
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<Array<{ text: string }>> => {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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);
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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<InfluxQueryTag, string[]>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
63
public/app/plugins/datasource/influxdb/specs/mocks.ts
Normal file
63
public/app/plugins/datasource/influxdb/specs/mocks.ts
Normal file
@ -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<InfluxOptions>): InfluxDatasource {
|
||||
return new InfluxDatasource(instanceSettings, templateSrvStub);
|
||||
}
|
||||
|
||||
export function getMockDSInstanceSettings(): DataSourceInstanceSettings<InfluxOptions> {
|
||||
return {
|
||||
id: 123,
|
||||
url: 'proxied',
|
||||
access: 'proxy',
|
||||
name: 'influxDb',
|
||||
readOnly: false,
|
||||
uid: 'influxdb-test',
|
||||
type: 'influxdb',
|
||||
meta: {
|
||||
id: 'influxdb-meta',
|
||||
type: PluginType.datasource,
|
||||
name: 'influxdb-test',
|
||||
info: {
|
||||
author: {
|
||||
name: 'observability-metrics',
|
||||
},
|
||||
version: 'v0.0.1',
|
||||
description: 'test',
|
||||
links: [],
|
||||
logos: {
|
||||
large: '',
|
||||
small: '',
|
||||
},
|
||||
updated: '',
|
||||
screenshots: [],
|
||||
},
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
},
|
||||
jsonData: { version: InfluxVersion.InfluxQL, httpMode: 'POST' },
|
||||
};
|
||||
}
|
@ -77,3 +77,5 @@ export interface InfluxQuery extends DataQuery {
|
||||
textEditor?: boolean;
|
||||
adhocFilters?: AdHocVariableFilter[];
|
||||
}
|
||||
|
||||
export type MetadataQueryType = 'TAG_KEYS' | 'TAG_VALUES' | 'MEASUREMENTS' | 'FIELDS' | 'RETENTION_POLICIES';
|
||||
|
Loading…
Reference in New Issue
Block a user