feat(mongodb): improve sidebar index management
This commit is contained in:
1 parent
22055e081f
commit
8e7b74d0ee
56 files changed
+3558
-246
No files matched your search
@@ -7,8 +7,23 @@ import * as api from "@/lib/backend/api";
|
||||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
import { notifyNacosNamespacesChanged } from "@/lib/nacos/nacosNamespaceCache";
|
||||
import { findSidebarActionTarget } from "@/lib/sidebar/sidebarActionTarget";
|
||||
import { isRenamableMongoCollection, mongoCollectionKindFromNode, mongoDropAllIndexesPreview, mongoDropCollectionPreview, mongoDropDatabasePreview, mongoDropIndexPreview, mongoRenameCollectionPreview } from "@/lib/sidebar/mongoCollectionMutation";
|
||||
import {
|
||||
MONGO_INDEX_KEY_TYPES,
|
||||
buildMongoCreateIndexRequest,
|
||||
isProtectedMongoIndex,
|
||||
isRenamableMongoCollection,
|
||||
mongoCollectionKindFromNode,
|
||||
mongoCreateIndexPreview,
|
||||
mongoDropAllIndexesPreview,
|
||||
mongoDropCollectionPreview,
|
||||
mongoDropIndexFailureCount,
|
||||
mongoDropDatabasePreview,
|
||||
mongoDropIndexPreview,
|
||||
mongoRenameCollectionPreview,
|
||||
} from "@/lib/sidebar/mongoCollectionMutation";
|
||||
import { supportsMongoAllDriverMutations, supportsMongoIndexMutations, supportsNativeMongoDriverMutations } from "@/lib/mongo/mongoCapabilities";
|
||||
import { runMongoSidebarMutation } from "@/lib/sidebar/runMongoSidebarMutation";
|
||||
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
|
||||
import {
|
||||
sidebarDangerTarget,
|
||||
sidebarFormTarget,
|
||||
@@ -38,6 +53,12 @@ import {
|
||||
renameMongoCollectionError,
|
||||
renameMongoCollectionPreview,
|
||||
renameMongoCollectionLoading,
|
||||
showCreateMongoIndexDialog,
|
||||
mongoCreateIndexForm,
|
||||
mongoCreateIndexFieldOptions,
|
||||
mongoCreateIndexError,
|
||||
mongoCreateIndexLoading,
|
||||
resetMongoCreateIndexForm,
|
||||
} from "@/components/sidebar/sidebarTreeDialogState";
|
||||
|
||||
interface SidebarDatabaseSpecificMutationRuntimeOptions {
|
||||
@@ -57,19 +78,27 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
const { toast } = useToast();
|
||||
const { activeNode, connectionStore } = options;
|
||||
|
||||
const canDropMongoDatabase = computed(() => {
|
||||
const config = activeNode.value.connectionId ? connectionStore.getConfig(activeNode.value.connectionId) : undefined;
|
||||
return activeNode.value.type === "mongo-db" && !!activeNode.value.database && config?.driver_profile !== "mongodb-legacy";
|
||||
});
|
||||
function usesAnyMongoDriver(node: Pick<TreeNode, "connectionId">): boolean {
|
||||
return !!node.connectionId && supportsMongoAllDriverMutations(connectionStore.getConfig(node.connectionId));
|
||||
}
|
||||
|
||||
function usesNativeMongoDriver(node: Pick<TreeNode, "connectionId">): boolean {
|
||||
return !!node.connectionId && supportsNativeMongoDriverMutations(connectionStore.getConfig(node.connectionId));
|
||||
}
|
||||
|
||||
function canMutateMongoIndexes(node: TreeNode): boolean {
|
||||
return !!node.connectionId && supportsMongoIndexMutations(connectionStore.getConfig(node.connectionId), mongoCollectionKindFromNode(node));
|
||||
}
|
||||
|
||||
const canDropMongoDatabase = computed(() => activeNode.value.type === "mongo-db" && !!activeNode.value.database && usesAnyMongoDriver(activeNode.value));
|
||||
|
||||
function canMutateMongoCollectionNode(node: TreeNode): boolean {
|
||||
if (node.type !== "mongo-collection" || !node.connectionId || !node.database) return false;
|
||||
const config = connectionStore.getConfig(node.connectionId);
|
||||
return config?.db_type === "mongodb" && config.driver_profile !== "mongodb-legacy";
|
||||
return usesAnyMongoDriver(node);
|
||||
}
|
||||
|
||||
function canRenameMongoCollectionNode(node: TreeNode): boolean {
|
||||
return canMutateMongoCollectionNode(node) && isRenamableMongoCollection(node.label, mongoCollectionKindFromNode(node));
|
||||
return canMutateMongoCollectionNode(node) && usesNativeMongoDriver(node) && isRenamableMongoCollection(node.label, mongoCollectionKindFromNode(node));
|
||||
}
|
||||
|
||||
const canDropMongoCollection = computed(() => canMutateMongoCollectionNode(activeNode.value));
|
||||
@@ -140,22 +169,104 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
|
||||
function canDropMongoIndexNode(node: TreeNode): boolean {
|
||||
if (node.type !== "index" || !node.connectionId || !node.database || !node.tableName) return false;
|
||||
const config = connectionStore.getConfig(node.connectionId);
|
||||
return config?.db_type === "mongodb" && config.driver_profile !== "mongodb-legacy" && mongoIndexNameForNode(node) !== "_id_";
|
||||
const isPrimary = !!(node.meta && "is_primary" in node.meta && node.meta.is_primary);
|
||||
return canMutateMongoIndexes(node) && !isProtectedMongoIndex({ name: mongoIndexNameForNode(node), is_primary: isPrimary });
|
||||
}
|
||||
|
||||
const canDropMongoIndex = computed(() => canDropMongoIndexNode(activeNode.value));
|
||||
|
||||
const canDropAllMongoIndexes = computed(() => activeNode.value.type === "mongo-collection" && canMutateMongoIndexes(activeNode.value));
|
||||
|
||||
function mongoIndexDropPreview(node: Pick<TreeNode, "database" | "tableName">, indexName: string): string {
|
||||
return mongoDropIndexPreview(node.database || "", node.tableName || "", indexName);
|
||||
}
|
||||
|
||||
const canDropAllMongoIndexes = computed(() => canMutateMongoCollectionNode(activeNode.value));
|
||||
|
||||
function mongoDropAllIndexesPreviewForNode(node: Pick<TreeNode, "database" | "label">): string {
|
||||
return mongoDropAllIndexesPreview(node.database || "", node.label);
|
||||
}
|
||||
|
||||
function canCreateMongoIndexNode(node: TreeNode): boolean {
|
||||
const collectionName = mongoIndexCollectionName(node);
|
||||
return !!collectionName && !!node.database && canMutateMongoIndexes(node);
|
||||
}
|
||||
|
||||
const canCreateMongoIndex = computed(() => canCreateMongoIndexNode(activeNode.value));
|
||||
const mongoCreateIndexCanSubmit = computed(() => mongoCreateIndexForm.value.fields.length > 0 && mongoCreateIndexForm.value.fields.every((field) => !!field.path.trim()));
|
||||
const mongoCreateIndexCanAddField = computed(() => mongoCreateIndexForm.value.fields.every((field) => !!field.path.trim()));
|
||||
|
||||
watch(
|
||||
mongoCreateIndexForm,
|
||||
() => {
|
||||
mongoCreateIndexError.value = "";
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function mongoIndexCollectionName(node: TreeNode): string {
|
||||
if (node.type === "mongo-collection") return node.label;
|
||||
return node.type === "group-indexes" ? node.tableName || "" : "";
|
||||
}
|
||||
|
||||
function prepareCreateMongoIndexDialog() {
|
||||
const node = activeNode.value;
|
||||
if (!canCreateMongoIndexNode(node) || !node.connectionId || !node.database) return;
|
||||
resetMongoCreateIndexForm();
|
||||
showCreateMongoIndexDialog.value = true;
|
||||
void connectionStore
|
||||
.listMongoCompletionFields(node.connectionId, node.database, mongoIndexCollectionName(node))
|
||||
.then((fields) => {
|
||||
const target = sidebarFormTarget.value ?? activeNode.value;
|
||||
if (showCreateMongoIndexDialog.value && target.id === node.id) mongoCreateIndexFieldOptions.value = fields.map((field) => field.name);
|
||||
})
|
||||
.catch(() => {
|
||||
// MongoDB is schemaless; users can still enter a field that was not sampled.
|
||||
});
|
||||
}
|
||||
|
||||
function addMongoCreateIndexField() {
|
||||
if (!mongoCreateIndexCanAddField.value) return;
|
||||
const nextId = Math.max(0, ...mongoCreateIndexForm.value.fields.map((field) => field.id)) + 1;
|
||||
mongoCreateIndexForm.value.fields.push({ id: nextId, path: "", type: "1" });
|
||||
}
|
||||
|
||||
function removeMongoCreateIndexField(id: number) {
|
||||
if (mongoCreateIndexForm.value.fields.length === 1) return;
|
||||
mongoCreateIndexForm.value.fields = mongoCreateIndexForm.value.fields.filter((field) => field.id !== id);
|
||||
}
|
||||
|
||||
async function confirmCreateMongoIndex() {
|
||||
const node = sidebarFormTarget.value ?? activeNode.value;
|
||||
const connectionId = node.connectionId;
|
||||
const database = node.database;
|
||||
const collectionName = mongoIndexCollectionName(node);
|
||||
if (!canCreateMongoIndexNode(node) || !connectionId || !database || !collectionName) return;
|
||||
|
||||
const request = buildMongoCreateIndexRequest(mongoCreateIndexForm.value);
|
||||
if (!request.valid) {
|
||||
mongoCreateIndexError.value = request.error === "field-duplicate" ? t("mongo.duplicateField", { field: request.field }) : t("contextMenu.createMongoIndexFieldRequired");
|
||||
return;
|
||||
}
|
||||
|
||||
mongoCreateIndexError.value = "";
|
||||
await runMongoSidebarMutation({
|
||||
connection: connectionStore.getConfig(connectionId),
|
||||
database,
|
||||
reviewText: mongoCreateIndexPreview(database, collectionName, request.keysJson, request.optionsJson),
|
||||
source: t("production.sourceSidebar"),
|
||||
loading: mongoCreateIndexLoading,
|
||||
beforeExecute: () => connectionStore.ensureConnected(connectionId),
|
||||
execute: () => api.mongoCreateIndex(connectionId, database, collectionName, request.keysJson, request.optionsJson),
|
||||
onSuccess: async (created) => {
|
||||
showCreateMongoIndexDialog.value = false;
|
||||
toast(t("contextMenu.createMongoIndexSuccess", { name: created.name, collection: collectionName }), 3000);
|
||||
await refreshMongoIndexTreeAfterMutation({ ...node, tableName: collectionName });
|
||||
},
|
||||
onError: (error) => {
|
||||
mongoCreateIndexError.value = translateBackendError(t, errorMessage(error));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function openCreateNacosNamespaceDialog() {
|
||||
createNacosNamespaceId.value = "";
|
||||
createNacosNamespaceName.value = "";
|
||||
@@ -288,7 +399,7 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
const node = sidebarDangerTarget.value ?? activeNode.value;
|
||||
const connectionId = node.connectionId;
|
||||
const database = node.database;
|
||||
if (node.type !== "mongo-db" || !connectionId || !database) return;
|
||||
if (node.type !== "mongo-db" || !connectionId || !database || !usesAnyMongoDriver(node)) return;
|
||||
await runMongoSidebarMutation({
|
||||
connection: connectionStore.getConfig(connectionId),
|
||||
database,
|
||||
@@ -296,13 +407,11 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
source: t("production.sourceSidebar"),
|
||||
loading: dropDatabaseLoading,
|
||||
beforeExecute: () => connectionStore.ensureConnected(connectionId),
|
||||
execute: async () => {
|
||||
await api.mongoDropDatabase(connectionId, database);
|
||||
await connectionStore.loadMongoDatabases(connectionId);
|
||||
},
|
||||
onSuccess: () => {
|
||||
execute: () => api.mongoDropDatabase(connectionId, database),
|
||||
onSuccess: async () => {
|
||||
toast(t("contextMenu.dropDatabaseSuccess", { name: node.label }), 3000);
|
||||
showDropDatabaseConfirm.value = false;
|
||||
await refreshMongoTreeAfterDrop(node, () => connectionStore.loadMongoDatabases(connectionId));
|
||||
},
|
||||
onError: toastMutationError,
|
||||
});
|
||||
@@ -321,28 +430,41 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
source: t("production.sourceSidebar"),
|
||||
loading: dropMongoCollectionLoading,
|
||||
beforeExecute: () => connectionStore.ensureConnected(connectionId),
|
||||
execute: async () => {
|
||||
await api.mongoDropCollection(connectionId, database, collectionName);
|
||||
await connectionStore.loadMongoCollections(connectionId, database);
|
||||
},
|
||||
onSuccess: () => {
|
||||
execute: () => api.mongoDropCollection(connectionId, database, collectionName),
|
||||
onSuccess: async () => {
|
||||
toast(t("contextMenu.dropCollectionSuccess", { name: collectionName }), 3000);
|
||||
showDropMongoCollectionConfirm.value = false;
|
||||
await refreshMongoTreeAfterDrop(node, async () => {
|
||||
// The final collection can remove its database; if the database
|
||||
// remains, refresh its preserved expanded children as well.
|
||||
await connectionStore.loadMongoDatabases(connectionId);
|
||||
await connectionStore.loadMongoCollections(connectionId, database);
|
||||
});
|
||||
},
|
||||
onError: toastMutationError,
|
||||
});
|
||||
}
|
||||
|
||||
function mongoIndexesGroupNodeId(node: Pick<TreeNode, "connectionId" | "database" | "schema" | "tableName" | "label">): string | null {
|
||||
if (!node.connectionId || !node.database) return null;
|
||||
const tableName = node.tableName || node.label;
|
||||
return node.schema ? `${node.connectionId}:${node.database}:${node.schema}:${tableName}:__indexes` : `${node.connectionId}:${node.database}:${tableName}:__indexes`;
|
||||
async function refreshMongoTreeAfterDrop(node: TreeNode, refresh: () => Promise<void>) {
|
||||
try {
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
connectionStore.removeTreeNode(node.id);
|
||||
toast(t("contextMenu.objectDropRefreshFailed", { message: translateBackendError(t, errorMessage(error)) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshMongoIndexTree(node: Pick<TreeNode, "connectionId" | "database" | "schema" | "tableName" | "label">) {
|
||||
const nodeId = mongoIndexesGroupNodeId(node);
|
||||
if (!node.connectionId || !node.database || !nodeId) return;
|
||||
await connectionStore.loadIndexes(node.connectionId, node.database, node.tableName || node.label, node.schema, nodeId);
|
||||
async function refreshMongoIndexTreeAfterMutation(node: Pick<TreeNode, "connectionId" | "database" | "tableName" | "label">) {
|
||||
if (!node.connectionId || !node.database) return;
|
||||
try {
|
||||
await refreshLoadedMongoIndexes(connectionStore, {
|
||||
connectionId: node.connectionId,
|
||||
database: node.database,
|
||||
collection: node.tableName || node.label,
|
||||
});
|
||||
} catch (error) {
|
||||
toast(t("contextMenu.mongoIndexRefreshFailed", { message: translateBackendError(t, errorMessage(error)) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDropMongoIndex() {
|
||||
@@ -360,11 +482,19 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
loading: dropMongoIndexLoading,
|
||||
beforeExecute: () => connectionStore.ensureConnected(connectionId),
|
||||
execute: async () => {
|
||||
await api.mongoDropIndexes(connectionId, database, tableName, JSON.stringify(indexName), true);
|
||||
await refreshMongoIndexTree(node);
|
||||
try {
|
||||
return await api.mongoDropIndexes(connectionId, database, tableName, JSON.stringify(indexName), true);
|
||||
} finally {
|
||||
await refreshMongoIndexTreeAfterMutation(node);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast(t("contextMenu.dropTableChildObjectSuccess", { name: indexName }), 3000);
|
||||
onSuccess: (result) => {
|
||||
const failed = mongoDropIndexFailureCount(result);
|
||||
if (failed > 0) {
|
||||
toast(t("contextMenu.dropIndexesPartialFailure", { success: result.dropped_names.length, failed }), 5000);
|
||||
} else {
|
||||
toast(t("contextMenu.dropTableChildObjectSuccess", { name: indexName }), 3000);
|
||||
}
|
||||
showDropMongoIndexConfirm.value = false;
|
||||
},
|
||||
onError: toastMutationError,
|
||||
@@ -375,7 +505,7 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
const node = sidebarDangerTarget.value ?? activeNode.value;
|
||||
const connectionId = node.connectionId;
|
||||
const database = node.database;
|
||||
if (!canMutateMongoCollectionNode(node) || !connectionId || !database) return;
|
||||
if (node.type !== "mongo-collection" || !canMutateMongoIndexes(node) || !connectionId || !database) return;
|
||||
const collectionName = node.label;
|
||||
await runMongoSidebarMutation({
|
||||
connection: connectionStore.getConfig(connectionId),
|
||||
@@ -385,12 +515,19 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
loading: dropAllMongoIndexesLoading,
|
||||
beforeExecute: () => connectionStore.ensureConnected(connectionId),
|
||||
execute: async () => {
|
||||
const result = await api.mongoDropIndexes(connectionId, database, collectionName, undefined, false);
|
||||
await refreshMongoIndexTree(node);
|
||||
return result;
|
||||
try {
|
||||
return await api.mongoDropIndexes(connectionId, database, collectionName, undefined, false);
|
||||
} finally {
|
||||
await refreshMongoIndexTreeAfterMutation(node);
|
||||
}
|
||||
},
|
||||
onSuccess: (result) => {
|
||||
toast(t("contextMenu.dropAllIndexesSuccess", { count: result.dropped_names.length, name: collectionName }), 3000);
|
||||
const failed = mongoDropIndexFailureCount(result);
|
||||
if (failed > 0) {
|
||||
toast(t("contextMenu.dropIndexesPartialFailure", { success: result.dropped_names.length, failed }), 5000);
|
||||
} else {
|
||||
toast(t("contextMenu.dropAllIndexesSuccess", { count: result.dropped_names.length, name: collectionName }), 3000);
|
||||
}
|
||||
showDropAllMongoIndexesConfirm.value = false;
|
||||
},
|
||||
onError: toastMutationError,
|
||||
@@ -411,9 +548,18 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
|
||||
mongoIndexNameForNode,
|
||||
canDropMongoIndexNode,
|
||||
canDropMongoIndex,
|
||||
mongoIndexDropPreview,
|
||||
canDropAllMongoIndexes,
|
||||
mongoIndexDropPreview,
|
||||
mongoDropAllIndexesPreview: mongoDropAllIndexesPreviewForNode,
|
||||
refreshMongoIndexTreeAfterMutation,
|
||||
canCreateMongoIndex,
|
||||
mongoIndexKeyTypes: MONGO_INDEX_KEY_TYPES,
|
||||
mongoCreateIndexCanSubmit,
|
||||
mongoCreateIndexCanAddField,
|
||||
prepareCreateMongoIndexDialog,
|
||||
addMongoCreateIndexField,
|
||||
removeMongoCreateIndexField,
|
||||
confirmCreateMongoIndex,
|
||||
openCreateNacosNamespaceDialog,
|
||||
confirmCreateNacosNamespace,
|
||||
openEditNacosNamespaceDialog,
|
||||
|
||||
Reference in new issue
Block a user