Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions table/src/components/TablePanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { VirtuosoMockContext } from 'react-virtuoso';
import { TimeSeriesData } from '@perses-dev/spec';
import { TableOptions, TimeSeriesTableProps } from '../models';
import {
MOCK_MULTI_QUERY_DATA_EMPTY,
MOCK_MULTI_QUERY_DATA_Q1,
MOCK_MULTI_QUERY_DATA_Q2,
MOCK_MULTI_QUERY_DATA_WITH_ZERO,
Expand Down Expand Up @@ -460,5 +461,44 @@ describe('TablePanel', () => {
},
TEST_TIMEOUT
);

it(
'should show N/A for columns defined in columnSettings but with no data in any query',
async () => {
// Simulates Request Hard column: defined in columnSettings as value #3,
// but the third query returns no data at all (e.g. no ResourceQuotas on cluster)
const options: TableOptions = {
columnSettings: [
{ name: 'timestamp', hide: true },
{ name: 'namespace', header: 'Namespace' },
{ name: 'value #1', header: 'Value 1' },
{ name: 'value #2', header: 'Value 2' },
{ name: 'value #3', header: 'Value 3 (empty query)' },
],
cellSettings: [{ condition: { kind: 'Misc', spec: { value: 'null' } }, text: 'N/A' }],
transforms: [
{ kind: 'MergeSeries', spec: {} },
{ kind: 'JoinByColumnValue', spec: { columns: ['namespace'] } },
],
enableFiltering: true,
};

// Q1 has data, Q2 has partial data, Q3 is completely empty
renderMultiQueryPanel(
[MOCK_MULTI_QUERY_DATA_Q1, MOCK_MULTI_QUERY_DATA_Q2, MOCK_MULTI_QUERY_DATA_EMPTY],
options
);

// Both namespaces should be present
expect(await screen.findByRole('cell', { name: 'ns-a' })).toBeInTheDocument();
expect(await screen.findByRole('cell', { name: 'ns-b' })).toBeInTheDocument();

// Value 3 column should show N/A for all rows since no query produced data for it
const naCells = await screen.findAllByRole('cell', { name: 'N/A' });
// At minimum: ns-b missing value #2 (1) + both rows missing value #3 (2) = 3 N/A cells
expect(naCells.length).toBeGreaterThanOrEqual(3);
},
TEST_TIMEOUT
);
});
});
10 changes: 9 additions & 1 deletion table/src/components/TablePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,16 @@ export function TablePanel({ contentDimensions, spec, queryResults }: TableProps
}
}

// Include column names from columnSettings so that columns with no data
// are still extended with undefined for cellSettings evaluation (e.g. N/A for null)
for (const col of spec.columnSettings ?? []) {
if (!result.includes(col.name)) {
result.push(col.name);
}
}

return result;
}, [data]);
}, [data, spec.columnSettings]);

const columnsFormat = useMemo(() => {
const columnsFormat: Record<string, FormatOptions> = {};
Expand Down
8 changes: 8 additions & 0 deletions table/src/test/mock-query-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ export const MOCK_MULTI_QUERY_DATA_WITH_ZERO: TimeSeriesData = {
],
};

// Query that returns no data at all — simulates a metric like cpu_request_hard
// when no ResourceQuotas exist on the cluster.
export const MOCK_MULTI_QUERY_DATA_EMPTY: TimeSeriesData = {
timeRange: { start: new Date(1666625535000), end: new Date(1666625535000) },
stepMs: 24379,
series: [],
};

export const MOCK_TIME_SERIES_QUERY_DEFINITION = {
kind: 'TimeSeriesQuery',
spec: {
Expand Down
Loading