diff --git a/src/datasource/components/QueryEditor/ProblemsQueryEditor.test.tsx b/src/datasource/components/QueryEditor/ProblemsQueryEditor.test.tsx
new file mode 100644
index 0000000..8a0a09b
--- /dev/null
+++ b/src/datasource/components/QueryEditor/ProblemsQueryEditor.test.tsx
@@ -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) =>
,
+ InlineField: ({ children }: any) => {children}
,
+ InlineFieldRow: ({ children }: any) => {children}
,
+ InlineFormLabel: ({ children }: any) => {children}
,
+ Input: (props: any) => ,
+ MultiSelect: (props: any) => ,
+}));
+
+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 = {}) => {
+ 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();
+
+ 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();
+
+ 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();
+
+ 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);
+ });
+ });
+});
diff --git a/src/datasource/components/QueryEditor/ProblemsQueryEditor.tsx b/src/datasource/components/QueryEditor/ProblemsQueryEditor.tsx
index 916cfa4..8b9d2e6 100644
--- a/src/datasource/components/QueryEditor/ProblemsQueryEditor.tsx
+++ b/src/datasource/components/QueryEditor/ProblemsQueryEditor.tsx
@@ -43,8 +43,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
const loadGroupOptions = async () => {
const groups = await datasource.zabbix.getAllGroups();
const options = groups?.map((group) => ({
- value: group.name,
- label: group.name,
+ value: group.name ?? '',
+ label: group.name ?? '',
}));
options.unshift(...getVariableOptions());
return options;
@@ -58,8 +58,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
const loadHostOptions = async (group: string) => {
const hosts = await datasource.zabbix.getAllHosts(group);
let options: Array> = hosts?.map((host) => ({
- value: host.name,
- label: host.name,
+ value: host.name ?? '',
+ label: host.name ?? '',
}));
options = _.uniqBy(options, (o) => o.value);
options.unshift({ value: '/.*/' });
@@ -75,8 +75,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
const loadAppOptions = async (group: string, host: string) => {
const apps = await datasource.zabbix.getAllApps(group, host);
let options: Array> = apps?.map((app) => ({
- value: app.name,
- label: app.name,
+ value: app.name ?? '',
+ label: app.name ?? '',
}));
options = _.uniqBy(options, (o) => o.value);
options.unshift(...getVariableOptions());
@@ -91,8 +91,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
const loadProxyOptions = async () => {
const proxies = await datasource.zabbix.getProxies();
const options = proxies?.map((proxy) => ({
- value: proxy.host,
- label: proxy.host,
+ value: (!!proxy.host ? proxy.host : proxy.name) ?? '',
+ label: (!!proxy.host ? proxy.host : proxy.name) ?? '',
}));
options.unshift(...getVariableOptions());
return options;