From 374ff576b127290c0283c8461714022fd40e0e78 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 10 Jul 2026 01:45:30 +0200 Subject: [PATCH] fix: render kafka consumer groups concisely --- config/platform-infra/kafka.yaml | 2 ++ scripts/src/platform-infra-kafka.ts | 34 ++++++++++++++++++++++++++--- scripts/src/platform-infra/entry.ts | 2 +- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/config/platform-infra/kafka.yaml b/config/platform-infra/kafka.yaml index 6b4c68cd..64430a46 100644 --- a/config/platform-infra/kafka.yaml +++ b/config/platform-infra/kafka.yaml @@ -160,5 +160,7 @@ validation: management: defaultTailLimit: 10 maxTailLimit: 50 + defaultGroupListLimit: 25 + maxGroupListLimit: 200 defaultShadowTopic: hwlab.event.v1 shadowProducerId: unidesk-platform-infra-kafka-cli diff --git a/scripts/src/platform-infra-kafka.ts b/scripts/src/platform-infra-kafka.ts index 6eaea8a4..991a4d8b 100644 --- a/scripts/src/platform-infra-kafka.ts +++ b/scripts/src/platform-infra-kafka.ts @@ -103,6 +103,8 @@ interface PlatformKafkaConfig { management: { defaultTailLimit: number; maxTailLimit: number; + defaultGroupListLimit: number; + maxGroupListLimit: number; defaultShadowTopic: string; shadowProducerId: string; }; @@ -189,7 +191,7 @@ export function kafkaHelp(): Record { "bun scripts/cli.ts platform-infra kafka status --target NC01 [--full|--raw]", "bun scripts/cli.ts platform-infra kafka validate --target NC01 [--full|--raw]", "bun scripts/cli.ts platform-infra kafka topics --node NC01", - "bun scripts/cli.ts platform-infra kafka groups --node NC01", + "bun scripts/cli.ts platform-infra kafka groups --node NC01 [--limit N] [--full|--raw]", "bun scripts/cli.ts platform-infra kafka offsets --node NC01 [--topic hwlab.event.v1]", "bun scripts/cli.ts platform-infra kafka tail --node NC01 --topic hwlab.event.v1 --limit 5", "bun scripts/cli.ts platform-infra kafka produce --node NC01 --topic hwlab.event.v1 --key ", @@ -317,6 +319,8 @@ function parseManagement(root: Record): PlatformKafkaConfig["ma return { defaultTailLimit: positiveInteger(management, "defaultTailLimit", "management"), maxTailLimit: positiveInteger(management, "maxTailLimit", "management"), + defaultGroupListLimit: positiveInteger(management, "defaultGroupListLimit", "management"), + maxGroupListLimit: positiveInteger(management, "maxGroupListLimit", "management"), defaultShadowTopic: topicNameField(management, "defaultShadowTopic", "management"), shadowProducerId: y.stringField(management, "shadowProducerId", "management"), }; @@ -332,6 +336,7 @@ function validateConfig(kafka: PlatformKafkaConfig): void { if (!kafka.topics.some((topic) => topic.name === kafka.validation.smokeTopic)) throw new Error(`${configLabel}.validation.smokeTopic must reference one of topics[].name`); if (!kafka.topics.some((topic) => topic.name === kafka.management.defaultShadowTopic)) throw new Error(`${configLabel}.management.defaultShadowTopic must reference one of topics[].name`); if (kafka.management.defaultTailLimit > kafka.management.maxTailLimit) throw new Error(`${configLabel}.management.defaultTailLimit must be <= management.maxTailLimit`); + if (kafka.management.defaultGroupListLimit > kafka.management.maxGroupListLimit) throw new Error(`${configLabel}.management.defaultGroupListLimit must be <= management.maxGroupListLimit`); if (kafka.defaults.switch.shadowConsumeEnabled) throw new Error(`${configLabel}.defaults.switch.shadowConsumeEnabled must stay false for shadow-produce-only stage`); const topicNames = new Set(); for (const topic of kafka.topics) { @@ -480,6 +485,7 @@ async function topics(config: UniDeskConfig, options: KafkaInspectOptions): Prom async function groups(config: UniDeskConfig, options: KafkaInspectOptions): Promise> { const kafka = readKafkaConfig(); const target = resolveTarget(kafka, options.targetId); + const limit = boundedGroupListLimit(kafka, options.limit); const result = await capture(config, target.route, ["sh"], groupsScript(kafka, target)); const parsed = parseJsonOutput(result.stdout); return { @@ -489,6 +495,7 @@ async function groups(config: UniDeskConfig, options: KafkaInspectOptions): Prom target: targetSummary(target), config: compactConfigSummary(kafka, target), query: parsed ?? null, + presentation: { limit }, remote: options.raw && parsed !== null ? parsed : compactCapture(result, { full: options.full || result.exitCode !== 0 }), }; } @@ -1415,6 +1422,8 @@ function compactConfigSummary(kafka: PlatformKafkaConfig, target: KafkaTarget): defaultShadowTopic: kafka.management.defaultShadowTopic, defaultTailLimit: kafka.management.defaultTailLimit, maxTailLimit: kafka.management.maxTailLimit, + defaultGroupListLimit: kafka.management.defaultGroupListLimit, + maxGroupListLimit: kafka.management.maxGroupListLimit, shadowProducerId: kafka.management.shadowProducerId, }, valuesPrinted: false, @@ -1612,13 +1621,25 @@ function renderTopics(result: Record): RenderedCliResult { function renderGroups(result: Record): RenderedCliResult { const query = record(result.query); - const groups = arrayRecords(query.groups).length > 0 ? arrayRecords(query.groups).map((item) => [stringValue(item.name)]) : (Array.isArray(query.groups) ? query.groups.map((item) => [String(item)]) : []); + const presentation = record(result.presentation); + const limit = Number(presentation.limit); + const names = Array.isArray(query.groups) + ? query.groups.flatMap((item) => { + if (typeof item === "string" && item.length > 0) return [item]; + const value = record(item).name; + return typeof value === "string" && value.length > 0 ? [value] : []; + }) + : []; + const shown = names.slice(0, Number.isInteger(limit) && limit > 0 ? limit : names.length); return rendered(result, "platform-infra kafka groups", [ "PLATFORM-INFRA KAFKA GROUPS", ...table(["TARGET", "NAMESPACE", "CLUSTER", "POD"], [[stringValue(query.target), stringValue(query.namespace), stringValue(query.cluster), stringValue(query.pod)]]), "", - ...(groups.length === 0 ? ["GROUPS -"] : table(["GROUP"], groups)), + ...table(["TOTAL", "SHOWN", "OMITTED"], [[String(names.length), String(shown.length), String(Math.max(0, names.length - shown.length))]]), + "", + ...(shown.length === 0 ? ["GROUPS -"] : table(["GROUP"], shown.map((name) => [name]))), ...remoteErrorLines(result), + ...(names.length > shown.length ? ["", `NEXT bun scripts/cli.ts platform-infra kafka groups --node ${stringValue(query.target)} --raw`] : []), ]); } @@ -1804,6 +1825,13 @@ function boundedLimit(kafka: PlatformKafkaConfig, limit: number | null): number return value; } +function boundedGroupListLimit(kafka: PlatformKafkaConfig, limit: number | null): number { + const value = limit ?? kafka.management.defaultGroupListLimit; + if (!Number.isInteger(value) || value < 1) throw new Error("group list limit must be a positive integer"); + if (value > kafka.management.maxGroupListLimit) throw new Error(`limit ${value} exceeds ${configLabel}.management.maxGroupListLimit=${kafka.management.maxGroupListLimit}`); + return value; +} + function buildShadowPayload(kafka: PlatformKafkaConfig, target: KafkaTarget, options: KafkaProduceOptions, topic: string): string { let userPayload: unknown = null; if (options.payload !== null) { diff --git a/scripts/src/platform-infra/entry.ts b/scripts/src/platform-infra/entry.ts index f961e673..e7c87a3b 100644 --- a/scripts/src/platform-infra/entry.ts +++ b/scripts/src/platform-infra/entry.ts @@ -453,7 +453,7 @@ export function platformInfraHelp(): unknown { "bun scripts/cli.ts platform-infra kafka status --target NC01", "bun scripts/cli.ts platform-infra kafka validate --target NC01", "bun scripts/cli.ts platform-infra kafka topics --node NC01", - "bun scripts/cli.ts platform-infra kafka groups --node NC01", + "bun scripts/cli.ts platform-infra kafka groups --node NC01 [--limit N] [--full|--raw]", "bun scripts/cli.ts platform-infra kafka offsets --node NC01 --topic hwlab.event.v1", "bun scripts/cli.ts platform-infra kafka tail --node NC01 --topic hwlab.event.v1 --limit 5", "bun scripts/cli.ts platform-infra kafka produce --node NC01 --topic hwlab.event.v1 --key ",