Skip to content

Commit 86b7328

Browse files
Variables: Allow fetching disabled items for Item type variable (#2109)
This PR adds support for showing disabled items when using the `Item` type template variable. Similar to how we support disabled items today in our query editor: <img width="435" height="254" alt="Screenshot 2025-10-21 at 9 00 11 AM" src="https://github.com/user-attachments/assets/832537c8-84c3-45fe-a85d-b16c8e15f759" /> In this example, the host contains a disabled item `CPU iowait time` <img width="1763" height="46" alt="Screenshot 2025-10-21 at 9 02 08 AM" src="https://github.com/user-attachments/assets/85419e88-280d-4dce-baee-bf403e1de05d" /> Which we can now show and hide from the variable in Grafana: https://github.com/user-attachments/assets/eca9327e-40a6-4852-92e9-71ff1ad9ea32 I also removed some deprecated types and packages :)! Fixes: #2025 --------- Co-authored-by: Copilot <[email protected]>
1 parent 045c708 commit 86b7328

File tree

5 files changed

+107
-40
lines changed

5 files changed

+107
-40
lines changed

.changeset/nasty-webs-repeat.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+
Add support for disabled items in Item variable type

src/datasource/components/VariableQueryEditor.tsx

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React, { PureComponent } from 'react';
22
import { parseLegacyVariableQuery } from '../utils';
3-
import { SelectableValue } from '@grafana/data';
43
import { VariableQuery, VariableQueryData, VariableQueryProps, VariableQueryTypes } from '../types';
54
import { ZabbixInput } from './ZabbixInput';
6-
import { InlineField, InlineFieldRow, InlineFormLabel, Input, Select } from '@grafana/ui';
5+
import { Combobox, ComboboxOption, InlineField, InlineFieldRow, InlineFormLabel, Input, Switch } from '@grafana/ui';
76

87
export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps, VariableQueryData> {
9-
queryTypes: Array<SelectableValue<VariableQueryTypes>> = [
8+
queryTypes: Array<ComboboxOption<VariableQueryTypes>> = [
109
{ value: VariableQueryTypes.Group, label: 'Group' },
1110
{ value: VariableQueryTypes.Host, label: 'Host' },
1211
{ value: VariableQueryTypes.Application, label: 'Application' },
@@ -23,6 +22,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
2322
application: '',
2423
itemTag: '',
2524
item: '',
25+
showDisabledItems: false,
2626
};
2727

2828
constructor(props: VariableQueryProps) {
@@ -69,34 +69,48 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
6969
};
7070

7171
handleQueryChange = () => {
72-
const { queryType, group, host, application, itemTag, item } = this.state;
73-
const queryModel = { queryType, group, host, application, itemTag, item };
72+
const { queryType, group, host, application, itemTag, item, showDisabledItems } = this.state;
73+
const queryModel = { queryType, group, host, application, itemTag, item, showDisabledItems };
7474
this.props.onChange(queryModel, `Zabbix - ${queryType}`);
7575
};
7676

77-
handleQueryTypeChange = (selectedItem: SelectableValue<VariableQueryTypes>) => {
77+
handleQueryTypeChange = (selectedItem: ComboboxOption<VariableQueryTypes>) => {
7878
this.setState({
7979
...this.state,
8080
selectedQueryType: selectedItem,
8181
queryType: selectedItem.value,
8282
});
8383

84-
const { group, host, application, itemTag, item } = this.state;
84+
const { group, host, application, itemTag, item, showDisabledItems } = this.state;
8585
const queryType = selectedItem.value;
86-
const queryModel = { queryType, group, host, application, itemTag, item };
86+
const queryModel = { queryType, group, host, application, itemTag, item, showDisabledItems };
87+
this.props.onChange(queryModel, `Zabbix - ${queryType}`);
88+
};
89+
90+
handleShowDisabledItemsChange = (evt: React.FormEvent<HTMLInputElement>) => {
91+
const showDisabledItems = (evt.target as any).checked;
92+
this.setState((prevState: VariableQueryData) => {
93+
return {
94+
...prevState,
95+
showDisabledItems: showDisabledItems,
96+
};
97+
});
98+
99+
const { queryType, group, host, application, itemTag, item } = this.state;
100+
const queryModel = { queryType, group, host, application, itemTag, item, showDisabledItems };
87101
this.props.onChange(queryModel, `Zabbix - ${queryType}`);
88102
};
89103

90104
render() {
91-
const { selectedQueryType, legacyQuery, group, host, application, itemTag, item } = this.state;
105+
const { selectedQueryType, legacyQuery, group, host, application, itemTag, item, showDisabledItems } = this.state;
92106
const { datasource } = this.props;
93107
const supportsItemTags = datasource?.zabbix?.isZabbix54OrHigherSync() || false;
94108

95109
return (
96110
<>
97111
<InlineFieldRow>
98-
<InlineField label="Query Type" labelWidth={16}>
99-
<Select
112+
<InlineField label="Query Type" labelWidth={18}>
113+
<Combobox
100114
width={30}
101115
value={selectedQueryType}
102116
options={this.queryTypes}
@@ -106,7 +120,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
106120
</InlineFieldRow>
107121

108122
<InlineFieldRow>
109-
<InlineField label="Group" labelWidth={16}>
123+
<InlineField label="Group" labelWidth={18}>
110124
<ZabbixInput
111125
width={30}
112126
value={group}
@@ -118,7 +132,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
118132

119133
{selectedQueryType.value !== VariableQueryTypes.Group && (
120134
<InlineFieldRow>
121-
<InlineField label="Host" labelWidth={16}>
135+
<InlineField label="Host" labelWidth={18}>
122136
<ZabbixInput
123137
width={30}
124138
value={host}
@@ -136,7 +150,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
136150
<>
137151
{supportsItemTags && (
138152
<InlineFieldRow>
139-
<InlineField label="Item Tag" labelWidth={16}>
153+
<InlineField label="Item Tag" labelWidth={18}>
140154
<ZabbixInput
141155
width={30}
142156
value={itemTag}
@@ -149,7 +163,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
149163

150164
{!supportsItemTags && (
151165
<InlineFieldRow>
152-
<InlineField label="Application" labelWidth={16}>
166+
<InlineField label="Application" labelWidth={18}>
153167
<ZabbixInput
154168
width={30}
155169
value={application}
@@ -163,7 +177,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
163177
{(selectedQueryType.value === VariableQueryTypes.Item ||
164178
selectedQueryType.value === VariableQueryTypes.ItemValues) && (
165179
<InlineFieldRow>
166-
<InlineField label="Item" labelWidth={16}>
180+
<InlineField label="Item" labelWidth={18}>
167181
<ZabbixInput
168182
width={30}
169183
value={item}
@@ -184,6 +198,15 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
184198
<Input value={legacyQuery} readOnly={true} />
185199
</>
186200
)}
201+
{selectedQueryType.value === VariableQueryTypes.Item && (
202+
<>
203+
<InlineFieldRow>
204+
<InlineField label="Show disabled items" labelWidth={18} style={{ alignItems: 'center' }}>
205+
<Switch value={showDisabledItems} onChange={this.handleShowDisabledItemsChange} />
206+
</InlineField>
207+
</InlineFieldRow>
208+
</>
209+
)}
187210
</>
188211
);
189212
}

src/datasource/datasource.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import responseHandler from './responseHandler';
1111
import problemsHandler from './problemsHandler';
1212
import { Zabbix } from './zabbix/zabbix';
1313
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPIConnector';
14-
import { ProblemDTO, VariableQueryTypes } from './types';
14+
import { LegacyVariableQuery, ProblemDTO, VariableQuery, VariableQueryTypes } from './types';
1515
import { ZabbixMetricsQuery, ShowProblemTypes } from './types/query';
1616
import { ZabbixDSOptions } from './types/config';
1717
import {
@@ -36,6 +36,7 @@ import {
3636
} from '@grafana/data';
3737
import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
3838
import { trackRequest } from './tracking';
39+
import { lastValueFrom } from 'rxjs';
3940

4041
export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDSOptions> {
4142
name: string;
@@ -212,14 +213,14 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
212213

213214
let rsp: any;
214215
try {
215-
rsp = await getBackendSrv()
216-
.fetch({
216+
rsp = await lastValueFrom(
217+
getBackendSrv().fetch({
217218
url: '/api/ds/query',
218219
method: 'POST',
219220
data: body,
220221
requestId,
221222
})
222-
.toPromise();
223+
);
223224
} catch (err) {
224225
return toDataQueryResponse(err);
225226
}
@@ -404,7 +405,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
404405
},
405406
};
406407

407-
const response: any = await getBackendSrv().fetch<any>(requestOptions).toPromise();
408+
const response: any = await lastValueFrom(getBackendSrv().fetch<any>(requestOptions));
408409
return response.data;
409410
}
410411

@@ -813,12 +814,12 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
813814
/**
814815
* Find metrics from templated request.
815816
*
816-
* @param {string} query Query from Templating
817+
* @param {LegacyVariableQuery} query Query from Templating
817818
* @param options
818819
* @return {string} Metric name - group, host, app or item or list
819820
* of metrics in "{metric1, metric2,..., metricN}" format.
820821
*/
821-
metricFindQuery(query, options) {
822+
metricFindQuery(query: LegacyVariableQuery, options) {
822823
let resultPromise;
823824
let queryModel = _.cloneDeep(query);
824825

@@ -835,6 +836,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
835836
queryModel[prop] = this.replaceTemplateVars(queryModel[prop], {});
836837
}
837838

839+
queryModel = queryModel as VariableQuery;
838840
const { group, host, application, item } = queryModel;
839841

840842
switch (queryModel.queryType) {
@@ -856,7 +858,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
856858
queryModel.host,
857859
queryModel.application,
858860
queryModel.itemTag,
859-
queryModel.item
861+
queryModel.item,
862+
{ showDisabledItems: queryModel.showDisabledItems }
860863
);
861864
break;
862865
case VariableQueryTypes.ItemValues:

src/datasource/specs/datasource.spec.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ describe('ZabbixDatasource', () => {
5656
},
5757
],
5858
range: {
59-
from: dateMath.parse('now-1h'),
60-
to: dateMath.parse('now'),
59+
from: dateMath.toDateTime('now-1h', {}),
60+
to: dateMath.toDateTime('now', {}),
6161
},
6262
};
6363

@@ -242,15 +242,15 @@ describe('ZabbixDatasource', () => {
242242

243243
for (const test of tests) {
244244
ctx.ds.metricFindQuery(test.query);
245-
expect(ctx.ds.zabbix.getGroups).toBeCalledWith(test.expect);
245+
expect(ctx.ds.zabbix.getGroups).toHaveBeenCalledWith(test.expect);
246246
ctx.ds.zabbix.getGroups.mockClear();
247247
}
248248
done();
249249
});
250250

251251
it('should return empty list for empty query', (done) => {
252252
ctx.ds.metricFindQuery('').then((result) => {
253-
expect(ctx.ds.zabbix.getGroups).toBeCalledTimes(0);
253+
expect(ctx.ds.zabbix.getGroups).toHaveBeenCalledTimes(0);
254254
ctx.ds.zabbix.getGroups.mockClear();
255255

256256
expect(result).toEqual([]);
@@ -268,7 +268,7 @@ describe('ZabbixDatasource', () => {
268268

269269
for (const test of tests) {
270270
ctx.ds.metricFindQuery(test.query);
271-
expect(ctx.ds.zabbix.getHosts).toBeCalledWith(test.expect[0], test.expect[1]);
271+
expect(ctx.ds.zabbix.getHosts).toHaveBeenCalledWith(test.expect[0], test.expect[1]);
272272
ctx.ds.zabbix.getHosts.mockClear();
273273
}
274274
done();
@@ -284,28 +284,35 @@ describe('ZabbixDatasource', () => {
284284

285285
for (const test of tests) {
286286
ctx.ds.metricFindQuery(test.query);
287-
expect(ctx.ds.zabbix.getApps).toBeCalledWith(test.expect[0], test.expect[1], test.expect[2]);
287+
expect(ctx.ds.zabbix.getApps).toHaveBeenCalledWith(test.expect[0], test.expect[1], test.expect[2]);
288288
ctx.ds.zabbix.getApps.mockClear();
289289
}
290290
done();
291291
});
292292

293293
it('should return items', (done) => {
294294
const tests = [
295-
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', undefined, '/.*/'] },
296-
{ query: '.*.*.*', expect: ['', '/.*/', '', undefined, '/.*/'] },
297-
{ query: 'Backend.backend01.*.*', expect: ['Backend', 'backend01', '', undefined, '/.*/'] },
298-
{ query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu', undefined, '/.*/'] },
295+
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
296+
{ query: '.*.*.*', expect: ['', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
297+
{
298+
query: 'Backend.backend01.*.*',
299+
expect: ['Backend', 'backend01', '', undefined, '/.*/', { showDisabledItems: undefined }],
300+
},
301+
{
302+
query: 'Back*.*.cpu.*',
303+
expect: ['Back*', '/.*/', 'cpu', undefined, '/.*/', { showDisabledItems: undefined }],
304+
},
299305
];
300306

301307
for (const test of tests) {
302308
ctx.ds.metricFindQuery(test.query);
303-
expect(ctx.ds.zabbix.getItems).toBeCalledWith(
309+
expect(ctx.ds.zabbix.getItems).toHaveBeenCalledWith(
304310
test.expect[0],
305311
test.expect[1],
306312
test.expect[2],
307313
test.expect[3],
308-
test.expect[4]
314+
test.expect[4],
315+
test.expect[5]
309316
);
310317
ctx.ds.zabbix.getItems.mockClear();
311318
}
@@ -316,7 +323,7 @@ describe('ZabbixDatasource', () => {
316323
let query = '*.*';
317324

318325
ctx.ds.metricFindQuery(query);
319-
expect(ctx.ds.zabbix.getHosts).toBeCalledWith('/.*/', '/.*/');
326+
expect(ctx.ds.zabbix.getHosts).toHaveBeenCalledWith('/.*/', '/.*/');
320327
done();
321328
});
322329

@@ -383,7 +390,33 @@ describe('ZabbixDatasource', () => {
383390
'HostFilter',
384391
'AppFilter',
385392
'TagFilter',
386-
'ItemFilter'
393+
'ItemFilter',
394+
{ showDisabledItems: undefined }
395+
);
396+
expect(result).toEqual([
397+
{ text: 'Item1', expandable: false },
398+
{ text: 'Item2', expandable: false },
399+
]);
400+
});
401+
402+
it('should return disabled items when queryType is Item and show disabled items is turned on', async () => {
403+
const query = {
404+
queryType: VariableQueryTypes.Item,
405+
group: 'GroupFilter',
406+
host: 'HostFilter',
407+
application: 'AppFilter',
408+
itemTag: 'TagFilter',
409+
item: 'ItemFilter',
410+
showDisabledItems: true,
411+
};
412+
const result = await ctx.ds.metricFindQuery(query, {});
413+
expect(ctx.ds.zabbix.getItems).toHaveBeenCalledWith(
414+
'GroupFilter',
415+
'HostFilter',
416+
'AppFilter',
417+
'TagFilter',
418+
'ItemFilter',
419+
{ showDisabledItems: true }
387420
);
388421
expect(result).toEqual([
389422
{ text: 'Item1', expandable: false },

src/datasource/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { BusEventWithPayload, DataSourceRef, SelectableValue } from '@grafana/data';
1+
import { BusEventWithPayload } from '@grafana/data';
2+
import { DataSourceRef } from '@grafana/schema';
3+
import { ComboboxOption } from '@grafana/ui';
24

35
// The paths of these files have moved around in Grafana and they don't resolve properly
46
// either. Safer not to bother trying to import them just for type hinting.
@@ -60,7 +62,7 @@ export interface VariableQueryProps {
6062
}
6163

6264
export interface VariableQueryData extends VariableQuery {
63-
selectedQueryType: SelectableValue<VariableQueryTypes>;
65+
selectedQueryType: ComboboxOption<VariableQueryTypes>;
6466
legacyQuery?: string;
6567
}
6668

@@ -72,6 +74,7 @@ export interface VariableQuery {
7274
itemTag?: string;
7375
item?: string;
7476
macro?: string;
77+
showDisabledItems?: boolean;
7578
}
7679

7780
export type LegacyVariableQuery = VariableQuery | string;

0 commit comments

Comments
 (0)