Render query results as tables and charts with workbook links - #97
ernestoongaro wants to merge 12 commits into
Conversation
…kbook query run and query wait stream NDJSON with rows as base64 Arrow and, on each job line, the model's metadata for every field: label, whether it is a dimension or a measure, data type, and number format. The CLI now decodes that stream (internal/result) for human output instead of passing it through, and renders what the model says: labels as headers, columns in query order, formats applied. If the first response's wait window elapses, query/wait is polled until every job has finished; a failing poll reports through the same error envelope as any API call, and rendering is buffered so a failure leaves nothing on stdout. --chart draws the same result as a bar chart: the first measure by the first dimension, or any column named with --chart-value / --chart-label by field name or label. --chart-style picks the look (bar, block, line, fill) and --chart-rows caps the bar count. It is accepted only on commands whose spec returns the stream and refused before any request otherwise; a resultType in the body is dropped, with a note in human mode. Nothing is inferred from column names or values; a query with no measure plots its first numeric column. Headers sit over their columns. --workbook asks for an ephemeral workbook and surfaces its link: under human-rendered output, or on stderr so a passed-through CSV/XLSX or JSON stdout stays the payload. Both flags live on the API command groups. Formats follow the documented reference: number/percent/id/big/billions/ millions/thousands, the currency, accounting and financial families with optional big and currency-code prefixes, Excel-style patterns including quoted literals, scaling commas, E+00 and pos;neg;zero sections, and d3-style date patterns on temporal dimensions. Field references and conditional formats need data the stream lacks and fall back to a plain readable number. Decimal Arrow columns decode to floats; failed jobs report the API's error_type and error_message. Human output takes the brand palette: Omni Pink bars and headers, Dark Gray borders and axis, Orange for negative bars, rounded borders. Also: JSON numbers in the generic human table keep every digit and never render in scientific notation, cell truncation counts terminal cells rather than bytes, and the spinner line is cleared fully on stop. --format json is unchanged: the stream still passes through byte for byte, and an explicitly requested JSON format refuses --chart. Adds github.com/apache/arrow-go/v18 (~5 MB of binary) to read the stream. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EgE3j1US29JGkhFmT3X3Bh
The query stream always carries rows in long form; the Omni app pivots them client-side from the model_job's pivots and column_limit, which the stream echoes. The decoder now keeps both, and Set.Pivot reshapes the rows: the remaining dimensions as rows, a column per pivot value and measure, pivot values in the stream's order (a value one row group lacks lands where a later group places it), capped at the column limit. --format human renders it as the app's table, pivot value over measure label. --chart now draws the app's bar table rather than one measure by one dimension: every dimension is a label column, so two-dimension rows no longer read as repeats of the first, and every measure gets a column of bars scaled to its own maximum. Pivoted, a measure's bars share one scale across its pivot columns. --chart-value and --chart-label narrow it to one measure or dimension; labels yield width first, then trailing columns are dropped with a note so a row never wraps. A bare field name now finds a column whose label reads differently (--chart-value count finds "Deals Count"). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X6VBPtgHzXK2fwVsBBwtt
- Strip control characters from API and warehouse text in human output (cell values, labels, JSON strings and keys, error details, the workbook link), so a value can't retitle the terminal or write the clipboard. A newline in a cell reads as a space instead of breaking the layout. - Currency symbols in patterns are consumed whole: £ and ¥ are two bytes, and a trailing one panicked; £0.00 ate a digit; £#,##0 went unrecognized. - # decimals show when they aren't trailing zeros (#,##0.## → 1,234.57). - big picks its unit after rounding: 999,999.9 → 1.00M, not 1000.00K. - Pivot columns merge the orders the row groups agree on and fall back to the pivot fields' own sort (descending when the query says so) for values no group relates, instead of appending them. - An unknown --chart kind is refused before the query runs. - The omitted-columns note counts measure columns, not pivot values. - Uint64 values past MaxInt64 no longer wrap negative. - A pivot chart with one measure names it once, and pivot values widen their column rather than being cut off. - Trim stale and repetitive comments; regenerate the README chart examples from the renderer and document fill and --workbook in the flags table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X6VBPtgHzXK2fwVsBBwtt
block looked the same as bar, and fill and line didn't earn their keep. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWypY6weErBPcs1YJSPuik
Bar is the only chart, so a kind value only cluttered the help. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWypY6weErBPcs1YJSPuik
Hiding dimensions without aggregating repeated row labels and misread the data. Narrowing measures is safe, and on a pivot it lets more pivot values fit, so --chart-value now names any number of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWypY6weErBPcs1YJSPuik
|
can you post some screenshots of the rendered output? |
There was a problem hiding this comment.
🟡 Changes recommended
Precision loss, pivot collisions, unsafe error output, and incompatible flag handling can produce incorrect behavior.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds human-readable query result rendering, charts, pivot support, polling, and workbook links to the generated Omni CLI.
Changes:
- Decodes Arrow NDJSON streams into formatted tables and charts.
- Adds pivoting, polling, sanitization, and workbook-link handling.
- Introduces result flags, supporting tests, documentation, and dependencies.
File summaries
| File | Description |
|---|---|
| README.md | Documents query rendering and flags. |
| CLAUDE.md | Updates architectural output guidance. |
| go.mod | Adds Arrow and rendering dependencies. |
| go.sum | Updates dependency checksums. |
| cmd/omni/main.go | Wires flags, body preparation, and rendering. |
| cmd/omni/agent_help.go | Documents new flags for agents. |
| cmd/omni/output.go | Integrates chart-aware response handling. |
| cmd/omni/output_test.go | Tests flags, bodies, links, and responses. |
| cmd/omni/spinner.go | Clears completed spinner lines. |
| cmd/omni/stream.go | Parses, polls, and renders query streams. |
| cmd/omni/stream_test.go | Tests polling and stream failures. |
| internal/config/config.go | Separates chosen and effective formats. |
| internal/openapi/generate.go | Annotates stream responses and body fields. |
| internal/openapi/generate_test.go | Tests generated annotations. |
| internal/output/bench_test.go | Benchmarks result rendering. |
| internal/output/chart.go | Implements terminal bar charts. |
| internal/output/chart_test.go | Tests chart and pivot layouts. |
| internal/output/format.go | Implements model-aware formatting. |
| internal/output/format_test.go | Tests numeric and temporal formats. |
| internal/output/human.go | Improves generic human rendering. |
| internal/output/human_test.go | Tests formatting and sanitization. |
| internal/output/result.go | Builds result tables and chart grids. |
| internal/output/sanitize.go | Sanitizes terminal-bound text. |
| internal/output/style.go | Defines shared output styles. |
| internal/result/bench_test.go | Benchmarks Arrow stream parsing. |
| internal/result/pivot.go | Reshapes long-form pivot results. |
| internal/result/pivot_test.go | Tests pivot ordering and limits. |
| internal/result/result.go | Decodes NDJSON and Arrow results. |
| internal/result/result_test.go | Tests stream decoding and metadata. |
Review details
Suppressed comments (1)
cmd/omni/stream.go:82
- The mixed-success path also hands API-controlled job failure text directly to Cobra after writing results. This bypasses terminal sanitization and produces a plain
Error:line instead of a JSON envelope when a piped--chartauto-resolves to JSON. Report it throughwriteErrorand returnapiError, matching poll failures.
return failed
- Files reviewed: 28/29 changed files
- Comments generated: 12
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
n8agrin
left a comment
There was a problem hiding this comment.
Generally LGTM. Please address the copilot feedback.
| its own scale; a query with pivots is drawn pivoted. Leave | ||
| resultType out of the body. Cannot be combined with an | ||
| explicitly requested JSON format. | ||
| --chart-value F Only these measures get bars, by field name or label; |
There was a problem hiding this comment.
What does the "F" mean here?
There was a problem hiding this comment.
It was shorthand for the field list (field names or labels). Too cryptic, so it now reads --chart-value FIELDS.
…t can't apply Failed query jobs now go through writeError, so their text is sanitized and JSON-mode stderr stays one document. --chart-value and --chart-rows need --chart, and --chart refuses a planOnly body. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
…bars Sanitized JSON keys that collide get a numbered suffix instead of overwriting a field. Pivot tuple keys are length-prefixed so string values can't collide. NaN and Inf draw no bar, and a one-cell truncation renders an ellipsis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
Arrow decimals and uint64 values past int64 decode to result.Decimal and render every digit in formats that don't scale the value. Excel patterns pad to their required integer digits (00000 renders 00012). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4

Query results now render as readable tables in human mode, using the model's field labels and number formats. Queries that exceed the initial wait window are polled until completion.
--chartfor terminal bar charts, with field selection and row limits.--workbookto request an ephemeral workbook and display its link.JSON-mode query streams continue to pass through unchanged. Explicit JSON output cannot be combined with
--chart.Validation:
make build,go test ./...,go vet ./..., andgit diff --checkpass. Regression coverage includes integer precision, control characters in model formats, and null workbook bodies.