Skip to content

Report CSV/HTML export infers its columns from the first 50 rows, so a field that first appears later is dropped from every row #11774

Description

@os-zhuang

Found while re-deriving #11642's population — a sweep of the repo for renders that truncate a list and say nothing. This one is not a printer and not a display cut: the truncation is on the column inference, and the data it loses never appears anywhere in the exported artifact.

Measured (read from source at origin/main945ffbea8; not executed)

packages/plugins/plugin-reports/src/report-service.ts:

functionpickFields(rows: any[],explicit?: string[]): string[]{if(explicit&&explicit.length>0)returnexplicit;constseen=newSet<string>();for(constrofrows.slice(0,50)){if(r&&typeofr==='object')for(constkofObject.keys(r))seen.add(k);}returnArray.from(seen);}functionrenderCsv(rows: any[],fields?: string[]): string{constcols=pickFields(rows,fields);consthead=cols.join(',');constbody=rows.map(r=>cols.map(c=>escapeCsvCell(r?.[c])).join(',')).join('\r\n');returnbody.length>0 ? `${head}\r\n${body}` : head;}

The sample is the first 50 rows; the projection is applied to all of them. So when fields is not given explicitly, any key whose first occurrence is at row 51 or later is absent from the header and its values are dropped from every row of the output — including the rows that do carry it.

Two renderers are affected, and renderReport's default: branch is CSV:

  • renderCsv (:130)
  • renderHtmlTable (:147)

Why this is not merely cosmetic

Sparse columns are the normal shape of report output: an optional field, a formula that only some records satisfy, a lookup that resolves for a subset. The first 50 rows are not a random sample either — they are the query's first page in its sort order, which for a report sorted by, say, status or created date is precisely the correlated case. And unlike a truncated print, the export gives no signal at all: the CSV is well-formed, every row has the same arity, and nothing says a column was inferred rather than declared. A recipient of a scheduled report attachment cannot tell.

Not proposed here

Scanning every row is the obvious fix and is O(rows x keys) on the export path, which is a real cost decision for large reports; a declared column set from the saved report's own schema may be the better answer. Either way it is a behaviour change on an export surface, so it is filed rather than folded into an unrelated card.

Filed unassigned, no labels — severity and route are triage's call.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions