Skip to content

Commit deddf09

Browse files
lazzy-pandak.morozovHaarolean
authored
Сlone topic functionality (FE) (#1825)
* Added Copy button in list of topics * Added copy topic functionality * Whitespaces removed * Removed extra string wrapper * Copy component removed, routing fixed, tests fixed * Ternary and returning null removed * Dublicated code refactored * Added tests for ternary header * Added enum for the form fields Co-authored-by: k.morozov <[email protected]> Co-authored-by: Roman Zabaluev <[email protected]>
1 parent 29ee6b1 commit deddf09

File tree

6 files changed

+114
-8
lines changed

6 files changed

+114
-8
lines changed

kafka-ui-react-app/src/components/Topics/List/List.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
TopicName,
77
} from 'redux/interfaces';
88
import { useParams } from 'react-router-dom';
9-
import { clusterTopicNewPath } from 'lib/paths';
9+
import { clusterTopicCopyPath, clusterTopicNewPath } from 'lib/paths';
1010
import usePagination from 'lib/hooks/usePagination';
1111
import ClusterContext from 'components/contexts/ClusterContext';
1212
import PageLoader from 'components/common/PageLoader/PageLoader';
@@ -125,6 +125,21 @@ const List: React.FC<TopicsListProps> = ({
125125
}
126126
);
127127

