Skip to content

Commit a7ebaf4

Browse files
committed
Add filters to Overview page: Filter by Broker, Time, and Topic
Signed-off-by: hemahg <hhg@redhat.com>
1 parent dda1026 commit a7ebaf4

20 files changed

+824
-48
lines changed

ui/api/nodes/actions.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
NodeRoles,
1717
BrokerStatus,
1818
ControllerStatus,
19+
NodeMetricsResponseSchema,
20+
NodeMetrics,
1921
} from "@/api/nodes/schema";
2022
import { filterUndefinedFromObj } from "@/utils/filterUndefinedFromObj";
2123

@@ -67,3 +69,14 @@ export async function getNodeConfiguration(
6769
(rawData) => ConfigResponseSchema.parse(rawData).data,
6870
);
6971
}
72+
73+
export async function getNodeMetrics(
74+
kafkaId: string,
75+
nodeId: number | string,
76+
): Promise<ApiResponse<NodeMetrics>> {
77+
return fetchData(
78+
`/api/kafkas/${kafkaId}/nodes/${nodeId}/metrics`,
79+
"",
80+
(rawData) => NodeMetricsResponseSchema.parse(rawData),
81+
);
82+
}

ui/api/nodes/schema.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ const ControllerStatusSchema = z.union([
1919
z.literal("Unknown"),
2020
]);
2121

22-
const NodePoolsSchema = z.record(z.string(), z.object({
23-
roles: z.array(z.string()),
24-
count: z.number(),
25-
}));
22+
const NodePoolsSchema = z.record(
23+
z.string(),
24+
z.object({
25+
roles: z.array(z.string()),
26+
count: z.number(),
27+
}),
28+
);
2629

2730
export const NodeSchema = z.object({
2831
id: z.string(),
@@ -116,6 +119,35 @@ const ConfigSchema = z.object({
116119
),
117120
});
118121

122+
export const MetricsSchema = z.object({
123+
values: z.record(
124+
z.string(),
125+
z.array(
126+
z.object({
127+
value: z.string(),
128+
nodeId: z.string(),
129+
}),
130+
),
131+
),
132+
ranges: z.record(
133+
z.string(),
134+
z.array(
135+
z.object({
136+
range: z.array(z.tuple([z.string(), z.string()])),
137+
nodeId: z.string().optional(),
138+
}),
139+
),
140+
),
141+
});
142+
143+
export const NodeMetricsResponseSchema = z.object({
144+
data: z.object({
145+
attributes: z.object({
146+
metrics: MetricsSchema.optional().nullable(),
147+
}),
148+
}),
149+
});
150+
119151
export type NodeConfig = z.infer<typeof ConfigSchema>;
120152

121153
export type BrokerStatus = z.infer<typeof BrokerStatusSchema>;
@@ -129,3 +161,5 @@ export type Statuses = z.infer<typeof StatusesSchema>;
129161
export const ConfigResponseSchema = z.object({
130162
data: ConfigSchema,
131163
});
164+
165+
export type NodeMetrics = z.infer<typeof NodeMetricsResponseSchema>;

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/ConnectedClusterChartsCard.tsx

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,10 @@ import {
88
CardTitle,
99
Title,
1010
} from "@/libs/patternfly/react-core";
11-
1211
import { useTranslations } from "next-intl";
1312
import { ClusterDetail } from "@/api/kafka/schema";
1413
import { ClusterChartsCard } from "@/components/ClusterOverview/ClusterChartsCard";
15-
16-
function timeSeriesMetrics(
17-
ranges: Record<string, { range: string[][]; nodeId?: string }[]> | undefined,
18-
rangeName: string,
19-
): Record<string, TimeSeriesMetrics> {
20-
const series: Record<string, TimeSeriesMetrics> = {};
21-
22-
if (ranges) {
23-
Object.values(ranges[rangeName] ?? {}).forEach((r) => {
24-
series[r.nodeId!] = r.range.reduce(
25-
(a, v) => ({ ...a, [v[0]]: parseFloat(v[1]) }),
26-
{} as TimeSeriesMetrics,
27-
);
28-
});
29-
}
30-
31-
return series;
32-
}
14+
import { timeSeriesMetrics } from "@/components/ClusterOverview/components/timeSeriesMetrics";
3315

3416
export async function ConnectedClusterChartsCard({
3517
cluster,
@@ -42,6 +24,9 @@ export async function ConnectedClusterChartsCard({
4224
const isVirtualKafkaCluster =
4325
res?.meta?.kind === "virtualkafkaclusters.kroxylicious.io";
4426

27+
const brokerList =
28+
res?.relationships?.nodes?.data?.map((n) => `Node ${n.id}`) ?? [];
29+
4530
const metricsUnavailable = res?.attributes.metrics === null;
4631

4732
console.log("is virtual kafka cluster", isVirtualKafkaCluster);
@@ -79,7 +64,9 @@ export async function ConnectedClusterChartsCard({
7964

8065
return (
8166
<ClusterChartsCard
67+
kafkaId={res?.id}
8268
isLoading={false}
69+
brokerList={brokerList}
8370
usedDiskSpace={timeSeriesMetrics(
8471
res?.attributes.metrics?.ranges,
8572
"volume_stats_used_bytes",

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

0 commit comments

Comments
 (0)