Fix proxies dropdown in Problems query editor (#2200)
Part 2 of #2197
Zabbix has two ways of processing and returning proxies depending on the
zabbix version being used:
1. Before 7.0.0 it uses `host`
2. After 7.0.0 it uses `name`
when we made the call to the backend, we accounted for this
[difference](592380851c/src/datasource/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts (L940C5-L944C6)).
However, in the frontend, we always populated the dropdown using
`proxy.host` regardless of the version customers were using.
So for customers that had proxies in their zabbix set up AND were using
a zabbix version `>-7.0.0`, the query editor would crash because we
ended up with a list of undefined options.
This PR changes it so that when `host` is not present, it uses `name` or
otherwise defaults to `''` to ensure that we never have and array of
options with undefined values.
This commit is contained in:
committed by
GitHub
parent
592380851c
commit
3a4f3280be
@@ -0,0 +1,132 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, waitFor } from '@testing-library/react';
|
||||||
|
import { ProblemsQueryEditor } from './ProblemsQueryEditor';
|
||||||
|
import { ShowProblemTypes, ZabbixTagEvalType } from '../../types/query';
|
||||||
|
|
||||||
|
const metricPickerSpy = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('../../../components', () => ({
|
||||||
|
MetricPicker: (props: any) => {
|
||||||
|
metricPickerSpy(props);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
getTemplateSrv: jest.fn(() => ({
|
||||||
|
getVariables: jest.fn(() => []),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@grafana/ui', () => ({
|
||||||
|
Combobox: (props: any) => <div {...props} />,
|
||||||
|
InlineField: ({ children }: any) => <div>{children}</div>,
|
||||||
|
InlineFieldRow: ({ children }: any) => <div>{children}</div>,
|
||||||
|
InlineFormLabel: ({ children }: any) => <div>{children}</div>,
|
||||||
|
Input: (props: any) => <input {...props} />,
|
||||||
|
MultiSelect: (props: any) => <div {...props} />,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const baseQuery: any = {
|
||||||
|
group: { filter: '' },
|
||||||
|
host: { filter: '' },
|
||||||
|
proxy: { filter: '' },
|
||||||
|
application: { filter: '' },
|
||||||
|
trigger: { filter: '' },
|
||||||
|
tags: { filter: '' },
|
||||||
|
evaltype: ZabbixTagEvalType.AndOr,
|
||||||
|
showProblems: ShowProblemTypes.Problems,
|
||||||
|
options: { severities: [] },
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildDatasource = (overrides: Partial<any> = {}) => {
|
||||||
|
const zabbix = {
|
||||||
|
getAllGroups: jest.fn().mockResolvedValue([]),
|
||||||
|
getAllHosts: jest.fn().mockResolvedValue([]),
|
||||||
|
getAllApps: jest.fn().mockResolvedValue([]),
|
||||||
|
getProxies: jest.fn().mockResolvedValue([]),
|
||||||
|
supportsApplications: jest.fn(() => true),
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
zabbix,
|
||||||
|
interpolateVariablesInQueries: jest.fn((queries: any[]) => queries),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ProblemsQueryEditor', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
metricPickerSpy.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses proxy name when host is missing', async () => {
|
||||||
|
const datasource = buildDatasource({
|
||||||
|
getProxies: jest.fn().mockResolvedValue([{ name: 'proxy-a' }]),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ProblemsQueryEditor query={baseQuery} datasource={datasource as any} onChange={jest.fn()} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const proxyCall = metricPickerSpy.mock.calls
|
||||||
|
.map((call) => call[0])
|
||||||
|
.find((props) => props?.placeholder === 'Proxy name' && props?.options);
|
||||||
|
|
||||||
|
expect(proxyCall).toBeTruthy();
|
||||||
|
expect(proxyCall.options).toEqual([{ value: 'proxy-a', label: 'proxy-a' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses proxy host when present', async () => {
|
||||||
|
const datasource = buildDatasource({
|
||||||
|
getProxies: jest.fn().mockResolvedValue([{ host: 'legacy-proxy' }]),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ProblemsQueryEditor query={baseQuery} datasource={datasource as any} onChange={jest.fn()} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const proxyCall = metricPickerSpy.mock.calls
|
||||||
|
.map((call) => call[0])
|
||||||
|
.find((props) => props?.placeholder === 'Proxy name' && props?.options);
|
||||||
|
|
||||||
|
expect(proxyCall).toBeTruthy();
|
||||||
|
expect(proxyCall.options).toEqual([{ value: 'legacy-proxy', label: 'legacy-proxy' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults missing option values to empty strings', async () => {
|
||||||
|
const datasource = buildDatasource({
|
||||||
|
getAllGroups: jest.fn().mockResolvedValue([{ name: 'group-a' }, { name: '' }, {}]),
|
||||||
|
getAllHosts: jest.fn().mockResolvedValue([{ name: 'host-a' }, { name: '' }, {}]),
|
||||||
|
getAllApps: jest.fn().mockResolvedValue([{ name: 'app-a' }, { name: '' }, {}]),
|
||||||
|
getProxies: jest.fn().mockResolvedValue([{ name: '' }, { host: '' }, { name: 'proxy-a' }]),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ProblemsQueryEditor query={baseQuery} datasource={datasource as any} onChange={jest.fn()} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const groupCall = metricPickerSpy.mock.calls
|
||||||
|
.map((call) => call[0])
|
||||||
|
.find((props) => props?.placeholder === 'Group name' && props?.options);
|
||||||
|
const hostCall = metricPickerSpy.mock.calls
|
||||||
|
.map((call) => call[0])
|
||||||
|
.find((props) => props?.placeholder === 'Host name' && props?.options);
|
||||||
|
const appCall = metricPickerSpy.mock.calls
|
||||||
|
.map((call) => call[0])
|
||||||
|
.find((props) => props?.placeholder === 'Application name' && props?.options);
|
||||||
|
const proxyCall = metricPickerSpy.mock.calls
|
||||||
|
.map((call) => call[0])
|
||||||
|
.find((props) => props?.placeholder === 'Proxy name' && props?.options);
|
||||||
|
|
||||||
|
const hasValidValues = (options: any[]) =>
|
||||||
|
options.every(
|
||||||
|
(option) => option.value !== undefined && (option.label !== undefined || option.value === '/.*/')
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(hasValidValues(groupCall.options)).toBe(true);
|
||||||
|
expect(hasValidValues(hostCall.options)).toBe(true);
|
||||||
|
expect(hasValidValues(appCall.options)).toBe(true);
|
||||||
|
expect(hasValidValues(proxyCall.options)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -43,8 +43,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const groups = await datasource.zabbix.getAllGroups();
|
const groups = await datasource.zabbix.getAllGroups();
|
||||||
const options = groups?.map((group) => ({
|
const options = groups?.map((group) => ({
|
||||||
value: group.name,
|
value: group.name ?? '',
|
||||||
label: group.name,
|
label: group.name ?? '',
|
||||||
}));
|
}));
|
||||||
options.unshift(...getVariableOptions());
|
options.unshift(...getVariableOptions());
|
||||||
return options;
|
return options;
|
||||||
@@ -58,8 +58,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name ?? '',
|
||||||
label: host.name,
|
label: host.name ?? '',
|
||||||
}));
|
}));
|
||||||
options = _.uniqBy(options, (o) => o.value);
|
options = _.uniqBy(options, (o) => o.value);
|
||||||
options.unshift({ value: '/.*/' });
|
options.unshift({ value: '/.*/' });
|
||||||
@@ -75,8 +75,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name ?? '',
|
||||||
label: app.name,
|
label: app.name ?? '',
|
||||||
}));
|
}));
|
||||||
options = _.uniqBy(options, (o) => o.value);
|
options = _.uniqBy(options, (o) => o.value);
|
||||||
options.unshift(...getVariableOptions());
|
options.unshift(...getVariableOptions());
|
||||||
@@ -91,8 +91,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
const loadProxyOptions = async () => {
|
const loadProxyOptions = async () => {
|
||||||
const proxies = await datasource.zabbix.getProxies();
|
const proxies = await datasource.zabbix.getProxies();
|
||||||
const options = proxies?.map((proxy) => ({
|
const options = proxies?.map((proxy) => ({
|
||||||
value: proxy.host,
|
value: (!!proxy.host ? proxy.host : proxy.name) ?? '',
|
||||||
label: proxy.host,
|
label: (!!proxy.host ? proxy.host : proxy.name) ?? '',
|
||||||
}));
|
}));
|
||||||
options.unshift(...getVariableOptions());
|
options.unshift(...getVariableOptions());
|
||||||
return options;
|
return options;
|
||||||
|
|||||||
Reference in New Issue
Block a user