]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12221 Add pagination to api/qualitygates/search
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 20 Jun 2019 15:24:52 +0000 (17:24 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 28 Jun 2019 06:45:45 +0000 (08:45 +0200)
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDtoTest.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java
server/sonar-server/src/main/resources/org/sonar/server/qualitygate/ws/search-example.json
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/SearchActionTest.java
sonar-ws/src/main/protobuf/ws-qualitygates.proto

index 3dd29f1e21417598c0ca2e0084122b2c3b0f2605..b510eef339ffe409a99bb3c623f52349e7044e42 100644 (file)
@@ -28,6 +28,7 @@ import javax.annotation.Nullable;
 public class ProjectQgateAssociationDto {
 
   private Long id;
+  private String key;
   private String name;
   private String gateId;
 
@@ -40,6 +41,15 @@ public class ProjectQgateAssociationDto {
     return this;
   }
 
+  public String getKey() {
+    return key;
+  }
+
+  public ProjectQgateAssociationDto setKey(String key) {
+    this.key = key;
+    return this;
+  }
+
   public String getName() {
     return name;
   }
@@ -59,10 +69,4 @@ public class ProjectQgateAssociationDto {
     return this;
   }
 
-  public ProjectQgateAssociation toQgateAssociation() {
-    return new ProjectQgateAssociation()
-      .setId(id)
-      .setName(name)
-      .setMember(gateId != null);
-  }
 }
index a2a05d73d07d6017ceb2283f66e5f180e98db4d9..547eeeacc09980b7a54d1bbcf405b12436fa1aa2 100644 (file)
@@ -4,7 +4,7 @@
 <mapper namespace="org.sonar.db.qualitygate.ProjectQgateAssociationMapper">
 
   <select id="selectProjects" parameterType="map" resultType="ProjectQgateAssociation">
-    SELECT proj.id as id, proj.name as name, prop.text_value as gateId
+    SELECT proj.id as id, proj.kee as key, proj.name as name, prop.text_value as gateId
     FROM projects proj
     LEFT JOIN properties prop ON prop.resource_id=proj.id AND prop.prop_key='sonar.qualitygate' AND prop.text_value = #{query.gateId}
     where
index 8033ad411f92f98333d853a0f31c3d1347f71fc7..2f0a99162fab4c6336699ce21bb0102fc0ae6e97 100644 (file)
@@ -56,11 +56,11 @@ public class ProjectQgateAssociationDaoTest {
       .build());
 
     assertThat(result)
-      .extracting(ProjectQgateAssociationDto::getId, ProjectQgateAssociationDto::getName, ProjectQgateAssociationDto::getGateId)
+      .extracting(ProjectQgateAssociationDto::getId, ProjectQgateAssociationDto::getKey, ProjectQgateAssociationDto::getName, ProjectQgateAssociationDto::getGateId)
       .containsExactlyInAnyOrder(
-        tuple(project1.getId(), project1.name(), qualityGate1.getId().toString()),
-        tuple(project2.getId(), project2.name(), qualityGate1.getId().toString()),
-        tuple(project3.getId(), project3.name(), null));
+        tuple(project1.getId(), project1.getKey(), project1.name(), qualityGate1.getId().toString()),
+        tuple(project2.getId(), project2.getKey(), project2.name(), qualityGate1.getId().toString()),
+        tuple(project3.getId(), project3.getKey(), project3.name(), null));
   }
 
   @Test
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDtoTest.java
deleted file mode 100644 (file)
index e63776f..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.db.qualitygate;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ProjectQgateAssociationDtoTest {
-
-  @Test
-  public void to_assoc_with_project_having_assoc() {
-    ProjectQgateAssociation project = new ProjectQgateAssociationDto()
-      .setId(1L)
-      .setName("polop")
-      .setGateId("10")
-      .toQgateAssociation();
-
-    assertThat(project.id()).isEqualTo(1);
-    assertThat(project.name()).isEqualTo("polop");
-    assertThat(project.isMember()).isTrue();
-  }
-
-  @Test
-  public void to_assoc_with_project_not_having_assoc() {
-    ProjectQgateAssociation project = new ProjectQgateAssociationDto()
-      .setId(1L)
-      .setName("polop")
-      .setGateId(null)
-      .toQgateAssociation();
-
-    assertThat(project.id()).isEqualTo(1);
-    assertThat(project.name()).isEqualTo("polop");
-    assertThat(project.isMember()).isFalse();
-  }
-
-}
index fcf81e8142c6fc72042b37feb8b1616bc6559402..743b0e6537043c649451c0cf90d7e5f9998ec5a4 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.qualitygate.ws;
 import com.google.common.io.Resources;
 import java.util.Collection;
 import java.util.List;
