Skip to content

Commit bbffa70

Browse files
Leshe4kaLeshe4kagermanosin
authored
FE: Improve navigation between clusters (#1331)
Co-authored-by: Leshe4ka <[email protected]> Co-authored-by: German Osin <[email protected]>
1 parent 9a39150 commit bbffa70

File tree

5 files changed

+83
-35
lines changed

5 files changed

+83
-35
lines changed

frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
clusterTopicsPath,
1515
kafkaConnectPath,
1616
} from 'lib/paths';
17-
import { useLocation } from 'react-router-dom';
17+
import { useLocation, useNavigate } from 'react-router-dom';
1818
import { useLocalStorage } from 'lib/hooks/useLocalStorage';
1919
import { ClusterColorKey } from 'theme/theme';
2020
import useScrollIntoView from 'lib/hooks/useScrollIntoView';
@@ -34,8 +34,10 @@ const ClusterMenu: FC<ClusterMenuProps> = ({
3434
}) => {
3535
const hasFeatureConfigured = (key: ClusterFeaturesEnum) =>
3636
features?.includes(key);
37+
3738
const [isOpen, setIsOpen] = useState(!!opened);
3839
const location = useLocation();
40+
const navigate = useNavigate();
3941
const [colorKey, setColorKey] = useLocalStorage<ClusterColorKey>(
4042
`clusterColor-${name}`,
4143
'transparent'
@@ -47,13 +49,25 @@ const ClusterMenu: FC<ClusterMenuProps> = ({
4749

4850
const { ref } = useScrollIntoView<HTMLUListElement>(opened);
4951

52+
const handleClusterNameClick = () => {
53+
if (!isOpen) {
54+
setIsOpen(true);
55+
}
56+
navigate(clusterBrokersPath(name));
57+
};
58+
59+
const handleToggleMenu = () => {
60+
setIsOpen((prev) => !prev);
61+
};
62+
5063
return (
5164
<S.ClusterList role="menu" $colorKey={colorKey} ref={ref}>
5265
<MenuTab
5366
title={name}
5467
status={status}
5568
isOpen={isOpen}
56-
toggleClusterMenu={() => setIsOpen((prev) => !prev)}
69+
toggleClusterMenu={handleToggleMenu}
70+
onClusterNameClick={handleClusterNameClick}
5771
setColorKey={setColorKey}
5872
isActive={opened}
5973
/>

frontend/src/components/Nav/ClusterMenu/__tests__/ClusterMenu.spec.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,33 @@ describe('ClusterMenu', () => {
2222
/>
2323
);
2424
const getMenuItems = () => screen.getAllByRole('menuitem');
25-
const getMenuItem = () => screen.getByRole('menuitem');
2625
const getBrokers = () => screen.getByTitle('Brokers');
27-
const getTopics = () => screen.getByTitle('Brokers');
28-
const getConsumers = () => screen.getByTitle('Brokers');
26+
const getTopics = () => screen.getByTitle('Topics');
27+
const getConsumers = () => screen.getByTitle('Consumers');
2928
const getKafkaConnect = () => screen.getByTitle('Kafka Connect');
3029
const getCluster = () => screen.getByText(onlineClusterPayload.name);
3130

31+
// Хелпер для клика по шеврону - ищем SVG с шевроном
32+
const clickChevron = async () => {
33+
const chevronSvg = document.querySelector('svg[viewBox="0 0 10 6"]');
34+
if (chevronSvg) {
35+
await userEvent.click(chevronSvg as Element);
36+
}
37+
};
38+
3239
it('renders cluster menu with default set of features', async () => {
3340
render(setupComponent(onlineClusterPayload));
3441
expect(getCluster()).toBeInTheDocument();
3542

3643
expect(getMenuItems().length).toEqual(1);
37-
await userEvent.click(getMenuItem());
44+
await clickChevron();
3845
expect(getMenuItems().length).toEqual(4);
3946

4047
expect(getBrokers()).toBeInTheDocument();
4148
expect(getTopics()).toBeInTheDocument();
4249
expect(getConsumers()).toBeInTheDocument();
4350
});
51+
4452
it('renders cluster menu with correct set of features', async () => {
4553
render(
4654
setupComponent({
@@ -53,7 +61,7 @@ describe('ClusterMenu', () => {
5361
})
5462
);
5563
expect(getMenuItems().length).toEqual(1);
56-
await userEvent.click(getMenuItem());
64+
await clickChevron();
5765
expect(getMenuItems().length).toEqual(7);
5866

5967
expect(getBrokers()).toBeInTheDocument();
@@ -63,6 +71,7 @@ describe('ClusterMenu', () => {
6371
expect(getKafkaConnect()).toBeInTheDocument();
6472
expect(screen.getByTitle('KSQL DB')).toBeInTheDocument();
6573
});
74+
6675
it('renders open cluster menu', () => {
6776
render(setupComponent(onlineClusterPayload, true), {
6877
initialEntries: [clusterConnectorsPath(onlineClusterPayload.name)],
@@ -74,6 +83,7 @@ describe('ClusterMenu', () => {
7483
expect(getTopics()).toBeInTheDocument();
7584
expect(getConsumers()).toBeInTheDocument();
7685
});
86+
7787
it('makes Kafka Connect link active', async () => {
7888
render(
7989
setupComponent({
@@ -83,7 +93,7 @@ describe('ClusterMenu', () => {
8393
{ initialEntries: [clusterConnectorsPath(onlineClusterPayload.name)] }
8494
);
8595
expect(getMenuItems().length).toEqual(1);
86-
await userEvent.click(getMenuItem());
96+
await clickChevron();
8797
expect(getMenuItems().length).toEqual(5);
8898

8999
const kafkaConnect = getKafkaConnect();

frontend/src/components/Nav/Menu/MenuTab.tsx

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,55 @@ export interface MenuTabProps {
1010
status: ServerStatus;
1111
isOpen: boolean;
1212
toggleClusterMenu: () => void;
13+
onClusterNameClick: () => void;
1314
setColorKey: Dispatch<SetStateAction<ClusterColorKey>>;
1415
isActive?: boolean;
1516
}
1617

1718
const MenuTab: FC<MenuTabProps> = ({
1819
title,
1920
toggleClusterMenu,
21+
onClusterNameClick,
2022
status,
2123
isOpen,
2224
setColorKey,
2325
isActive = false,
24-
}) => (
25-
<S.MenuItem
26-
$variant="primary"
27-
onClick={toggleClusterMenu}
28-
$isActive={isActive}
29-
>
30-
<S.ContentWrapper>
31-
<S.StatusIconWrapper>
32-
<S.StatusIcon status={status} aria-label="status">
33-
<title>{status}</title>
34-
</S.StatusIcon>
35-
</S.StatusIconWrapper>
36-
37-
<S.Title title={title}>{title}</S.Title>
38-
</S.ContentWrapper>
39-
40-
<S.ActionsWrapper>
41-
<S.ColorPickerWrapper>
42-
<MenuColorPicker setColorKey={setColorKey} />
43-
</S.ColorPickerWrapper>
44-
45-
<S.ChevronWrapper>
46-
<S.ChevronIcon $isOpen={isOpen} />
47-
</S.ChevronWrapper>
48-
</S.ActionsWrapper>
49-
</S.MenuItem>
50-
);
26+
}) => {
27+
const handleNameClick = (e: React.MouseEvent) => {
28+
e.stopPropagation();
29+
onClusterNameClick();
30+
};
31+
32+
const handleChevronClick = (e: React.MouseEvent) => {
33+
e.stopPropagation();
34+
toggleClusterMenu();
35+
};
36+
37+
return (
38+
<S.MenuItem $variant="primary" $isActive={isActive}>
39+
<S.ContentWrapper onClick={handleNameClick} style={{ cursor: 'pointer' }}>
40+
<S.StatusIconWrapper>
41+
<S.StatusIcon status={status} aria-label="status">
42+
<title>{status}</title>
43+
</S.StatusIcon>
44+
</S.StatusIconWrapper>
45+
46+
<S.Title title={title}>{title}</S.Title>
47+
</S.ContentWrapper>
48+
49+
<S.ActionsWrapper>
50+
<S.ColorPickerWrapper>
51+
<MenuColorPicker setColorKey={setColorKey} />
52+
</S.ColorPickerWrapper>
53+
54+
<S.ChevronClickArea onClick={handleChevronClick}>
55+
<S.ChevronWrapper>
56+
<S.ChevronIcon $isOpen={isOpen} />
57+
</S.ChevronWrapper>
58+
</S.ChevronClickArea>
59+
</S.ActionsWrapper>
60+
</S.MenuItem>
61+
);
62+
};
5163

5264
export default MenuTab;

frontend/src/components/Nav/Menu/__tests__/MenuTab.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const toggleClusterMenuMock = jest.fn();
1111
describe('MenuTab component', () => {
1212
const setupWrapper = (props?: Partial<MenuTabProps>) => (
1313
<MenuTab
14+
onClusterNameClick={() => {}}
1415
setColorKey={() => {}}
1516
status={ServerStatus.ONLINE}
1617
isOpen

frontend/src/components/Nav/Menu/styled.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const ContentWrapper = styled.div`
5050
display: flex;
5151
align-items: center;
5252
column-gap: 4px;
53+
width: 100%;
5354
`;
5455

5556
export const Title = styled.div`
@@ -85,6 +86,16 @@ export const StatusIcon = styled.circle.attrs({
8586
`;
8687
});
8788

89+
export const ChevronClickArea = styled.div`
90+
display: flex;
91+
align-items: center;
92+
justify-content: center;
93+
cursor: pointer;
94+
padding: 4px 6px;
95+
margin: -4px -6px;
96+
border-radius: 4px;
97+
`;
98+
8899
export const ChevronWrapper = styled.svg.attrs({
89100
viewBox: '0 0 10 6',
90101
xmlns: 'http://www.w3.org/2000/svg',

0 commit comments

Comments
 (0)