From 3e626d3aa59bdc76f5df760a7c93e8eb2087d700 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Mon, 29 Dec 2025 18:49:13 +0100 Subject: [PATCH] Add item count warning in query editor for large result sets (#2152) ## Summary Adds a non-intrusive warning banner in the query editor that alerts users when their query matches a large number of items (>= 500). This helps users understand that their query may return a large amount of data and suggests using more specific filters. Part of https://github.com/grafana/oss-big-tent-squad/issues/127 ## Changes - Added `ITEM_COUNT_WARNING_THRESHOLD` constant (500 items) in `src/datasource/constants.ts` - Created new `ItemCountWarning` component in `src/datasource/components/ItemCountWarning.tsx` - Updated `MetricsQueryEditor` to track and report the count of items matching the current filter - Integrated the warning component into the main `QueryEditor` component ## How it works - When items are loaded for the dropdown in the Metrics query editor, the component counts how many items match the current item filter - If using a regex filter like `/.*/`, it applies the regex to count matching items - If the count is >= 500, a warning banner appears at the top of the query editor - The warning is purely informational - queries still execute normally - The warning only appears for the "Metrics" query type ## Screenshot The warning appears as a subtle banner with a warning icon: > I set the limit as 5 just to show the warning grafik ## Why Queries that match thousands of items via wildcard filters (e.g., `/.*/`) can return massive amounts of data and potentially overload the Zabbix server. This proactive warning helps users make informed decisions about their query scope without adding friction to the normal query flow. --- .../components/ItemCountWarning.tsx | 27 +++++++++++++++++ src/datasource/components/QueryEditor.tsx | 12 ++++++-- .../QueryEditor/MetricsQueryEditor.tsx | 30 ++++++++++++++++--- src/datasource/constants.ts | 2 ++ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/datasource/components/ItemCountWarning.tsx diff --git a/src/datasource/components/ItemCountWarning.tsx b/src/datasource/components/ItemCountWarning.tsx new file mode 100644 index 0000000..cf80aec --- /dev/null +++ b/src/datasource/components/ItemCountWarning.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Alert } from '@grafana/ui'; +import { ITEM_COUNT_WARNING_THRESHOLD } from '../constants'; + +interface ItemCountWarningProps { + itemCount: number; + threshold?: number; +} + +/** + * A warning banner that displays when the query matches too many items. + * This is a non-intrusive warning that doesn't block the query flow. + */ +export const ItemCountWarning: React.FC = ({ + itemCount, + threshold = ITEM_COUNT_WARNING_THRESHOLD, +}) => { + if (itemCount < threshold) { + return null; + } + + return ( + + This query matches {itemCount} items and may return a large amount of data. Consider using more specific filters. + + ); +}; diff --git a/src/datasource/components/QueryEditor.tsx b/src/datasource/components/QueryEditor.tsx index 111b1ad..646ec71 100644 --- a/src/datasource/components/QueryEditor.tsx +++ b/src/datasource/components/QueryEditor.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { QueryEditorProps } from '@grafana/data'; import { Combobox, ComboboxOption, InlineField, Stack } from '@grafana/ui'; import * as c from '../constants'; @@ -16,6 +16,7 @@ import { ServicesQueryEditor } from './QueryEditor/ServicesQueryEditor'; import { TriggersQueryEditor } from './QueryEditor/TriggersQueryEditor'; import { UserMacrosQueryEditor } from './QueryEditor/UserMacrosQueryEditor'; import { QueryEditorRow } from './QueryEditor/QueryEditorRow'; +import { ItemCountWarning } from './ItemCountWarning'; const zabbixQueryTypeOptions: Array> = [ { @@ -113,6 +114,7 @@ export interface ZabbixQueryEditorProps extends QueryEditorProps {} export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQueryEditorProps) => { + const [itemCount, setItemCount] = useState(0); const queryDefaults = getDefaultQuery(); query = { ...queryDefaults, ...query }; query.options = { ...queryDefaults.options, ...query.options }; @@ -152,7 +154,12 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ const renderMetricsEditor = () => { return ( <> - + ); @@ -198,6 +205,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ return ( + {queryType === c.MODE_METRICS && } diff --git a/src/datasource/components/QueryEditor/MetricsQueryEditor.tsx b/src/datasource/components/QueryEditor/MetricsQueryEditor.tsx index d416994..2907103 100644 --- a/src/datasource/components/QueryEditor/MetricsQueryEditor.tsx +++ b/src/datasource/components/QueryEditor/MetricsQueryEditor.tsx @@ -16,9 +16,10 @@ export interface Props { query: ZabbixMetricsQuery; datasource: ZabbixDatasource; onChange: (query: ZabbixMetricsQuery) => void; + onItemCountChange?: (count: number) => void; } -export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => { +export const MetricsQueryEditor = ({ query, datasource, onChange, onItemCountChange }: Props) => { const interpolatedQuery = useInterpolatedQuery(datasource, query); const loadGroupOptions = async () => { @@ -94,12 +95,30 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => { return options; }, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]); - const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => { + const loadItemOptions = async (group: string, host: string, app: string, itemTag: string, itemFilter: string) => { const options = { itemtype: 'num', showDisabledItems: query.options.showDisabledItems, }; const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options); + + // Count items that match the current item filter for the warning + let matchingItemCount = items?.length || 0; + if (itemFilter && items?.length) { + // If there's an item filter, count how many items match it + const filterRegex = + itemFilter.startsWith('/') && itemFilter.endsWith('/') ? new RegExp(itemFilter.slice(1, -1)) : null; + if (filterRegex) { + matchingItemCount = items.filter((item) => filterRegex.test(item.name)).length; + } else if (itemFilter) { + // Exact match or partial match + matchingItemCount = items.filter((item) => item.name === itemFilter || item.name.includes(itemFilter)).length; + } + } + + // Report the matching item count + onItemCountChange?.(matchingItemCount); + let itemOptions: Array> = items?.map((item) => ({ value: item.name, label: item.name, @@ -114,7 +133,8 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => { interpolatedQuery.group.filter, interpolatedQuery.host.filter, interpolatedQuery.application.filter, - interpolatedQuery.itemTag.filter + interpolatedQuery.itemTag.filter, + interpolatedQuery.item.filter ); return options; }, [ @@ -122,6 +142,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => { interpolatedQuery.host.filter, interpolatedQuery.application.filter, interpolatedQuery.itemTag.filter, + interpolatedQuery.item.filter, ]); // Update suggestions on every metric change @@ -129,6 +150,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => { const hostFilter = interpolatedQuery.host?.filter; const appFilter = interpolatedQuery.application?.filter; const tagFilter = interpolatedQuery.itemTag?.filter; + const itemFilter = interpolatedQuery.item?.filter; useEffect(() => { fetchGroups(); @@ -148,7 +170,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => { useEffect(() => { fetchItems(); - }, [groupFilter, hostFilter, appFilter, tagFilter]); + }, [groupFilter, hostFilter, appFilter, tagFilter, itemFilter]); const onFilterChange = (prop: string) => { return (value: string) => { diff --git a/src/datasource/constants.ts b/src/datasource/constants.ts index a087a2c..29d4e98 100644 --- a/src/datasource/constants.ts +++ b/src/datasource/constants.ts @@ -49,3 +49,5 @@ export const MIN_SLA_INTERVAL = 3600; export const RANGE_VARIABLE_VALUE = 'range_series'; export const DEFAULT_ZABBIX_PROBLEMS_LIMIT = 1001; + +export const ITEM_COUNT_WARNING_THRESHOLD = 500;