Skip to content

Commit 795f16e

Browse files
authored
Merge branch 'main' into issue/1262
2 parents 26f6d44 + bbffa70 commit 795f16e

File tree

8 files changed

+129
-49
lines changed

8 files changed

+129
-49
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',

frontend/src/components/Schemas/List/List.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import Search from 'components/common/Search/Search';
1313
import PlusIcon from 'components/common/Icons/PlusIcon';
1414
import Table, { LinkCell } from 'components/common/NewTable';
1515
import { ColumnDef } from '@tanstack/react-table';
16-
import { Action, SchemaSubject, ResourceType } from 'generated-sources';
16+
import {
17+
Action,
18+
SchemaSubject,
19+
ResourceType,
20+
SchemaColumnsToSort,
21+
SortOrder,
22+
} from 'generated-sources';
1723
import { useNavigate, useSearchParams } from 'react-router-dom';
1824
import { PER_PAGE } from 'lib/constants';
1925
import { useGetSchemas } from 'lib/hooks/api/schemas';
@@ -27,14 +33,18 @@ const List: React.FC = () => {
2733
const navigate = useNavigate();
2834
const [searchParams] = useSearchParams();
2935
const {
30-
isFetching,
36+
isInitialLoading,
3137
isError,
3238
data = { pageCount: 1, schemas: [] as SchemaSubject[] },
3339
} = useGetSchemas({
3440
clusterName,
3541
page: Number(searchParams.get('page') || 1),
3642
perPage: Number(searchParams.get('perPage') || PER_PAGE),
3743
search: searchParams.get('q') || '',
44+
orderBy: (searchParams.get('sortBy') as SchemaColumnsToSort) ?? undefined,
45+
sortOrder:
46+
(searchParams.get('sortDirection')?.toUpperCase() as SortOrder) ||
47+
undefined,
3848
});
3949

4050
const columns = React.useMemo<ColumnDef<SchemaSubject>[]>(
@@ -53,7 +63,12 @@ const List: React.FC = () => {
5363
},
5464
{ header: 'Id', accessorKey: 'id', size: 120 },
5565
{ header: 'Type', accessorKey: 'schemaType', size: 120 },
56-
{ header: 'Version', accessorKey: 'version', size: 120 },
66+
{
67+
header: 'Version',
68+
accessorKey: 'version',
69+
size: 120,
70+
enableSorting: false,
71+
},
5772
{
5873
header: 'Compatibility',
5974
accessorKey: 'compatibilityLevel',
@@ -86,7 +101,7 @@ const List: React.FC = () => {
86101
<ControlPanelWrapper hasInput>
87102
<Search placeholder="Search by Schema Name" />
88103
</ControlPanelWrapper>
89-
{isFetching || isError ? (
104+
{isInitialLoading || isError ? (
90105
<PageLoader />
91106
) : (
92107
<Table

frontend/src/components/Schemas/Schemas.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
1-
import React from 'react';
1+
import React, { Suspense } from 'react';
22
import { Route, Routes } from 'react-router-dom';
33
import {
44
clusterSchemaEditRelativePath,
55
clusterSchemaNewRelativePath,
66
clusterSchemaSchemaDiffRelativePath,
77
RouteParams,
88
} from 'lib/paths';
9-
import List from 'components/Schemas/List/List';
109
import Details from 'components/Schemas/Details/Details';
1110
import New from 'components/Schemas/New/New';
1211
import Edit from 'components/Schemas/Edit/Edit';
1312
import Diff from 'components/Schemas/Diff/Diff';
13+
import PageLoader from 'components/common/PageLoader/PageLoader';
14+
15+
const List = React.lazy(() => import('./List/List'));
1416

1517
const Schemas: React.FC = () => {
1618
return (
17-
<Routes>
18-
<Route index element={<List />} />
19-
<Route path={clusterSchemaNewRelativePath} element={<New />} />
20-
<Route path={RouteParams.subject} element={<Details />} />
21-
<Route path={clusterSchemaEditRelativePath} element={<Edit />} />
22-
<Route path={clusterSchemaSchemaDiffRelativePath} element={<Diff />} />
23-
</Routes>
19+
<Suspense fallback={<PageLoader />}>
20+
<Routes>
21+
<Route index element={<List />} />
22+
<Route path={clusterSchemaNewRelativePath} element={<New />} />
23+
<Route path={RouteParams.subject} element={<Details />} />
24+
<Route path={clusterSchemaEditRelativePath} element={<Edit />} />
25+
<Route path={clusterSchemaSchemaDiffRelativePath} element={<Diff />} />
26+
</Routes>
27+
</Suspense>
2428
);
2529
};
2630

frontend/src/lib/hooks/api/schemas.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,28 @@ export function useGetSchemas({
4545
page,
4646
perPage,
4747
search,
48+
orderBy,
49+
sortOrder,
4850
}: GetSchemasRequest) {
4951
return useQuery<SchemaSubjectsResponse>({
50-
queryKey: [SCHEMA_QUERY_KEY, clusterName, page, perPage, search],
52+
queryKey: [
53+
SCHEMA_QUERY_KEY,
54+
clusterName,
55+
page,
56+
perPage,
57+
search,
58+
sortOrder,
59+
orderBy,
60+
],
61+
keepPreviousData: true,
5162
queryFn: () =>
5263
schemasApiClient.getSchemas({
5364
clusterName,
5465
page,
5566
perPage,
5667
search: search || undefined,
68+
sortOrder,
69+
orderBy,
5770
}),
5871
});
5972
}

0 commit comments

Comments
 (0)