Skip to content

Commit 76998e5

Browse files
authored
Merge branch 'main' into issues/1245
2 parents 9f4bf6f + 19cdfcc commit 76998e5

File tree

18 files changed

+189
-68
lines changed

18 files changed

+189
-68
lines changed

.github/dependabot.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ updates:
1111
labels:
1212
- "type/dependencies"
1313
- "scope/backend"
14+
ignore:
15+
# Disable dependabot pull requests for Netty
16+
# In general, our Netty references are temporary overrides, usually applied to address transitive Spring vulnerabilities, and should be configured with caution
17+
# In general, having conflicting Netty versions in the classpath is not recommended
18+
- dependency-name: "io.netty:*"
1419
groups:
1520
spring-boot-dependencies:
1621
patterns:
@@ -23,16 +28,10 @@ updates:
2328
exclude-patterns:
2429
- "org.springframework.boot:*"
2530
- "io.spring.dependency-management"
26-
# All netty references are temporary overwrites that must be set carefully
27-
# We do not need dependabot to send pull requests
28-
- "io.netty:*"
2931
other-dependencies:
3032
exclude-patterns:
3133
- "org.springframework.boot:*"
3234
- "io.spring.dependency-management"
33-
# All netty references are temporary overwrites that must be set carefully
34-
# We do not need dependabot to send pull requests
35-
- "io.netty:*"
3635
patterns:
3736
- "*"
3837
update-types:

api/src/main/java/io/kafbat/ui/controller/TopicsController.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import static io.kafbat.ui.model.rbac.permission.TopicAction.EDIT;
88
import static io.kafbat.ui.model.rbac.permission.TopicAction.VIEW;
99
import static java.util.stream.Collectors.toList;
10-
import static org.apache.commons.lang3.Strings.CI;
1110

1211
import io.kafbat.ui.api.TopicsApi;
1312
import io.kafbat.ui.config.ClustersProperties;
@@ -362,6 +361,10 @@ private Comparator<InternalTopic> getComparatorForTopic(
362361
case OUT_OF_SYNC_REPLICAS -> Comparator.comparing(t -> t.getReplicas() - t.getInSyncReplicas());
363362
case REPLICATION_FACTOR -> Comparator.comparing(InternalTopic::getReplicationFactor);
364363
case SIZE -> Comparator.comparing(InternalTopic::getSegmentSize);
364+
case MESSAGES_COUNT -> Comparator.comparing(
365+
InternalTopic::getMessagesCount,
366+
Comparator.nullsLast(Long::compareTo)
367+
);
365368
default -> defaultComparator;
366369
};
367370
}

api/src/main/java/io/kafbat/ui/mapper/ClusterMapper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.apache.kafka.common.resource.ResourceType;
5252
import org.mapstruct.Mapper;
5353
import org.mapstruct.Mapping;
54+
import org.openapitools.jackson.nullable.JsonNullable;
5455

