Repository: provectus/kafka-ui
Issue: Show "Loading" when waiting for consumers list
Labels: type/enhancement
good first issue
scope/frontend
status/accepted

### Is your proposal related to a problem?

Go to Topics -> select topic -> go to Consumers tab.
When Consumers tab is rendering the table is empty, when consumers request executed table is updated with responded values. 

### Describe the solution you'd like

I think we should show 'loading..' when waiting for consumers response. Since this request can be pretty long table ca stay empty for some time, which is can confuse users. 

### Describe alternatives you've considered

<!--
  Let us know about other solutions you've tried or researched.
-->


### Additional context

<!--
  Is there anything else you can add about the proposal?
  You might want to link to related issues here, if you haven't already.
-->



Issue comments:

Pull request review comments:
why we export this interface?
To use it in the test file.

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 5c67e23007f..7877dcd19ab 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
@@ -8,11 +8,13 @@ import { Tag } from 'components/common/Tag/Tag.styled';
 import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.styled';
 import { Link } from 'react-router-dom';
 import getTagColor from 'components/ConsumerGroups/Utils/TagColor';
+import PageLoader from 'components/common/PageLoader/PageLoader';
 
-interface Props extends Topic, TopicDetails {
+export interface Props extends Topic, TopicDetails {
   clusterName: ClusterName;
   topicName: TopicName;
   consumerGroups: ConsumerGroup[];
+  isFetched: boolean;
   fetchTopicConsumerGroups(
     clusterName: ClusterName,
     topicName: TopicName
@@ -24,11 +26,16 @@ const TopicConsumerGroups: React.FC<Props> = ({
   fetchTopicConsumerGroups,
   clusterName,
   topicName,
+  isFetched,
 }) => {
   React.useEffect(() => {
     fetchTopicConsumerGroups(clusterName, topicName);
   }, [clusterName, fetchTopicConsumerGroups, topicName]);
 
+  if (!isFetched) {
+    return <PageLoader />;
+  }
+
   return (
     <div>
       <Table isFullwidth>
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 e0f8d3d0be1..23922bb2e75 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
@@ -3,7 +3,10 @@ import { RootState, TopicName, ClusterName } from 'redux/interfaces';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
 import { fetchTopicConsumerGroups } from 'redux/actions';
 import TopicConsumerGroups from 'components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups';
-import { getTopicConsumerGroups } from 'redux/reducers/topics/selectors';
+import {
+  getTopicConsumerGroups,
+  getTopicsConsumerGroupsFetched,
+} from 'redux/reducers/topics/selectors';
 
 interface RouteProps {
   clusterName: ClusterName;
@@ -23,6 +26,7 @@ const mapStateToProps = (
   consumerGroups: getTopicConsumerGroups(state, topicName),
   topicName,
   clusterName,
+  isFetched: getTopicsConsumerGroupsFetched(state),
 });
 
 const mapDispatchToProps = {
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 32ba5a22b84..a4675785db1 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
@@ -1,12 +1,14 @@
 import React from 'react';
-import { shallow } from 'enzyme';
-import ConsumerGroups from 'components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups';
+import { render } from 'lib/testHelpers';
+import { screen } from '@testing-library/react';
+import ConsumerGroups, {
+  Props,
+} from 'components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups';
 import { ConsumerGroupState } from 'generated-sources';
 
-describe('Details', () => {
-  const mockFn = jest.fn();
-  const mockClusterName = 'local';
-  const mockTopicName = 'local';
+describe('TopicConsumerGroups', () => {
+  const mockClusterName = 'localClusterName';
+  const mockTopicName = 'localTopicName';
   const mockWithConsumerGroup = [
     {
       groupId: 'amazon.msk.canary.group.broker-7',
@@ -30,29 +32,40 @@ describe('Details', () => {
     },
   ];
 
-  it("don't render ConsumerGroups in Topic", () => {
-    const component = shallow(
+  const setUpComponent = (props: Partial<Props> = {}) => {
+    const { name, topicName, consumerGroups, isFetched } = props;
+
+    return render(
       <ConsumerGroups
         clusterName={mockClusterName}
-        consumerGroups={[]}
-        name={mockTopicName}
-        fetchTopicConsumerGroups={mockFn}
-        topicName={mockTopicName}
+        consumerGroups={consumerGroups?.length ? consumerGroups : []}
+        name={name || mockTopicName}
+        fetchTopicConsumerGroups={jest.fn()}
+        topicName={topicName || mockTopicName}
+        isFetched={'isFetched' in props ? !!isFetched : false}
       />
     );
-    expect(component.find('td').text()).toEqual('No active consumer groups');
+  };
+
+  describe('Default Setup', () => {
+    beforeEach(() => {
+      setUpComponent();
+    });
+    it('should view the Page loader when it is fetching state', () => {
+      expect(screen.getByRole('progressbar')).toBeInTheDocument();
+    });
+  });
+
+  it("don't render ConsumerGroups in Topic", () => {
+    setUpComponent({ isFetched: true });
+    expect(screen.getByText(/No active consumer groups/i)).toBeInTheDocument();
   });
 
   it('render ConsumerGroups in Topic', () => {
-    const component = shallow(
-      <ConsumerGroups
-        clusterName={mockClusterName}
-        consumerGroups={mockWithConsumerGroup}
-        name={mockTopicName}
-        fetchTopicConsumerGroups={mockFn}
-        topicName={mockTopicName}
-      />
-    );
-    expect(component.exists('tbody')).toBeTruthy();
+    setUpComponent({
+      consumerGroups: mockWithConsumerGroup,
+      isFetched: true,
+    });
+    expect(screen.getAllByRole('rowgroup')).toHaveLength(2);
   });
 });
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 ee59bc97352..aac3c856a0b 100644
--- a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts
+++ b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts
@@ -33,6 +33,10 @@ const getReplicationFactorUpdateStatus = createLeagcyFetchingSelector(
 );
 const getTopicDeletingStatus = createLeagcyFetchingSelector('DELETE_TOPIC');
 
+const getTopicConsumerGroupsStatus = createLeagcyFetchingSelector(
+  'GET_TOPIC_CONSUMER_GROUPS'
+);
+
 export const getIsTopicDeleted = createSelector(
   getTopicDeletingStatus,
   (status) => status === 'fetched'
@@ -88,6 +92,11 @@ export const getTopicReplicationFactorUpdated = createSelector(
   (status) => status === 'fetched'
 );
 
+export const getTopicsConsumerGroupsFetched = createSelector(
+  getTopicConsumerGroupsStatus,
+  (status) => status === 'fetched'
+);
+
 export const getTopicList = createSelector(
   getAreTopicsFetched,
   getAllNames,


Test patch: