fix(grid): enable copy as SQL for selected cells
This commit is contained in:
1 parent
84f1498a4b
commit
cd6f99fbc5
2 files changed
+101
-12
No files matched your search
@@ -197,7 +197,7 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
expect(state.canCopyRow.value).toBe(true);
|
||||
});
|
||||
|
||||
it("builds SQL UPDATE from only selected writable columns while retaining a hidden primary key", async () => {
|
||||
it("builds SQL UPDATE using the full row when right-clicking a selected cell", async () => {
|
||||
const item = row([7, "Ada", true]);
|
||||
const matrix: CellSelectionMatrix = {
|
||||
rowIndexes: [0],
|
||||
@@ -242,11 +242,11 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
hasRowSelection: computed(() => false),
|
||||
};
|
||||
vi.mocked(extractDataGridSelection).mockResolvedValueOnce({
|
||||
text: "UPDATE users SET active = TRUE WHERE id = 7;",
|
||||
text: "UPDATE users SET `name` = 'Ada', `active` = TRUE WHERE `id` = 7;",
|
||||
mimeType: "application/sql",
|
||||
fileExtension: "sql",
|
||||
rowCount: 1,
|
||||
columnCount: 1,
|
||||
columnCount: 2,
|
||||
});
|
||||
const state = useDataGridExport(options);
|
||||
|
||||
@@ -256,12 +256,12 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
expect(extractDataGridSelection).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
extractor: "sql-updates",
|
||||
selectedColumnIndexes: [0],
|
||||
rows: [[true, 7]],
|
||||
selectionKind: "cells",
|
||||
selectedColumnIndexes: [0, 1],
|
||||
rows: [["Ada", true, 7]],
|
||||
selectionKind: "rows",
|
||||
}),
|
||||
);
|
||||
expect(copyToClipboard).toHaveBeenCalledWith("UPDATE users SET active = TRUE WHERE id = 7;");
|
||||
expect(copyToClipboard).toHaveBeenCalledWith("UPDATE users SET `name` = 'Ada', `active` = TRUE WHERE `id` = 7;");
|
||||
});
|
||||
|
||||
it("builds a SELECT request for exactly one explicitly selected cell", async () => {
|
||||
@@ -364,6 +364,88 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
expect(createExportState({ ...editableTable, tableName: "" }, ["id", "name"], { ...matrix, columnIndexes: [1], columns: ["name"], rows: [["Ada"]] }, [7, "Ada"]).canCopyWithExtractor("sql-select")).toBe(false);
|
||||
});
|
||||
|
||||
// Regression tests for https://github.com/t8y2/dbx/issues/6272
|
||||
it("enables SELECT copy when sourceColumns is undefined by falling back to display names", () => {
|
||||
const matrix: CellSelectionMatrix = {
|
||||
rowIndexes: [0],
|
||||
columnIndexes: [0],
|
||||
columns: ["id"],
|
||||
rows: [[7]],
|
||||
};
|
||||
const item = row([7, "Ada"]);
|
||||
const state = useDataGridExport({
|
||||
columns: computed(() => ["id", "name"]),
|
||||
displayItems: computed(() => [item]),
|
||||
sql: computed(() => undefined),
|
||||
tableMeta: computed(() => editableTable),
|
||||
databaseType: computed(() => "mysql"),
|
||||
connectionId: computed(() => "connection-1"),
|
||||
database: computed(() => "dbx"),
|
||||
context: computed(() => "table-data"),
|
||||
sourceColumns: computed(() => ["id", "name"]),
|
||||
allColumns: computed(() => ["id", "name"]),
|
||||
allDisplayItems: computed(() => [item]),
|
||||
allSourceColumns: computed(() => undefined),
|
||||
visibleColumnIndexes: computed(() => [0, 1]),
|
||||
columnTypes: computed(() => ["int", "varchar"]),
|
||||
whereInput: computed(() => undefined),
|
||||
orderBy: computed(() => undefined),
|
||||
exportBatchSize: computed(() => 1000),
|
||||
hasCellSelection: computed(() => true),
|
||||
selectedCells: computed(() => matrix),
|
||||
selectedCellMatrix: computed(() => matrix),
|
||||
selectedRange: computed(() => ({ startRow: 0, endRow: 0, startCol: 0, endCol: 0 })),
|
||||
contextCell: ref(null),
|
||||
contextSelectionIsSynthetic: ref(false),
|
||||
getRowItem: (rowId) => (rowId === item.id ? item : undefined),
|
||||
selectedRowIds: ref(new Set<number>()),
|
||||
hasRowSelection: computed(() => false),
|
||||
});
|
||||
|
||||
expect(state.canCopyWithExtractor("sql-select")).toBe(true);
|
||||
});
|
||||
|
||||
it("enables INSERT/UPDATE when right-clicking a previously selected primary-key cell", () => {
|
||||
const item = row([7, "Ada"]);
|
||||
const matrix: CellSelectionMatrix = {
|
||||
rowIndexes: [0],
|
||||
columnIndexes: [0],
|
||||
columns: ["id"],
|
||||
rows: [[7]],
|
||||
};
|
||||
const state = useDataGridExport({
|
||||
columns: computed(() => ["id", "name"]),
|
||||
displayItems: computed(() => [item]),
|
||||
sql: computed(() => undefined),
|
||||
tableMeta: computed(() => editableTable),
|
||||
databaseType: computed(() => "mysql"),
|
||||
connectionId: computed(() => "connection-1"),
|
||||
database: computed(() => "dbx"),
|
||||
context: computed(() => "table-data"),
|
||||
sourceColumns: computed(() => ["id", "name"]),
|
||||
allColumns: computed(() => ["id", "name"]),
|
||||
allDisplayItems: computed(() => [item]),
|
||||
allSourceColumns: computed(() => ["id", "name"]),
|
||||
visibleColumnIndexes: computed(() => [0, 1]),
|
||||
columnTypes: computed(() => ["int", "varchar"]),
|
||||
whereInput: computed(() => undefined),
|
||||
orderBy: computed(() => undefined),
|
||||
exportBatchSize: computed(() => 1000),
|
||||
hasCellSelection: computed(() => true),
|
||||
selectedCells: computed(() => matrix),
|
||||
selectedCellMatrix: computed(() => matrix),
|
||||
selectedRange: computed(() => ({ startRow: 0, endRow: 0, startCol: 0, endCol: 0 })),
|
||||
contextCell: ref({ rowId: item.id, rowIndex: 0, col: 0 }),
|
||||
contextSelectionIsSynthetic: ref(false),
|
||||
getRowItem: (rowId) => (rowId === item.id ? item : undefined),
|
||||
selectedRowIds: ref(new Set<number>()),
|
||||
hasRowSelection: computed(() => false),
|
||||
});
|
||||
|
||||
expect(state.canCopyWithExtractor("sql-updates")).toBe(true);
|
||||
expect(state.canCopyWithExtractor("sql-inserts")).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves drag-reordered visible column order when copying", async () => {
|
||||
vi.mocked(extractDataGridSelection).mockResolvedValue({ text: "x", mimeType: "text/csv", fileExtension: "csv", rowCount: 1, columnCount: 3 });
|
||||
const matrix: CellSelectionMatrix = {
|
||||
|
||||
@@ -92,9 +92,15 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
const matrix = options.selectedCellMatrix.value;
|
||||
const isMultiCellSelection = !!matrix && (matrix.rowIndexes.length > 1 || matrix.columnIndexes.length > 1);
|
||||
// A right-click sets contextCell.col to the clicked column (≥ 0); the test
|
||||
// harness and non-right-click paths leave it at -1. Only treat a 1×1 matrix
|
||||
// as synthetic when there's a real right-click context.
|
||||
// harness and non-right-click paths leave it at -1.
|
||||
const hasRightClickContext = !!options.contextCell.value && options.contextSelectionIsSynthetic.value;
|
||||
// When the user already has a single-cell selection and right-clicks the same
|
||||
// cell, contextSelectionIsSynthetic is false but we still have a valid context
|
||||
// cell. For INSERT/UPDATE extractors, we include all visible non-PK columns
|
||||
// so the "has writable column" check doesn't fail when the selected cell
|
||||
// happens to be the primary key. sql-select intentionally keeps using only
|
||||
// the selected cell via the matrix branch below.
|
||||
const hasNonSyntheticContextCell = !!options.contextCell.value && options.contextCell.value.col >= 0 && !options.contextSelectionIsSynthetic.value && !!matrix && !isMultiCellSelection;
|
||||
|
||||
if (options.hasRowSelection.value && options.selectedRowIds.value.size > 0) {
|
||||
const items = options.displayItems.value.filter((item) => options.selectedRowIds.value.has(item.id) && !item.isDraft);
|
||||
@@ -108,8 +114,9 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
sourceRowIds = items.map((item) => item.id);
|
||||
selectedSourceIndexes = dedupeColumnIndexes(matrix.columnIndexes.map((index) => visibleIndexes[index] ?? index)).filter((index) => index < fullColumns.length);
|
||||
if (options.hasColumnSelection.value) selectionKind = "columns";
|
||||
} else if (hasRightClickContext && options.contextCell.value) {
|
||||
// A SELECT targets the clicked cell; existing extractors keep their full-row context behavior.
|
||||
} else if ((hasRightClickContext || (hasNonSyntheticContextCell && extractor !== "sql-select")) && options.contextCell.value) {
|
||||
// A SELECT targets the clicked cell; other extractors use the full row
|
||||
// so that right-clicking a selected PK cell still enables INSERT/UPDATE.
|
||||
const item = fullItemsById.get(options.contextCell.value.rowId);
|
||||
if (item && !item.isDraft) {
|
||||
sourceRows = [item.data];
|
||||
@@ -248,7 +255,7 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
|
||||
if (!request?.tableMeta?.tableName.trim() || request.rows.length !== 1) return false;
|
||||
if (request.selectionKind === "columns") return false;
|
||||
if (request.selectionKind === "cells" && request.selectedColumnIndexes.length !== 1) return false;
|
||||
return request.columns.length > 0 && request.columns.every((column) => !!column.sourceName?.trim());
|
||||
return request.columns.length > 0 && request.columns.every((column) => !!(column.sourceName ?? column.displayName)?.trim());
|
||||
}
|
||||
if (extractor === "raw") {
|
||||
const request = buildRequest(extractor, extractorOptions);
|
||||
|
||||
Reference in new issue
Block a user