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 https://github.com/grafana/grafana-zabbix/issues/1852
This commit is contained in:
committed by
GitHub
parent
2d9714a4db
commit
045c708c69
5
.changeset/cuddly-cloths-join.md
Normal file
5
.changeset/cuddly-cloths-join.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'grafana-zabbix': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix support of applicationids filters with Zabbix problems for versions older than 5.0.x
|
||||||
@@ -102,6 +102,58 @@ describe('Zabbix API connector', () => {
|
|||||||
expect(result).toBe(0);
|
expect(result).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getProblems', () => {
|
||||||
|
it('sends full filter payload with application ids when supported', async () => {
|
||||||
|
const zabbixAPIConnector = new ZabbixAPIConnector(true, true, 123);
|
||||||
|
zabbixAPIConnector.version = '7.0.0';
|
||||||
|
zabbixAPIConnector.request = jest.fn(() => Promise.resolve([{ eventid: '1' }]));
|
||||||
|
|
||||||
|
await zabbixAPIConnector.getProblems(['21'], ['31'], ['41'], true, {
|
||||||
|
timeFrom: 100,
|
||||||
|
timeTo: 200,
|
||||||
|
recent: 'true',
|
||||||
|
severities: [3, 4],
|
||||||
|
limit: 50,
|
||||||
|
acknowledged: 0,
|
||||||
|
tags: [{ tag: 'service', value: 'foo' }],
|
||||||
|
evaltype: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(zabbixAPIConnector.request).toHaveBeenCalledWith('problem.get', {
|
||||||
|
output: 'extend',
|
||||||
|
selectAcknowledges: 'extend',
|
||||||
|
selectSuppressionData: 'extend',
|
||||||
|
selectTags: 'extend',
|
||||||
|
source: '0',
|
||||||
|
object: '0',
|
||||||
|
sortfield: ['eventid'],
|
||||||
|
sortorder: 'DESC',
|
||||||
|
evaltype: 1,
|
||||||
|
groupids: ['21'],
|
||||||
|
hostids: ['31'],
|
||||||
|
applicationids: ['41'],
|
||||||
|
recent: 'true',
|
||||||
|
severities: [3, 4],
|
||||||
|
acknowledged: 0,
|
||||||
|
tags: [{ tag: 'service', value: 'foo' }],
|
||||||
|
limit: 50,
|
||||||
|
time_from: 100,
|
||||||
|
time_till: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('omits applicationids when applications are unsupported', async () => {
|
||||||
|
const zabbixAPIConnector = new ZabbixAPIConnector(true, true, 123);
|
||||||
|
zabbixAPIConnector.version = '7.0.0';
|
||||||
|
zabbixAPIConnector.request = jest.fn(() => Promise.resolve([{ eventid: '1' }]));
|
||||||
|
|
||||||
|
await zabbixAPIConnector.getProblems(['21'], ['31'], ['41'], false, {});
|
||||||
|
|
||||||
|
const [, params] = (zabbixAPIConnector.request as jest.Mock).mock.calls.at(-1)!;
|
||||||
|
expect(params.applicationids).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const triggers = [
|
const triggers = [
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ export class ZabbixAPIConnector {
|
|||||||
return sliResponse;
|
return sliResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProblems(groupids, hostids, applicationids, options): Promise<ZBXProblem[]> {
|
getProblems(groupids, hostids, applicationids, supportsApplications, options): Promise<ZBXProblem[]> {
|
||||||
const { timeFrom, timeTo, recent, severities, limit, acknowledged, tags, evaltype } = options;
|
const { timeFrom, timeTo, recent, severities, limit, acknowledged, tags, evaltype } = options;
|
||||||
|
|
||||||
const params: any = {
|
const params: any = {
|
||||||
@@ -498,7 +498,6 @@ export class ZabbixAPIConnector {
|
|||||||
// preservekeys: '1',
|
// preservekeys: '1',
|
||||||
groupids,
|
groupids,
|
||||||
hostids,
|
hostids,
|
||||||
applicationids,
|
|
||||||
recent,
|
recent,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -527,6 +526,10 @@ export class ZabbixAPIConnector {
|
|||||||
params.time_till = timeTo;
|
params.time_till = timeTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (supportsApplications) {
|
||||||
|
params.applicationids = applicationids;
|
||||||
|
}
|
||||||
|
|
||||||
return this.request('problem.get', params).then(utils.mustArray);
|
return this.request('problem.get', params).then(utils.mustArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { Zabbix } from './zabbix';
|
import { Zabbix } from './zabbix';
|
||||||
|
import { joinTriggersWithEvents } from '../problemsHandler';
|
||||||
|
|
||||||
|
jest.mock('../problemsHandler', () => ({
|
||||||
|
joinTriggersWithEvents: jest.fn(),
|
||||||
|
joinTriggersWithProblems: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'@grafana/runtime',
|
'@grafana/runtime',
|
||||||
@@ -110,4 +116,42 @@ describe('Zabbix', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getProblemsHistory', () => {
|
||||||
|
const ctx = { url: 'http://localhost' };
|
||||||
|
let zabbix: Zabbix;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
zabbix = new Zabbix(ctx);
|
||||||
|
zabbix.getGroups = jest.fn().mockResolvedValue([{ groupid: '21' }]);
|
||||||
|
zabbix.getHosts = jest.fn().mockResolvedValue([{ hostid: '31' }]);
|
||||||
|
zabbix.getApps = jest.fn().mockResolvedValue([{ applicationid: '41' }]);
|
||||||
|
zabbix.supportsApplications = jest.fn().mockReturnValue(true);
|
||||||
|
zabbix.zabbixAPI.getEventsHistory = jest.fn().mockResolvedValue([{ objectid: '501' }]);
|
||||||
|
zabbix.zabbixAPI.getTriggersByIds = jest.fn().mockResolvedValue([{ triggerid: '501' }]);
|
||||||
|
(joinTriggersWithEvents as jest.Mock).mockReturnValue([{ triggerid: '501' }]);
|
||||||
|
zabbix.filterTriggersByProxy = jest.fn().mockResolvedValue([{ triggerid: '501' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds the history query and returns filtered triggers', async () => {
|
||||||
|
const result = await zabbix.getProblemsHistory('group.*', 'host.*', 'app.*', 'proxy-foo', {
|
||||||
|
valueFromEvent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(zabbix.zabbixAPI.getEventsHistory).toHaveBeenCalledWith(['21'], ['31'], ['41'], { valueFromEvent: true });
|
||||||
|
expect(joinTriggersWithEvents).toHaveBeenCalledWith([{ objectid: '501' }], [{ triggerid: '501' }], {
|
||||||
|
valueFromEvent: true,
|
||||||
|
});
|
||||||
|
expect(zabbix.filterTriggersByProxy).toHaveBeenCalledWith([{ triggerid: '501' }], 'proxy-foo');
|
||||||
|
expect(result).toEqual([{ triggerid: '501' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('omits applicationids when applications are unsupported', async () => {
|
||||||
|
(zabbix.supportsApplications as jest.Mock).mockReturnValue(false);
|
||||||
|
|
||||||
|
await zabbix.getProblemsHistory('group.*', 'host.*', 'app.*', undefined, {});
|
||||||
|
|
||||||
|
expect(zabbix.zabbixAPI.getEventsHistory).toHaveBeenCalledWith(['21'], ['31'], undefined, {});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -509,7 +509,15 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
|
|
||||||
return query;
|
return query;
|
||||||
})
|
})
|
||||||
.then((query) => this.zabbixAPI.getProblems(query.groupids, query.hostids, query.applicationids, options))
|
.then((query) =>
|
||||||
|
this.zabbixAPI.getProblems(
|
||||||
|
query.groupids,
|
||||||
|
query.hostids,
|
||||||
|
query.applicationids,
|
||||||
|
this.supportsApplications(),
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
.then((problems) => {
|
.then((problems) => {
|
||||||
const triggerids = problems?.map((problem) => problem.objectid);
|
const triggerids = problems?.map((problem) => problem.objectid);
|
||||||
return Promise.all([Promise.resolve(problems), this.zabbixAPI.getTriggersByIds(triggerids)]);
|
return Promise.all([Promise.resolve(problems), this.zabbixAPI.getTriggersByIds(triggerids)]);
|
||||||
@@ -533,7 +541,7 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
const [filteredGroups, filteredHosts, filteredApps] = results;
|
const [filteredGroups, filteredHosts, filteredApps] = results;
|
||||||
const query: any = {};
|
const query: any = {};
|
||||||
|
|
||||||
if (appFilter) {
|
if (appFilter && this.supportsApplications()) {
|
||||||
query.applicationids = _.flatten(_.map(filteredApps, 'applicationid'));
|
query.applicationids = _.flatten(_.map(filteredApps, 'applicationid'));
|
||||||
}
|
}
|
||||||
if (hostFilter) {
|
if (hostFilter) {
|
||||||
@@ -579,7 +587,15 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
|
|
||||||
return query;
|
return query;
|
||||||
})
|
})
|
||||||
.then((query) => this.zabbixAPI.getProblems(query.groupids, query.hostids, query.applicationids, options))
|
.then((query) =>
|
||||||
|
this.zabbixAPI.getProblems(
|
||||||
|
query.groupids,
|
||||||
|
query.hostids,
|
||||||
|
query.applicationids,
|
||||||
|
this.supportsApplications(),
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
.then((problems) => findByFilter(problems, triggerFilter))
|
.then((problems) => findByFilter(problems, triggerFilter))
|
||||||
.then((problems) => {
|
.then((problems) => {
|
||||||
const triggerids = problems?.map((problem) => problem.objectid);
|
const triggerids = problems?.map((problem) => problem.objectid);
|
||||||
|
|||||||
Reference in New Issue
Block a user