5556
@Mapper(componentModel = "spring")
5657
public interface ClusterMapper {
@@ -104,6 +105,14 @@ default ConfigSynonymDTO toConfigSynonym(ConfigEntry.ConfigSynonym config) {
104105

105106
TopicDTO toTopic(InternalTopic topic);
106107

108+
default <T> JsonNullable<T> toJsonNullable(T value) {
109+
if (value == null) {
110+
return JsonNullable.undefined();
111+
} else {
112+
return JsonNullable.of(value);
113+
}
114+
}
115+
107116
PartitionDTO toPartition(InternalPartition topic);
108117

109118
BrokerDTO toBrokerDto(InternalBroker broker);

api/src/main/java/io/kafbat/ui/model/InternalTopic.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import lombok.Data;
1111
import org.apache.kafka.clients.admin.ConfigEntry;
1212
import org.apache.kafka.clients.admin.TopicDescription;
13-
import org.apache.kafka.common.TopicPartition;
1413

1514
@Data
1615
@Builder(toBuilder = true)
@@ -143,4 +142,16 @@ public static InternalTopic from(TopicDescription topicDescription,
143142
return topic.build();
144143
}
145144

145+
public @Nullable Long getMessagesCount() {
146+
Long result = null;
147+
if (cleanUpPolicy.equals(CleanupPolicy.DELETE)) {
148+
result = 0L;
149+
if (partitions != null && !partitions.isEmpty()) {
150+
for (InternalPartition partition : partitions.values()) {
151+
result += (partition.getOffsetMax() - partition.getOffsetMin());
152+
}
153+
}
154+
}
155+
return result;
156+
}
146157
}

contract-typespec/api/topics.tsp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ enum TopicColumnsToSort {
156156
TOTAL_PARTITIONS,
157157
REPLICATION_FACTOR,
158158
SIZE,
159+
MESSAGES_COUNT
159160
}
160161

161162
model Topic {
@@ -170,6 +171,7 @@ model Topic {
170171
bytesInPerSec?: float64;
171172
bytesOutPerSec?: float64;
172173
underReplicatedPartitions?: int32;
174+
messagesCount?: int64 | null;
173175
cleanUpPolicy?: CleanUpPolicy;
174176
partitions?: Partition[];
175177
}

contract/src/main/resources/swagger/kafbat-ui-api.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,6 +2692,7 @@ components:
26922692
- TOTAL_PARTITIONS
26932693
- REPLICATION_FACTOR
26942694
- SIZE
2695+
- MESSAGES_COUNT
26952696

26962697
SchemaColumnsToSort:
26972698
type: string
@@ -2747,6 +2748,10 @@ components:
27472748
type: array
27482749
items:
27492750
$ref: "#/components/schemas/Partition"
2751+
messagesCount:
2752+
type: integer
2753+
format: int64
2754+
nullable: true
27502755
required:
27512756
- name
27522757

frontend/src/components/ConsumerGroups/Details/ListItem.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const ListItem: React.FC<Props> = ({ clusterName, name, consumers }) => {
3131
useDeleteConsumerGroupOffsetsMutation(consumerProps);
3232

3333
const getTotalconsumerLag = () => {
34+
if (consumers.every((consumer) => consumer?.consumerLag === null)) {
35+
return 'N/A';
36+
}
3437
let count = 0;
3538
consumers.forEach((consumer) => {
3639
count += consumer?.consumerLag || 0;

frontend/src/components/ConsumerGroups/List.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CONSUMER_GROUP_STATE_TOOLTIPS, PER_PAGE } from 'lib/constants';
1616
import { useConsumerGroups } from 'lib/hooks/api/consumers';
1717
import Tooltip from 'components/common/Tooltip/Tooltip';
1818
import ResourcePageHeading from 'components/common/ResourcePageHeading/ResourcePageHeading';
19+
import { useLocalStoragePersister } from 'components/common/NewTable/ColumnResizer/lib';
1920

2021
const List = () => {
2122
const { clusterName } = useAppParams<ClusterNameRoute>();
@@ -47,6 +48,7 @@ const List = () => {
4748
to={encodeURIComponent(`${getValue<string | number>()}`)}
4849
/>
4950
),
51+
size: 600,
5052
},
5153
{
5254
id: ConsumerGroupOrdering.MEMBERS,
@@ -96,6 +98,8 @@ const List = () => {
9698
[]
9799
);
98100

101+
const columnSizingPersister = useLocalStoragePersister('Consumers');
102+
99103
return (
100104
<>
101105
<ResourcePageHeading text="Consumers" />
@@ -118,6 +122,8 @@ const List = () => {
118122
clusterConsumerGroupDetailsPath(clusterName, original.groupId)
119123
)
120124
}
125+
enableColumnResizing
126+
columnSizingPersister={columnSizingPersister}
121127
disabled={consumerGroups.isFetching}
122128
/>
123129
</>

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();

0 commit comments

Comments
 (0)