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 <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Jocelyn Collado-Kuri
2025-10-28 19:57:55 -07:00
committed by GitHub
parent 045c708c69
commit 86b7328f39
5 changed files with 107 additions and 40 deletions

View File

@@ -1,12 +1,11 @@
import React, { PureComponent } from 'react';
import { parseLegacyVariableQuery } from '../utils';
import { SelectableValue } from '@grafana/data';
import { VariableQuery, VariableQueryData, VariableQueryProps, VariableQueryTypes } from '../types';
import { ZabbixInput } from './ZabbixInput';
import { InlineField, InlineFieldRow, InlineFormLabel, Input, Select } from '@grafana/ui';
import { Combobox, ComboboxOption, InlineField, InlineFieldRow, InlineFormLabel, Input, Switch } from '@grafana/ui';
export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps, VariableQueryData> {
queryTypes: Array<SelectableValue<VariableQueryTypes>> = [
queryTypes: Array<ComboboxOption<VariableQueryTypes>> = [
{ value: VariableQueryTypes.Group, label: 'Group' },
{ value: VariableQueryTypes.Host, label: 'Host' },
{ value: VariableQueryTypes.Application, label: 'Application' },
@@ -23,6 +22,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
application: '',
itemTag: '',
item: '',
showDisabledItems: false,
};
constructor(props: VariableQueryProps) {
@@ -69,34 +69,48 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
};
handleQueryChange = () => {
const { queryType, group, host, application, itemTag, item } = this.state;
const queryModel = { queryType, group, host, application, itemTag, item };
const { queryType, group, host, application, itemTag, item, showDisabledItems } = this.state;
const queryModel = { queryType, group, host, application, itemTag, item, showDisabledItems };
this.props.onChange(queryModel, `Zabbix - ${queryType}`);
};
handleQueryTypeChange = (selectedItem: SelectableValue<VariableQueryTypes>) => {
handleQueryTypeChange = (selectedItem: ComboboxOption<VariableQueryTypes>) => {
this.setState({
...this.state,
selectedQueryType: selectedItem,
queryType: selectedItem.value,
});
const { group, host, application, itemTag, item } = this.state;
const { group, host, application, itemTag, item, showDisabledItems } = this.state;
const queryType = selectedItem.value;
const queryModel = { queryType, group, host, application, itemTag, item };
const queryModel = { queryType, group, host, application, itemTag, item, showDisabledItems };
this.props.onChange(queryModel, `Zabbix - ${queryType}`);
};
handleShowDisabledItemsChange = (evt: React.FormEvent<HTMLInputElement>) => {
const showDisabledItems = (evt.target as any).checked;
this.setState((prevState: VariableQueryData) => {
return {
...prevState,
showDisabledItems: showDisabledItems,
};
});
const { queryType, group, host, application, itemTag, item } = this.state;
const queryModel = { queryType, group, host, application, itemTag, item, showDisabledItems };
this.props.onChange(queryModel, `Zabbix - ${queryType}`);
};
render() {
const { selectedQueryType, legacyQuery, group, host, application, itemTag, item } = this.state;
const { selectedQueryType, legacyQuery, group, host, application, itemTag, item, showDisabledItems } = this.state;
const { datasource } = this.props;
const supportsItemTags = datasource?.zabbix?.isZabbix54OrHigherSync() || false;
return (
<>
<InlineFieldRow>
<InlineField label="Query Type" labelWidth={16}>
<Select
<InlineField label="Query Type" labelWidth={18}>
<Combobox
width={30}
value={selectedQueryType}
options={this.queryTypes}
@@ -106,7 +120,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
</InlineFieldRow>
<InlineFieldRow>
<InlineField label="Group" labelWidth={16}>
<InlineField label="Group" labelWidth={18}>
<ZabbixInput
width={30}
value={group}
@@ -118,7 +132,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
{selectedQueryType.value !== VariableQueryTypes.Group && (
<InlineFieldRow>
<InlineField label="Host" labelWidth={16}>
<InlineField label="Host" labelWidth={18}>
<ZabbixInput
width={30}
value={host}
@@ -136,7 +150,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
<>
{supportsItemTags && (
<InlineFieldRow>
<InlineField label="Item Tag" labelWidth={16}>
<InlineField label="Item Tag" labelWidth={18}>
<ZabbixInput
width={30}
value={itemTag}
@@ -149,7 +163,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
{!supportsItemTags && (
<InlineFieldRow>
<InlineField label="Application" labelWidth={16}>
<InlineField label="Application" labelWidth={18}>
<ZabbixInput
width={30}
value={application}
@@ -163,7 +177,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
{(selectedQueryType.value === VariableQueryTypes.Item ||
selectedQueryType.value === VariableQueryTypes.ItemValues) && (
<InlineFieldRow>
<InlineField label="Item" labelWidth={16}>
<InlineField label="Item" labelWidth={18}>
<ZabbixInput
width={30}
value={item}
@@ -184,6 +198,15 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
<Input value={legacyQuery} readOnly={true} />
</>
)}
{selectedQueryType.value === VariableQueryTypes.Item && (
<>
<InlineFieldRow>
<InlineField label="Show disabled items" labelWidth={18} style={{ alignItems: 'center' }}>
<Switch value={showDisabledItems} onChange={this.handleShowDisabledItemsChange} />
</InlineField>
</InlineFieldRow>
</>
)}
</>
);
}

View File

@@ -11,7 +11,7 @@ import responseHandler from './responseHandler';
import problemsHandler from './problemsHandler';
import { Zabbix } from './zabbix/zabbix';
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPIConnector';
import { ProblemDTO, VariableQueryTypes } from './types';
import { LegacyVariableQuery, ProblemDTO, VariableQuery, VariableQueryTypes } from './types';
import { ZabbixMetricsQuery, ShowProblemTypes } from './types/query';
import { ZabbixDSOptions } from './types/config';
import {
@@ -36,6 +36,7 @@ import {
} from '@grafana/data';
import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
import { trackRequest } from './tracking';
import { lastValueFrom } from 'rxjs';
export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDSOptions> {
name: string;
@@ -212,14 +213,14 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
let rsp: any;
try {
rsp = await getBackendSrv()
.fetch({
rsp = await lastValueFrom(
getBackendSrv().fetch({
url: '/api/ds/query',
method: 'POST',
data: body,
requestId,
})
.toPromise();
);
} catch (err) {
return toDataQueryResponse(err);
}
@@ -404,7 +405,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
},
};
const response: any = await getBackendSrv().fetch<any>(requestOptions).toPromise();
const response: any = await lastValueFrom(getBackendSrv().fetch<any>(requestOptions));
return response.data;
}
@@ -813,12 +814,12 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
/**
* Find metrics from templated request.
*
* @param {string} query Query from Templating
* @param {LegacyVariableQuery} query Query from Templating
* @param options
* @return {string} Metric name - group, host, app or item or list
* of metrics in "{metric1, metric2,..., metricN}" format.
*/
metricFindQuery(query, options) {
metricFindQuery(query: LegacyVariableQuery, options) {
let resultPromise;
let queryModel = _.cloneDeep(query);
@@ -835,6 +836,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
queryModel[prop] = this.replaceTemplateVars(queryModel[prop], {});
}
queryModel = queryModel as VariableQuery;
const { group, host, application, item } = queryModel;
switch (queryModel.queryType) {
@@ -856,7 +858,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
queryModel.host,
queryModel.application,
queryModel.itemTag,
queryModel.item
queryModel.item,
{ showDisabledItems: queryModel.showDisabledItems }
);
break;
case VariableQueryTypes.ItemValues:

View File

@@ -56,8 +56,8 @@ describe('ZabbixDatasource', () => {
},
],
range: {
from: dateMath.parse('now-1h'),
to: dateMath.parse('now'),
from: dateMath.toDateTime('now-1h', {}),
to: dateMath.toDateTime('now', {}),
},
};
@@ -242,7 +242,7 @@ describe('ZabbixDatasource', () => {
for (const test of tests) {
ctx.ds.metricFindQuery(test.query);
expect(ctx.ds.zabbix.getGroups).toBeCalledWith(test.expect);
expect(ctx.ds.zabbix.getGroups).toHaveBeenCalledWith(test.expect);
ctx.ds.zabbix.getGroups.mockClear();
}
done();
@@ -250,7 +250,7 @@ describe('ZabbixDatasource', () => {
it('should return empty list for empty query', (done) => {
ctx.ds.metricFindQuery('').then((result) => {
expect(ctx.ds.zabbix.getGroups).toBeCalledTimes(0);
expect(ctx.ds.zabbix.getGroups).toHaveBeenCalledTimes(0);
ctx.ds.zabbix.getGroups.mockClear();
expect(result).toEqual([]);
@@ -268,7 +268,7 @@ describe('ZabbixDatasource', () => {
for (const test of tests) {
ctx.ds.metricFindQuery(test.query);
expect(ctx.ds.zabbix.getHosts).toBeCalledWith(test.expect[0], test.expect[1]);
expect(ctx.ds.zabbix.getHosts).toHaveBeenCalledWith(test.expect[0], test.expect[1]);
ctx.ds.zabbix.getHosts.mockClear();
}
done();
@@ -284,7 +284,7 @@ describe('ZabbixDatasource', () => {
for (const test of tests) {
ctx.ds.metricFindQuery(test.query);
expect(ctx.ds.zabbix.getApps).toBeCalledWith(test.expect[0], test.expect[1], test.expect[2]);
expect(ctx.ds.zabbix.getApps).toHaveBeenCalledWith(test.expect[0], test.expect[1], test.expect[2]);
ctx.ds.zabbix.getApps.mockClear();
}
done();
@@ -292,20 +292,27 @@ describe('ZabbixDatasource', () => {
it('should return items', (done) => {
const tests = [
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', undefined, '/.*/'] },
{ query: '.*.*.*', expect: ['', '/.*/', '', undefined, '/.*/'] },
{ query: 'Backend.backend01.*.*', expect: ['Backend', 'backend01', '', undefined, '/.*/'] },
{ query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu', undefined, '/.*/'] },
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
{ query: '.*.*.*', expect: ['', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
{
query: 'Backend.backend01.*.*',
expect: ['Backend', 'backend01', '', undefined, '/.*/', { showDisabledItems: undefined }],
},
{
query: 'Back*.*.cpu.*',
expect: ['Back*', '/.*/', 'cpu', undefined, '/.*/', { showDisabledItems: undefined }],
},
];
for (const test of tests) {
ctx.ds.metricFindQuery(test.query);
expect(ctx.ds.zabbix.getItems).toBeCalledWith(
expect(ctx.ds.zabbix.getItems).toHaveBeenCalledWith(
test.expect[0],
test.expect[1],
test.expect[2],
test.expect[3],
test.expect[4]
test.expect[4],
test.expect[5]
);
ctx.ds.zabbix.getItems.mockClear();
}
@@ -316,7 +323,7 @@ describe('ZabbixDatasource', () => {
let query = '*.*';
ctx.ds.metricFindQuery(query);
expect(ctx.ds.zabbix.getHosts).toBeCalledWith('/.*/', '/.*/');
expect(ctx.ds.zabbix.getHosts).toHaveBeenCalledWith('/.*/', '/.*/');
done();
});
@@ -383,7 +390,33 @@ describe('ZabbixDatasource', () => {
'HostFilter',
'AppFilter',
'TagFilter',
'ItemFilter'
'ItemFilter',
{ showDisabledItems: undefined }
);
expect(result).toEqual([
{ text: 'Item1', expandable: false },
{ text: 'Item2', expandable: false },
]);
});
it('should return disabled items when queryType is Item and show disabled items is turned on', async () => {
const query = {
queryType: VariableQueryTypes.Item,
group: 'GroupFilter',
host: 'HostFilter',
application: 'AppFilter',
itemTag: 'TagFilter',
item: 'ItemFilter',
showDisabledItems: true,
};
const result = await ctx.ds.metricFindQuery(query, {});
expect(ctx.ds.zabbix.getItems).toHaveBeenCalledWith(
'GroupFilter',
'HostFilter',
'AppFilter',
'TagFilter',
'ItemFilter',
{ showDisabledItems: true }
);
expect(result).toEqual([
{ text: 'Item1', expandable: false },

View File

@@ -1,4 +1,6 @@
import { BusEventWithPayload, DataSourceRef, SelectableValue } from '@grafana/data';
import { BusEventWithPayload } from '@grafana/data';
import { DataSourceRef } from '@grafana/schema';
import { ComboboxOption } from '@grafana/ui';
// The paths of these files have moved around in Grafana and they don't resolve properly
// either. Safer not to bother trying to import them just for type hinting.
@@ -60,7 +62,7 @@ export interface VariableQueryProps {
}
export interface VariableQueryData extends VariableQuery {
selectedQueryType: SelectableValue<VariableQueryTypes>;
selectedQueryType: ComboboxOption<VariableQueryTypes>;
legacyQuery?: string;
}
@@ -72,6 +74,7 @@ export interface VariableQuery {
itemTag?: string;
item?: string;
macro?: string;
showDisabledItems?: boolean;
}
export type LegacyVariableQuery = VariableQuery | string;