128+
const getSelectedTopic = (): string => {
129+
const name = Array.from(tableState.selectedIds)[0];
130+
const selectedTopic =
131+
tableState.data.find(
132+
(topic: TopicWithDetailedInfo) => topic.name === name
133+
) || {};
134+
135+
return Object.keys(selectedTopic)
136+
.map((x: string) => {
137+
const value = selectedTopic[x as keyof typeof selectedTopic];
138+
return value && x !== 'partitions' ? `${x}=${value}` : null;
139+
})
140+
.join('&');
141+
};
142+
128143
const handleSwitch = React.useCallback(() => {
129144
setShowInternal(!showInternal);
130145
history.push(`${pathname}?page=1&perPage=${perPage || PER_PAGE}`);
@@ -295,6 +310,20 @@ const List: React.FC<TopicsListProps> = ({
295310
>
296311
Delete selected topics
297312
</Button>
313+
{tableState.selectedCount === 1 && (
314+
<Button
315+
buttonSize="M"
316+
buttonType="secondary"
317+
isLink
318+
to={{
319+
pathname: clusterTopicCopyPath(clusterName),
320+
search: `?${getSelectedTopic()}`,
321+
}}
322+
>
323+
Copy selected topic
324+
</Button>
325+
)}
326+
298327
<Button
299328
buttonSize="M"
300329
buttonType="secondary"

kafka-ui-react-app/src/components/Topics/New/New.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from 'redux/actions';
1111
import { useDispatch } from 'react-redux';
1212
import { getResponse } from 'lib/errorHandling';
13-
import { useHistory, useParams } from 'react-router';
13+
import { useHistory, useLocation, useParams } from 'react-router';
1414
import { yupResolver } from '@hookform/resolvers/yup';
1515
import { topicFormValidationSchema } from 'lib/yupExtended';
1616
import PageHeading from 'components/common/PageHeading/PageHeading';
@@ -19,6 +19,14 @@ interface RouterParams {
1919
clusterName: ClusterName;
2020
}
2121

22+
enum Filters {
23+
NAME = 'name',
24+
PARTITION_COUNT = 'partitionCount',
25+
REPLICATION_FACTOR = 'replicationFactor',
26+
INSYNC_REPLICAS = 'inSyncReplicas',
27+
CLEANUP_POLICY = 'Delete',
28+
}
29+
2230
const New: React.FC = () => {
2331
const methods = useForm<TopicFormData>({
2432
mode: 'all',
@@ -29,6 +37,15 @@ const New: React.FC = () => {
2937
const history = useHistory();
3038
const dispatch = useDispatch();
3139

40+
const { search } = useLocation();
41+
const params = new URLSearchParams(search);
42+
43+
const name = params.get(Filters.NAME) || '';
44+
const partitionCount = params.get(Filters.PARTITION_COUNT) || 1;
45+
const replicationFactor = params.get(Filters.REPLICATION_FACTOR) || 1;
46+
const inSyncReplicas = params.get(Filters.INSYNC_REPLICAS) || 1;
47+
const cleanUpPolicy = params.get(Filters.CLEANUP_POLICY) || 'Delete';
48+
3249
const onSubmit = async (data: TopicFormData) => {
3350
try {
3451
await topicsApiClient.createTopic({
@@ -50,9 +67,14 @@ const New: React.FC = () => {
5067

5168
return (
5269
<>
53-
<PageHeading text="Create new Topic" />
70+
<PageHeading text={search ? 'Copy Topic' : 'Create new Topic'} />
5471
<FormProvider {...methods}>
5572
<TopicForm
73+
topicName={name}
74+
cleanUpPolicy={cleanUpPolicy}
75+
partitionCount={Number(partitionCount)}
76+
replicationFactor={Number(replicationFactor)}
77+
inSyncReplicas={Number(inSyncReplicas)}
5678
isSubmitting={methods.formState.isSubmitting}
5779
onSubmit={methods.handleSubmit(onSubmit)}
5880
/>

kafka-ui-react-app/src/components/Topics/New/__test__/New.spec.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { Provider } from 'react-redux';
77
import { screen, waitFor } from '@testing-library/react';
88
import { createMemoryHistory } from 'history';
99
import fetchMock from 'fetch-mock-jest';
10-
import { clusterTopicNewPath, clusterTopicPath } from 'lib/paths';
10+
import {
11+
clusterTopicCopyPath,
12+
clusterTopicNewPath,
13+
clusterTopicPath,
14+
} from 'lib/paths';
1115
import userEvent from '@testing-library/user-event';
1216
import { render } from 'lib/testHelpers';
1317

@@ -31,6 +35,11 @@ const renderComponent = (history = historyMock, store = storeMock) =>
3135
<New />
3236
</Provider>
3337
</Route>
38+
<Route path={clusterTopicCopyPath(':clusterName')}>
39+
<Provider store={store}>
40+
<New />
41+
</Provider>
42+
</Route>
3443
<Route path={clusterTopicPath(':clusterName', ':topicName')}>
3544
New topic path
3645
</Route>
@@ -42,6 +51,32 @@ describe('New', () => {
4251
fetchMock.reset();
4352
});
4453

54+
it('checks header for create new', async () => {
55+
const mockedHistory = createMemoryHistory({
56+
initialEntries: [clusterTopicNewPath(clusterName)],
57+
});
58+
renderComponent(mockedHistory);
59+
expect(
60+
screen.getByRole('heading', { name: 'Create new Topic' })
61+
).toHaveTextContent('Create new Topic');
62+
});
63+
64+
it('checks header for copy', async () => {
65+
const mockedHistory = createMemoryHistory({
66+
initialEntries: [
67+
{
68+
pathname: clusterTopicCopyPath(clusterName),
69+
search: `?name=test`,
70+
},
71+
],
72+
});
73+
74+
renderComponent(mockedHistory);
75+
expect(
76+
screen.getByRole('heading', { name: 'Copy Topic' })
77+
).toHaveTextContent('Copy Topic');
78+
});
79+
4580
it('validates form', async () => {
4681
const mockedHistory = createMemoryHistory({
4782
initialEntries: [clusterTopicNewPath(clusterName)],

kafka-ui-react-app/src/components/Topics/Topics.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { Switch } from 'react-router-dom';
33
import {
4+
clusterTopicCopyPath,
45
clusterTopicNewPath,
56
clusterTopicPath,
67
clusterTopicsPath,
@@ -23,6 +24,11 @@ const Topics: React.FC = () => (
2324
path={clusterTopicNewPath(':clusterName')}
2425
component={New}
2526
/>
27+
<BreadcrumbRoute
28+
exact
29+
path={clusterTopicCopyPath(':clusterName')}
30+
component={New}
31+
/>
2632
<BreadcrumbRoute
2733
path={clusterTopicPath(':clusterName', ':topicName')}
2834
component={TopicContainer}

kafka-ui-react-app/src/components/Topics/shared/Form/TopicForm.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import * as S from './TopicForm.styled';
1616

1717
export interface Props {
1818
topicName?: TopicName;
19+
partitionCount?: number;
20+
replicationFactor?: number;
21+
inSyncReplicas?: number;
22+
cleanUpPolicy?: string;
1923
isEditing?: boolean;
2024
isSubmitting: boolean;
2125
onSubmit: (e: React.BaseSyntheticEvent) => Promise<void>;
@@ -40,11 +44,19 @@ const TopicForm: React.FC<Props> = ({
4044
isEditing,
4145
isSubmitting,
4246
onSubmit,
47+
partitionCount,
48+
replicationFactor,
49+
inSyncReplicas,
50+
cleanUpPolicy,
4351
}) => {
4452
const {
4553
control,
4654
formState: { errors },
4755
} = useFormContext();
56+
const getCleanUpPolicy =
57+
CleanupPolicyOptions.find((option: SelectOption) => {
58+
return option.value === cleanUpPolicy?.toLowerCase();
59+
})?.value || CleanupPolicyOptions[0].value;
4860
return (
4961
<StyledForm onSubmit={onSubmit}>
5062
<fieldset disabled={isSubmitting}>
@@ -75,7 +87,7 @@ const TopicForm: React.FC<Props> = ({
7587
type="number"
7688
placeholder="Number of partitions"
7789
min="1"
78-
defaultValue="1"
90+
defaultValue={partitionCount}
7991
name="partitions"
8092
/>
8193
<FormError>
@@ -91,7 +103,7 @@ const TopicForm: React.FC<Props> = ({
91103
type="number"
92104
placeholder="Replication Factor"
93105
min="1"
94-
defaultValue="1"
106+
defaultValue={replicationFactor}
95107
name="replicationFactor"
96108
/>
97109
<FormError>
@@ -112,7 +124,7 @@ const TopicForm: React.FC<Props> = ({
112124
type="number"
113125
placeholder="Min In Sync Replicas"
114126
min="1"
115-
defaultValue="1"
127+
defaultValue={inSyncReplicas}
116128
name="minInsyncReplicas"
117129
/>
118130
<FormError>
@@ -135,7 +147,7 @@ const TopicForm: React.FC<Props> = ({
135147
id="topicFormCleanupPolicy"
136148
aria-labelledby="topicFormCleanupPolicyLabel"
137149
name={name}
138-
value={CleanupPolicyOptions[0].value}
150+
value={getCleanUpPolicy}
139151
onChange={onChange}
140152
minWidth="250px"
141153
options={CleanupPolicyOptions}

kafka-ui-react-app/src/lib/paths.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const clusterTopicsPath = (clusterName: ClusterName) =>
5353
`${clusterPath(clusterName)}/topics`;
5454
export const clusterTopicNewPath = (clusterName: ClusterName) =>
5555
`${clusterPath(clusterName)}/topics/create-new`;
56+
export const clusterTopicCopyPath = (clusterName: ClusterName) =>
57+
`${clusterPath(clusterName)}/topics/copy`;
5658
export const clusterTopicPath = (
5759
clusterName: ClusterName,
5860
topicName: TopicName

0 commit comments

Comments
 (0)