Harden visualization input normalization and table mapping

This commit is contained in:
Dmitry Lyalin
2026-02-10 20:47:11 -05:00
parent e907822dd5
commit 7a9a003430
4 changed files with 202 additions and 3 deletions

View File

@@ -128,6 +128,38 @@ describe('VisualizationResultDisplay', () => {
expect(frame).toContain('Showing truncated data (8 original items)');
});
it('renders table object rows with humanized columns without blank cells', () => {
const visualization: VisualizationDisplay = {
type: 'visualization',
kind: 'table',
title: 'Server Health Check',
data: {
columns: ['Server Name', 'CPU %', 'Memory GB', 'Status'],
rows: [
{
server_name: 'api-1',
cpu_percent: 62,
memoryGb: 8,
status: 'healthy',
},
],
},
meta: {
truncated: false,
originalItemCount: 1,
},
};
const { lastFrame } = render(
<VisualizationResultDisplay visualization={visualization} width={100} />,
);
const frame = lastFrame();
expect(frame).toContain('Server Name');
expect(frame).toContain('api-1');
expect(frame).toContain('healthy');
});
it('renders diagram visualization with UML-like nodes', () => {
const visualization: VisualizationDisplay = {
type: 'visualization',

View File

@@ -137,6 +137,42 @@ function normalizeCell(value: unknown): PrimitiveCell {
return JSON.stringify(value);
}
function normalizeLookupKey(value: string): string {
return value.toLowerCase().replace(/[^a-z0-9]/g, '');
}
function getRecordValueByColumn(
record: Record<string, unknown>,
column: string,
): unknown {
if (Object.prototype.hasOwnProperty.call(record, column)) {
return record[column];
}
const normalizedColumn = normalizeLookupKey(column);
if (normalizedColumn.length === 0) {
return undefined;
}
const entries = Object.entries(record).map(([key, value]) => ({
key,
value,
normalized: normalizeLookupKey(key),
}));
const exact = entries.find((entry) => entry.normalized === normalizedColumn);
if (exact) {
return exact.value;
}
const partial = entries.find(
(entry) =>
entry.normalized.includes(normalizedColumn) ||
normalizedColumn.includes(entry.normalized),
);
return partial?.value;
}
function makeBar(value: number, max: number, width: number): string {
const safeMax = max <= 0 ? 1 : max;
const ratio = Math.max(0, Math.min(1, value / safeMax));
@@ -234,7 +270,9 @@ function asTable(data: Record<string, unknown>): {
const record = row;
const keys = columns.length > 0 ? columns : Object.keys(record);
return keys.map((key) => normalizeCell(record[key]));
return keys.map((key) =>
normalizeCell(getRecordValueByColumn(record, key)),
);
});
const inferredColumns =