Repository: provectus/kafka-ui
Issue: Topic Details: Display consumers
Labels: type/enhancement
scope/frontend

Add ability to view list of consumers for topic.

Issue comments:
Add consumer groups list details per topic level

Pull request review comments:

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx
index ec65254113e..47c29cbe639 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx
@@ -1,12 +1,15 @@
 import React from 'react';
-import { Topic, TopicDetails, ConsumerGroup } from 'generated-sources';
+import {
+  Topic,
+  TopicDetails,
+  ConsumerTopicPartitionDetail,
+} from 'generated-sources';
 import { ClusterName, TopicName } from 'redux/interfaces';
-import ListItem from 'components/ConsumerGroups/List/ListItem';
 
 interface Props extends Topic, TopicDetails {
   clusterName: ClusterName;
   topicName: TopicName;
-  consumerGroups: Array<ConsumerGroup>;
+  consumerGroups: ConsumerTopicPartitionDetail[];
   fetchTopicConsumerGroups(
     clusterName: ClusterName,
     topicName: TopicName
@@ -26,20 +29,29 @@ const TopicConsumerGroups: React.FC<Props> = ({
   return (
     <div className="box">
       {consumerGroups.length > 0 ? (
-        <table className="table is-striped is-fullwidth is-hoverable">
+        <table className="table is-striped is-fullwidth">
           <thead>
             <tr>
-              <th>Consumer group ID</th>
-              <th>Num of consumers</th>
-              <th>Num of topics</th>
+              <th>Group ID</th>
+              <th>Consumer ID</th>
+              <th>Host</th>
+              <th>Partition</th>
+              <th>Messages behind</th>
+              <th>Current offset</th>
+              <th>End offset</th>
             </tr>
           </thead>
           <tbody>
-            {consumerGroups.map((consumerGroup) => (
-              <ListItem
-                key={consumerGroup.consumerGroupId}
-                consumerGroup={consumerGroup}
-              />
+            {consumerGroups.map((consumer) => (
+              <tr>
+                <td>{consumer.groupId}</td>
+                <td>{consumer.consumerId}</td>
+                <td>{consumer.host}</td>
+                <td>{consumer.partition}</td>
+                <td>{consumer.messagesBehind}</td>
+                <td>{consumer.currentOffset}</td>
+                <td>{consumer.endOffset}</td>
+              </tr>
             ))}
           </tbody>
         </table>
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroupsContainer.ts b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroupsContainer.ts
index 79798528b07..e0f8d3d0be1 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroupsContainer.ts
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroupsContainer.ts
@@ -20,7 +20,7 @@ const mapStateToProps = (
     },
   }: OwnProps
 ) => ({
-  consumerGroups: getTopicConsumerGroups(state),
+  consumerGroups: getTopicConsumerGroups(state, topicName),
   topicName,
   clusterName,
 });
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/__test__/TopicConsumerGroups.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/__test__/TopicConsumerGroups.spec.tsx
index 7c7d1e729b6..17ad0c969e3 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/__test__/TopicConsumerGroups.spec.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/__test__/TopicConsumerGroups.spec.tsx
@@ -8,8 +8,26 @@ describe('Details', () => {
   const mockTopicName = 'local';
   const mockWithConsumerGroup = [
     {
-      clusterId: '1',
-      consumerGroupId: '1',
+      groupId: 'messages-consumer',
+      consumerId:
+        'consumer-messages-consumer-1-122fbf98-643b-491d-8aec-c0641d2513d0',
+      topic: 'messages',
+      host: '/172.31.9.153',
+      partition: 6,
+      currentOffset: 394,
+      endOffset: 394,
+      messagesBehind: 0,
+    },
+    {
+      groupId: 'messages-consumer',
+      consumerId:
+        'consumer-messages-consumer-1-122fbf98-643b-491d-8aec-c0641d2513d0',
+      topic: 'messages',
+      host: '/172.31.9.153',
+      partition: 7,
+      currentOffset: 384,
+      endOffset: 384,
+      messagesBehind: 0,
     },
   ];
 
diff --git a/kafka-ui-react-app/src/redux/interfaces/topic.ts b/kafka-ui-react-app/src/redux/interfaces/topic.ts
index 90e7331897d..f56cff06b6c 100644
--- a/kafka-ui-react-app/src/redux/interfaces/topic.ts
+++ b/kafka-ui-react-app/src/redux/interfaces/topic.ts
@@ -7,6 +7,7 @@ import {
   GetTopicMessagesRequest,
   ConsumerGroup,
   TopicColumnsToSort,
+  TopicConsumerGroups,
 } from 'generated-sources';
 
 export type TopicName = Topic['name'];
@@ -40,6 +41,7 @@ export interface TopicFormCustomParams {
 
 export interface TopicWithDetailedInfo extends Topic, TopicDetails {
   config?: TopicConfig[];
+  consumerGroups?: TopicConsumerGroups;
 }
 
 export interface TopicsState {
diff --git a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts
index 16801fade4e..2936d1c2ac8 100644
--- a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts
+++ b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts
@@ -16,8 +16,6 @@ export const getTopicMessages = (state: RootState) =>
   topicsState(state).messages;
 export const getTopicListTotalPages = (state: RootState) =>
   topicsState(state).totalPages;
-export const getTopicConsumerGroups = (state: RootState) =>
-  topicsState(state).consumerGroups;
 
 const getTopicListFetchingStatus = createFetchingSelector('GET_TOPICS');
 const getTopicDetailsFetchingStatus =
@@ -138,3 +136,9 @@ export const getIsTopicInternal = createSelector(
   getTopicByName,
   ({ internal }) => !!internal
 );
+
+export const getTopicConsumerGroups = createSelector(
+  getTopicMap,
+  getTopicName,
+  (topics, topicName) => topics[topicName].consumerGroups?.consumers || []
+);


Test patch: