Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
clusterTopicsPath,
kafkaConnectPath,
} from 'lib/paths';
import { useLocation } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLocalStorage } from 'lib/hooks/useLocalStorage';
import { ClusterColorKey } from 'theme/theme';
import useScrollIntoView from 'lib/hooks/useScrollIntoView';
Expand All @@ -34,8 +34,10 @@ const ClusterMenu: FC<ClusterMenuProps> = ({
}) => {
const hasFeatureConfigured = (key: ClusterFeaturesEnum) =>
features?.includes(key);

const [isOpen, setIsOpen] = useState(!!opened);
const location = useLocation();
const navigate = useNavigate();
const [colorKey, setColorKey] = useLocalStorage<ClusterColorKey>(
`clusterColor-${name}`,
'transparent'
Expand All @@ -47,13 +49,25 @@ const ClusterMenu: FC<ClusterMenuProps> = ({

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

const handleClusterNameClick = () => {
if (!isOpen) {
setIsOpen(true);
}
navigate(clusterBrokersPath(name));
};

const handleToggleMenu = () => {
setIsOpen((prev) => !prev);
};

return (
<S.ClusterList role="menu" $colorKey={colorKey} ref={ref}>
<MenuTab
title={name}
status={status}
isOpen={isOpen}
toggleClusterMenu={() => setIsOpen((prev) => !prev)}
toggleClusterMenu={handleToggleMenu}
onClusterNameClick={handleClusterNameClick}
setColorKey={setColorKey}
isActive={opened}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,33 @@ describe('ClusterMenu', () => {
/>
);
const getMenuItems = () => screen.getAllByRole('menuitem');
const getMenuItem = () => screen.getByRole('menuitem');
const getBrokers = () => screen.getByTitle('Brokers');
const getTopics = () => screen.getByTitle('Brokers');
const getConsumers = () => screen.getByTitle('Brokers');
const getTopics = () => screen.getByTitle('Topics');
const getConsumers = () => screen.getByTitle('Consumers');
const getKafkaConnect = () => screen.getByTitle('Kafka Connect');
const getCluster = () => screen.getByText(onlineClusterPayload.name);

// Хелпер для клика по шеврону - ищем SVG с шевроном
const clickChevron = async () => {
const chevronSvg = document.querySelector('svg[viewBox="0 0 10 6"]');
if (chevronSvg) {
await userEvent.click(chevronSvg as Element);
}
};

it('renders cluster menu with default set of features', async () => {
render(setupComponent(onlineClusterPayload));
expect(getCluster()).toBeInTheDocument();

expect(getMenuItems().length).toEqual(1);
await userEvent.click(getMenuItem());
await clickChevron();
expect(getMenuItems().length).toEqual(4);

expect(getBrokers()).toBeInTheDocument();
expect(getTopics()).toBeInTheDocument();
expect(getConsumers()).toBeInTheDocument();
});

it('renders cluster menu with correct set of features', async () => {
render(
setupComponent({
Expand All @@ -53,7 +61,7 @@ describe('ClusterMenu', () => {
})
);
expect(getMenuItems().length).toEqual(1);
await userEvent.click(getMenuItem());
await clickChevron();
expect(getMenuItems().length).toEqual(7);

expect(getBrokers()).toBeInTheDocument();
Expand All @@ -63,6 +71,7 @@ describe('ClusterMenu', () => {
expect(getKafkaConnect()).toBeInTheDocument();
expect(screen.getByTitle('KSQL DB')).toBeInTheDocument();
});

it('renders open cluster menu', () => {
render(setupComponent(onlineClusterPayload, true), {
initialEntries: [clusterConnectorsPath(onlineClusterPayload.name)],
Expand All @@ -74,6 +83,7 @@ describe('ClusterMenu', () => {
expect(getTopics()).toBeInTheDocument();
expect(getConsumers()).toBeInTheDocument();
});

it('makes Kafka Connect link active', async () => {
render(
setupComponent({
Expand All @@ -83,7 +93,7 @@ describe('ClusterMenu', () => {
{ initialEntries: [clusterConnectorsPath(onlineClusterPayload.name)] }
);
expect(getMenuItems().length).toEqual(1);
await userEvent.click(getMenuItem());
await clickChevron();
expect(getMenuItems().length).toEqual(5);

const kafkaConnect = getKafkaConnect();
Expand Down
66 changes: 39 additions & 27 deletions frontend/src/components/Nav/Menu/MenuTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,55 @@ export interface MenuTabProps {
status: ServerStatus;
isOpen: boolean;
toggleClusterMenu: () => void;
onClusterNameClick: () => void;
setColorKey: Dispatch<SetStateAction<ClusterColorKey>>;
isActive?: boolean;
}

const MenuTab: FC<MenuTabProps> = ({
title,
toggleClusterMenu,
onClusterNameClick,
status,
isOpen,
setColorKey,
isActive = false,
}) => (
<S.MenuItem
$variant="primary"
onClick={toggleClusterMenu}
$isActive={isActive}
>
<S.ContentWrapper>
<S.StatusIconWrapper>
<S.StatusIcon status={status} aria-label="status">
<title>{status}</title>
</S.StatusIcon>
</S.StatusIconWrapper>

<S.Title title={title}>{title}</S.Title>
</S.ContentWrapper>

<S.ActionsWrapper>
<S.ColorPickerWrapper>
<MenuColorPicker setColorKey={setColorKey} />
</S.ColorPickerWrapper>

<S.ChevronWrapper>
<S.ChevronIcon $isOpen={isOpen} />
</S.ChevronWrapper>
</S.ActionsWrapper>
</S.MenuItem>
);
}) => {
const handleNameClick = (e: React.MouseEvent) => {
e.stopPropagation();
onClusterNameClick();
};

const handleChevronClick = (e: React.MouseEvent) => {
e.stopPropagation();
toggleClusterMenu();
};

return (
<S.MenuItem $variant="primary" $isActive={isActive}>
<S.ContentWrapper onClick={handleNameClick} style={{ cursor: 'pointer' }}>
<S.StatusIconWrapper>
<S.StatusIcon status={status} aria-label="status">
<title>{status}</title>
</S.StatusIcon>
</S.StatusIconWrapper>

<S.Title title={title}>{title}</S.Title>
</S.ContentWrapper>

<S.ActionsWrapper>
<S.ColorPickerWrapper>
<MenuColorPicker setColorKey={setColorKey} />
</S.ColorPickerWrapper>

<S.ChevronClickArea onClick={handleChevronClick}>
<S.ChevronWrapper>
<S.ChevronIcon $isOpen={isOpen} />
</S.ChevronWrapper>
</S.ChevronClickArea>
</S.ActionsWrapper>
</S.MenuItem>
);
};

export default MenuTab;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const toggleClusterMenuMock = jest.fn();
describe('MenuTab component', () => {
const setupWrapper = (props?: Partial<MenuTabProps>) => (
<MenuTab
onClusterNameClick={() => {}}
setColorKey={() => {}}
status={ServerStatus.ONLINE}
isOpen
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/Nav/Menu/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const ContentWrapper = styled.div`
display: flex;
align-items: center;
column-gap: 4px;
width: 100%;
`;

export const Title = styled.div`
Expand Down Expand Up @@ -85,6 +86,16 @@ export const StatusIcon = styled.circle.attrs({
`;
});

export const ChevronClickArea = styled.div`
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 4px 6px;
margin: -4px -6px;
border-radius: 4px;
`;

export const ChevronWrapper = styled.svg.attrs({
viewBox: '0 0 10 6',
xmlns: 'http://www.w3.org/2000/svg',
Expand Down
Loading