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 fcbbb52053..893c23474e 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.PartitionsIncreaseDTO; 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 class TopicsController extends AbstractController implements TopicsApi { @Valid Boolean showInternal, @Valid String search, @Valid TopicColumnsToSortDTO orderBy, + @Valid SortOrderDTO sortOrder, ServerWebExchange exchange) { return topicsService .getTopics( @@ -74,7 +76,8 @@ public class TopicsController extends AbstractController implements TopicsApi { 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 2a505cd00e..d115f99c31 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.PartitionsIncreaseDTO; 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 class TopicsService { Optional nullablePerPage, Optional showInternal, Optional search, - Optional sortBy) { + Optional sortBy, + Optional 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 @@ public class TopicsService { Optional nullablePerPage, Optional showInternal, Optional search, - Optional sortBy) { + Optional sortBy, + Optional sortOrder) { return geTopicsForPagination() .map(paginatingTopics -> { Predicate 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 topics = paginatingTopics.stream() .filter(topic -> !topic.isInternal() || showInternal.map(i -> topic.isInternal() == i).orElse(true)) @@ -422,7 +427,7 @@ public class TopicsService { 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-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 1b107bd622..455d04cc03 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.assertj.core.api.Assertions.assertThat; 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 @@ class TopicsServicePaginationTest { 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 @@ class TopicsServicePaginationTest { ); 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 @@ class TopicsServicePaginationTest { ); 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 @@ class TopicsServicePaginationTest { 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 @@ class TopicsServicePaginationTest { 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 @@ class TopicsServicePaginationTest { 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 @@ class TopicsServicePaginationTest { 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 @@ class TopicsServicePaginationTest { .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())); } } 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 0f024a3c50..befb7894a9 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: