Merge branch 'master' into DISCUSSION-4109_sr_serde_serialize
This commit is contained in:
commit
b4f1e7d93c
21 changed files with 1350 additions and 1227 deletions
4
.github/workflows/frontend.yaml
vendored
4
.github/workflows/frontend.yaml
vendored
|
@ -25,11 +25,11 @@ jobs:
|
|||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: pnpm/action-setup@v2.4.0
|
||||
with:
|
||||
version: 7.4.0
|
||||
version: 8.6.12
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: "16.15.0"
|
||||
node-version: "18.17.1"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "./kafka-ui-react-app/pnpm-lock.yaml"
|
||||
- name: Install Node dependencies
|
||||
|
|
|
@ -16,6 +16,8 @@ import java.util.stream.Stream;
|
|||
|
||||
public class BrokersConfigTab extends BasePage {
|
||||
|
||||
protected List<SelenideElement> editBtn = $$x("//button[@aria-label='editAction']");
|
||||
protected SelenideElement searchByKeyField = $x("//input[@placeholder='Search by Key or Value']");
|
||||
protected SelenideElement sourceInfoIcon = $x("//div[text()='Source']/..//div/div[@class]");
|
||||
protected SelenideElement sourceInfoTooltip = $x("//div[text()='Source']/..//div/div[@style]");
|
||||
protected ElementsCollection editBtns = $$x("//button[@aria-label='editAction']");
|
||||
|
|
|
@ -1 +1 @@
|
|||
v16.15.0
|
||||
v18.17.1
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
"vite-plugin-ejs": "^1.6.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "v16.15.0",
|
||||
"pnpm": "^7.4.0"
|
||||
"node": "v18.17.1",
|
||||
"pnpm": "^8.6.12"
|
||||
}
|
||||
}
|
||||
|
|
2450
kafka-ui-react-app/pnpm-lock.yaml
generated
2450
kafka-ui-react-app/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -34,14 +34,19 @@ const Configs: React.FC = () => {
|
|||
|
||||
const getData = () => {
|
||||
return data
|
||||
.filter(
|
||||
(item) =>
|
||||
item.name.toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
|
||||
-1
|
||||
)
|
||||
.filter((item) => {
|
||||
const nameMatch = item.name
|
||||
.toLocaleLowerCase()
|
||||
.includes(keyword.toLocaleLowerCase());
|
||||
return nameMatch
|
||||
? true
|
||||
: item.value &&
|
||||
item.value
|
||||
.toLocaleLowerCase()
|
||||
.includes(keyword.toLocaleLowerCase()); // try to match the keyword on any of the item.value elements when nameMatch fails but item.value exists
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a.source === b.source) return 0;
|
||||
|
||||
return a.source === ConfigSource.DYNAMIC_BROKER_CONFIG ? -1 : 1;
|
||||
});
|
||||
};
|
||||
|
@ -95,7 +100,7 @@ const Configs: React.FC = () => {
|
|||
<S.SearchWrapper>
|
||||
<Search
|
||||
onChange={setKeyword}
|
||||
placeholder="Search by Key"
|
||||
placeholder="Search by Key or Value"
|
||||
value={keyword}
|
||||
/>
|
||||
</S.SearchWrapper>
|
||||
|
|
|
@ -13,7 +13,7 @@ import { brokersPayload } from 'lib/fixtures/brokers';
|
|||
import { clusterStatsPayload } from 'lib/fixtures/clusters';
|
||||
|
||||
const clusterName = 'local';
|
||||
const brokerId = 1;
|
||||
const brokerId = 200;
|
||||
const activeClassName = 'is-active';
|
||||
const brokerLogdir = {
|
||||
pageText: 'brokerLogdir',
|
||||
|
|
|
@ -73,13 +73,13 @@ const BrokersList: React.FC = () => {
|
|||
header: 'Broker ID',
|
||||
accessorKey: 'brokerId',
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
cell: ({ row: { id }, getValue }) => (
|
||||
cell: ({ getValue }) => (
|
||||
<S.RowCell>
|
||||
<LinkCell
|
||||
value={`${getValue<string | number>()}`}
|
||||
to={encodeURIComponent(`${getValue<string | number>()}`)}
|
||||
/>
|
||||
{id === String(activeControllers) && (
|
||||
{getValue<string | number>() === activeControllers && (
|
||||
<Tooltip
|
||||
value={<CheckMarkRoundIcon />}
|
||||
content="Active Controller"
|
||||
|
|
|
@ -56,11 +56,11 @@ describe('BrokersList Component', () => {
|
|||
});
|
||||
it('opens broker when row clicked', async () => {
|
||||
renderComponent();
|
||||
await userEvent.click(screen.getByRole('cell', { name: '0' }));
|
||||
await userEvent.click(screen.getByRole('cell', { name: '100' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedUsedNavigate).toBeCalledWith(
|
||||
clusterBrokerPath(clusterName, '0')
|
||||
clusterBrokerPath(clusterName, '100')
|
||||
)
|
||||
);
|
||||
});
|
||||
|
@ -124,6 +124,39 @@ describe('BrokersList Component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('BrokersList', () => {
|
||||
describe('when the brokers are loaded', () => {
|
||||
const testActiveControllers = 0;
|
||||
beforeEach(() => {
|
||||
(useBrokers as jest.Mock).mockImplementation(() => ({
|
||||
data: brokersPayload,
|
||||
}));
|
||||
(useClusterStats as jest.Mock).mockImplementation(() => ({
|
||||
data: clusterStatsPayload,
|
||||
}));
|
||||
});
|
||||
|
||||
it(`Indicates correct active cluster`, async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
it(`Correct display even if there is no active cluster: ${testActiveControllers} `, async () => {
|
||||
(useClusterStats as jest.Mock).mockImplementation(() => ({
|
||||
data: {
|
||||
...clusterStatsPayload,
|
||||
activeControllers: testActiveControllers,
|
||||
},
|
||||
}));
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when diskUsage is empty', () => {
|
||||
beforeEach(() => {
|
||||
(useBrokers as jest.Mock).mockImplementation(() => ({
|
||||
|
@ -157,11 +190,11 @@ describe('BrokersList Component', () => {
|
|||
});
|
||||
it('opens broker when row clicked', async () => {
|
||||
renderComponent();
|
||||
await userEvent.click(screen.getByRole('cell', { name: '1' }));
|
||||
await userEvent.click(screen.getByRole('cell', { name: '100' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedUsedNavigate).toBeCalledWith(
|
||||
clusterBrokerPath(clusterName, '1')
|
||||
clusterBrokerPath(clusterName, '100')
|
||||
)
|
||||
);
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ enum Filters {
|
|||
PARTITION_COUNT = 'partitionCount',
|
||||
REPLICATION_FACTOR = 'replicationFactor',
|
||||
INSYNC_REPLICAS = 'inSyncReplicas',
|
||||
CLEANUP_POLICY = 'Delete',
|
||||
CLEANUP_POLICY = 'cleanUpPolicy',
|
||||
}
|
||||
|
||||
const New: React.FC = () => {
|
||||
|
|
|
@ -60,16 +60,16 @@ describe('New', () => {
|
|||
await userEvent.clear(screen.getByPlaceholderText('Topic Name'));
|
||||
await userEvent.tab();
|
||||
await expect(
|
||||
screen.getByText('name is a required field')
|
||||
screen.getByText('Topic Name is required')
|
||||
).toBeInTheDocument();
|
||||
await userEvent.type(
|
||||
screen.getByLabelText('Number of partitions *'),
|
||||
screen.getByLabelText('Number of Partitions *'),
|
||||
minValue
|
||||
);
|
||||
await userEvent.clear(screen.getByLabelText('Number of partitions *'));
|
||||
await userEvent.clear(screen.getByLabelText('Number of Partitions *'));
|
||||
await userEvent.tab();
|
||||
await expect(
|
||||
screen.getByText('Number of partitions is required and must be a number')
|
||||
screen.getByText('Number of Partitions is required and must be a number')
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(createTopicMock).not.toHaveBeenCalled();
|
||||
|
@ -89,7 +89,7 @@ describe('New', () => {
|
|||
renderComponent(clusterTopicNewPath(clusterName));
|
||||
await userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
|
||||
await userEvent.type(
|
||||
screen.getByLabelText('Number of partitions *'),
|
||||
screen.getByLabelText('Number of Partitions *'),
|
||||
minValue
|
||||
);
|
||||
await userEvent.click(screen.getByText('Create topic'));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import styled from 'styled-components';
|
||||
import Input from 'components/common/Input/Input';
|
||||
|
||||
export const Column = styled.div`
|
||||
display: flex;
|
||||
|
@ -16,6 +17,10 @@ export const CustomParamsHeading = styled.h4`
|
|||
color: ${({ theme }) => theme.heading.h4};
|
||||
`;
|
||||
|
||||
export const MessageSizeInput = styled(Input)`
|
||||
min-width: 195px;
|
||||
`;
|
||||
|
||||
export const Label = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
|
|
@ -109,12 +109,12 @@ const TopicForm: React.FC<Props> = ({
|
|||
{!isEditing && (
|
||||
<div>
|
||||
<InputLabel htmlFor="topicFormNumberOfPartitions">
|
||||
Number of partitions *
|
||||
Number of Partitions *
|
||||
</InputLabel>
|
||||
<Input
|
||||
id="topicFormNumberOfPartitions"
|
||||
type="number"
|
||||
placeholder="Number of partitions"
|
||||
placeholder="Number of Partitions"
|
||||
min="1"
|
||||
name="partitions"
|
||||
positiveOnly
|
||||
|
@ -228,7 +228,7 @@ const TopicForm: React.FC<Props> = ({
|
|||
<InputLabel htmlFor="topicFormMaxMessageBytes">
|
||||
Maximum message size in bytes
|
||||
</InputLabel>
|
||||
<Input
|
||||
<S.MessageSizeInput
|
||||
id="topicFormMaxMessageBytes"
|
||||
type="number"
|
||||
placeholder="Maximum message size"
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('TopicForm', () => {
|
|||
|
||||
expectByRoleAndNameToBeInDocument('textbox', 'Topic Name *');
|
||||
|
||||
expectByRoleAndNameToBeInDocument('spinbutton', 'Number of partitions *');
|
||||
expectByRoleAndNameToBeInDocument('spinbutton', 'Number of Partitions *');
|
||||
expectByRoleAndNameToBeInDocument('spinbutton', 'Replication Factor');
|
||||
|
||||
expectByRoleAndNameToBeInDocument('spinbutton', 'Min In Sync Replicas');
|
||||
|
|
|
@ -7,6 +7,7 @@ const CheckMarkRoundIcon: React.FC = () => {
|
|||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
role="tooltip"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { BrokerConfig, BrokersLogdirs, ConfigSource } from 'generated-sources';
|
||||
|
||||
export const brokersPayload = [
|
||||
{ id: 1, host: 'b-1.test.kafka.amazonaws.com', port: 9092 },
|
||||
{ id: 2, host: 'b-2.test.kafka.amazonaws.com', port: 9092 },
|
||||
{ id: 100, host: 'b-1.test.kafka.amazonaws.com', port: 9092 },
|
||||
{ id: 200, host: 'b-2.test.kafka.amazonaws.com', port: 9092 },
|
||||
];
|
||||
|
||||
const partition = {
|
||||
|
|
|
@ -32,15 +32,15 @@ export const clustersPayload: Cluster[] = [
|
|||
|
||||
export const clusterStatsPayload = {
|
||||
brokerCount: 2,
|
||||
activeControllers: 1,
|
||||
activeControllers: 100,
|
||||
onlinePartitionCount: 138,
|
||||
offlinePartitionCount: 0,
|
||||
inSyncReplicasCount: 239,
|
||||
outOfSyncReplicasCount: 0,
|
||||
underReplicatedPartitionCount: 0,
|
||||
diskUsage: [
|
||||
{ brokerId: 0, segmentSize: 334567, segmentCount: 245 },
|
||||
{ brokerId: 1, segmentSize: 12345678, segmentCount: 121 },
|
||||
{ brokerId: 100, segmentSize: 334567, segmentCount: 245 },
|
||||
{ brokerId: 200, segmentSize: 12345678, segmentCount: 121 },
|
||||
],
|
||||
version: '2.2.1',
|
||||
};
|
||||
|
|
|
@ -76,7 +76,6 @@ const formatTopicCreation = (form: TopicFormData): TopicCreation => {
|
|||
partitions,
|
||||
replicationFactor,
|
||||
cleanupPolicy,
|
||||
retentionBytes,
|
||||
retentionMs,
|
||||
maxMessageBytes,
|
||||
minInSyncReplicas,
|
||||
|
@ -86,7 +85,6 @@ const formatTopicCreation = (form: TopicFormData): TopicCreation => {
|
|||
const configs = {
|
||||
'cleanup.policy': cleanupPolicy,
|
||||
'retention.ms': retentionMs.toString(),
|
||||
'retention.bytes': retentionBytes.toString(),
|
||||
'max.message.bytes': maxMessageBytes.toString(),
|
||||
'min.insync.replicas': minInSyncReplicas.toString(),
|
||||
...Object.values(customParams || {}).reduce(topicReducer, {}),
|
||||
|
|
|
@ -66,17 +66,17 @@ export const topicFormValidationSchema = yup.object().shape({
|
|||
name: yup
|
||||
.string()
|
||||
.max(249)
|
||||
.required()
|
||||
.required('Topic Name is required')
|
||||
.matches(
|
||||
TOPIC_NAME_VALIDATION_PATTERN,
|
||||
'Only alphanumeric, _, -, and . allowed'
|
||||
),
|
||||
partitions: yup
|
||||
.number()
|
||||
.min(1)
|
||||
.min(1, 'Number of Partitions must be greater than or equal to 1')
|
||||
.max(2147483647)
|
||||
.required()
|
||||
.typeError('Number of partitions is required and must be a number'),
|
||||
.typeError('Number of Partitions is required and must be a number'),
|
||||
replicationFactor: yup.string(),
|
||||
minInSyncReplicas: yup.string(),
|
||||
cleanupPolicy: yup.string().required(),
|
||||
|
|
|
@ -44,7 +44,6 @@ export interface TopicFormData {
|
|||
minInSyncReplicas: number;
|
||||
cleanupPolicy: string;
|
||||
retentionMs: number;
|
||||
retentionBytes: number;
|
||||
maxMessageBytes: number;
|
||||
customParams: {
|
||||
name: string;
|
||||
|
|
4
pom.xml
4
pom.xml
|
@ -49,8 +49,8 @@
|
|||
<testcontainers.version>1.17.5</testcontainers.version>
|
||||
|
||||
<!-- Frontend dependency versions -->
|
||||
<node.version>v16.15.0</node.version>
|
||||
<pnpm.version>v7.4.0</pnpm.version>
|
||||
<node.version>v18.17.1</node.version>
|
||||
<pnpm.version>v8.6.12</pnpm.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<fabric8-maven-plugin.version>0.42.1</fabric8-maven-plugin.version>
|
||||
|
|
Loading…
Add table
Reference in a new issue