feat(query): jump to PostgreSQL error position from result messages

This commit is contained in:
amwps290 authored and GitHub committed 2026-09-15 17:39:59 +08:00
1 parent 571fc07d85
commit 5a0e74eec6
36 files changed
+2079 -23

No files matched your search

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed } from "vue";
import { Bot, Wrench } from "@lucide/vue";
import { Bot, LocateFixed, Wrench } from "@lucide/vue";
import { useI18n } from "vue-i18n";
import { Button } from "@/components/ui/button";
import type { BackendError } from "@/lib/backend/errorUtils";
@@ -10,20 +10,28 @@ const props = defineProps<{
errorMessage: string;
backendError?: BackendError;
connectionId?: string;
/** Driver-reported error position, when the backend provides one. */
errorPosition?: { line: number; column: number };
}>();
const emit = defineEmits<{
changeConnectionTimeout: [];
changeQueryTimeout: [];
fixWithAi: [errorMessage: string];
locateError: [];
}>();
const { t } = useI18n();
const showConnectionTimeout = computed(() => !!props.connectionId && isConnectionTimeoutErrorMessage(props.errorMessage, props.backendError));
const showQueryTimeout = computed(() => !!props.connectionId && !showConnectionTimeout.value && isQueryTimeoutErrorMessage(props.errorMessage, props.backendError));
const showLocateError = computed(() => !!props.errorPosition);
</script>
<template>
<Button v-if="showLocateError" variant="outline" size="sm" class="h-7 gap-1.5 px-2.5 text-xs" @click="emit('locateError')">
<LocateFixed class="h-3.5 w-3.5" />
{{ t("editor.locateError", { line: errorPosition!.line, column: errorPosition!.column }) }}
</Button>
<Button v-if="showConnectionTimeout" variant="outline" size="sm" class="h-7 gap-1.5 px-2.5 text-xs" @click="emit('changeConnectionTimeout')">
<Wrench class="h-3.5 w-3.5" />
{{ t("editor.changeConnectionTimeout") }}
@@ -7,17 +7,26 @@ import i18n from "@/i18n";
const mountedApps: App[] = [];
async function mountActions(errorMessage: string, onChangeConnectionTimeout = vi.fn()) {
async function mountActions(errorMessage: string, options: { onChangeConnectionTimeout?: () => void; errorPosition?: { line: number; column: number }; onLocateError?: () => void } = {}) {
const onChangeConnectionTimeout = options.onChangeConnectionTimeout ?? vi.fn();
const onLocateError = options.onLocateError ?? vi.fn();
const host = document.createElement("div");
document.body.appendChild(host);
const app = createApp({
setup: () => () => h(QueryErrorActions, { errorMessage, connectionId: "postgres-1", onChangeConnectionTimeout }),
setup: () => () =>
h(QueryErrorActions, {
errorMessage,
connectionId: "postgres-1",
errorPosition: options.errorPosition,
onChangeConnectionTimeout,
onLocateError,
}),
});
mountedApps.push(app);
app.use(i18n);
app.mount(host);
await nextTick();
return { host, onChangeConnectionTimeout };
return { host, onChangeConnectionTimeout, onLocateError };
}
afterEach(() => {
@@ -35,6 +44,23 @@ describe("QueryErrorActions", () => {
expect(onChangeConnectionTimeout).toHaveBeenCalledOnce();
});
it("offers a locate-error action when the backend reports a position", async () => {
const { host, onLocateError } = await mountActions("ERROR: relation does not exist", {
errorPosition: { line: 2, column: 6 },
});
const locateButton = Array.from(host.querySelectorAll("button")).find((button) => button.textContent?.includes("Locate error"));
expect(locateButton?.textContent).toContain("line 2, col 6");
locateButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(onLocateError).toHaveBeenCalledOnce();
});
it("hides the locate-error action without a position", async () => {
const { host } = await mountActions("ERROR: relation does not exist");
expect(host.textContent).not.toContain("Locate error");
});
it("does not offer connection timeout settings for query timeouts", async () => {
const { host } = await mountActions("Query timed out after 30 seconds");