-
Notifications
You must be signed in to change notification settings - Fork 97
Add a button to copy the result of ad-hoc queries #5462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds a copy button for ad-hoc query results and individual SQL values, implementing export functionality in CSJV (Comma-Separated JSON Values) format. It also fixes issue #4294 regarding copy/paste from ad-hoc query output and addresses query result flickering in Firefox.
Changes:
- Adds
tableToCSJV()function to export query results as comma-separated JSON values - Creates reusable
SQLValueTooltipcomponent with integrated copy button for displaying and copying SQL values - Refactors tooltip system to use generic type parameters for better type safety
- Updates positioning and styling for copy buttons across multiple components
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| js-packages/web-console/src/lib/functions/sql.ts | Adds display/serialization functions for SQL values and CSJV export functionality |
| js-packages/web-console/src/lib/compositions/common/usePopoverTooltip.svelte.ts | Adds generic type parameter for better type safety |
| js-packages/web-console/src/lib/components/relationData/SQLValue.svelte | Simplifies props interface by removing formatter function parameter |
| js-packages/web-console/src/lib/components/other/SQLValueTooltip.svelte | New component for displaying SQL values with copy button in tooltip |
| js-packages/web-console/src/lib/components/other/ClipboardCopyButton.svelte | Updates to accept Parameter type (string or function) |
| js-packages/web-console/src/lib/components/adhoc/Query.svelte | Integrates copy button for query results and updated tooltip component |
| js-packages/web-console/src/lib/components/pipelines/editor/ChangeStream.svelte | Updates to use new tooltip component |
| js-packages/web-console/src/lib/components/other/ScrollDownFab.svelte | Adds optional class prop for positioning |
| js-packages/web-console/src/lib/components/pipelines/editor/MonitoringPanel.svelte | Updates styling for Pipeline ID copy button |
| js-packages/web-console/README.md | Minor formatting fix (adds blank line) |
| js-packages/web-console/.prettierignore | Adds .svelte-kit/ and /build/ directories |
| <ClipboardCopyButton value={pipeline.current.id} class="h-8 w-auto preset-tonal-surface"> | ||
| <ClipboardCopyButton | ||
| value={pipeline.current.id} | ||
| class="h-8 w-auto! gap-2 preset-tonal-surface px-4" |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class w-auto! on line 144 uses non-standard Tailwind syntax. In standard Tailwind CSS, the important modifier should be written as !w-auto (with the exclamation mark before the utility). If this is intentional for a specific framework or preprocessor, it should be documented. Otherwise, it should be changed to !w-auto.
| class="h-8 w-auto! gap-2 preset-tonal-surface px-4" | |
| class="h-8 !w-auto gap-2 preset-tonal-surface px-4" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
w-auto! is valid and preferred syntax in the new TailwindCSS 4
| <div class="flex flex-nowrap justify-between gap-2"> | ||
| <span class="flex-1">{tooltipData ? displaySQLValue(tooltipData.value) : ''}</span> | ||
| {#if tooltipData} | ||
| <ClipboardCopyButton class="flex-none p-0" value={() => serializeSQLValue(tooltipData!.value)} |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On line 26, there's a non-null assertion tooltipData!.value used within the function passed to ClipboardCopyButton. While the parent conditional on line 25 checks if tooltipData exists, there's a potential race condition where the user could click the copy button just as tooltipData becomes undefined. Consider capturing the value in a local variable before passing it to avoid potential runtime errors.
| // Build header row with column names as JSON strings | ||
| lines[0] = columns.map((col) => JSONbig.stringify(col.name)).join(',') | ||
|
|
||
| // Build data rows in single pass - only include rows with cells (skip errors/warnings) |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on line 91 says "only include rows with cells (skip errors/warnings)" but this information is somewhat misleading. The comment should clarify that data rows have the cells property while error/warning rows have error or warning properties, and only data rows are included in the export.
| // Build data rows in single pass - only include rows with cells (skip errors/warnings) | |
| // Build data rows in a single pass: data rows have a `cells` property, | |
| // while error/warning rows use `error` or `warning`. Only data rows are exported. |
| if (typeof value === 'object' && 'format' in value && typeof value.format === 'function') { | ||
| // Handle Dayjs objects - convert to ISO string | ||
| return (value as Dayjs).toISOString() | ||
| } |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Dayjs detection relies on duck-typing by checking for a format method. While this works in the current context since the input type is constrained to SQLValueJS, it would be more explicit and maintainable to use value.constructor.name === 'Dayjs' or import and check against the Dayjs class directly. This would make the code more robust and easier to understand.
| const columns = result.columns | ||
|
|
||
| // Pre-allocate array for worst case (header + all rows) | ||
| const lines = new Array<string>(rows.length + 1) | ||
|
|
||
| // Build header row with column names as JSON strings | ||
| lines[0] = columns.map((col) => JSONbig.stringify(col.name)).join(',') |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function doesn't handle the edge case where result.columns might be empty or undefined. If columns is empty, line 89 would produce an empty header string. While this might be acceptable behavior, it would be more robust to add validation or document this behavior explicitly.
| const columns = result.columns | |
| // Pre-allocate array for worst case (header + all rows) | |
| const lines = new Array<string>(rows.length + 1) | |
| // Build header row with column names as JSON strings | |
| lines[0] = columns.map((col) => JSONbig.stringify(col.name)).join(',') | |
| const columns = result.columns ?? [] | |
| // Pre-allocate array for worst case (header + all rows) | |
| const lines = new Array<string>(rows.length + 1) | |
| // Build header row with column names as JSON strings | |
| lines[0] = columns.length > 0 ? columns.map((col) => JSONbig.stringify(col.name)).join(',') : '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documented this behavior
| export const serializeSQLValue = (value: SQLValueJS): string => { | ||
| return JSONbig.stringify(toJSONValue(value)) | ||
| } | ||
|
|
||
| /** |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The serializeSQLValue function doesn't have any error handling, unlike displaySQLValue which has a try-catch block. If JSONbig.stringify throws an error (e.g., with circular references or other edge cases), this will propagate to the caller. Consider adding error handling similar to displaySQLValue to prevent crashes when copying values.
| export const serializeSQLValue = (value: SQLValueJS): string => { | |
| return JSONbig.stringify(toJSONValue(value)) | |
| } | |
| /** | |
| * Serialize SQLValueJS to a JSON string representation suitable for CSV export | |
| */ | |
| export const serializeSQLValue = (value: SQLValueJS): string => { | |
| try { | |
| return JSONbig.stringify(toJSONValue(value)) | |
| } catch (e) { | |
| console.log('Failed to serialize SQL value', e) | |
| return '' | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handling is implemented in the call site of serializeSQLValue. The function itself should throw on error
6b1bc18 to
7945da2
Compare
| const lines = new Array<string>(rows.length + 1) | ||
|
|
||
| // Build header row with column names as JSON strings | ||
| // If there are no columns - the first row will be empty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's possible to write a query that produces no columns
Signed-off-by: Karakatiza666 <[email protected]>
7945da2 to
3705978
Compare
The implementation correctly handles nested collection types.
The result:
Also, Fix #4294: Cannot copy/paste value from adhoc-query output

Fixed adhoc query result flickering in Firefox