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[] = [