From 984f0652962acc5500add650f7c812c6168411e4 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Mon, 26 Jan 2026 15:58:40 +0000
Subject: [PATCH] Fix column visibility toggles not working in problem panel
(#2228)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Fix column visibility in problem panel
- [x] Understand the issue: Column IDs in
`initialState.columnVisibility` don't match actual column accessor IDs
- [x] Fix the column ID mapping in Problems.tsx
- [x] Add tests to verify column visibility works correctly
- [x] Fix reactivity issue: Moved columnVisibility from initialState to
state with useMemo
- [x] Add datasource column definition and visibility mapping
- [x] Optimize datasource cell rendering
- [x] Suppress React Compiler warning for useReactTable
- [x] All tests passing (9/9)
- [x] Build successful
- [x] Lint warnings reduced from 65 to 64
## Summary
Fixed three issues with column visibility in the problem panel:
1. **Incorrect column ID mappings**: Fixed three column IDs to match
actual column accessor IDs.
2. **Non-reactive column visibility**: Moved `columnVisibility` from
`initialState` to `state` with `useMemo`.
3. **Missing datasource column**: Added datasource column definition and
visibility mapping.
4. **Lint warning**: Suppressed React Compiler warning for
`useReactTable` hook (known TanStack Table issue).
All column visibility controls now correctly show/hide their respective
columns without requiring a page refresh.
Original prompt
>
> ----
>
> *This section details on the original issue you should resolve*
>
> Change column in problem panel not working on
6.1.1
> **Describe the bug**
> Since the upgrade from plugin 6.0.3 to 6.1.1, i notice that status
column appears (it was not the case before the upgrade) on existing
problem panels.
> I tried to disable it, and the button was on disable state. The
configuration is great, but not the display. Then i tried to disable or
enable all the colums and it doesn't work too.
> I tried on a new dashboard with a new problem panel and it is the same
issue.
> Font size doesn't work too. All other options seems to work.
> Grafana server has been restarted.
>
> **Expected behavior**
> Enable or disable the column should change the display of the problem
panel.
>
> **Screenshots**
>
>
> **Network data**
> If it's related to metric data visualization would be great to get the
raw query and response for the network request (check this in browser
dev tools network tab, there you can see metric requests, please include
the request body and request response)
>
> **Software versions**
>
> | Grafana | Zabbix | Grafana-Zabbix Plugin |
> | ------- | ------ | --------------------- |
> | 12.3.0 | 7.2.15 | 6.1.1 |
>
>
> Looks like the regression was introduced via
https://github.com/grafana/grafana-zabbix/pull/2131/changes#diff-85e7fa6e5295bf97f8bf82eabcb5807d695248b9fcd325acd58a8af02824cb70L207
>
> ## Comments on the Issue (you are @copilot in this section)
>
>
> @yesoreyeram
> I can reproduce this issue. Adding to the backlog for further
investigation.
>
>
- Fixes grafana/grafana-zabbix#2213
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/grafana/grafana-zabbix/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yesoreyeram <153843+yesoreyeram@users.noreply.github.com>
Co-authored-by: Jocelyn Collado-Kuri
Co-authored-by: jcolladokuri <20448042+jcolladokuri@users.noreply.github.com>
---
.../components/Problems/Problems.test.tsx | 138 ++++++++++++++++++
.../components/Problems/Problems.tsx | 51 +++++--
2 files changed, 174 insertions(+), 15 deletions(-)
diff --git a/src/panel-triggers/components/Problems/Problems.test.tsx b/src/panel-triggers/components/Problems/Problems.test.tsx
index 56aad0a..49a04d7 100644
--- a/src/panel-triggers/components/Problems/Problems.test.tsx
+++ b/src/panel-triggers/components/Problems/Problems.test.tsx
@@ -33,10 +33,12 @@ describe('ProblemList', () => {
hostGroups: false,
hostProxy: false,
severityField: true,
+ statusField: true,
statusIcon: true,
opdataField: false,
ackField: true,
showTags: true,
+ showDatasourceName: false,
ageField: true,
customLastChangeFormat: false,
lastChangeFormat: '',
@@ -111,5 +113,141 @@ describe('ProblemList', () => {
expect(ageHeader).toBeInTheDocument();
});
+
+ it('should not render the age column header when ageField is disabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, ageField: false },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const ageHeader = headers.find((header) => header.textContent === 'Age');
+
+ expect(ageHeader).toBeUndefined();
+ });
+ });
+
+ describe('Status Field', () => {
+ it('should render the status column header when statusField is enabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, statusField: true },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const statusHeader = headers.find((header) => header.textContent === 'Status');
+
+ expect(statusHeader).toBeInTheDocument();
+ });
+
+ it('should not render the status column header when statusField is disabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, statusField: false },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const statusHeader = headers.find((header) => header.textContent === 'Status');
+
+ expect(statusHeader).toBeUndefined();
+ });
+ });
+
+ describe('Severity Field', () => {
+ it('should render the severity column header when severityField is enabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, severityField: true },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const severityHeader = headers.find((header) => header.textContent === 'Severity');
+
+ expect(severityHeader).toBeInTheDocument();
+ });
+
+ it('should not render the severity column header when severityField is disabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, severityField: false },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const severityHeader = headers.find((header) => header.textContent === 'Severity');
+
+ expect(severityHeader).toBeUndefined();
+ });
+ });
+
+ describe('Ack Field', () => {
+ it('should render the ack column header when ackField is enabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, ackField: true },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const ackHeader = headers.find((header) => header.textContent === 'Ack');
+
+ expect(ackHeader).toBeInTheDocument();
+ });
+
+ it('should not render the ack column header when ackField is disabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, ackField: false },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const ackHeader = headers.find((header) => header.textContent === 'Ack');
+
+ expect(ackHeader).toBeUndefined();
+ });
+ });
+
+ describe('Datasource Field', () => {
+ it('should not render the datasource column header when showDatasourceName is disabled', () => {
+ const props = {
+ ...defaultProps,
+ panelOptions: { ...defaultPanelOptions, showDatasourceName: false },
+ problems: [createMockProblem('1', 1609459200)],
+ };
+
+ render();
+
+ const table = screen.getByRole('table');
+ const headers = within(table).getAllByRole('columnheader');
+ const datasourceHeader = headers.find((header) => header.textContent === 'Datasource');
+
+ expect(datasourceHeader).toBeUndefined();
+ });
});
});
diff --git a/src/panel-triggers/components/Problems/Problems.tsx b/src/panel-triggers/components/Problems/Problems.tsx
index c103db3..3ef77c8 100644
--- a/src/panel-triggers/components/Problems/Problems.tsx
+++ b/src/panel-triggers/components/Problems/Problems.tsx
@@ -23,7 +23,7 @@ import {
getPaginationRowModel,
useReactTable,
} from '@tanstack/react-table';
-import { reportInteraction } from '@grafana/runtime';
+import { getDataSourceSrv, reportInteraction } from '@grafana/runtime';
import { ProblemDetails } from './ProblemDetails';
import { capitalizeFirstLetter, parseCustomTagColumns } from './utils';
@@ -191,6 +191,19 @@ export const ProblemList = (props: ProblemListProps) => {
/>
),
}),
+ columnHelper.accessor('datasource', {
+ header: 'Datasource',
+ size: 120,
+ cell: ({ cell }) => {
+ const datasource = cell.getValue();
+ let dsName: string = datasource as string;
+ if ((datasource as DataSourceRef)?.uid) {
+ const dsInstance = getDataSourceSrv().getInstanceSettings((datasource as DataSourceRef).uid);
+ dsName = dsInstance?.name || dsName;
+ }
+ return {dsName};
+ },
+ }),
columnHelper.accessor('timestamp', {
id: 'age',
header: 'Age',
@@ -268,6 +281,27 @@ export const ProblemList = (props: ProblemListProps) => {
}));
}, [effectivePageSize]);
+ // Column visibility state derived from panelOptions
+ const columnVisibility = useMemo(
+ () => ({
+ host: panelOptions.hostField,
+ hostTechName: panelOptions.hostTechNameField,
+ groups: panelOptions.hostGroups,
+ proxy: panelOptions.hostProxy,
+ priority: panelOptions.severityField,
+ statusIcon: panelOptions.statusIcon,
+ value: panelOptions.statusField,
+ opdata: panelOptions.opdataField,
+ acknowledged: panelOptions.ackField,
+ tags: panelOptions.showTags,
+ datasource: panelOptions.showDatasourceName,
+ age: panelOptions.ageField,
+ }),
+ [panelOptions]
+ );
+
+ // https://github.com/TanStack/table/issues/6137
+ // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Table's useReactTable returns functions that cannot be memoized
const table = useReactTable({
data: problems,
columns,
@@ -276,25 +310,12 @@ export const ProblemList = (props: ProblemListProps) => {
state: {
columnSizing,
pagination,
+ columnVisibility,
},
onPaginationChange: setPagination,
meta: {
panelOptions,
},
- initialState: {
- columnVisibility: {
- host: panelOptions.hostField,
- hostTechName: panelOptions.hostTechNameField,
- groups: panelOptions.hostGroups,
- proxy: panelOptions.hostProxy,
- severity: panelOptions.severityField,
- statusIcon: panelOptions.statusIcon,
- opdata: panelOptions.opdataField,
- ack: panelOptions.ackField,
- tags: panelOptions.showTags,
- age: panelOptions.ageField,
- },
- },
onColumnSizingChange: (updater) => {
const newSizing = typeof updater === 'function' ? updater(columnSizing) : updater;
setColumnSizing(newSizing);