From 7886cdc6135635611b90678c199fbf55fdc49044 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Tue, 14 Jul 2026 17:06:35 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(sidebar):=20=E6=8E=92?= =?UTF-8?q?=E9=99=A4=E5=B7=B2=E5=BD=92=E5=B1=9E=E8=BF=9E=E6=8E=A5=E7=9A=84?= =?UTF-8?q?=E5=88=86=E7=BB=84=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建分组时仅展示未归属其他分组的连接 - 编辑分组时保留当前分组已有成员 - 无可选连接时显示明确空状态并补充多语言文案 - 增加连接筛选回归测试 --- .../components/SidebarNestedGroupMenu.test.ts | 44 ++++++++++++++- .../sidebar/SidebarEntityModals.tsx | 53 +++++++++++++++---- shared/i18n/de-DE.json | 1 + shared/i18n/en-US.json | 1 + shared/i18n/ja-JP.json | 1 + shared/i18n/ru-RU.json | 1 + shared/i18n/zh-CN.json | 1 + shared/i18n/zh-TW.json | 1 + 8 files changed, 91 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/SidebarNestedGroupMenu.test.ts b/frontend/src/components/SidebarNestedGroupMenu.test.ts index 11824347..c0d1b38b 100644 --- a/frontend/src/components/SidebarNestedGroupMenu.test.ts +++ b/frontend/src/components/SidebarNestedGroupMenu.test.ts @@ -1,7 +1,10 @@ import { readFileSync } from 'node:fs'; import { describe, expect, it, vi } from 'vitest'; -import { buildConnectionTagParentOptions } from './sidebar/SidebarEntityModals'; +import { + buildConnectionTagParentOptions, + buildConnectionTagSelectableConnections, +} from './sidebar/SidebarEntityModals'; import { buildSidebarLegacyNodeMenuItems } from './sidebar/sidebarLegacyNodeMenu'; const locales = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP', 'de-DE', 'ru-RU'] as const; @@ -22,6 +25,42 @@ describe('Sidebar nested group menu', () => { expect(options).toEqual([{ value: 'other', label: 'Other' }]); }); + it('only offers unassigned connections when creating and retains the edited group members', () => { + const connections = [ + { + id: 'host-current', + name: 'Current group host', + config: { type: 'mysql', host: 'current.local', port: 3306, user: 'root' }, + }, + { + id: 'host-other', + name: 'Other group host', + config: { type: 'mysql', host: 'other.local', port: 3306, user: 'root' }, + }, + { + id: 'host-unassigned', + name: 'Ungrouped host', + config: { type: 'mysql', host: 'unassigned.local', port: 3306, user: 'root' }, + }, + ]; + const connectionTags = [ + { id: 'current', name: 'Current', connectionIds: ['host-current'] }, + { id: 'other', name: 'Other', connectionIds: ['host-other'] }, + ]; + + expect( + buildConnectionTagSelectableConnections(connections, connectionTags, '') + .map((connection) => connection.id), + ).toEqual(['host-unassigned']); + expect( + buildConnectionTagSelectableConnections(connections, connectionTags, 'current') + .map((connection) => connection.id), + ).toEqual(['host-current', 'host-unassigned']); + expect( + buildConnectionTagSelectableConnections(connections.slice(0, 2), connectionTags, ''), + ).toEqual([]); + }); + it('preselects the clicked legacy group when creating a child group', () => { const createTagForm = { resetFields: vi.fn(), @@ -69,6 +108,8 @@ describe('Sidebar nested group menu', () => { it('keeps modal and both menu implementations aligned with nested grouping', () => { expect(modalSource).toContain('name="parentTagId"'); expect(modalSource).toContain('parentTagId,'); + expect(modalSource).toContain('Empty.PRESENTED_IMAGE_SIMPLE'); + expect(modalSource).toContain("sidebar.modal.tag.no_available_connections"); expect(legacyMenuSource).toContain("key: 'new-child-tag'"); expect(legacyMenuSource).toContain("t('connection.sidebar.group.newSubgroup')"); expect(v2MenuSource).toContain("| 'new-subgroup'"); @@ -81,6 +122,7 @@ describe('Sidebar nested group menu', () => { 'connection.sidebar.group.newSubgroup', 'sidebar.field.parent_group', 'sidebar.placeholder.parent_group', + 'sidebar.modal.tag.no_available_connections', 'connection.sidebar.group.deleteConfirmContent', 'sidebar.modal.confirm_delete_tag.content', ].forEach((key) => { diff --git a/frontend/src/components/sidebar/SidebarEntityModals.tsx b/frontend/src/components/sidebar/SidebarEntityModals.tsx index 16c1a19c..dfa330f9 100644 --- a/frontend/src/components/sidebar/SidebarEntityModals.tsx +++ b/frontend/src/components/sidebar/SidebarEntityModals.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Checkbox, Form, Input, Select, Space } from 'antd'; +import { Checkbox, Empty, Form, Input, Select, Space } from 'antd'; import type { FormInstance } from 'antd/es/form'; import { FolderOpenOutlined } from '@ant-design/icons'; import Modal from '../common/ResizableDraggableModal'; @@ -49,6 +49,26 @@ export const buildConnectionTagParentOptions = ( }); }; +export const buildConnectionTagSelectableConnections = ( + connections: SavedConnection[], + connectionTags: ConnectionTag[], + editingTagId: string, +): SavedConnection[] => { + const currentTagId = editingTagId.trim(); + const connectionIdsAssignedToOtherTags = new Set(); + + connectionTags.forEach((tag) => { + if (tag.id === currentTagId) return; + tag.connectionIds.forEach((connectionId) => { + connectionIdsAssignedToOtherTags.add(connectionId); + }); + }); + + return connections.filter( + (connection) => !connectionIdsAssignedToOtherTags.has(connection.id), + ); +}; + const resolveConnectionTagParentId = ( value: unknown, connectionTags: ConnectionTag[], @@ -168,8 +188,17 @@ export const SidebarEntityModals: React.FC = ({ renameSavedQueryTarget, setRenameSavedQueryTarget, handleRenameSavedQuery, -}) => ( - <> +}) => { + const editingTagId = renameViewTarget?.type === 'tag' + ? String(renameViewTarget?.dataRef?.id || '') + : ''; + const selectableConnections = buildConnectionTagSelectableConnections( + connections, + connectionTags, + editingTagId, + ); + + return (<> , @@ -181,9 +210,6 @@ export const SidebarEntityModals: React.FC = ({ styles={{ content: modalPanelStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 10 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 12 } }} onOk={() => { createTagForm.validateFields().then(values => { - const editingTagId = renameViewTarget?.type === 'tag' - ? String(renameViewTarget?.dataRef?.id || '') - : ''; const parentTagId = resolveConnectionTagParentId( values.parentTagId, connectionTags, @@ -221,7 +247,7 @@ export const SidebarEntityModals: React.FC = ({ placeholder={t('sidebar.placeholder.parent_group')} options={buildConnectionTagParentOptions( connectionTags, - renameViewTarget?.type === 'tag' ? String(renameViewTarget?.dataRef?.id || '') : '', + editingTagId, )} /> @@ -229,11 +255,16 @@ export const SidebarEntityModals: React.FC = ({
- {connections.map(conn => ( + {selectableConnections.length > 0 ? selectableConnections.map(conn => ( {conn.name} {conn.config.host ? `(${conn.config.host})` : ''} - ))} + )) : ( + + )}
@@ -380,5 +411,5 @@ export const SidebarEntityModals: React.FC = ({
- -); + ); +}; diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index 8608398d..de2631b5 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -7296,6 +7296,7 @@ "sidebar.modal.tag.create_title": "Gruppe erstellen", "sidebar.modal.tag.edit_description": "Gruppenname und enthaltene Verbindungen aktualisieren.", "sidebar.modal.tag.edit_title": "Gruppe bearbeiten", + "sidebar.modal.tag.no_available_connections": "Alle Verbindungen gehören bereits anderen Gruppen an", "sidebar.object.function": "Funktion", "sidebar.object.procedure": "Prozedur", "sidebar.object.view": "Ansicht", diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index 6a7e9f69..e79b01d1 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -7296,6 +7296,7 @@ "sidebar.modal.tag.create_title": "New group", "sidebar.modal.tag.edit_description": "Update the group name and included connections.", "sidebar.modal.tag.edit_title": "Edit group", + "sidebar.modal.tag.no_available_connections": "All connections already belong to other groups", "sidebar.object.function": "Function", "sidebar.object.procedure": "Procedure", "sidebar.object.view": "View", diff --git a/shared/i18n/ja-JP.json b/shared/i18n/ja-JP.json index 0f2b586c..770db595 100644 --- a/shared/i18n/ja-JP.json +++ b/shared/i18n/ja-JP.json @@ -7296,6 +7296,7 @@ "sidebar.modal.tag.create_title": "グループを作成", "sidebar.modal.tag.edit_description": "グループ名と含める接続を更新します。", "sidebar.modal.tag.edit_title": "グループを編集", + "sidebar.modal.tag.no_available_connections": "すべての接続は他のグループに所属しています", "sidebar.object.function": "関数", "sidebar.object.procedure": "プロシージャ", "sidebar.object.view": "ビュー", diff --git a/shared/i18n/ru-RU.json b/shared/i18n/ru-RU.json index fd498cf0..0352be4a 100644 --- a/shared/i18n/ru-RU.json +++ b/shared/i18n/ru-RU.json @@ -7296,6 +7296,7 @@ "sidebar.modal.tag.create_title": "Создать группу", "sidebar.modal.tag.edit_description": "Обновите имя группы и входящие в нее подключения.", "sidebar.modal.tag.edit_title": "Изменить группу", + "sidebar.modal.tag.no_available_connections": "Все подключения уже входят в другие группы", "sidebar.object.function": "Функция", "sidebar.object.procedure": "Процедура", "sidebar.object.view": "Представление", diff --git a/shared/i18n/zh-CN.json b/shared/i18n/zh-CN.json index 3985659b..e3b0233d 100644 --- a/shared/i18n/zh-CN.json +++ b/shared/i18n/zh-CN.json @@ -7296,6 +7296,7 @@ "sidebar.modal.tag.create_title": "新建分组", "sidebar.modal.tag.edit_description": "更新分组名称和包含的连接。", "sidebar.modal.tag.edit_title": "编辑分组", + "sidebar.modal.tag.no_available_connections": "所有连接已归属其他分组", "sidebar.object.function": "函数", "sidebar.object.procedure": "存储过程", "sidebar.object.view": "视图", diff --git a/shared/i18n/zh-TW.json b/shared/i18n/zh-TW.json index d7a03578..d1a1c1c9 100644 --- a/shared/i18n/zh-TW.json +++ b/shared/i18n/zh-TW.json @@ -7296,6 +7296,7 @@ "sidebar.modal.tag.create_title": "建立分組", "sidebar.modal.tag.edit_description": "更新分組名稱和包含的連線。", "sidebar.modal.tag.edit_title": "編輯分組", + "sidebar.modal.tag.no_available_connections": "所有連線已歸屬其他群組", "sidebar.object.function": "函式", "sidebar.object.procedure": "預存程序", "sidebar.object.view": "檢視",