From 30b058eb8ee9921e5bc2f0409ee529c8f3a639a6 Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Fri, 18 Aug 2023 12:11:47 +0200 Subject: SONAR-20181 Support pageSize=0 --- .../server/common/user/service/UserServiceIT.java | 9 +++ .../sonar/server/common/PaginationInformation.java | 85 ++++++++++++++++++++++ .../common/user/UsersSearchResponseGenerator.java | 4 +- .../server/common/user/service/UserService.java | 4 +- .../server/common/PaginationInformationTest.java | 58 +++++++++++++++ .../org/sonar/server/v2/api/model/RestPage.java | 4 +- .../api/user/controller/DefaultUserController.java | 6 +- .../UsersSearchRestResponseGenerator.java | 6 +- .../UsersSearchRestResponseGeneratorTest.java | 19 ++--- .../org/sonar/server/user/ws/SearchAction.java | 6 +- .../server/user/ws/SearchWsReponseGenerator.java | 10 +-- 11 files changed, 183 insertions(+), 28 deletions(-) create mode 100644 server/sonar-webserver-common/src/main/java/org/sonar/server/common/PaginationInformation.java create mode 100644 server/sonar-webserver-common/src/test/java/org/sonar/server/common/PaginationInformationTest.java diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java index 0083f73827b..29d711b5d99 100644 --- a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java +++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java @@ -365,6 +365,15 @@ public class UserServiceIT { } + @Test + public void search_whenPageSizeIsZero_shouldOnlyReturnPaginationInfo() { + IntStream.rangeClosed(0, 9).forEach(i -> db.users().insertUser(u -> u.setLogin("user-" + i).setName("User " + i))); + + SearchResults users = userService.findUsers(UsersSearchRequest.builder().setPageSize(0).build()); + assertThat(users.searchResults()).isEmpty(); + assertThat(users.total()).isEqualTo(10); + } + @Test public void return_empty_result_when_no_user() { SearchResults users = userService.findUsers(SEARCH_REQUEST); diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/PaginationInformation.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/PaginationInformation.java new file mode 100644 index 00000000000..323593268c4 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/PaginationInformation.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common; + +import static org.sonar.api.utils.Preconditions.checkArgument; + +public class PaginationInformation { + private final int pageSize; + private final int pageIndex; + private final int total; + + private PaginationInformation(int pageSize, int pageIndex, int total) { + checkArgument(pageSize >= 0, "Page size must be positive. Got %s", pageSize); + checkArgument(pageIndex >= 1, "Page index must be strictly positive. Got %s", pageIndex); + checkArgument(total >= 0, "Total items must be positive. Got %s", total); + this.pageSize = pageSize; + this.pageIndex = pageIndex; + this.total = total; + } + + public static PaginationInformation.Builder forPageIndex(int pageIndex) { + return new PaginationInformation.Builder(pageIndex); + } + + /** + * Page index, >= 1. + */ + public int pageIndex() { + return pageIndex; + } + + /** + * Maximum number of items per page. It is >= 0. + */ + public int pageSize() { + return pageSize; + } + + /** + * Total number of items. It is >= 0. + */ + public int total() { + return total; + } + + public static int offset(int pageIndex, int pageSize) { + return (pageIndex - 1) * pageSize; + } + + public static class Builder { + private int pageSize; + private int pageIndex; + + private Builder(int pageIndex) { + this.pageIndex = pageIndex; + } + + public PaginationInformation.Builder withPageSize(int pageSize) { + this.pageSize = pageSize; + return this; + } + + public PaginationInformation andTotal(int total) { + return new PaginationInformation(pageSize, pageIndex, total); + } + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/UsersSearchResponseGenerator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/UsersSearchResponseGenerator.java index 81025de5e19..edd6672ad72 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/UsersSearchResponseGenerator.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/UsersSearchResponseGenerator.java @@ -20,11 +20,11 @@ package org.sonar.server.common.user; import java.util.List; -import org.sonar.api.utils.Paging; +import org.sonar.server.common.PaginationInformation; import org.sonar.server.common.user.service.UserSearchResult; public interface UsersSearchResponseGenerator { - T toUsersForResponse(List userSearchResults, Paging paging); + T toUsersForResponse(List userSearchResults, PaginationInformation paginationInformation); } diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java index c8957fb3c97..b3059f0a10d 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java @@ -76,7 +76,9 @@ public class UserService { UserQuery userQuery = buildUserQuery(request); try (DbSession dbSession = dbClient.openSession(false)) { int totalUsers = dbClient.userDao().countUsers(dbSession, userQuery); - + if (request.getPageSize() == 0) { + return new SearchResults<>(List.of(), totalUsers); + } List searchResults = performSearch(dbSession, userQuery, request.getPage(), request.getPageSize()); return new SearchResults<>(searchResults, totalUsers); } diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/PaginationInformationTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/PaginationInformationTest.java new file mode 100644 index 00000000000..c3f51edc3a7 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/PaginationInformationTest.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class PaginationInformationTest { + + @Test + public void paginationInformation_whenPageIndexIsZero_shouldThrow() { + assertThatIllegalArgumentException().isThrownBy(() -> PaginationInformation.forPageIndex(0).withPageSize(1).andTotal(1)) + .withMessage("Page index must be strictly positive. Got 0"); + } + @Test + public void paginationInformation_whenPageSizeIsNegative_shouldThrow() { + assertThatIllegalArgumentException().isThrownBy(() -> PaginationInformation.forPageIndex(1).withPageSize(-1).andTotal(1)) + .withMessage("Page size must be positive. Got -1"); + } + @Test + public void paginationInformation_withPageSizeAndTotalSetTo0_shouldBeCreatedCorrectly() { + PaginationInformation paginationInformation = PaginationInformation.forPageIndex(1).withPageSize(0).andTotal(0); + + assertThat(paginationInformation.pageIndex()).isOne(); + assertThat(paginationInformation.pageSize()).isZero(); + assertThat(paginationInformation.total()).isZero(); + } + @Test + public void paginationInformation_withHighPositiveValues_shouldBeCreatedCorrectly() { + PaginationInformation paginationInformation = PaginationInformation.forPageIndex(1000).withPageSize(2344).andTotal(13213); + + assertThat(paginationInformation.pageIndex()).isEqualTo(1000); + assertThat(paginationInformation.pageSize()).isEqualTo(2344); + assertThat(paginationInformation.total()).isEqualTo(13213); + } + + + +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/RestPage.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/RestPage.java index 49572e66038..d7b667480c8 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/RestPage.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/RestPage.java @@ -27,9 +27,9 @@ import javax.validation.constraints.Positive; import org.jetbrains.annotations.Nullable; public record RestPage( - @Min(1) + @Min(0) @Max(500) - @Schema(defaultValue = DEFAULT_PAGE_SIZE, description = "Number of results per page") + @Schema(defaultValue = DEFAULT_PAGE_SIZE, description = "Number of results per page. A value of 0 will only return the pagination information.") Integer pageSize, @Positive diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/DefaultUserController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/DefaultUserController.java index 113b7b00608..7a100dfdb77 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/DefaultUserController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/DefaultUserController.java @@ -21,7 +21,7 @@ package org.sonar.server.v2.api.user.controller; import java.util.Optional; import javax.annotation.Nullable; -import org.sonar.api.utils.Paging; +import org.sonar.server.common.PaginationInformation; import org.sonar.server.common.SearchResults; import org.sonar.server.common.user.service.UserCreateRequest; import org.sonar.server.common.user.service.UserSearchResult; @@ -37,7 +37,7 @@ import org.sonar.server.v2.api.user.request.UserCreateRestRequest; import org.sonar.server.v2.api.user.request.UsersSearchRestRequest; import org.sonar.server.v2.api.user.response.UsersSearchRestResponse; -import static org.sonar.api.utils.Paging.forPageIndex; +import static org.sonar.server.common.PaginationInformation.forPageIndex; import static org.sonar.server.exceptions.BadRequestException.checkRequest; public class DefaultUserController implements UserController { @@ -60,7 +60,7 @@ public class DefaultUserController implements UserController { checkRequest(!RestSortOrder.DESC.equals(order), "order parameter is present for doc-demo purpose, it will be removed."); SearchResults userSearchResults = userService.findUsers(toUserSearchRequest(usersSearchRestRequest, page)); - Paging paging = forPageIndex(page.pageIndex()).withPageSize(page.pageSize()).andTotal(userSearchResults.total()); + PaginationInformation paging = forPageIndex(page.pageIndex()).withPageSize(page.pageSize()).andTotal(userSearchResults.total()); return usersSearchResponseGenerator.toUsersForResponse(userSearchResults.searchResults(), paging); } diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGenerator.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGenerator.java index ee4ba94f2aa..f92c3e3784d 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGenerator.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGenerator.java @@ -24,8 +24,8 @@ import java.util.Objects; import java.util.Optional; import org.jetbrains.annotations.Nullable; import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.Paging; import org.sonar.db.user.UserDto; +import org.sonar.server.common.PaginationInformation; import org.sonar.server.common.user.UsersSearchResponseGenerator; import org.sonar.server.common.user.service.UserSearchResult; import org.sonar.server.user.UserSession; @@ -45,9 +45,9 @@ public class UsersSearchRestResponseGenerator implements UsersSearchResponseGene } @Override - public UsersSearchRestResponse toUsersForResponse(List userSearchResults, Paging paging) { + public UsersSearchRestResponse toUsersForResponse(List userSearchResults, PaginationInformation paginationInformation) { List usersForResponse = toUsersForResponse(userSearchResults); - PageRestResponse pageRestResponse = new PageRestResponse(paging.pageIndex(), paging.pageSize(), paging.total()); + PageRestResponse pageRestResponse = new PageRestResponse(paginationInformation.pageIndex(), paginationInformation.pageSize(), paginationInformation.total()); return new UsersSearchRestResponse(usersForResponse, pageRestResponse); } diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGeneratorTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGeneratorTest.java index 741201135c9..665e80a2761 100644 --- a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGeneratorTest.java +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/user/converter/UsersSearchRestResponseGeneratorTest.java @@ -28,8 +28,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.Paging; import org.sonar.db.user.UserDto; +import org.sonar.server.common.PaginationInformation; import org.sonar.server.common.user.service.UserSearchResult; import org.sonar.server.user.UserSession; import org.sonar.server.v2.api.response.PageRestResponse; @@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.server.common.PaginationInformation.forPageIndex; @RunWith(MockitoJUnitRunner.class) public class UsersSearchRestResponseGeneratorTest { @@ -54,7 +55,7 @@ public class UsersSearchRestResponseGeneratorTest { @Test public void toUsersForResponse_whenNoResults_mapsCorrectly() { - Paging paging = Paging.forPageIndex(1).withPageSize(2).andTotal(3); + PaginationInformation paging = forPageIndex(1).withPageSize(2).andTotal(3); UsersSearchRestResponse usersForResponse = usersSearchRestResponseGenerator.toUsersForResponse(List.of(), paging); @@ -67,7 +68,7 @@ public class UsersSearchRestResponseGeneratorTest { when(userSession.isLoggedIn()).thenReturn(true); when(userSession.isSystemAdministrator()).thenReturn(true); - Paging paging = Paging.forPageIndex(1).withPageSize(2).andTotal(3); + PaginationInformation paging = forPageIndex(1).withPageSize(2).andTotal(3); UserSearchResult userSearchResult1 = mockSearchResult(1, true); UserSearchResult userSearchResult2 = mockSearchResult(2, false); @@ -105,7 +106,7 @@ public class UsersSearchRestResponseGeneratorTest { public void toUsersForResponse_whenNonAdmin_mapsNonAdminFields() { when(userSession.isLoggedIn()).thenReturn(true); - Paging paging = Paging.forPageIndex(1).withPageSize(2).andTotal(3); + PaginationInformation paging = forPageIndex(1).withPageSize(2).andTotal(3); UserSearchResult userSearchResult1 = mockSearchResult(1, true); UserSearchResult userSearchResult2 = mockSearchResult(2, false); @@ -134,7 +135,7 @@ public class UsersSearchRestResponseGeneratorTest { @Test public void toUsersForResponse_whenAnonymous_returnsOnlyNameAndLogin() { - Paging paging = Paging.forPageIndex(1).withPageSize(2).andTotal(3); + PaginationInformation paging = forPageIndex(1).withPageSize(2).andTotal(3); UserSearchResult userSearchResult1 = mockSearchResult(1, true); UserSearchResult userSearchResult2 = mockSearchResult(2, false); @@ -182,10 +183,10 @@ public class UsersSearchRestResponseGeneratorTest { return userSearchResult; } - private static void assertPaginationInformationAreCorrect(Paging paging, PageRestResponse pageRestResponse) { - assertThat(pageRestResponse.pageIndex()).isEqualTo(paging.pageIndex()); - assertThat(pageRestResponse.pageSize()).isEqualTo(paging.pageSize()); - assertThat(pageRestResponse.total()).isEqualTo(paging.total()); + private static void assertPaginationInformationAreCorrect(PaginationInformation paginationInformation, PageRestResponse pageRestResponse) { + assertThat(pageRestResponse.pageIndex()).isEqualTo(paginationInformation.pageIndex()); + assertThat(pageRestResponse.pageSize()).isEqualTo(paginationInformation.pageSize()); + assertThat(pageRestResponse.total()).isEqualTo(paginationInformation.total()); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java index 8864fb41673..f25b35e8889 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java @@ -24,7 +24,7 @@ import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.api.utils.Paging; +import org.sonar.server.common.PaginationInformation; import org.sonar.server.common.SearchResults; import org.sonar.server.common.user.service.UserSearchResult; import org.sonar.server.common.user.service.UserService; @@ -38,7 +38,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static org.sonar.api.server.ws.WebService.Param.PAGE; import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; -import static org.sonar.api.utils.Paging.forPageIndex; +import static org.sonar.server.common.PaginationInformation.forPageIndex; import static org.sonar.server.ws.WsUtils.writeProtobuf; public class SearchAction implements UsersWsAction { @@ -171,7 +171,7 @@ public class SearchAction implements UsersWsAction { private Users.SearchWsResponse doHandle(UsersSearchRequest request) { SearchResults userSearchResults = userService.findUsers(request); - Paging paging = forPageIndex(request.getPage()).withPageSize(request.getPageSize()).andTotal(userSearchResults.total()); + PaginationInformation paging = forPageIndex(request.getPage()).withPageSize(request.getPageSize()).andTotal(userSearchResults.total()); return searchWsReponseGenerator.toUsersForResponse(userSearchResults.searchResults(), paging); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchWsReponseGenerator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchWsReponseGenerator.java index f8437217973..d346a405be4 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchWsReponseGenerator.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchWsReponseGenerator.java @@ -22,8 +22,8 @@ package org.sonar.server.user.ws; import java.util.List; import java.util.Objects; import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.Paging; import org.sonar.db.user.UserDto; +import org.sonar.server.common.PaginationInformation; import org.sonar.server.common.user.UsersSearchResponseGenerator; import org.sonar.server.common.user.service.UserSearchResult; import org.sonar.server.user.UserSession; @@ -42,13 +42,13 @@ public class SearchWsReponseGenerator implements UsersSearchResponseGenerator userSearchResults, Paging paging) { + public Users.SearchWsResponse toUsersForResponse(List userSearchResults, PaginationInformation paginationInformation) { Users.SearchWsResponse.Builder responseBuilder = newBuilder(); userSearchResults.forEach(user -> responseBuilder.addUsers(toSearchResponsUser(user))); responseBuilder.getPagingBuilder() - .setPageIndex(paging.pageIndex()) - .setPageSize(paging.pageSize()) - .setTotal(paging.total()) + .setPageIndex(paginationInformation.pageIndex()) + .setPageSize(paginationInformation.pageSize()) + .setTotal(paginationInformation.total()) .build(); return responseBuilder.build(); } -- cgit v1.2.3