From 70e6b2b80c417d8850ea4e44adf87a9fe765180b Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Mon, 18 May 2026 11:04:20 +0200 Subject: [PATCH] fix: respect explicit table column order for integer-like column names Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/lib/components/DisplayResult.svelte | 47 ++++++++++++------- .../lib/components/table/AutoDataTable.svelte | 12 +++-- .../lib/components/table/tableUtils.test.ts | 37 +++++++++++++++ .../src/lib/components/table/tableUtils.ts | 10 +++- 4 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 frontend/src/lib/components/table/tableUtils.test.ts diff --git a/frontend/src/lib/components/DisplayResult.svelte b/frontend/src/lib/components/DisplayResult.svelte index 8ac06662d8..c6d24280d6 100644 --- a/frontend/src/lib/components/DisplayResult.svelte +++ b/frontend/src/lib/components/DisplayResult.svelte @@ -378,7 +378,10 @@ } } - function handleArrayOfObjectsHeaders(json: any) { + function handleArrayOfObjectsHeaders(json: any): { + objects: any + headersOverride: string[] | undefined + } { // handle possible a first row of headers if ( Array.isArray(json) && @@ -401,10 +404,10 @@ rows[i - 1] = obj } - return rows + return { objects: rows, headersOverride: headers } } - return json + return { objects: json, headersOverride: undefined } } type InputObject = { [key: string]: number[] } @@ -432,7 +435,10 @@ return result } - function arrayOfRowsToObjects(input: any) { + function arrayOfRowsToObjects(input: any): { + objects: any + headersOverride: string[] | undefined + } { if (Array.isArray(input) && input.length > 0) { // handle possible first row of headers if ( @@ -444,20 +450,23 @@ const headers = input[0] const rows = input.slice(1) - return rows.map((row) => { - const obj: { [key: string]: string } = {} + return { + objects: rows.map((row) => { + const obj: { [key: string]: string } = {} - for (let i = 0; i < headers.length; i++) { - obj[headers[i]] = row[i] - } + for (let i = 0; i < headers.length; i++) { + obj[headers[i]] = row[i] + } - return obj - }) + return obj + }), + headersOverride: headers + } } else { - return input + return { objects: input, headersOverride: undefined } } } - return [] + return { objects: [], headersOverride: undefined } } export function openDrawer() { @@ -624,22 +633,26 @@ {:else if !forceJson && resultKind === 'table-row'} {@const data = typeof result === 'object' && 'table-row' in result ? result['table-row'] : result} + {@const tableData = arrayOfRowsToObjects(data)} div]:h-full [&>div]:min-h-[10rem]' : ''} - objects={arrayOfRowsToObjects(data)} + objects={tableData.objects} + headersOverride={tableData.headersOverride} /> {:else if !forceJson && resultKind === 'table-row-object'} {@const data = typeof result === 'object' && 'table-row-object' in result ? result['table-row-object'] : result} + {@const tableData = handleArrayOfObjectsHeaders(data)} div]:h-full [&>div]:min-h-[10rem]' : ''} - objects={handleArrayOfObjectsHeaders(data)} + objects={tableData.objects} + headersOverride={tableData.headersOverride} /> {:else if !forceJson && resultKind === 'html'}
@@ -1016,9 +1029,7 @@ {#if largeObject}
{#if resultApiPath && shouldDownloadViaClient()} - {:else} diff --git a/frontend/src/lib/components/table/AutoDataTable.svelte b/frontend/src/lib/components/table/AutoDataTable.svelte index b6f70d2307..4ab1628d71 100644 --- a/frontend/src/lib/components/table/AutoDataTable.svelte +++ b/frontend/src/lib/components/table/AutoDataTable.svelte @@ -21,10 +21,11 @@ import DownloadCsv from './DownloadCsv.svelte' interface Props { objects?: Array> + headersOverride?: string[] class?: string } - let { objects = [], class: className }: Props = $props() + let { objects = [], headersOverride, class: className }: Props = $props() let currentPage = $state(1) let perPage = $state(25) @@ -36,8 +37,11 @@ }[] = $state([]) let headers: string[] = $state([]) - function recomputeObjectsAndHeaders(objects: Array>) { - ;[headers, structuredObjects] = computeStructuredObjectsAndHeaders(objects) + function recomputeObjectsAndHeaders( + objects: Array>, + headersOverride: string[] | undefined + ) { + ;[headers, structuredObjects] = computeStructuredObjectsAndHeaders(objects, headersOverride) } function adjustCurrentPage() { @@ -160,7 +164,7 @@ } } run(() => { - recomputeObjectsAndHeaders(objects) + recomputeObjectsAndHeaders(objects, headersOverride) }) run(() => { perPage && adjustCurrentPage() diff --git a/frontend/src/lib/components/table/tableUtils.test.ts b/frontend/src/lib/components/table/tableUtils.test.ts new file mode 100644 index 0000000000..f9f5251b63 --- /dev/null +++ b/frontend/src/lib/components/table/tableUtils.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' + +import { computeStructuredObjectsAndHeaders } from './tableUtils' + +describe('computeStructuredObjectsAndHeaders', () => { + const rows = [ + { 'Pokemon name': 'Pikachu', Type: 'Electric', '1234': 'nil', 'Main strength': 'Speed' }, + { 'Pokemon name': 'Charizard', Type: 'Fire/Flying', '1234': 'nil', 'Main strength': 'Attack' } + ] + + it('respects the explicit header order even with integer-like column names', () => { + const [headers] = computeStructuredObjectsAndHeaders(rows, [ + 'Pokemon name', + 'Type', + '1234', + 'Main strength' + ]) + expect(headers).toEqual(['Pokemon name', 'Type', '1234', 'Main strength']) + }) + + it('keeps extra keys not present in the override (appended at the end)', () => { + const [headers] = computeStructuredObjectsAndHeaders( + [{ 'Pokemon name': 'Pikachu', Type: 'Electric', extra: 'x' }], + ['Pokemon name', 'Type'] + ) + expect(headers).toEqual(['Pokemon name', 'Type', 'extra']) + }) + + it('without an override, integer-like keys are reordered by Object.keys (documented baseline)', () => { + const [headers] = computeStructuredObjectsAndHeaders(rows) + expect(headers).toEqual(['1234', 'Pokemon name', 'Type', 'Main strength']) + }) + + it('returns empty arrays for non-array input', () => { + expect(computeStructuredObjectsAndHeaders({} as any)).toEqual([[], []]) + }) +}) diff --git a/frontend/src/lib/components/table/tableUtils.ts b/frontend/src/lib/components/table/tableUtils.ts index db935ebe02..dda09ed6dd 100644 --- a/frontend/src/lib/components/table/tableUtils.ts +++ b/frontend/src/lib/components/table/tableUtils.ts @@ -8,7 +8,10 @@ export function isEmail(value: string) { return value?.includes('@') } -export function computeStructuredObjectsAndHeaders(objects: Array>): [ +export function computeStructuredObjectsAndHeaders( + objects: Array>, + headersOverride?: string[] +): [ string[], { _id: number @@ -18,7 +21,10 @@ export function computeStructuredObjectsAndHeaders(objects: Array { let rowData = obj && typeof obj == 'object' ? obj : {} if (Array.isArray(rowData)) {