Add "Copy As" submenu to Notebook Query Result context menus - #22395
Add "Copy As" submenu to Notebook Query Result context menus#22395Lewis Sanchez (lewis-sanchez) wants to merge 17 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a “Copy As” submenu to the Notebook query results grid context menu (matching the regular query editor grid UX) and implements the associated client-side formatting/copying behavior, including an iframe→extension-host path for displaying errors.
Changes:
- Adds a “Copy As” submenu to the notebook grid context menu with CSV/JSON/INSERT INTO/IN clause formatting.
- Plumbs
postMessagethrough the notebook renderer so the context menu can surface errors via the extension host. - Introduces unit tests covering the new formatter behaviors.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| localization/xliff/vscode-mssql.xlf | Adds localized string entry for the IN-clause single-column error. |
| extensions/mssql/test/unit/notebooks/notebookContextMenu.test.ts | New unit tests validating CSV/JSON/IN clause/INSERT formatting behavior. |
| extensions/mssql/src/webviews/pages/NotebookRenderer/notebookResultsOutput.tsx | Passes postMessage down to the result grid component. |
| extensions/mssql/src/webviews/pages/NotebookRenderer/notebookResultGrid.tsx | Extends grid props with postMessage and wires it into the context menu plugin. |
| extensions/mssql/src/webviews/pages/NotebookRenderer/notebookResultGrid.css | Updates context menu styling and adds focused/hover/submenu indicator styling. |
| extensions/mssql/src/webviews/pages/NotebookRenderer/notebookRendererEntry.tsx | Passes renderer postMessage into the notebook result grid. |
| extensions/mssql/src/webviews/pages/NotebookRenderer/notebookContextMenu.plugin.ts | Implements submenu + new “Copy As” actions and client-side formatters; adds keyboard navigation and error posting. |
| extensions/mssql/src/webviews/common/locConstants.ts | Adds localized constant for the IN-clause single-column error message. |
| extensions/mssql/src/sharedInterfaces/notebookQueryResult.ts | Adds NotebookShowErrorMessage interface for renderer messaging. |
| extensions/mssql/src/notebooks/sqlNotebookController.ts | Handles showError renderer messages by calling vscode.window.showErrorMessage. |
| extensions/mssql/l10n/bundle.l10n.json | Adds localized string entry for the IN-clause single-column error. |
PR Changes
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #22395 +/- ##
===========================================
- Coverage 88.25% 75.60% -12.65%
===========================================
Files 415 408 -7
Lines 131028 131339 +311
Branches 8393 8439 +46
===========================================
- Hits 115640 99303 -16337
- Misses 15388 32036 +16648
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (5)
extensions/mssql/src/webviews/pages/NotebookRenderer/notebookContextMenu.plugin.ts:216
- The context menu DOM is built from generic elements without ARIA roles, so screen readers won’t announce it as a menu. Add appropriate roles (and apply similarly to the submenu container) so assistive tech can interpret the structure.
const menu = document.createElement("div");
menu.className = "nb-context-menu";
extensions/mssql/src/webviews/pages/NotebookRenderer/notebookContextMenu.plugin.ts:390
- Menu items are created as plain elements without a semantic role. This reduces screen reader usability and can make keyboard navigation expectations unclear. Mark items with role="menuitem" (and consider adding aria-haspopup/aria-expanded on the submenu item).
const item = document.createElement("div");
item.className = "nb-context-menu-item";
extensions/mssql/src/webviews/pages/NotebookRenderer/notebookContextMenu.plugin.ts:714
- formatAsInClause can emit syntactically invalid SQL when a numeric-typed cell has an empty display value: the current logic treats it as a numeric literal and outputs a blank token in the IN list. Treat empty/whitespace numeric values as NULL (or quote them) to keep the output valid.
const rawVal = cellVal?.displayValue ?? "";
const val = cellVal?.isNull
? "NULL"
: isNumeric && !/[eE]/.test(rawVal)
? rawVal
: this.sqlStr(rawVal);
extensions/mssql/src/webviews/pages/NotebookRenderer/notebookContextMenu.plugin.ts:756
- formatAsInsertInto can emit invalid SQL when a numeric-typed cell has an empty/whitespace display value: it returns the raw empty string as a literal (e.g.,
(, ...)). Handle empty numeric values explicitly (e.g., emit NULL) before deciding whether to quote.
const cellVal = item?.[col.field!];
if (cellVal?.isNull) return "NULL";
const val = cellVal?.displayValue ?? "";
return isNumeric && !/[eE]/.test(val) ? val : this.sqlStr(val);
extensions/mssql/test/unit/notebooks/notebookContextMenu.test.ts:30
- This test file mutates global objects (global.navigator and global.Slick) at module load time and never restores them. That can leak state into other test files and create order-dependent failures. Prefer setting these globals in suite setup (before tests that construct NotebookContextMenu) and restoring them in teardown.
// Mock navigator.platform for isMac() in notebookContextMenu.plugin
// Use Object.defineProperty because navigator is read-only in Electron
Object.defineProperty(global, "navigator", {
value: {
platform: "Win32",
clipboard: {
writeText: async () => {},
},
},
writable: true,
configurable: true,
});
// Slick.EventHandler is a class field — mock the global before any test instantiates NotebookContextMenu.
(global as any).Slick = {
EventHandler: class {
subscribe() {}
unsubscribeAll() {}
},
};
| } else { | ||
| const displayVal = cellVal?.displayValue ?? ""; | ||
| val = isJsonNumber ? displayVal : JSON.stringify(displayVal); | ||
| } |
Description
This PR fixes #22209
This PR adds a sub-context menu for "Copy As" commands, so that they are similar to the ones present in the query results grid for regular query editors. There are a total of 4 "Copy As" commands that are being added:
Because the iframe where the query results grid is rendered doesn't have a way to communicate back to SQL Tools Service, the copying logic had to be implemented client side, otherwise we most likely could have reused the same copying functionality used by the regular query results grid.
The new context menu options appear in the results grid that appears after a query is executed in a SQL notebook:

Result Grid and Notebook Result Grid
Result Grid:
Copy as CSV
Copy as JSON
Copy as INSERT INTO
Copy as IN Clause
Notebook Result Grid:
Copy as CSV
Copy as JSON
Copy as INSERT INTO
Copy as IN clause
Provide a clear, concise summary of the changes in this PR. What problem does it solve? Why is it needed? Link any related issues using issue closing keywords.
Code Changes Checklist
npm run test)Reviewers: Please read our reviewer guidelines