Repository: provectus/kafka-ui
Issue: Create parameters of request for sorting topics
Labels: type/enhancement
good first issue
scope/backend
scope/frontend
status/accepted
status/confirmed
status/pending-frontend

**Describe the bug**
We can't get sorted names of topics 




**Steps to Reproduce**
Steps to reproduce the behavior:

1. 

**Expected behavior**
On clicking sorting button we should be able to have soretd list of names from A-Z and from Z-A. Now we get only A-Z

![image](https://user-images.githubusercontent.com/96067981/149346164-b63011bf-e88e-4fdd-85d3-f78daf0de03f.png)


**Additional context**
It will be better to send additional params in request


Issue comments:
Hello there NelyDavtyan! 👋

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. 👀
TopicsService#getComparatorForTopic implement sorting order (ASC/DESC)

Pull request review comments:

Patch that resolved the issue:
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/TopicsController.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/TopicsController.java
index fcbbb520538..893c23474e2 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/TopicsController.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/TopicsController.java
@@ -5,6 +5,7 @@
 import com.provectus.kafka.ui.model.PartitionsIncreaseResponseDTO;
 import com.provectus.kafka.ui.model.ReplicationFactorChangeDTO;
 import com.provectus.kafka.ui.model.ReplicationFactorChangeResponseDTO;
+import com.provectus.kafka.ui.model.SortOrderDTO;
 import com.provectus.kafka.ui.model.TopicColumnsToSortDTO;
 import com.provectus.kafka.ui.model.TopicConfigDTO;
 import com.provectus.kafka.ui.model.TopicCreationDTO;
@@ -66,6 +67,7 @@ public Mono<ResponseEntity<TopicsResponseDTO>> getTopics(String clusterName, @Va
                                                         @Valid Boolean showInternal,
                                                         @Valid String search,
                                                         @Valid TopicColumnsToSortDTO orderBy,
+                                                        @Valid SortOrderDTO sortOrder,
                                                         ServerWebExchange exchange) {
     return topicsService
         .getTopics(
@@ -74,7 +76,8 @@ public Mono<ResponseEntity<TopicsResponseDTO>> getTopics(String clusterName, @Va
             Optional.ofNullable(perPage),
             Optional.ofNullable(showInternal),
             Optional.ofNullable(search),
-            Optional.ofNullable(orderBy)
+            Optional.ofNullable(orderBy),
+            Optional.ofNullable(sortOrder)
         ).map(ResponseEntity::ok);
   }
 
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/TopicsService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/TopicsService.java
index 2a505cd00e3..d115f99c318 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/TopicsService.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/TopicsService.java
@@ -20,6 +20,7 @@
 import com.provectus.kafka.ui.model.PartitionsIncreaseResponseDTO;
 import com.provectus.kafka.ui.model.ReplicationFactorChangeDTO;
 import com.provectus.kafka.ui.model.ReplicationFactorChangeResponseDTO;
+import com.provectus.kafka.ui.model.SortOrderDTO;
 import com.provectus.kafka.ui.model.TopicColumnsToSortDTO;
 import com.provectus.kafka.ui.model.TopicConfigDTO;
 import com.provectus.kafka.ui.model.TopicCreationDTO;
@@ -67,10 +68,11 @@ public Mono<TopicsResponseDTO> getTopics(KafkaCluster cluster,
                                            Optional<Integer> nullablePerPage,
                                            Optional<Boolean> showInternal,
                                            Optional<String> search,
-                                           Optional<TopicColumnsToSortDTO> sortBy) {
+                                           Optional<TopicColumnsToSortDTO> sortBy,
+                                           Optional<SortOrderDTO> sortOrder) {
     return adminClientService.get(cluster).flatMap(ac ->
       new Pagination(ac, metricsCache.get(cluster))
-          .getPage(pageNum, nullablePerPage, showInternal, search, sortBy)
+          .getPage(pageNum, nullablePerPage, showInternal, search, sortBy, sortOrder)
           .flatMap(page ->
               loadTopics(cluster, page.getTopics())
                   .map(topics ->
@@ -409,12 +411,15 @@ Mono<Page> getPage(
         Optional<Integer> nullablePerPage,
         Optional<Boolean> showInternal,
         Optional<String> search,
-        Optional<TopicColumnsToSortDTO> sortBy) {
+        Optional<TopicColumnsToSortDTO> sortBy,
+        Optional<SortOrderDTO> sortOrder) {
       return geTopicsForPagination()
           .map(paginatingTopics -> {
             Predicate<Integer> positiveInt = i -> i > 0;
             int perPage = nullablePerPage.filter(positiveInt).orElse(DEFAULT_PAGE_SIZE);
             var topicsToSkip = (pageNum.filter(positiveInt).orElse(1) - 1) * perPage;
+            var comparator = sortOrder.isEmpty() || !sortOrder.get().equals(SortOrderDTO.DESC)
+                ? getComparatorForTopic(sortBy) : getComparatorForTopic(sortBy).reversed();
             List<InternalTopic> topics = paginatingTopics.stream()
                 .filter(topic -> !topic.isInternal()
                     || showInternal.map(i -> topic.isInternal() == i).orElse(true))
@@ -422,7 +427,7 @@ Mono<Page> getPage(
                     search
                         .map(s -> StringUtils.containsIgnoreCase(topic.getName(), s))
                         .orElse(true))
-                .sorted(getComparatorForTopic(sortBy))
+                .sorted(comparator)
                 .collect(toList());
             var totalPages = (topics.size() / perPage)
                 + (topics.size() % perPage == 0 ? 0 : 1);
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 0f024a3c503..befb7894a9c 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
@@ -295,6 +295,11 @@ paths:
           required: false
           schema:
             $ref: '#/components/schemas/TopicColumnsToSort'
+        - name: sortOrder
+          in: query
+          required: false
+          schema:
+            $ref: '#/components/schemas/SortOrder'
       responses:
         200:
           description: OK
@@ -1729,6 +1734,12 @@ components:
         - TOTAL_PARTITIONS
         - REPLICATION_FACTOR
 
+    SortOrder:
+      type: string
+      enum:
+        - ASC
+        - DESC
+
     Topic:
       type: object
       properties:


Test patch:
diff --git a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/TopicsServicePaginationTest.java b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/TopicsServicePaginationTest.java
index 1b107bd622b..455d04cc037 100644
--- a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/TopicsServicePaginationTest.java
+++ b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/TopicsServicePaginationTest.java
@@ -4,8 +4,10 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import com.provectus.kafka.ui.model.SortOrderDTO;
 import com.provectus.kafka.ui.model.TopicColumnsToSortDTO;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -46,12 +48,35 @@ public void shouldListFirst25Topics() {
 
     var topics = pagination.getPage(
         Optional.empty(), Optional.empty(), Optional.empty(),
-        Optional.empty(), Optional.empty()).block();
+        Optional.empty(), Optional.empty(), Optional.empty()).block();
     assertThat(topics.getTotalPages()).isEqualTo(4);
     assertThat(topics.getTopics()).hasSize(25);
     assertThat(topics.getTopics()).isSorted();
   }
 
+  @Test
+  public void shouldListFirst25TopicsSortedByNameDescendingOrder() {
+    var topicDescriptions = IntStream.rangeClosed(1, 100).boxed()
+        .map(Objects::toString)
+        .map(name -> new TopicDescription(name, false, List.of()))
+        .collect(Collectors.toList());
+    init(topicDescriptions);
+
+    var topics = pagination.getPage(
+        Optional.empty(), Optional.empty(), Optional.empty(),
+        Optional.empty(), Optional.of(TopicColumnsToSortDTO.NAME), Optional.of(SortOrderDTO.DESC)).block();
+    assertThat(topics.getTotalPages()).isEqualTo(4);
+    assertThat(topics.getTopics()).hasSize(25);
+    assertThat(topics.getTopics()).isSortedAccordingTo(Comparator.reverseOrder());
+    assertThat(topics.getTopics()).containsExactlyElementsOf(
+        topicDescriptions.stream()
+            .map(TopicDescription::name)
+            .sorted(Comparator.reverseOrder())
+            .limit(25)
+            .collect(Collectors.toList())
+    );
+  }
+
   @Test
   public void shouldCalculateCorrectPageCountForNonDivisiblePageSize() {
     init(
@@ -62,7 +87,7 @@ public void shouldCalculateCorrectPageCountForNonDivisiblePageSize() {
     );
 
     var topics = pagination.getPage(Optional.of(4), Optional.of(33),
-        Optional.empty(), Optional.empty(), Optional.empty()).block();
+        Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()).block();
     assertThat(topics.getTotalPages()).isEqualTo(4);
     assertThat(topics.getTopics()).hasSize(1)
         .first().isEqualTo("99");
@@ -78,7 +103,7 @@ public void shouldCorrectlyHandleNonPositivePageNumberAndPageSize() {
     );
 
     var topics = pagination.getPage(Optional.of(0), Optional.of(-1),
-        Optional.empty(), Optional.empty(), Optional.empty()).block();
+        Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()).block();
     assertThat(topics.getTotalPages()).isEqualTo(4);
     assertThat(topics.getTopics()).hasSize(25);
     assertThat(topics.getTopics()).isSorted();
@@ -95,7 +120,7 @@ public void shouldListBotInternalAndNonInternalTopics() {
 
     var topics = pagination.getPage(
         Optional.empty(), Optional.empty(), Optional.of(true),
-        Optional.empty(), Optional.empty()).block();
+        Optional.empty(), Optional.empty(), Optional.empty()).block();
     assertThat(topics.getTotalPages()).isEqualTo(4);
     assertThat(topics.getTopics()).hasSize(25);
     assertThat(topics.getTopics()).isSorted();
@@ -113,7 +138,7 @@ public void shouldListOnlyNonInternalTopics() {
 
     var topics = pagination.getPage(
         Optional.empty(), Optional.empty(), Optional.of(true),
-        Optional.empty(), Optional.empty()).block();
+        Optional.empty(), Optional.empty(), Optional.empty()).block();
     assertThat(topics.getTotalPages()).isEqualTo(4);
     assertThat(topics.getTopics()).hasSize(25);
     assertThat(topics.getTopics()).isSorted();
@@ -131,7 +156,7 @@ public void shouldListOnlyTopicsContainingOne() {
 
     var topics = pagination.getPage(
         Optional.empty(), Optional.empty(), Optional.empty(),
-        Optional.of("1"), Optional.empty()).block();
+        Optional.of("1"), Optional.empty(), Optional.empty()).block();
     assertThat(topics.getTotalPages()).isEqualTo(1);
     assertThat(topics.getTopics()).hasSize(20);
     assertThat(topics.getTopics()).isSorted();
@@ -151,7 +176,7 @@ public void shouldListTopicsOrderedByPartitionsCount() {
 
     var topics = pagination.getPage(
         Optional.empty(), Optional.empty(), Optional.empty(),
-        Optional.empty(), Optional.of(TopicColumnsToSortDTO.TOTAL_PARTITIONS)).block();
+        Optional.empty(), Optional.of(TopicColumnsToSortDTO.TOTAL_PARTITIONS), Optional.empty()).block();
     assertThat(topics.getTotalPages()).isEqualTo(4);
     assertThat(topics.getTopics()).hasSize(25);
     assertThat(topics.getTopics()).containsExactlyElementsOf(
@@ -159,6 +184,18 @@ public void shouldListTopicsOrderedByPartitionsCount() {
             .map(TopicDescription::name)
             .limit(25)
             .collect(Collectors.toList()));
+
+    var topicsSortedDesc = pagination.getPage(
+        Optional.empty(), Optional.empty(), Optional.empty(),
+        Optional.empty(), Optional.of(TopicColumnsToSortDTO.TOTAL_PARTITIONS), Optional.of(SortOrderDTO.DESC)).block();
+    assertThat(topicsSortedDesc.getTotalPages()).isEqualTo(4);
+    assertThat(topicsSortedDesc.getTopics()).hasSize(25);
+    assertThat(topicsSortedDesc.getTopics()).containsExactlyElementsOf(
+        topicDescriptions.stream()
+            .sorted((a, b) -> b.partitions().size() - a.partitions().size())
+            .map(TopicDescription::name)
+            .limit(25)
+            .collect(Collectors.toList()));
   }
 
 }