+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;
@@ -31,13 +32,13 @@ import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.qualitygate.ProjectQgateAssociation;
 import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
 import org.sonar.db.qualitygate.ProjectQgateAssociationQuery;
 import org.sonar.db.qualitygate.QGateWithOrgDto;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Qualitygates;
 
+import static java.util.stream.Collectors.toList;
 import static org.sonar.api.server.ws.WebService.Param.SELECTED;
 import static org.sonar.api.utils.Paging.forPageIndex;
 import static org.sonar.db.qualitygate.ProjectQgateAssociationQuery.ANY;
@@ -66,6 +67,10 @@ public class SearchAction implements QualityGatesWsAction {
         "Only authorized projects for current user will be returned.")
       .setSince("4.3")
       .setResponseExample(Resources.getResource(this.getClass(), "search-example.json"))
+      .setChangelog(
+        new Change("7.9", "New field 'paging' in response"),
+        new Change("7.9", "New field 'key' returning the project key in 'results' response"),
+        new Change("7.9", "Field 'more' is deprecated in the response"))
       .setHandler(this);
 
     action.createParam(PARAM_GATE_ID)
@@ -97,45 +102,42 @@ public class SearchAction implements QualityGatesWsAction {
 
       OrganizationDto organization = wsSupport.getOrganization(dbSession, request);
       QGateWithOrgDto qualityGate = wsSupport.getByOrganizationAndId(dbSession, organization, request.mandatoryParamAsLong(PARAM_GATE_ID));
-      Association associations = find(dbSession,
-        ProjectQgateAssociationQuery.builder()
-          .qualityGate(qualityGate)
-          .membership(request.param(PARAM_QUERY) == null ? request.param(SELECTED) : ANY)
-          .projectSearch(request.param(PARAM_QUERY))
-          .pageIndex(request.paramAsInt(PARAM_PAGE))
-          .pageSize(request.paramAsInt(PARAM_PAGE_SIZE))
-          .build());
-
-      Qualitygates.SearchResponse.Builder createResponse = Qualitygates.SearchResponse.newBuilder()
-        .setMore(associations.hasMoreResults());
-
-      for (ProjectQgateAssociation project : associations.projects()) {
+
+      ProjectQgateAssociationQuery projectQgateAssociationQuery = ProjectQgateAssociationQuery.builder()
+        .qualityGate(qualityGate)
+        .membership(request.param(PARAM_QUERY) == null ? request.param(SELECTED) : ANY)
+        .projectSearch(request.param(PARAM_QUERY))
+        .pageIndex(request.paramAsInt(PARAM_PAGE))
+        .pageSize(request.paramAsInt(PARAM_PAGE_SIZE))
+        .build();
+      List<ProjectQgateAssociationDto> projects = dbClient.projectQgateAssociationDao().selectProjects(dbSession, projectQgateAssociationQuery);
+      List<ProjectQgateAssociationDto> authorizedProjects = keepAuthorizedProjects(dbSession, projects);
+      Paging paging = forPageIndex(projectQgateAssociationQuery.pageIndex())
+        .withPageSize(projectQgateAssociationQuery.pageSize())
+        .andTotal(authorizedProjects.size());
+      List<ProjectQgateAssociationDto> paginatedProjects = getPaginatedProjects(authorizedProjects, paging);
+
+      Qualitygates.SearchResponse.Builder createResponse = Qualitygates.SearchResponse.newBuilder().setMore(paging.hasNextPage());
+      createResponse.getPagingBuilder()
+        .setPageIndex(paging.pageIndex())
+        .setPageSize(paging.pageSize())
+        .setTotal(paging.total())
+        .build();
+
+      for (ProjectQgateAssociationDto project : paginatedProjects) {
         createResponse.addResultsBuilder()
-          .setId(project.id())
-          .setName(project.name())
-          .setSelected(project.isMember());
+          .setId(project.getId())
+          .setName(project.getName())
+          .setKey(project.getKey())
+          .setSelected(project.getGateId() != null);
       }
 
       writeProtobuf(createResponse.build(), request, response);
     }
   }
 
