Skip to content

Commit d2bdc46

Browse files
committed
Add filter to filter by topic name
Signed-off-by: hemahg <hhg@redhat.com>
1 parent 1da2b40 commit d2bdc46

File tree

9 files changed

+267
-51
lines changed

9 files changed

+267
-51
lines changed

ui/api/topics/actions.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
Topic,
1818
TopicCreateResponse,
1919
TopicCreateResponseSchema,
20+
TopicMetrics,
21+
TopicMetricsResponseSchema,
2022
TopicResponse,
2123
TopicsResponse,
2224
TopicsResponseSchema,
@@ -206,3 +208,14 @@ export async function setTopicAsViewed(kafkaId: string, topicId: string) {
206208
return viewedTopics;
207209
}
208210
}
211+
212+
export async function gettopicMetrics(
213+
kafkaId: string,
214+
topicId: number | string,
215+
): Promise<ApiResponse<TopicMetrics>> {
216+
return fetchData(
217+
`/api/kafkas/${kafkaId}/topics/${topicId}/metrics`,
218+
"",
219+
(rawData) => TopicMetricsResponseSchema.parse(rawData),
220+
);
221+
}

ui/api/topics/schema.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,12 @@ export type TopicStatus = z.infer<typeof TopicStatusSchema>;
7171
const TopicSchema = z.object({
7272
id: z.string(),
7373
type: z.literal("topics"),
74-
meta: z.object({
75-
managed: z.boolean().optional(),
76-
privileges: z.array(z.string()).optional(),
77-
}).optional(),
74+
meta: z
75+
.object({
76+
managed: z.boolean().optional(),
77+
privileges: z.array(z.string()).optional(),
78+
})
79+
.optional(),
7880
attributes: z.object({
7981
name: z.string().optional(),
8082
status: TopicStatusSchema.optional(),
@@ -86,10 +88,13 @@ const TopicSchema = z.object({
8688
totalLeaderLogBytes: z.number().optional().nullable(),
8789
}),
8890
relationships: z.object({
89-
consumerGroups: z.object({
90-
meta: z.record(z.string(), z.any()).optional(),
91-
data: z.array(z.any()),
92-
}).optional().nullable(),
91+
consumerGroups: z
92+
.object({
93+
meta: z.record(z.string(), z.any()).optional(),
94+
data: z.array(z.any()),
95+
})
96+
.optional()
97+
.nullable(),
9398
}),
9499
});
95100
export const TopicResponse = z.object({
@@ -115,7 +120,7 @@ const TopicListItemSchema = z.object({
115120
totalLeaderLogBytes: true,
116121
}),
117122
relationships: TopicSchema.shape.relationships.pick({
118-
consumerGroups: true
123+
consumerGroups: true,
119124
}),
120125
});
121126
export type TopicListItem = z.infer<typeof TopicListItemSchema>;
@@ -145,6 +150,36 @@ export const TopicsResponseSchema = z.object({
145150
}),
146151
data: z.array(TopicListItemSchema),
147152
});
153+
154+
export const MetricsSchema = z.object({
155+
values: z.record(
156+
z.string(),
157+
z.array(
158+
z.object({
159+
value: z.string(),
160+
}),
161+
),
162+
),
163+
ranges: z.record(
164+
z.string(),
165+
z.array(
166+
z.object({
167+
range: z.array(z.tuple([z.string(), z.string()])),
168+
}),
169+
),
170+
),
171+
});
172+
173+
export const TopicMetricsResponseSchema = z.object({
174+
data: z.object({
175+
id: z.string(),
176+
type: z.literal("topicMetrics"),
177+
attributes: z.object({
178+
metrics: MetricsSchema.optional().nullable(),
179+
}),
180+
}),
181+
});
182+
148183
export type TopicsResponse = z.infer<typeof TopicsResponseSchema>;
149184

