Skip to content

Commit 045c708

Browse files
authored
Fix: ensure that applicationids parameter only gets passed when the datasource supports it. (#2110)
Zabbix 5.0.x supported filtering `Problems` feature with `applications`. When this got removed, we removed the filter dropdown from the UI, but failed to check whether applications were supported before sending out the request with the parameters. This was causing dashboards that had been created with zabbix version `5.0.x` to fail when querying with newer versions of our plugin with error: `Invalid params. Invalid parameter "/": unexpected parameter "applicationids".` These changes now ensure that we also check whether applications filter should be supported before sending the backend request to fetch problems. How to test: - use the attached JSON file. This was created using zabbix50 and applying an `applicationids` filter for `Problems` query type OR - run the `zabbix50` test environment: ``` cd devenv/zabbix50 docker-compose up -d ``` - create a dashboard that queries for `Problems` and filters with applications then export the dashboard JSON - stop the `zabbix50` test environment and start the `zabbix74` test environment ``` docker-compose stop cd ../zabbix74 docker-compose up -d ``` - import the dashboard you created above, it should load and work as expected. Bottom panel was created using zabbix50 and it used the application filter. Both panels now load as expected: <img width="2558" height="1018" alt="Screenshot 2025-10-21 at 2 28 25 PM" src="https://github.com/user-attachments/assets/9613d59b-3f88-420c-9897-f8d988b3d2f0" /> Fixes #1852
1 parent 2d9714a commit 045c708

File tree

5 files changed

+125
-5
lines changed

5 files changed

+125
-5
lines changed

.changeset/cuddly-cloths-join.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'grafana-zabbix': minor
3+
---
4+
5+
Fix support of applicationids filters with Zabbix problems for versions older than 5.0.x

src/datasource/zabbix/connectors/zabbix_api/zabbixAPIConnector.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,58 @@ describe('Zabbix API connector', () => {
102102
expect(result).toBe(0);
103103
});
104104
});
105+
106+
describe('getProblems', () => {
107+
it('sends full filter payload with application ids when supported', async () => {
108+
const zabbixAPIConnector = new ZabbixAPIConnector(true, true, 123);
109+
zabbixAPIConnector.version = '7.0.0';
110+
zabbixAPIConnector.request = jest.fn(() => Promise.resolve([{ eventid: '1' }]));
111+
112+
await zabbixAPIConnector.getProblems(['21'], ['31'], ['41'], true, {
113+
timeFrom: 100,
114+
timeTo: 200,
115+
recent: 'true',
116+
severities: [3, 4],
117+
limit: 50,
118+
acknowledged: 0,
119+
tags: [{ tag: 'service', value: 'foo' }],
120+
evaltype: 1,
121+
});
122+
123+
expect(zabbixAPIConnector.request).toHaveBeenCalledWith('problem.get', {
124+
output: 'extend',
125+
selectAcknowledges: 'extend',
126+
selectSuppressionData: 'extend',
127+
selectTags: 'extend',
128+
source: '0',
129+
object: '0',
130+
sortfield: ['eventid'],
131+
sortorder: 'DESC',
132+
evaltype: 1,
133+
groupids: ['21'],
134+
hostids: ['31'],
135+
applicationids: ['41'],
136+
recent: 'true',
137+
severities: [3, 4],
138+
acknowledged: 0,
139+
tags: [{ tag: 'service', value: 'foo' }],
140+
limit: 50,
141+
time_from: 100,
142+
time_till: 200,
143+
});
144+
});
145+
146+
it('omits applicationids when applications are unsupported', async () => {
147+
const zabbixAPIConnector = new ZabbixAPIConnector(true, true, 123);
148+
zabbixAPIConnector.version = '7.0.0';
149+
zabbixAPIConnector.request = jest.fn(() => Promise.resolve([{ eventid: '1' }]));
150+
151+
await zabbixAPIConnector.getProblems(['21'], ['31'], ['41'], false, {});
152+
153+
const [, params] = (zabbixAPIConnector.request as jest.Mock).mock.calls.at(-1)!;
154+
expect(params.applicationids).toBeUndefined();
155+
});
156+
});
105157
});
106158