-  private SearchAction.Association find(DbSession dbSession, ProjectQgateAssociationQuery query) {
-    List<ProjectQgateAssociationDto> projects = dbClient.projectQgateAssociationDao().selectProjects(dbSession, query);
-    List<ProjectQgateAssociationDto> authorizedProjects = keepAuthorizedProjects(dbSession, projects);
-
-    Paging paging = forPageIndex(query.pageIndex())
-      .withPageSize(query.pageSize())
-      .andTotal(authorizedProjects.size());
-    return new SearchAction.Association(toProjectAssociations(getPaginatedProjects(authorizedProjects, paging)), paging.hasNextPage());
-  }
-
   private static List<ProjectQgateAssociationDto> getPaginatedProjects(List<ProjectQgateAssociationDto> projects, Paging paging) {
-    return projects.stream().skip(paging.offset()).limit(paging.pageSize()).collect(MoreCollectors.toList());
-  }
-
-  private static List<ProjectQgateAssociation> toProjectAssociations(List<ProjectQgateAssociationDto> dtos) {
-    return dtos.stream().map(ProjectQgateAssociationDto::toQgateAssociation).collect(MoreCollectors.toList());
+    return projects.stream().skip(paging.offset()).limit(paging.pageSize()).collect(toList());
   }
 
   private List<ProjectQgateAssociationDto> keepAuthorizedProjects(DbSession dbSession, List<ProjectQgateAssociationDto> projects) {
@@ -149,22 +151,4 @@ public class SearchAction implements QualityGatesWsAction {
     Collection<Long> authorizedProjectIds = dbClient.authorizationDao().keepAuthorizedProjectIds(dbSession, projectIds, userSession.getUserId(), UserRole.USER);
     return projects.stream().filter(project -> authorizedProjectIds.contains(project.getId())).collect(MoreCollectors.toList());
   }
-
-  private static class Association {
-    private List<ProjectQgateAssociation> projects;
-    private boolean hasMoreResults;
-
-    private Association(List<ProjectQgateAssociation> projects, boolean hasMoreResults) {
-      this.projects = projects;
-      this.hasMoreResults = hasMoreResults;
-    }
-
-    public List<ProjectQgateAssociation> projects() {
-      return projects;
-    }
-
-    public boolean hasMoreResults() {
-      return hasMoreResults;
-    }
-  }
 }
index 7ad6eed5a242238ef1bf367da5a384321230a4d5..97332a0480b3f8e8febd36f1579adf9608365988 100644 (file)
@@ -1,15 +1,21 @@
 {
-  "more": true,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2
+  },
   "results": [
     {
       "id": 1,
       "name": "Simple Java project analyzed with the SonarQube Runner",
+      "key": "somple-java",
       "selected": true
     },
     {
       "id": 4,
       "name": "My Project",
-      "selected": true
+      "key": "my-project",
+      "selected": false
     }
   ]
 }
index 84768e600b8b23543993039d26dd0990e7efdd86..453ad38190d3fb97410c5d6251edb5a3bf8422ba 100644 (file)
@@ -69,25 +69,6 @@ public class SearchActionTest {
     new QualityGatesWsSupport(dbClient, userSession, defaultOrganizationProvider));
   private WsActionTester ws = new WsActionTester(underTest);
 
-  @Test
-  public void definition() {
-    WebService.Action action = ws.getDef();
-
-    assertThat(action).isNotNull();
-    assertThat(action.isInternal()).isFalse();
-    assertThat(action.isPost()).isFalse();
-    assertThat(action.responseExampleAsString()).isNotEmpty();
-    assertThat(action.params())
-      .extracting(WebService.Param::key, WebService.Param::isRequired)
-      .containsExactlyInAnyOrder(
-        tuple("gateId", true),
-        tuple("query", false),
-        tuple("organization", false),
-        tuple("selected", false),
-        tuple("page", false),
-        tuple("pageSize", false));
-  }
-
   @Test
   public void search_projects_of_a_quality_gate_from_an_organization() {
     OrganizationDto organization = db.organizations().insert();
@@ -101,9 +82,8 @@ public class SearchActionTest {
       .executeProtobuf(SearchResponse.class);
 
     assertThat(response.getResultsList())
-      .extracting(Result::getId, Result::getName)
-      .containsExactlyInAnyOrder(tuple(project.getId(), project.name()));
-    assertThat(response.getMore()).isFalse();
+      .extracting(Result::getId, Result::getKey, Result::getName)
+      .containsExactlyInAnyOrder(tuple(project.getId(), project.getKey(), project.name()));
   }
 
   @Test
@@ -120,7 +100,6 @@ public class SearchActionTest {
     assertThat(response.getResultsList())
       .extracting(Result::getId, Result::getName)
       .containsExactlyInAnyOrder(tuple(project.getId(), project.name()));
-    assertThat(response.getMore()).isFalse();
   }
 
   @Test
@@ -151,10 +130,10 @@ public class SearchActionTest {
       .executeProtobuf(SearchResponse.class);
 
     assertThat(response.getResultsList())
-      .extracting(Result::getName, Result::getSelected)
+      .extracting(Result::getName, Result::getKey, Result::getSelected)
       .containsExactlyInAnyOrder(
-        tuple(associatedProject.name(), true),
-        tuple(unassociatedProject.name(), false));
+        tuple(associatedProject.name(), associatedProject.getKey(), true),
+        tuple(unassociatedProject.name(), unassociatedProject.getKey(), false));
   }
 
   @Test
