mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
fix(ai): constrain explain plan canvas overflow
This commit is contained in:
@@ -2614,8 +2614,8 @@ async function openExternalUrl(url: string) {
|
||||
<GitBranch class="h-3 w-3" />
|
||||
{{ t("explain.title") }}
|
||||
</Button>
|
||||
<div v-if="step.toolName === 'explain_query' && step.explainData && connection?.db_type" class="mb-1">
|
||||
<ExplainPlanViewer :plan="parseExplainFromData(step.explainData, connection.db_type)" class="max-h-64" />
|
||||
<div v-if="step.toolName === 'explain_query' && step.explainData && connection?.db_type" class="mb-1 h-64 overflow-hidden rounded border">
|
||||
<ExplainPlanViewer :plan="parseExplainFromData(step.explainData, connection.db_type)" />
|
||||
</div>
|
||||
<div v-else-if="step.isError && step.toolResult" class="text-[10px] text-red-600 dark:text-red-400">{{ step.toolResult }}</div>
|
||||
<div v-else-if="step.toolResult" class="max-h-48 overflow-auto text-[10px] text-muted-foreground whitespace-pre-wrap">{{ step.toolResult }}</div>
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const source = readFileSync(new URL("../AiAssistant.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("AI assistant explain_query canvas containment", () => {
|
||||
it("bounds the embedded ExplainPlanViewer to a fixed, clipped height", () => {
|
||||
const match = source.match(/<div v-if="step\.toolName === 'explain_query' && step\.explainData[^>]*class="([^"]*)"/);
|
||||
expect(match, "explain_query wrapper div not found").not.toBeNull();
|
||||
const wrapperClass = match![1];
|
||||
expect(wrapperClass).toContain("h-64");
|
||||
expect(wrapperClass).toContain("overflow-hidden");
|
||||
});
|
||||
|
||||
it("does not rely on an unresolvable max-height alone to contain the canvas", () => {
|
||||
expect(source).not.toContain('<ExplainPlanViewer :plan="parseExplainFromData(step.explainData, connection.db_type)" class="max-h-64" />');
|
||||
});
|
||||
});
|
||||
@@ -72,18 +72,20 @@ function tableCellText(value: unknown): string {
|
||||
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col bg-background">
|
||||
<div class="h-9 shrink-0 border-b px-3 flex items-center gap-2 text-xs">
|
||||
<span class="inline-flex items-center gap-1 rounded border bg-muted px-2 py-0.5 font-medium">
|
||||
<div class="h-9 shrink-0 border-b px-3 flex items-center gap-2 text-xs overflow-x-auto overflow-y-hidden">
|
||||
<span class="shrink-0 whitespace-nowrap inline-flex items-center gap-1 rounded border bg-muted px-2 py-0.5 font-medium">
|
||||
<GitBranch class="h-3.5 w-3.5" />
|
||||
{{ t("explain.title") }}
|
||||
</span>
|
||||
<span v-if="plan || hasTableView" class="text-muted-foreground">
|
||||
<span v-if="plan || hasTableView" class="shrink-0 whitespace-nowrap text-muted-foreground">
|
||||
{{ plan?.databaseType.toUpperCase() || "MYSQL" }}<template v-if="plan"> · {{ t("explain.nodeCount", { count: nodeCount }) }}</template>
|
||||
</span>
|
||||
<span v-if="plan?.databaseType === 'dameng' && isRawString && rawContent.includes('->')" class="ml-1 inline-flex items-center gap-1 rounded bg-green-100 px-1.5 py-0.5 font-semibold text-green-700 dark:bg-green-900/30 dark:text-green-300" style="font-size: 10px">A-TRACE</span>
|
||||
<span v-if="measuredRowsLabel" class="ml-1 inline-flex items-center gap-1 rounded bg-green-100 px-1.5 py-0.5 font-semibold text-green-700 dark:bg-green-900/30 dark:text-green-300" style="font-size: 10px">{{ measuredRowsLabel }}</span>
|
||||
<span class="flex-1" />
|
||||
<div v-if="plan || hasTableView" class="inline-flex rounded-md border bg-muted/40 p-0.5">
|
||||
<span v-if="plan?.databaseType === 'dameng' && isRawString && rawContent.includes('->')" class="shrink-0 whitespace-nowrap ml-1 inline-flex items-center gap-1 rounded bg-green-100 px-1.5 py-0.5 font-semibold text-green-700 dark:bg-green-900/30 dark:text-green-300" style="font-size: 10px"
|
||||
>A-TRACE</span
|
||||
>
|
||||
<span v-if="measuredRowsLabel" class="shrink-0 whitespace-nowrap ml-1 inline-flex items-center gap-1 rounded bg-green-100 px-1.5 py-0.5 font-semibold text-green-700 dark:bg-green-900/30 dark:text-green-300" style="font-size: 10px">{{ measuredRowsLabel }}</span>
|
||||
<span class="flex-1 min-w-2" />
|
||||
<div v-if="plan || hasTableView" class="shrink-0 inline-flex rounded-md border bg-muted/40 p-0.5">
|
||||
<Button v-if="plan" size="sm" :variant="activeView === 'canvas' ? 'secondary' : 'ghost'" class="h-6 px-2 text-xs gap-1" @click="activeView = 'canvas'">
|
||||
<Workflow class="h-3.5 w-3.5" />
|
||||
{{ t("explain.canvas") }}
|
||||
|
||||
@@ -44,3 +44,21 @@ describe("ExplainPlanViewer canvas view", () => {
|
||||
expect(tabOrder).toEqual(["canvas", "tree", "summary", "raw", "table"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ExplainPlanViewer header at narrow widths", () => {
|
||||
it("keeps the title badge and node-count label on one line instead of letting them wrap", () => {
|
||||
const headerMatch = viewerSource.match(/<div class="h-9 shrink-0[^"]*"[^>]*>([\s\S]*?)<\/div>\s*\n\s*<div v-if="loading/);
|
||||
expect(headerMatch, "explain plan header block not found").not.toBeNull();
|
||||
const header = headerMatch![0];
|
||||
expect(header).toContain("overflow-x-auto");
|
||||
// Title badge and "MYSQL · N nodes" label must not be flex-shrinkable, or a
|
||||
// narrow host (e.g. the AI chat panel) squeezes their text into a
|
||||
// near-zero width and the browser wraps it one character per line.
|
||||
expect(header).toMatch(/<span class="shrink-0 whitespace-nowrap inline-flex[^"]*">\s*<GitBranch/);
|
||||
expect(header).toMatch(/<span v-if="plan \|\| hasTableView" class="shrink-0 whitespace-nowrap text-muted-foreground">/);
|
||||
});
|
||||
|
||||
it("keeps the view-switcher button group from shrinking away", () => {
|
||||
expect(viewerSource).toContain('<div v-if="plan || hasTableView" class="shrink-0 inline-flex rounded-md border bg-muted/40 p-0.5">');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user