fix(kingbase): compat V8 routine source and add extension details
This commit is contained in:
18 files changed
+315
-6
No files matched your search
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { Package } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import type { ExtensionInfo, TreeNode } from "@/types/database";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
node: TreeNode;
|
||||
}>();
|
||||
|
||||
const open = ref(false);
|
||||
const extension = computed<ExtensionInfo>(() => {
|
||||
const meta = props.node.meta as ExtensionInfo | undefined;
|
||||
return {
|
||||
name: meta?.name || props.node.label,
|
||||
version: meta?.version || "-",
|
||||
schema: meta?.schema || props.node.schema || null,
|
||||
comment: meta?.comment || null,
|
||||
};
|
||||
});
|
||||
|
||||
function show() {
|
||||
open.value = true;
|
||||
}
|
||||
|
||||
defineExpose({ show });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex min-w-0 items-center gap-2 pr-8">
|
||||
<Package class="h-4 w-4 shrink-0 text-violet-500" />
|
||||
<span class="truncate">{{ t("extension.detailsTitle") }}</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<dl class="overflow-hidden rounded-md border text-sm">
|
||||
<div class="grid grid-cols-[7rem_minmax(0,1fr)] border-b px-3 py-2.5">
|
||||
<dt class="text-muted-foreground">{{ t("extension.name") }}</dt>
|
||||
<dd class="min-w-0 break-words font-medium">{{ extension.name }}</dd>
|
||||
</div>
|
||||
<div class="grid grid-cols-[7rem_minmax(0,1fr)] border-b px-3 py-2.5">
|
||||
<dt class="text-muted-foreground">{{ t("connection.version") }}</dt>
|
||||
<dd class="min-w-0 break-words">{{ extension.version }}</dd>
|
||||
</div>
|
||||
<div class="grid grid-cols-[7rem_minmax(0,1fr)] border-b px-3 py-2.5">
|
||||
<dt class="text-muted-foreground">Schema</dt>
|
||||
<dd class="min-w-0 break-words">{{ extension.schema || "-" }}</dd>
|
||||
</div>
|
||||
<div class="grid grid-cols-[7rem_minmax(0,1fr)] px-3 py-2.5">
|
||||
<dt class="text-muted-foreground">{{ t("structureEditor.comment") }}</dt>
|
||||
<dd class="min-w-0 whitespace-pre-wrap break-words">{{ extension.comment || "-" }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="open = false">{{ t("common.close") }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,80 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { createApp, defineComponent, h, nextTick, ref, type App } from "vue";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import i18n from "@/i18n";
|
||||
import type { TreeNode } from "@/types/database";
|
||||
|
||||
vi.mock("@/components/ui/dialog", async () => {
|
||||
const { defineComponent, h } = await import("vue");
|
||||
const passthrough = defineComponent({
|
||||
setup(_props, { slots }) {
|
||||
return () => h("div", slots.default?.());
|
||||
},
|
||||
});
|
||||
const dialog = defineComponent({
|
||||
props: { open: Boolean },
|
||||
setup(props, { slots }) {
|
||||
return () => (props.open ? h("div", slots.default?.()) : null);
|
||||
},
|
||||
});
|
||||
return { Dialog: dialog, DialogContent: passthrough, DialogFooter: passthrough, DialogHeader: passthrough, DialogTitle: passthrough };
|
||||
});
|
||||
|
||||
vi.mock("@/components/ui/button", async () => {
|
||||
const { defineComponent, h } = await import("vue");
|
||||
return {
|
||||
Button: defineComponent({
|
||||
setup(_props, { slots }) {
|
||||
return () => h("button", slots.default?.());
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
import ExtensionDetailsDialog from "@/components/objects/ExtensionDetailsDialog.vue";
|
||||
|
||||
const mountedApps: App[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const app of mountedApps.splice(0)) app.unmount();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
describe("ExtensionDetailsDialog", () => {
|
||||
it("shows metadata already loaded for the extension tree node", async () => {
|
||||
const node: TreeNode = {
|
||||
id: "connection:database:public:sys_stat_statements",
|
||||
label: "sys_stat_statements",
|
||||
type: "extension",
|
||||
schema: "public",
|
||||
meta: {
|
||||
name: "sys_stat_statements",
|
||||
version: "1.10",
|
||||
schema: "public",
|
||||
comment: "track planning and execution statistics",
|
||||
},
|
||||
};
|
||||
const dialog = ref<InstanceType<typeof ExtensionDetailsDialog> | null>(null);
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
const app = createApp(
|
||||
defineComponent({
|
||||
setup() {
|
||||
return () => h(ExtensionDetailsDialog, { ref: dialog, node });
|
||||
},
|
||||
}),
|
||||
);
|
||||
mountedApps.push(app);
|
||||
app.use(i18n);
|
||||
app.mount(container);
|
||||
dialog.value?.show();
|
||||
await nextTick();
|
||||
|
||||
const text = document.body.textContent || "";
|
||||
expect(text).toContain("sys_stat_statements");
|
||||
expect(text).toContain("1.10");
|
||||
expect(text).toContain("public");
|
||||
expect(text).toContain("track planning and execution statistics");
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,7 @@ import TreeItem from "./TreeItem.vue";
|
||||
import SidebarTreeRuntimeHost from "./SidebarTreeRuntimeHost.vue";
|
||||
import SidebarTreeItemDialogs from "./SidebarTreeItemDialogs.vue";
|
||||
import InstallExtensionDialog from "@/components/objects/InstallExtensionDialog.vue";
|
||||
import ExtensionDetailsDialog from "@/components/objects/ExtensionDetailsDialog.vue";
|
||||
import { RecycleScroller } from "vue-virtual-scroller";
|
||||
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
||||
import LightDropdown from "@/components/ui/LightDropdown.vue";
|
||||
@@ -79,6 +80,8 @@ const sidebarDangerDialogConfirming = ref(false);
|
||||
const sidebarTreeItemDialogController = ref<Record<string, any> | null>(null);
|
||||
const sidebarInstallExtensionTarget = ref<TreeNode | null>(null);
|
||||
const sidebarInstallExtensionDialogRef = ref<InstanceType<typeof InstallExtensionDialog> | null>(null);
|
||||
const sidebarExtensionDetailsTarget = ref<TreeNode | null>(null);
|
||||
const sidebarExtensionDetailsDialogRef = ref<InstanceType<typeof ExtensionDetailsDialog> | null>(null);
|
||||
const sidebarTreeRuntimeHostRef = ref<SidebarTreeRuntimeHostInstance | null>(null);
|
||||
const sidebarTreeRuntime = createSidebarTreeRuntime();
|
||||
const sidebarTreeRuntimeInitialNode: TreeNode = { id: "__sidebar-runtime__", label: "", type: "connection-group" };
|
||||
@@ -1284,6 +1287,12 @@ async function openSidebarInstallExtension(node: TreeNode) {
|
||||
sidebarInstallExtensionDialogRef.value?.show();
|
||||
}
|
||||
|
||||
async function openSidebarExtensionDetails(node: TreeNode) {
|
||||
sidebarExtensionDetailsTarget.value = createSidebarActionTarget(node);
|
||||
await nextTick();
|
||||
sidebarExtensionDetailsDialogRef.value?.show();
|
||||
}
|
||||
|
||||
function beginSidebarAction(): number {
|
||||
sidebarActionGeneration += 1;
|
||||
sidebarDdlOpen.value = false;
|
||||
@@ -1751,6 +1760,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
|
||||
@open-danger-dialog="openSidebarDangerDialog"
|
||||
@open-dialog-controller="updateSidebarTreeItemDialogController"
|
||||
@open-install-extension="openSidebarInstallExtension"
|
||||
@open-extension-details="openSidebarExtensionDetails"
|
||||
/>
|
||||
<div class="connection-tree-search sticky top-0 z-10 bg-background px-2 py-1">
|
||||
<div class="relative flex items-center gap-1">
|
||||
@@ -2056,6 +2066,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
|
||||
</SidebarDangerConfirmDialog>
|
||||
<SidebarTreeItemDialogs v-if="sidebarTreeItemDialogController" :key="sidebarTreeItemDialogController.node?.id" :controller="sidebarTreeItemDialogController" @closed="sidebarTreeItemDialogController = null" />
|
||||
<InstallExtensionDialog v-if="sidebarInstallExtensionTarget" ref="sidebarInstallExtensionDialogRef" :node="sidebarInstallExtensionTarget" @close="refreshSidebarActionTarget" @changed="refreshSidebarActionTarget" />
|
||||
<ExtensionDetailsDialog v-if="sidebarExtensionDetailsTarget" ref="sidebarExtensionDetailsDialogRef" :node="sidebarExtensionDetailsTarget" />
|
||||
<div v-if="store.treeNodes.length === 0" class="px-3 py-8 text-center text-muted-foreground text-xs">
|
||||
{{ t("sidebar.noConnections") }}
|
||||
</div>
|
||||
|
||||
@@ -342,6 +342,7 @@ const emit = defineEmits<{
|
||||
"open-danger-dialog": [request: SidebarDangerDialogRequest];
|
||||
"open-dialog-controller": [controller: Record<string, any> | null];
|
||||
"open-install-extension": [node: TreeNode];
|
||||
"open-extension-details": [node: TreeNode];
|
||||
}>();
|
||||
|
||||
const {
|
||||
@@ -760,6 +761,8 @@ function runRowClickAction(clickDetail: number) {
|
||||
scheduleOpenData(node);
|
||||
} else if (action === "open-source") {
|
||||
openObjectSourceDialog(false);
|
||||
} else if (action === "open-extension-details") {
|
||||
emit("open-extension-details", node);
|
||||
} else if (isDocumentBrowserTreeNode(node.type)) {
|
||||
openMongoTreeData(node);
|
||||
} else if (action === "toggle") {
|
||||
@@ -1067,6 +1070,8 @@ function onDoubleClick(event: MouseEvent) {
|
||||
activateDataTableFromDoubleClick();
|
||||
} else if (action === "open-source") {
|
||||
openObjectSourceDialog(false);
|
||||
} else if (action === "open-extension-details") {
|
||||
emit("open-extension-details", activeNode.value);
|
||||
} else if (action === "open-saved-sql") {
|
||||
openSavedSqlFile();
|
||||
} else if (action === "toggle" && (activeNode.value.type === "mongo-gridfs" || isDocumentBrowserTreeNode(activeNode.value.type))) {
|
||||
@@ -4395,6 +4400,8 @@ function buildSpecialSidebarMenu(context: SidebarMenuFactoryContext): boolean {
|
||||
|
||||
// 8.5 Extension
|
||||
if (node.type === "extension") {
|
||||
items.push({ label: t("extension.viewDetails"), action: () => emit("open-extension-details", node), icon: Info });
|
||||
items.push({ label: "", separator: true });
|
||||
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user