Repository: provectus/kafka-ui
Issue: Add sorting order to the consumer groups page.
Labels: type/enhancement
good first issue
scope/backend
scope/frontend
status/accepted
status/pending-frontend

### Is your proposal related to a problem?

  We can sort consumer groups by groupId, number of members, or state, but can't set order direction(ASC/DESC)
<!--

-->



### Describe the solution you'd like

  We need to pass an additional parameter from UI, to define sort order direction, similar functionality to Topic page sorting.
<!--

-->




<!--
-->



### Additional context

<!--
-->



Issue comments:
Hello there ValentinPrischepa! 👋

Thank you and congratulations 🎉 for opening your very first issue in this project! 💖

In case you want to claim this issue, please comment down below! We will try to get back to you as soon as we can. 👀

Pull request review comments:

Patch that resolved the issue:
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/ConsumerGroupsController.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/ConsumerGroupsController.java
index bca15d22032..28ce1bbadad 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/ConsumerGroupsController.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/ConsumerGroupsController.java
@@ -11,6 +11,7 @@
 import com.provectus.kafka.ui.model.ConsumerGroupOrderingDTO;
 import com.provectus.kafka.ui.model.ConsumerGroupsPageResponseDTO;
 import com.provectus.kafka.ui.model.PartitionOffsetDTO;
+import com.provectus.kafka.ui.model.SortOrderDTO;
 import com.provectus.kafka.ui.service.ConsumerGroupService;
 import com.provectus.kafka.ui.service.OffsetsResetService;
 import java.util.Map;
@@ -80,13 +81,15 @@ public Mono<ResponseEntity<ConsumerGroupsPageResponseDTO>> getConsumerGroupsPage
       Integer perPage,
       String search,
       ConsumerGroupOrderingDTO orderBy,
+      SortOrderDTO sortOrderDto,
       ServerWebExchange exchange) {
     return consumerGroupService.getConsumerGroupsPage(
             getCluster(clusterName),
             Optional.ofNullable(page).filter(i -> i > 0).orElse(1),
             Optional.ofNullable(perPage).filter(i -> i > 0).orElse(defaultConsumerGroupsPageSize),
             search,
-            Optional.ofNullable(orderBy).orElse(ConsumerGroupOrderingDTO.NAME)
+            Optional.ofNullable(orderBy).orElse(ConsumerGroupOrderingDTO.NAME),
+            Optional.ofNullable(sortOrderDto).orElse(SortOrderDTO.ASC)
         )
         .map(this::convertPage)
         .map(ResponseEntity::ok);
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java
index a0e67950a21..e49d728adf9 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ConsumerGroupService.java
@@ -3,6 +3,7 @@
 import com.provectus.kafka.ui.model.ConsumerGroupOrderingDTO;
 import com.provectus.kafka.ui.model.InternalConsumerGroup;
 import com.provectus.kafka.ui.model.KafkaCluster;
+import com.provectus.kafka.ui.model.SortOrderDTO;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -113,13 +114,18 @@ public Mono<ConsumerGroupsPage> getConsumerGroupsPage(
       int page,
       int perPage,
       @Nullable String search,
-      ConsumerGroupOrderingDTO orderBy) {
+      ConsumerGroupOrderingDTO orderBy,
+      SortOrderDTO sortOrderDto
+  ) {
+    var comparator = sortOrderDto.equals(SortOrderDTO.ASC)
+        ? getPaginationComparator(orderBy)
+        : getPaginationComparator(orderBy).reversed();
     return adminClientService.get(cluster).flatMap(ac ->
         describeConsumerGroups(ac, search).flatMap(descriptions ->
             getConsumerGroups(
                 ac,
                 descriptions.stream()
-                    .sorted(getPaginationComparator(orderBy))
+                    .sorted(comparator)
                     .skip((long) (page - 1) * perPage)
                     .limit(perPage)
                     .collect(Collectors.toList())
diff --git a/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml b/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml
index befb7894a9c..6fd9c4efa64 100644
--- a/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml
+++ b/kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml
@@ -652,6 +652,11 @@ paths:
           required: false
           schema:
             $ref: '#/components/schemas/ConsumerGroupOrdering'
+        - name: sortOrder
+          in: query
+          required: false
+          schema:
+            $ref: '#/components/schemas/SortOrder'
       responses:
         200:
           description: OK


Test patch:
diff --git a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/KafkaConsumerGroupTests.java b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/KafkaConsumerGroupTests.java
index 16b2a14036e..f98ea2cb092 100644
--- a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/KafkaConsumerGroupTests.java
+++ b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/KafkaConsumerGroupTests.java
@@ -86,7 +86,6 @@ void shouldBeBadRequestWhenConsumerGroupIsActive() {
         .isBadRequest();
   }
 
-
   @Test
   void shouldReturnConsumerGroupsWithPagination() throws Exception {
     try (var groups1 = startConsumerGroups(3, "cgPageTest1");
@@ -116,6 +115,21 @@ void shouldReturnConsumerGroupsWithPagination() throws Exception {
             assertThat(page.getConsumerGroups())
                 .isSortedAccordingTo(Comparator.comparing(ConsumerGroupDTO::getGroupId));
           });
+
+      webTestClient
+            .get()
+            .uri("/api/clusters/{clusterName}/consumer-groups/paged?perPage=10&&search"
+                + "=cgPageTest&orderBy=NAME&sortOrder=DESC", LOCAL)
+            .exchange()
+            .expectStatus()
+            .isOk()
+            .expectBody(ConsumerGroupsPageResponseDTO.class)
+            .value(page -> {
+              assertThat(page.getPageCount()).isEqualTo(1);
+              assertThat(page.getConsumerGroups().size()).isEqualTo(5);
+              assertThat(page.getConsumerGroups())
+                  .isSortedAccordingTo(Comparator.comparing(ConsumerGroupDTO::getGroupId).reversed());
+            });
     }
   }