107159
const triggers = [

src/datasource/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ export class ZabbixAPIConnector {
482482
return sliResponse;
483483
}
484484

485-
getProblems(groupids, hostids, applicationids, options): Promise<ZBXProblem[]> {
485+
getProblems(groupids, hostids, applicationids, supportsApplications, options): Promise<ZBXProblem[]> {
486486
const { timeFrom, timeTo, recent, severities, limit, acknowledged, tags, evaltype } = options;
487487

488488
const params: any = {
@@ -498,7 +498,6 @@ export class ZabbixAPIConnector {
498498
// preservekeys: '1',
499499
groupids,
500500
hostids,
501-
applicationids,
502501
recent,
503502
};
504503

@@ -527,6 +526,10 @@ export class ZabbixAPIConnector {
527526
params.time_till = timeTo;
528527
}
529528

529+
if (supportsApplications) {
530+
params.applicationids = applicationids;
531+
}
532+
530533
return this.request('problem.get', params).then(utils.mustArray);
531534
}
532535

src/datasource/zabbix/zabbix.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import { Zabbix } from './zabbix';
2+
import { joinTriggersWithEvents } from '../problemsHandler';
3+
4+
jest.mock('../problemsHandler', () => ({
5+
joinTriggersWithEvents: jest.fn(),
6+
joinTriggersWithProblems: jest.fn(),
7+
}));
28

39
jest.mock(
410
'@grafana/runtime',
@@ -110,4 +116,42 @@ describe('Zabbix', () => {
110116
});
111117
});
112118
});
119+
120+
describe('getProblemsHistory', () => {
121+
const ctx = { url: 'http://localhost' };
122+
let zabbix: Zabbix;
123+
124+
beforeEach(() => {
125+
zabbix = new Zabbix(ctx);
126+
zabbix.getGroups = jest.fn().mockResolvedValue([{ groupid: '21' }]);
127+
zabbix.getHosts = jest.fn().mockResolvedValue([{ hostid: '31' }]);
128+
zabbix.getApps = jest.fn().mockResolvedValue([{ applicationid: '41' }]);
129+
zabbix.supportsApplications = jest.fn().mockReturnValue(true);
130+
zabbix.zabbixAPI.getEventsHistory = jest.fn().mockResolvedValue([{ objectid: '501' }]);
131+
zabbix.zabbixAPI.getTriggersByIds = jest.fn().mockResolvedValue([{ triggerid: '501' }]);
132+
(joinTriggersWithEvents as jest.Mock).mockReturnValue([{ triggerid: '501' }]);
133+
zabbix.filterTriggersByProxy = jest.fn().mockResolvedValue([{ triggerid: '501' }]);
134+
});
135+
136+
it('builds the history query and returns filtered triggers', async () => {
137+
const result = await zabbix.getProblemsHistory('group.*', 'host.*', 'app.*', 'proxy-foo', {
138+
valueFromEvent: true,
139+
});
140+
141+
expect(zabbix.zabbixAPI.getEventsHistory).toHaveBeenCalledWith(['21'], ['31'], ['41'], { valueFromEvent: true });
142+
expect(joinTriggersWithEvents).toHaveBeenCalledWith([{ objectid: '501' }], [{ triggerid: '501' }], {
143+
valueFromEvent: true,
144+
});
145+
expect(zabbix.filterTriggersByProxy).toHaveBeenCalledWith([{ triggerid: '501' }], 'proxy-foo');
146+
expect(result).toEqual([{ triggerid: '501' }]);
147+
});
148+
149+
it('omits applicationids when applications are unsupported', async () => {
150+
(zabbix.supportsApplications as jest.Mock).mockReturnValue(false);
151+
152+
await zabbix.getProblemsHistory('group.*', 'host.*', 'app.*', undefined, {});
153+
154+
expect(zabbix.zabbixAPI.getEventsHistory).toHaveBeenCalledWith(['21'], ['31'], undefined, {});
155+
});
156+
});
113157
});

src/datasource/zabbix/zabbix.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,15 @@ export class Zabbix implements ZabbixConnector {
509509

510510
return query;
511511
})
512-
.then((query) => this.zabbixAPI.getProblems(query.groupids, query.hostids, query.applicationids, options))
512+
.then((query) =>
513+
this.zabbixAPI.getProblems(
514+
query.groupids,
515+
query.hostids,
516+
query.applicationids,
517+
this.supportsApplications(),
518+
options
519+
)
520+
)
513521
.then((problems) => {
514522
const triggerids = problems?.map((problem) => problem.objectid);
515523
return Promise.all([Promise.resolve(problems), this.zabbixAPI.getTriggersByIds(triggerids)]);
@@ -533,7 +541,7 @@ export class Zabbix implements ZabbixConnector {
533541
const [filteredGroups, filteredHosts, filteredApps] = results;
534542
const query: any = {};
535543

536-
if (appFilter) {
544+
if (appFilter && this.supportsApplications()) {
537545
query.applicationids = _.flatten(_.map(filteredApps, 'applicationid'));
538546
}
539547
if (hostFilter) {
@@ -579,7 +587,15 @@ export class Zabbix implements ZabbixConnector {
579587

580588
return query;
581589
})
582-
.then((query) => this.zabbixAPI.getProblems(query.groupids, query.hostids, query.applicationids, options))
590+
.then((query) =>
591+
this.zabbixAPI.getProblems(
592+
query.groupids,
593+
query.hostids,
594+
query.applicationids,
595+
this.supportsApplications(),
596+
options
597+
)
598+
)
583599
.then((problems) => findByFilter(problems, triggerFilter))
584600
.then((problems) => {
585601
const triggerids = problems?.map((problem) => problem.objectid);

0 commit comments

Comments
 (0)