mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-22 08:53:46 +08:00
🐛 fix(sidebar): 排除已归属连接的分组选项
- 创建分组时仅展示未归属其他分组的连接 - 编辑分组时保留当前分组已有成员 - 无可选连接时显示明确空状态并补充多语言文案 - 增加连接筛选回归测试
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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<string>();
|
||||
|
||||
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<SidebarEntityModalsProps> = ({
|
||||
renameSavedQueryTarget,
|
||||
setRenameSavedQueryTarget,
|
||||
handleRenameSavedQuery,
|
||||
}) => (
|
||||
<>
|
||||
}) => {
|
||||
const editingTagId = renameViewTarget?.type === 'tag'
|
||||
? String(renameViewTarget?.dataRef?.id || '')
|
||||
: '';
|
||||
const selectableConnections = buildConnectionTagSelectableConnections(
|
||||
connections,
|
||||
connectionTags,
|
||||
editingTagId,
|
||||
);
|
||||
|
||||
return (<>
|
||||
<Modal
|
||||
title={renderSidebarModalTitle(
|
||||
<FolderOpenOutlined />,
|
||||
@@ -181,9 +210,6 @@ export const SidebarEntityModals: React.FC<SidebarEntityModalsProps> = ({
|
||||
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<SidebarEntityModalsProps> = ({
|
||||
placeholder={t('sidebar.placeholder.parent_group')}
|
||||
options={buildConnectionTagParentOptions(
|
||||
connectionTags,
|
||||
renameViewTarget?.type === 'tag' ? String(renameViewTarget?.dataRef?.id || '') : '',
|
||||
editingTagId,
|
||||
)}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -229,11 +255,16 @@ export const SidebarEntityModals: React.FC<SidebarEntityModalsProps> = ({
|
||||
<Checkbox.Group style={{ width: '100%' }}>
|
||||
<div style={modalScrollSectionStyle}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{connections.map(conn => (
|
||||
{selectableConnections.length > 0 ? selectableConnections.map(conn => (
|
||||
<Checkbox key={conn.id} value={conn.id}>
|
||||
{conn.name} {conn.config.host ? `(${conn.config.host})` : ''}
|
||||
</Checkbox>
|
||||
))}
|
||||
)) : (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={t('sidebar.modal.tag.no_available_connections')}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
</Checkbox.Group>
|
||||
@@ -380,5 +411,5 @@ export const SidebarEntityModals: React.FC<SidebarEntityModalsProps> = ({
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
</>);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user