fix: record failed data grid saves in history

This commit is contained in:
vrustx
2026-06-01 19:00:35 +08:00
committed by skyler
parent 87a43644de
commit 302f48ec8b
2 changed files with 116 additions and 8 deletions
@@ -577,10 +577,11 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
statements: string[],
rollbackStatements: string[],
elapsed: number,
historyResult?: { affected_rows?: number },
historyResult?: { affected_rows?: number; success?: boolean; error?: string },
) {
if (!connectionId.value || !database.value || !tableMeta.value) return;
const connName = connectionStore.getConfig(connectionId.value)?.name || "";
const success = historyResult?.success ?? true;
const details = {
schema: tableMeta.value.schema,
table: tableMeta.value.tableName,
@@ -588,7 +589,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
updated_rows: dirtyRows.value.size,
deleted_rows: deletedRows.value.size,
statements,
rollback_statements: rollbackStatements,
rollback_statements: success ? rollbackStatements : [],
error: success ? undefined : historyResult?.error,
};
await historyStore.add({
connection_id: connectionId.value,
@@ -596,16 +598,35 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
database: database.value,
sql: statements.join("\n"),
execution_time_ms: elapsed,
success: true,
success,
error: success ? undefined : historyResult?.error,
activity_kind: "data_change",
operation: dataChangeOperation(),
target: tableHistoryTarget(),
affected_rows: historyResult?.affected_rows ?? statements.length,
rollback_sql: rollbackStatements.length ? rollbackStatements.join("\n") : undefined,
affected_rows: success ? (historyResult?.affected_rows ?? statements.length) : undefined,
rollback_sql: success && rollbackStatements.length ? rollbackStatements.join("\n") : undefined,
details_json: JSON.stringify(details),
});
}
async function recordFailedDataGridHistory(
statements: string[],
rollbackStatements: string[],
start: number,
error: unknown,
) {
const message = normalizeDataGridSaveError(databaseType.value, error);
try {
await recordDataGridHistory(statements, rollbackStatements, Date.now() - start, {
success: false,
error: message,
});
} catch (historyError) {
console.warn("[DBX] failed to record data grid history", historyError);
}
return message;
}
function reloadCurrentData() {
options.emit(
"reload",
@@ -691,7 +712,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
preparedSave?.executionSchema,
);
} catch (e: any) {
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
saveError.value = await recordFailedDataGridHistory(stmts, rollbackStmts, start, e);
isSaving.value = false;
return;
}
@@ -699,7 +720,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
try {
apiResult = await api.executeBatch(connectionId.value, database.value, stmts);
} catch (e: any) {
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
saveError.value = await recordFailedDataGridHistory(stmts, rollbackStmts, start, e);
isSaving.value = false;
return;
}
@@ -709,7 +730,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
await onExecuteSql.value(sqlStmt);
}
} catch (e: any) {
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
saveError.value = await recordFailedDataGridHistory(stmts, rollbackStmts, start, e);
isSaving.value = false;
return;
}
+87
View File
@@ -443,3 +443,90 @@ test("saving manually typed JSON from a MySQL grid normalizes smart quotes", asy
`UPDATE "settings" SET "payload" = '{"2:3":"3:4","3:2":"4:3","21:9":"16:9"}' WHERE "id" = 1;`,
]);
});
test("failed table data save records a failed history entry", async () => {
setActivePinia(createPinia());
installBrowserTestGlobals();
const permissionError = "Statement 1 failed: Server error: ERROR 42000 (1142): UPDATE command denied to user";
const savedHistoryEntries: Array<Record<string, unknown>> = [];
globalThis.fetch = (async (input, init) => {
const url = String(input);
if (url === "/api/query/prepare-data-grid-save") {
const body = JSON.parse(String(init?.body ?? "{}"));
const options = body.options as DataGridSaveStatementOptions;
return new Response(
JSON.stringify({
statements: mockPreparedSaveStatements(options),
rollbackStatements: [`UPDATE "pp_questions" SET "title" = 'Old title' WHERE "id" = 1;`],
executionSchema: options.tableMeta.schema,
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
}
if (url === "/api/query/execute-in-transaction") {
return new Response(permissionError, { status: 500 });
}
if (url === "/api/history/save") {
savedHistoryEntries.push(JSON.parse(String(init?.body ?? "{}")).entry);
return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } });
}
return new Response(`unexpected request: ${url}`, { status: 500 });
}) as typeof fetch;
const result = computed(() => ({
columns: ["id", "title"],
rows: [[1, "Old title"] as CellValue[]],
}));
const rowStatusFilter = ref<"all" | "changed" | "edited" | "new" | "deleted">("all");
const editor = useDataGridEditor({
result,
editable: computed(() => true),
databaseType: computed(() => "mysql"),
connectionId: computed(() => "conn-1"),
database: computed(() => "app_db"),
tableMeta: computed(() => ({
tableName: "pp_questions",
columns: [column("id", true), column("title")],
primaryKeys: ["id"],
})),
onExecuteSql: computed(() => undefined),
customSave: computed(() => undefined),
sql: computed(() => "SELECT id, title FROM pp_questions"),
searchText: ref(""),
whereFilterInput: ref(""),
orderByInput: ref(""),
rowStatusFilter,
pageSize: ref(50),
currentPage: ref(1),
getRowItem: (rowId) => {
if (rowId !== 0) return undefined;
return {
id: 0,
sourceIndex: 0,
data: result.value.rows[0],
isNew: false,
isDeleted: false,
isDirtyCol: [false, false],
status: "clean",
};
},
emit: () => {},
});
editor.applyCellValue(0, 1, "New title");
await editor.saveChanges();
assert.equal(editor.saveError.value, permissionError);
assert.equal(editor.dirtyRows.value.size, 1);
assert.equal(savedHistoryEntries.length, 1);
const historyEntry = savedHistoryEntries[0];
assert.equal(historyEntry.success, false);
assert.equal(historyEntry.error, permissionError);
assert.equal(historyEntry.activity_kind, "data_change");
assert.equal(historyEntry.operation, "UPDATE");
assert.equal(historyEntry.target, "pp_questions");
assert.equal(historyEntry.rollback_sql, undefined);
assert.equal(historyEntry.affected_rows, undefined);
assert.equal(historyEntry.sql, `UPDATE "pp_questions" SET "title" = 'New title' WHERE "id" = 1;`);
});