InfluxDB: InfluxQL: query editor: skip fields in metadata queries (#42543)

* influxdb: influxql: query editor: skip fields for metadata

* test added

* removed forgotten line

* updated test name

* updated comment

* simplified test code
This commit is contained in:
Gábor Farkas 2021-12-17 15:09:02 +01:00 committed by GitHub
parent db3cc738d6
commit db18acff15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 157 additions and 5 deletions

View File

@ -0,0 +1,125 @@
import React from 'react';
import { InfluxQuery } from '../../types';
import InfluxDatasource from '../../datasource';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Editor } from './Editor';
import * as mockedMeta from '../../influxQLMetadataQuery';
jest.mock('../../influxQLMetadataQuery', () => {
return {
getTagKeysForMeasurementAndTags: jest
.fn()
// first time we are called when the widget mounts,
// we respond by saying `cpu, host` are the real tags
.mockReturnValueOnce(Promise.resolve(['cpu', 'host']))
// afterwards we will be called once when we click
// on a tag-key in the WHERE section.
// it does not matter what we return, as long as it is
// promise-of-a-list-of-strings
.mockReturnValueOnce(Promise.resolve([])),
getTagValues: jest
.fn()
// it does not matter what we return, as long as it is
// promise-of-a-list-of-strings
.mockReturnValueOnce(Promise.resolve([])),
getAllMeasurementsForTags: jest
.fn()
// it does not matter what we return, as long as it is
// promise-of-a-list-of-strings
.mockReturnValueOnce(Promise.resolve([])),
};
});
beforeEach(() => {
(mockedMeta.getTagKeysForMeasurementAndTags as jest.Mock).mockClear();
});
const ONLY_TAGS = [
{
key: 'cpu',
operator: '=',
value: 'cpu1',
},
{
condition: 'AND',
key: 'host',
operator: '=',
value: 'host2',
},
];
const query: InfluxQuery = {
refId: 'A',
policy: 'default',
tags: [
{
key: 'cpu',
operator: '=',
value: 'cpu1',
},
{
condition: 'AND',
key: 'host',
operator: '=',
value: 'host2',
},
{
condition: 'AND',
key: 'field1',
operator: '=',
value: '45',
},
],
select: [
[
{
type: 'field',
params: ['usage_idle'],
},
],
],
measurement: 'cpudata',
};
describe('InfluxDB InfluxQL Visual Editor field-filtering', () => {
it('should not send fields in tag-structures to metadata queries', async () => {
const onChange = jest.fn();
const onRunQuery = jest.fn();
const datasource: InfluxDatasource = ({
metricFindQuery: () => Promise.resolve([]),
} as unknown) as InfluxDatasource;
render(<Editor query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />);
// when the editor-widget mounts, it calls getTagKeysForMeasurementAndTags
expect(mockedMeta.getTagKeysForMeasurementAndTags).toHaveBeenCalledTimes(1);
// we click the WHERE/cpu button
await act(async () => {
userEvent.click(screen.getByRole('button', { name: 'cpu' }));
});
// and verify getTagKeysForMeasurementAndTags was called again,
// and in the tags-param we did not receive the `field1` part.
expect(mockedMeta.getTagKeysForMeasurementAndTags).toHaveBeenCalledTimes(2);
expect((mockedMeta.getTagKeysForMeasurementAndTags as jest.Mock).mock.calls[1][2]).toStrictEqual(ONLY_TAGS);
// now we click on the WHERE/host2 button
await act(async () => {
userEvent.click(screen.getByRole('button', { name: 'host2' }));
});
// 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);
// now we click on the FROM/cpudata button
await act(async () => {
userEvent.click(screen.getByRole('button', { name: 'cpudata' }));
});
// verify `getTagValues` was called once, and in the tags-param we did not receive `field1`
expect(mockedMeta.getAllMeasurementsForTags).toHaveBeenCalledTimes(1);
expect((mockedMeta.getAllMeasurementsForTags as jest.Mock).mock.calls[0][1]).toStrictEqual(ONLY_TAGS);
});
});

View File

@ -36,7 +36,9 @@ jest.mock('./Seg', () => {
function assertEditor(query: InfluxQuery, textContent: string) {
const onChange = jest.fn();
const onRunQuery = jest.fn();
const datasource: InfluxDatasource = {} as InfluxDatasource;
const datasource: InfluxDatasource = ({
metricFindQuery: () => Promise.resolve([]),
} as unknown) as InfluxDatasource;
const { container } = render(
<Editor query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
);

View File

@ -53,6 +53,12 @@ function withTemplateVariableOptions(optionsPromise: Promise<string[]>): Promise
return optionsPromise.then((options) => [...getTemplateVariableOptions(), ...options]);
}
// it is possible to add fields into the `InfluxQueryTag` structures, and they do work,
// but in some cases, when we do metadata queries, we have to remove them from the queries.
function filterTags(parts: InfluxQueryTag[], allTagKeys: Set<string>): InfluxQueryTag[] {
return parts.filter((t) => allTagKeys.has(t.key));
}
export const Editor = (props: Props): JSX.Element => {
const uniqueId = useUniqueId();
const formatAsId = `influxdb-qe-format-as-${uniqueId}`;
@ -63,6 +69,12 @@ export const Editor = (props: Props): JSX.Element => {
const { datasource } = props;
const { measurement, policy } = query;
const allTagKeys = useMemo(() => {
return getTagKeysForMeasurementAndTags(measurement, policy, [], datasource).then((tags) => {
return new Set(tags);
});
}, [measurement, policy, datasource]);
const selectLists = useMemo(() => {
const dynamicSelectPartOptions = new Map([
[
@ -80,8 +92,11 @@ export const Editor = (props: Props): JSX.Element => {
// the following function is not complicated enough to memoize, but it's result
// is used in both memoized and un-memoized parts, so we have no choice
const getTagKeys = useMemo(() => {
return () => getTagKeysForMeasurementAndTags(measurement, policy, query.tags ?? [], datasource);
}, [measurement, policy, query.tags, datasource]);
return () =>
allTagKeys.then((keys) =>
getTagKeysForMeasurementAndTags(measurement, policy, filterTags(query.tags ?? [], keys), datasource)
);
}, [measurement, policy, query.tags, datasource, allTagKeys]);
const groupByList = useMemo(() => {
const dynamicGroupByPartOptions = new Map([['tag_0', getTagKeys]]);
@ -118,7 +133,13 @@ export const Editor = (props: Props): JSX.Element => {
getPolicyOptions={() => getAllPolicies(datasource)}
getMeasurementOptions={(filter) =>
withTemplateVariableOptions(
getAllMeasurementsForTags(filter === '' ? undefined : filter, query.tags ?? [], datasource)
allTagKeys.then((keys) =>
getAllMeasurementsForTags(
filter === '' ? undefined : filter,
filterTags(query.tags ?? [], keys),
datasource
)
)
)
}
onChange={handleFromSectionChange}
@ -131,7 +152,11 @@ export const Editor = (props: Props): JSX.Element => {
onChange={handleTagsSectionChange}
getTagKeyOptions={getTagKeys}
getTagValueOptions={(key: string) =>
withTemplateVariableOptions(getTagValues(key, measurement, policy, query.tags ?? [], datasource))
withTemplateVariableOptions(
allTagKeys.then((keys) =>
getTagValues(key, measurement, policy, filterTags(query.tags ?? [], keys), datasource)
)
)
}
/>
</SegmentSection>