Skip to content

Commit 3b663e6

Browse files
authored
feat: add alert and tooltips for Kafka group type and protocol columns (#2401)
* Add group alert Signed-off-by: hemahg <hhg@redhat.com> * Add link Signed-off-by: hemahg <hhg@redhat.com> * Update Group stories Signed-off-by: hemahg <hhg@redhat.com> * Update alert message according to review comment Signed-off-by: hemahg <hhg@redhat.com> --------- Signed-off-by: hemahg <hhg@redhat.com>
1 parent 68ea8b5 commit 3b663e6

File tree

3 files changed

+155
-103
lines changed

3 files changed

+155
-103
lines changed
Lines changed: 147 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
"use client";
22

3-
import { ConsumerGroup, GroupType, ConsumerGroupState } from "@/api/groups/schema";
3+
import {
4+
ConsumerGroup,
5+
GroupType,
6+
ConsumerGroupState,
7+
} from "@/api/groups/schema";
48
import { useRouter } from "@/i18n/routing";
59
import { useFilterParams } from "@/utils/useFilterParams";
6-
import { useOptimistic, useState, useTransition } from "react";
10+
import { useEffect, useOptimistic, useState, useTransition } from "react";
711
import {
812
ConsumerGroupColumn,
913
ConsumerGroupColumns,
1014
ConsumerGroupsTable,
1115
SortableColumns,
1216
} from "./ConsumerGroupsTable";
1317
import { ResetOffsetModal } from "./[groupId]/members/ResetOffsetModal";
18+
import {
19+
Alert,
20+
AlertActionCloseButton,
21+
AlertActionLink,
22+
Grid,
23+
GridItem,
24+
} from "@/libs/patternfly/react-core";
25+
import { useTranslations } from "next-intl";
26+
import { clientConfig as config } from "@/utils/config";
1427

1528
export type ConnectedConsumerGroupTableProps = {
1629
kafkaId: string;
@@ -54,6 +67,7 @@ export function ConnectedConsumerGroupTable({
5467
kafkaId,
5568
}: ConnectedConsumerGroupTableProps) {
5669
const router = useRouter();
70+
const t = useTranslations("GroupsTable");
5771
const _updateUrl = useFilterParams({ perPage, sort, sortDir });
5872
const [_, startTransition] = useTransition();
5973
const [state, addOptimistic] = useOptimistic<
@@ -96,6 +110,19 @@ export function ConnectedConsumerGroupTable({
96110
[],
97111
);
98112
const [consumerGroupId, setConsumerGroupId] = useState<string>("");
113+
const [isAlertVisible, setAlertVisible] = useState(true);
114+
115+
const [showLearning, setShowLearning] = useState(false);
116+
117+
useEffect(() => {
118+
config().then((cfg) => {
119+
setShowLearning(cfg.showLearning);
120+
});
121+
}, []);
122+
123+
const handleClose = () => {
124+
setAlertVisible(false);
125+
};
99126

100127
const closeResetOffsetModal = () => {
101128
setResetOffsetModalOpen(false);
@@ -104,107 +131,127 @@ export function ConnectedConsumerGroupTable({
104131
};
105132

106133
return (
107-
<>
108-
<ConsumerGroupsTable
109-
total={consumerGroupCount}
110-
page={page}
111-
perPage={state.perPage}
112-
onPageChange={(newPage, perPage) => {
113-
startTransition(() => {
114-
const pageDiff = newPage - page;
115-
switch (pageDiff) {
116-
case -1:
117-
updateUrl({ perPage, page: prevPageCursor });
118-
break;
119-
case 1:
120-
updateUrl({ perPage, page: nextPageCursor });
121-
break;
122-
default:
123-
updateUrl({ perPage });
124-
break;
134+
<Grid hasGutter>
135+
<GridItem>
136+
{isAlertVisible && (
137+
<Alert
138+
variant="info"
139+
isInline
140+
title={t("groups_alert")}
141+
actionClose={<AlertActionCloseButton onClose={handleClose} />}
142+
actionLinks={
143+
showLearning ? (
144+
<AlertActionLink component="a" href={t("external_link")}>
145+
{t("learn_more")}
146+
</AlertActionLink>
147+
) : null
125148
}
126-
addOptimistic({ perPage });
127-
});
128-
}}
129-
groups={state.groups}
130-
isColumnSortable={(col) => {
131-
if (!SortableColumns.includes(col)) {
132-
return undefined;
133-
}
134-
const activeIndex = ConsumerGroupColumns.indexOf(state.sort);
135-
const columnIndex = ConsumerGroupColumns.indexOf(col);
136-
return {
137-
label: col as string,
138-
columnIndex,
139-
onSort: () => {
140-
startTransition(() => {
141-
const newSortDir =
142-
activeIndex === columnIndex
143-
? state.sortDir === "asc"
144-
? "desc"
145-
: "asc"
146-
: "asc";
147-
updateUrl({
148-
sort: col,
149-
sortDir: newSortDir,
150-
});
151-
addOptimistic({ sort: col, sortDir: newSortDir });
152-
});
153-
},
154-
sortBy: {
155-
index: activeIndex,
156-
direction: state.sortDir,
157-
defaultDirection: "asc",
158-
},
159-
isFavorites: undefined,
160-
};
161-
}}
162-
filterName={state.id}
163-
onFilterNameChange={(id) => {
164-
startTransition(() => {
165-
updateUrl({ id });
166-
addOptimistic({ id });
167-
});
168-
}}
169-
filterType={state.type}
170-
onFilterTypeChange={(type) => {
171-
startTransition(() => {
172-
updateUrl({ type });
173-
addOptimistic({ type });
174-
});
175-
}}
176-
filterState={state.consumerGroupState}
177-
onFilterStateChange={(consumerGroupState) => {
178-
startTransition(() => {
179-
updateUrl({ consumerGroupState });
180-
addOptimistic({ consumerGroupState });
181-
});
182-
}}
183-
onClearAllFilters={clearFilters}
184-
kafkaId={kafkaId}
185-
onResetOffset={(row) => {
186-
startTransition(() => {
187-
if (row.attributes.state === "STABLE") {
188-
setResetOffsetModalOpen(true);
189-
setConsumerGroupMembers(
190-
row.attributes.members?.map((member) => member.memberId) || [],
191-
);
192-
setConsumerGroupId(row.id);
193-
} else if (row.attributes.state === "EMPTY") {
194-
router.push(`${baseurl}/${row.id}/reset-offset`);
149+
/>
150+
)}
151+
</GridItem>
152+
<GridItem>
153+
<ConsumerGroupsTable
154+
total={consumerGroupCount}
155+
page={page}
156+
perPage={state.perPage}
157+
onPageChange={(newPage, perPage) => {
158+
startTransition(() => {
159+
const pageDiff = newPage - page;
160+
switch (pageDiff) {
161+
case -1:
162+
updateUrl({ perPage, page: prevPageCursor });
163+
break;
164+
case 1:
165+
updateUrl({ perPage, page: nextPageCursor });
166+
break;
167+
default:
168+
updateUrl({ perPage });
169+
break;
170+
}
171+
addOptimistic({ perPage });
172+
});
173+
}}
174+
groups={state.groups}
175+
isColumnSortable={(col) => {
176+
if (!SortableColumns.includes(col)) {
177+
return undefined;
195178
}
196-
});
197-
}}
198-
/>
199-
{isResetOffsetModalOpen && (
200-
<ResetOffsetModal
201-
members={consumerGroupMembers}
202-
isResetOffsetModalOpen={isResetOffsetModalOpen}
203-
onClickClose={closeResetOffsetModal}
204-
consumerGroupId={consumerGroupId}
179+
const activeIndex = ConsumerGroupColumns.indexOf(state.sort);
180+
const columnIndex = ConsumerGroupColumns.indexOf(col);
181+
return {
182+
label: col as string,
183+
columnIndex,
184+
onSort: () => {
185+
startTransition(() => {
186+
const newSortDir =
187+
activeIndex === columnIndex
188+
? state.sortDir === "asc"
189+
? "desc"
190+
: "asc"
191+
: "asc";
192+
updateUrl({
193+
sort: col,
194+
sortDir: newSortDir,
195+
});
196+
addOptimistic({ sort: col, sortDir: newSortDir });
197+
});
198+
},
199+
sortBy: {
200+
index: activeIndex,
201+
direction: state.sortDir,
202+
defaultDirection: "asc",
203+
},
204+
isFavorites: undefined,
205+
};
206+
}}
207+
filterName={state.id}
208+
onFilterNameChange={(id) => {
209+
startTransition(() => {
210+
updateUrl({ id });
211+
addOptimistic({ id });
212+
});
213+
}}
214+
filterType={state.type}
215+
onFilterTypeChange={(type) => {
216+
startTransition(() => {
217+
updateUrl({ type });
218+
addOptimistic({ type });
219+
});
220+
}}
221+
filterState={state.consumerGroupState}
222+
onFilterStateChange={(consumerGroupState) => {
223+
startTransition(() => {
224+
updateUrl({ consumerGroupState });
225+
addOptimistic({ consumerGroupState });
226+
});
227+
}}
228+
onClearAllFilters={clearFilters}
205229
kafkaId={kafkaId}
230+
onResetOffset={(row) => {
231+
startTransition(() => {
232+
if (row.attributes.state === "STABLE") {
233+
setResetOffsetModalOpen(true);
234+
setConsumerGroupMembers(
235+
row.attributes.members?.map((member) => member.memberId) ||
236+
[],
237+
);
238+
setConsumerGroupId(row.id);
239+
} else if (row.attributes.state === "EMPTY") {
240+
router.push(`${baseurl}/${row.id}/reset-offset`);
241+
}
242+
});
243+
}}
206244
/>
207-
)}
208-
</>
245+
{isResetOffsetModalOpen && (
246+
<ResetOffsetModal
247+
members={consumerGroupMembers}
248+
isResetOffsetModalOpen={isResetOffsetModalOpen}
249+
onClickClose={closeResetOffsetModal}
250+
consumerGroupId={consumerGroupId}
251+
kafkaId={kafkaId}
252+
/>
253+
)}
254+
</GridItem>
255+
</Grid>
209256
);
210257
}

ui/app/[locale]/(authorized)/kafka/[kafkaId]/groups/ConsumerGroupsTable.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const generateConsumerGroup = (
2424
attributes: {
2525
groupId,
2626
state,
27+
type: ["Classic", "Consumer", "Share", "Streams"][Number(id) % 4],
28+
protocol: ["consumer", "connect", "share", "streams"][Number(id) % 4],
2729
offsets:
2830
lag3 !== undefined
2931
? [{ lag: lag1 }, { lag: lag2 }, { lag: lag3 }]

ui/messages/en.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,9 +474,9 @@
474474
"no_consumer_groups": "No groups",
475475
"group_id": "Group ID",
476476
"type": "Type",
477-
"type_tooltip": "<p>The implementation type of the Kafka group (for example Classic, Consumer, or Streams).</p>",
477+
"type_tooltip": "<p>The Kafka group type, such as Classic, Consumer, Streams, or Share.</p>",
478478
"protocol": "Protocol",
479-
"protocol_tooltip": "<p>The protocol used by group members to coordinate partition assignments and rebalancing (for example consumer, connect, or streams).</p>",
479+
"protocol_tooltip": "<p>The protocol used by group members to coordinate partition assignments and rebalancing.</p>",
480480
"state": "State",
481481
"state_tooltip": "<p>Reflects the current operational state of the group.</p><p>Possible states include 'Stable,' 'Rebalancing,' or 'Empty.' 'Stable' indicates normal functioning, 'Rebalancing' means ongoing adjustments to the group's members, and 'Empty' suggests no active members.</p><p>If in the 'Empty' state, consider adding members to the group.</p>",
482482
"overall_lag": "Overall lag",
@@ -543,7 +543,10 @@
543543
"time_in_local": "Date-time is in local time zone",
544544
"change_to_utc": "Change to UTC",
545545
"no_offsets_found": "No offsets would be changed",
546-
"no_offsets_found_description": "The selected timestamp did not match any offsets."
546+
"no_offsets_found_description": "The selected timestamp did not match any offsets.",
547+
"groups_alert": "Group types identify how Kafka client applications coordinate work. Types include Classic, Consumer, Share, and Streams. Consumer groups use a newer protocol than Classic groups.",
548+
"learn_more": "Learn more",
549+
"external_link": "https://www.streamshub.io/docs/StreamsHub-Console/main/#con-groups-page-str"
547550
},
548551
"CreateTopic": {
549552
"title": "Topic creation wizard",

0 commit comments

Comments
 (0)