150185
export const TopicCreateResponseSchema = z.object({
@@ -154,3 +189,5 @@ export const TopicCreateResponseSchema = z.object({
154189
});
155190

156191
export type TopicCreateResponse = z.infer<typeof TopicCreateResponseSchema>;
192+
193+
export type TopicMetrics = z.infer<typeof TopicMetricsResponseSchema>;

ui/app/[locale]/(authorized)/kafka/[kafkaId]/overview/ConnectedTopicChartsCard.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { ApiResponse } from "@/api/api";
12
import { ClusterDetail } from "@/api/kafka/schema";
3+
import { TopicsResponse } from "@/api/topics/schema";
24
import { TopicChartsCard } from "@/components/ClusterOverview/TopicChartsCard";
35

46
function timeSeriesMetrics(
@@ -21,13 +23,30 @@ function timeSeriesMetrics(
2123

2224
export async function ConnectedTopicChartsCard({
2325
cluster,
26+
topics,
2427
}: {
2528
cluster: Promise<ClusterDetail | null>;
29+
topics: Promise<ApiResponse<TopicsResponse>>;
2630
}) {
2731
const res = await cluster;
2832

33+
const topicResponse = await topics;
34+
35+
const topicList =
36+
topicResponse.payload?.data
37+
?.map((topic) => ({
38+
id: topic.id,
39+
name: topic.attributes.name,
40+
}))
41+
.filter(
42+
(topic): topic is { id: string; name: string } =>
43+
!!topic.id && !!topic.name,
44+
) ?? [];
45+
2946
return (
3047
<TopicChartsCard
48+
kafkaId={res?.id}
49+
topicList={topicList}
3150
isLoading={false}
3251
isVirtualKafkaCluster={
3352
res?.meta?.kind === "virtualkafkaclusters.kroxylicious.io"

ui/app/[locale]/(authorized)/kafka/[kafkaId]/overview/page.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ export default async function OverviewPage({
2828
"name,namespace,creationTimestamp,status,kafkaVersion,nodes,listeners,conditions,metrics",
2929
}).then((r) => r.payload ?? null);
3030

31-
const topics = getTopics(params.kafkaId, { fields: "status", pageSize: 1 });
31+
const topicsSummary = getTopics(params.kafkaId, {
32+
fields: "status",
33+
pageSize: 1,
34+
});
35+
36+
const topicsForCharts = getTopics(params.kafkaId, {
37+
fields: "name",
38+
pageSize: 100,
39+
});
3240
const consumerGroups = getConsumerGroups(params.kafkaId, {
3341
fields: "groupId,state",
3442
});
@@ -45,9 +53,14 @@ export default async function OverviewPage({
4553
consumerGroups={consumerGroups}
4654
/>
4755
}
48-
topicsPartitions={<ConnectedTopicsPartitionsCard data={topics} />}
56+
topicsPartitions={<ConnectedTopicsPartitionsCard data={topicsSummary} />}
4957
clusterCharts={<ConnectedClusterChartsCard cluster={kafkaCluster} />}
50-
topicCharts={<ConnectedTopicChartsCard cluster={kafkaCluster} />}
58+
topicCharts={
59+
<ConnectedTopicChartsCard
60+
cluster={kafkaCluster}
61+
topics={topicsForCharts}
62+
/>
63+
}
5164
recentTopics={<ConnectedRecentTopics data={viewedTopics} />}
5265
/>
5366
);

ui/components/ClusterOverview/ClusterChartsCard.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Meta, StoryObj } from "@storybook/nextjs";
22
import { ClusterChartsCard } from "./ClusterChartsCard";
33

4+
const brokers = ["broker1", "broker2"];
5+
46
// Mock data for charts
57
const mockTimestamps = {
68
"2024-01-01T00:00:00Z": 40,
@@ -37,6 +39,7 @@ export const Default: Story = {
3739
availableDiskSpace: sampleAvailable,
3840
memoryUsage: sampleUsages,
3941
cpuUsage: sampleUsages,
42+
brokerList: brokers,
4043
},
4144
};
4245

ui/components/ClusterOverview/ClusterChartsCard.tsx

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import { FilterByBroker } from "./components/FilterByBroker";
1919
import { useState } from "react";
2020
import { useNodeMetrics } from "./components/useNodeMetric";
2121

22+
function hasMetrics(metrics?: Record<string, TimeSeriesMetrics>) {
23+
return metrics && Object.keys(metrics).length > 0;
24+
}
25+
2226
type ClusterChartsCardProps = {
2327
brokerList: string[];
2428
usedDiskSpace: Record<string, TimeSeriesMetrics>;
@@ -51,6 +55,18 @@ export function ClusterChartsCard({
5155
const cpuMetrics = useNodeMetrics(kafkaId, cpuBroker);
5256
const memMetrics = useNodeMetrics(kafkaId, memBroker);
5357

58+
const diskHasMetrics = hasMetrics(
59+
diskMetrics.data?.volume_stats_used_bytes ?? usedDiskSpace,
60+
);
61+
62+
const cpuHasMetrics = hasMetrics(
63+
cpuMetrics.data?.cpu_usage_seconds ?? cpuUsage,
64+
);
65+
66+
const memHasMetrics = hasMetrics(
67+
memMetrics.data?.memory_usage_bytes ?? memoryUsage,
68+
);
69+
5470
const disableFilter = isLoading || !kafkaId || brokerList.length <= 1;
5571

5672
return (
@@ -74,12 +90,15 @@ export function ClusterChartsCard({
7490
<ChartSkeletonLoader />
7591
) : (
7692
<>
77-
<FilterByBroker
78-
brokerList={brokerList}
79-
selectedBroker={diskBroker}
80-
onSetSelectedBroker={setDiskBroker}
81-
disableToolbar={disableFilter || diskMetrics.isLoading}
82-
/>
93+
{!isLoading && diskHasMetrics && (
94+
<FilterByBroker
95+
brokerList={brokerList}
96+
selectedBroker={diskBroker}
97+
onSetSelectedBroker={setDiskBroker}
98+
disableToolbar={disableFilter || diskMetrics.isLoading}
99+
/>
100+
)}
101+
83102
<ChartDiskUsage
84103
usages={
85104
diskMetrics.data?.volume_stats_used_bytes ?? usedDiskSpace
@@ -103,12 +122,15 @@ export function ClusterChartsCard({
103122
<ChartSkeletonLoader />
104123
) : (
105124
<>
106-
<FilterByBroker
107-
brokerList={brokerList}
108-
selectedBroker={cpuBroker}
109-
onSetSelectedBroker={setCpuBroker}
110-
disableToolbar={disableFilter || cpuMetrics.isLoading}
111-
/>
125+
{!isLoading && cpuHasMetrics && (
126+
<FilterByBroker
127+
brokerList={brokerList}
128+
selectedBroker={cpuBroker}
129+
onSetSelectedBroker={setCpuBroker}
130+
disableToolbar={disableFilter || cpuMetrics.isLoading}
131+
/>
132+
)}
133+
112134
<ChartCpuUsage
113135
usages={cpuMetrics.data?.cpu_usage_seconds ?? cpuUsage}
114136
/>
@@ -126,12 +148,15 @@ export function ClusterChartsCard({
126148
<ChartSkeletonLoader />
127149
) : (
128150
<>
129-
<FilterByBroker
130-
brokerList={brokerList}
131-
selectedBroker={memBroker}
132-
onSetSelectedBroker={setMemBroker}
133-
disableToolbar={disableFilter || memMetrics.isLoading}
134-
/>
151+
{!isLoading && memHasMetrics && (
152+
<FilterByBroker
153+
brokerList={brokerList}
154+
selectedBroker={memBroker}
155+
onSetSelectedBroker={setMemBroker}
156+
disableToolbar={disableFilter || memMetrics.isLoading}
157+
/>
158+
)}
159+
135160
<ChartMemoryUsage
136161
usages={memMetrics.data?.memory_usage_bytes ?? memoryUsage}
137162
/>

0 commit comments

Comments
 (0)