@@ -309,25 +288,53 @@ public class SearchActionTest {
   }
 
   @Test
-  public void more_is_true_when_not_all_project_fit_in_page_size() {
+  public void test_pagination_on_many_pages() {
     OrganizationDto organization = db.organizations().insert();
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate(organization);
     for (int i = 0; i < 20; i++) {
       ComponentDto project = db.components().insertPublicProject(organization);
       db.qualityGates().associateProjectToQualityGate(project, qualityGate);
     }
+    userSession.addPermission(ADMINISTER_QUALITY_GATES, organization);
+
+    SearchResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_ID, valueOf(qualityGate.getId()))
+      .setParam(PARAM_ORGANIZATION, organization.getKey())
+      .setParam(PARAM_PAGE_SIZE, valueOf(5))
+      .setParam(PARAM_PAGE, valueOf(2))
+      .executeProtobuf(SearchResponse.class);
 
+    assertThat(response)
+      .extracting(SearchResponse::getMore,
+        searchResponse -> searchResponse.getPaging().getPageIndex(),
+        searchResponse -> searchResponse.getPaging().getPageSize(),
+        searchResponse -> searchResponse.getPaging().getTotal())
+      .contains(true, 2, 5, 20);
+  }
+
+  @Test
+  public void test_pagination_on_one_page() {
+    OrganizationDto organization = db.organizations().insert();
+    QualityGateDto qualityGate = db.qualityGates().insertQualityGate(organization);
+    for (int i = 0; i < 20; i++) {
+      ComponentDto project = db.components().insertPublicProject(organization);
+      db.qualityGates().associateProjectToQualityGate(project, qualityGate);
+    }
     userSession.addPermission(ADMINISTER_QUALITY_GATES, organization);
 
     SearchResponse response = ws.newRequest()
       .setParam(PARAM_GATE_ID, valueOf(qualityGate.getId()))
       .setParam(PARAM_ORGANIZATION, organization.getKey())
-      .setParam(PARAM_PAGE_SIZE, valueOf(10))
+      .setParam(PARAM_PAGE_SIZE, valueOf(100))
       .setParam(PARAM_PAGE, valueOf(1))
       .executeProtobuf(SearchResponse.class);
 
-    assertThat(response.getMore()).isTrue();
-    assertThat(response.getResultsCount()).isEqualTo(10);
+    assertThat(response)
+      .extracting(SearchResponse::getMore,
+        searchResponse -> searchResponse.getPaging().getPageIndex(),
+        searchResponse -> searchResponse.getPaging().getPageSize(),
+        searchResponse -> searchResponse.getPaging().getTotal())
+      .contains(false, 1, 100, 20);
   }
 
   @Test
@@ -380,4 +387,23 @@ public class SearchActionTest {
       .executeProtobuf(SearchResponse.class);
   }
 
+  @Test
+  public void definition() {
+    WebService.Action action = ws.getDef();
+
+    assertThat(action).isNotNull();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.isPost()).isFalse();
+    assertThat(action.responseExampleAsString()).isNotEmpty();
+    assertThat(action.params())
+      .extracting(WebService.Param::key, WebService.Param::isRequired)
+      .containsExactlyInAnyOrder(
+        tuple("gateId", true),
+        tuple("query", false),
+        tuple("organization", false),
+        tuple("selected", false),
+        tuple("page", false),
+        tuple("pageSize", false));
+  }
+
 }
index 3c43684ab18ff26569718f3374772b3bdca3ef90..a904acba9117f06ff6ebb911862ee37d9473c11a 100644 (file)
@@ -20,6 +20,8 @@ syntax = "proto2";
 
 package sonarqube.ws.qualitygate;
 
+import "ws-commons.proto";
+
 option java_package = "org.sonarqube.ws";
 option java_outer_classname = "Qualitygates";
 option optimize_for = SPEED;
@@ -132,13 +134,16 @@ message ShowWsResponse {
 
 // GET api/qualitygates/search
 message SearchResponse {
+  // Deprecated since 7.9
   optional bool more = 1;
   repeated Result results = 2;
+  optional sonarqube.ws.commons.Paging paging = 3;
 
   message Result {
     optional int64 id = 1;
     optional string name = 2;
     optional bool selected = 3;
+    optional string key = 4;
   }
 }