From 3ada0d15e6132d9f88f6f25f605e5d3f081a8db0 Mon Sep 17 00:00:00 2001 From: Christos Diamantis <154525755+christos-diamantis@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:36:36 +0200 Subject: [PATCH] Add the ability to convert specific tags to columns on problems panel (#2113) This closes #2112 Added the ability on problems panel to specify a comma separated list of tag names to be converted to columns. If a tag name is present multiple times, it will return the value of all tags separated with comma. For optimum readability, the tag names are Capitalized for the visible column name. Also, for optimum readability, the custom tags are always placed before the "Tags" column. In case a tag is not there for a problem, an empty string is returned. --------- Co-authored-by: Jocelyn Collado-Kuri --- .changeset/wet-monkeys-smoke.md | 5 +++ .../components/Problems/Problems.tsx | 31 +++++++++++++++ .../components/Problems/utils.test.ts | 38 +++++++++++++++++++ .../components/Problems/utils.ts | 12 ++++++ src/panel-triggers/module.tsx | 11 ++++++ src/panel-triggers/types.ts | 2 + 6 files changed, 99 insertions(+) create mode 100644 .changeset/wet-monkeys-smoke.md create mode 100644 src/panel-triggers/components/Problems/utils.test.ts create mode 100644 src/panel-triggers/components/Problems/utils.ts diff --git a/.changeset/wet-monkeys-smoke.md b/.changeset/wet-monkeys-smoke.md new file mode 100644 index 0000000..d4dc57d --- /dev/null +++ b/.changeset/wet-monkeys-smoke.md @@ -0,0 +1,5 @@ +--- +'grafana-zabbix': patch +--- + +Enhance the problems panel with the ability to convert specific tags to columns. Single or multiple tags supported. diff --git a/src/panel-triggers/components/Problems/Problems.tsx b/src/panel-triggers/components/Problems/Problems.tsx index 022c7ee..c103db3 100644 --- a/src/panel-triggers/components/Problems/Problems.tsx +++ b/src/panel-triggers/components/Problems/Problems.tsx @@ -25,6 +25,7 @@ import { } from '@tanstack/react-table'; import { reportInteraction } from '@grafana/runtime'; import { ProblemDetails } from './ProblemDetails'; +import { capitalizeFirstLetter, parseCustomTagColumns } from './utils'; export interface ProblemListProps { problems: ProblemDTO[]; @@ -47,6 +48,33 @@ export interface ProblemListProps { const columnHelper = createColumnHelper(); +const buildCustomTagColumns = (customTagColumns?: string) => { + const tagNames = parseCustomTagColumns(customTagColumns); + + return tagNames.map((tagName) => + columnHelper.accessor( + (row) => { + const tags = row.tags ?? []; + const values = tags + .filter((t) => t.tag === tagName) + .map((t) => t.value) + .filter(Boolean); + + return values.length ? values.join(', ') : ''; + }, + { + id: `problem-tag_${tagName}`, + header: capitalizeFirstLetter(tagName), + size: 150, + meta: { + className: `problem-tag_${tagName}`, + }, + cell: ({ getValue }) => {getValue() as string}, + } + ) + ); +}; + export const ProblemList = (props: ProblemListProps) => { const { pageSize, @@ -72,6 +100,8 @@ export const ProblemList = (props: ProblemListProps) => { const columns = useMemo(() => { const highlightNewerThan = panelOptions.highlightNewEvents && panelOptions.highlightNewerThan; + const customTagColumns = buildCustomTagColumns(panelOptions.customTagColumns); + return [ columnHelper.accessor('host', { header: 'Host', @@ -146,6 +176,7 @@ export const ProblemList = (props: ProblemListProps) => { size: 70, cell: ({ cell }) => , }), + ...customTagColumns, columnHelper.accessor('tags', { header: 'Tags', size: 150, diff --git a/src/panel-triggers/components/Problems/utils.test.ts b/src/panel-triggers/components/Problems/utils.test.ts new file mode 100644 index 0000000..d2ac388 --- /dev/null +++ b/src/panel-triggers/components/Problems/utils.test.ts @@ -0,0 +1,38 @@ +import { capitalizeFirstLetter, parseCustomTagColumns } from './utils'; + +describe('capitalizeFirstLetter', () => { + it('capitalizes first letter and lowercases the rest', () => { + expect(capitalizeFirstLetter('zabbixgrafana')).toBe('Zabbixgrafana'); + expect(capitalizeFirstLetter('ZABBIXGRAFANA')).toBe('Zabbixgrafana'); + expect(capitalizeFirstLetter('zAbBiXgRaFaNa')).toBe('Zabbixgrafana'); + }); + + it('returns empty string for empty input', () => { + expect(capitalizeFirstLetter('')).toBe(''); + }); + + it('handles single-character strings', () => { + expect(capitalizeFirstLetter('a')).toBe('A'); + expect(capitalizeFirstLetter('A')).toBe('A'); + }); +}); + +describe('parseCustomTagColumns', () => { + it('returns empty array for undefined or empty input', () => { + expect(parseCustomTagColumns(undefined)).toEqual([]); + expect(parseCustomTagColumns('')).toEqual([]); + expect(parseCustomTagColumns(' ')).toEqual([]); + }); + + it('splits comma-separated values and trims whitespace', () => { + expect(parseCustomTagColumns('env, region ,service')).toEqual(['env', 'region', 'service']); + }); + + it('filters out empty values', () => { + expect(parseCustomTagColumns('env,, ,region,')).toEqual(['env', 'region']); + }); + + it('preserves order', () => { + expect(parseCustomTagColumns('a,b,c')).toEqual(['a', 'b', 'c']); + }); +}); diff --git a/src/panel-triggers/components/Problems/utils.ts b/src/panel-triggers/components/Problems/utils.ts new file mode 100644 index 0000000..714d9ee --- /dev/null +++ b/src/panel-triggers/components/Problems/utils.ts @@ -0,0 +1,12 @@ +export const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); + +export const parseCustomTagColumns = (customTagColumns?: string): string[] => { + if (!customTagColumns) { + return []; + } + + return customTagColumns + .split(',') + .map((tagName) => tagName.trim()) + .filter(Boolean); +}; diff --git a/src/panel-triggers/module.tsx b/src/panel-triggers/module.tsx index 4254c7f..b91d548 100644 --- a/src/panel-triggers/module.tsx +++ b/src/panel-triggers/module.tsx @@ -220,6 +220,17 @@ export const plugin = new PanelPlugin(ProblemsPanel) name: 'Datasource name', defaultValue: defaultPanelOptions.showDatasourceName, category: ['Fields'], + }) + // Select tag name to display as column + .addTextInput({ + path: 'customTagColumns', + name: 'Tags to columns', + defaultValue: '', + description: 'Comma-separated list of tag names to display as columns (e.g., component, scope, environment)', + settings: { + placeholder: 'component, scope, target', + }, + category: ['Fields'], }); }); diff --git a/src/panel-triggers/types.ts b/src/panel-triggers/types.ts index 27d4a1c..8e2bce5 100644 --- a/src/panel-triggers/types.ts +++ b/src/panel-triggers/types.ts @@ -42,6 +42,8 @@ export interface ProblemsPanelOptions { okEventColor: TriggerColor; ackEventColor: TriggerColor; markAckEvents?: boolean; + // Custom tag names to display as column + customTagColumns?: string; } export const DEFAULT_SEVERITY: TriggerSeverity[] = [