]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9245 Return leak period in api/components/search_projects
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 23 May 2017 11:55:45 +0000 (13:55 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 9 Jun 2017 06:26:48 +0000 (08:26 +0200)
it/it-tests/src/test/java/it/projectSearch/SearchProjectsTest.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
sonar-ws/src/main/protobuf/ws-components.proto

index 4906a0481ce865b946679c3ec2de6e5df53fbff1..cf94dbdf0260c3e0b7a453a6ce34d9c121240451 100644 (file)
@@ -25,7 +25,6 @@ import it.Category6Suite;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import javax.annotation.Nullable;
 import org.assertj.core.groups.Tuple;
 import org.junit.After;
 import org.junit.Before;
@@ -40,17 +39,18 @@ import org.sonarqube.ws.client.organization.CreateWsRequest;
 import org.sonarqube.ws.client.project.CreateRequest;
 import util.ItUtils;
 
-import static com.sonar.orchestrator.build.SonarScanner.create;
 import static it.Category6Suite.enableOrganizationsSupport;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.groups.Tuple.tuple;
+import static util.ItUtils.concat;
 import static util.ItUtils.deleteOrganizationsIfExists;
 import static util.ItUtils.newAdminWsClient;
 import static util.ItUtils.newProjectKey;
 import static util.ItUtils.projectDir;
 import static util.ItUtils.restoreProfile;
+import static util.ItUtils.sanitizeTimezones;
 import static util.ItUtils.setServerProperty;
 
 /**
@@ -86,7 +86,7 @@ public class SearchProjectsTest {
   @Test
   public void filter_projects_by_measure_values() throws Exception {
     String projectKey = newProjectKey();
-    analyzeProject(projectKey,"shared/xoo-sample");
+    analyzeProject(projectKey, "shared/xoo-sample");
 
     verifyFilterMatches(projectKey, "ncloc > 1");
     verifyFilterMatches(projectKey, "ncloc > 1 and comment_lines < 10000");
@@ -96,18 +96,55 @@ public class SearchProjectsTest {
   @Test
   public void find_projects_with_no_data() throws Exception {
     String projectKey = newProjectKey();
-    analyzeProject(projectKey,"shared/xoo-sample");
+    analyzeProject(projectKey, "shared/xoo-sample");
 
     verifyFilterMatches(projectKey, "coverage = NO_DATA");
     verifyFilterDoesNotMatch("ncloc = NO_DATA");
   }
 
+  @Test
+  public void provisioned_projects_should_be_included_to_results() throws Exception {
+    String projectKey = newProjectKey();
+    newAdminWsClient(orchestrator).projects().create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organizationKey).build());
+
+    SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organizationKey).build());
+
+    assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(projectKey);
+  }
+
+  @Test
+  public void return_leak_period_date() throws Exception {
+    setServerProperty(orchestrator, "sonar.leak.period", "previous_version");
+    // This project has a leak period
+    String projectKey1 = newProjectKey();
+    analyzeProject(projectKey1, "shared/xoo-sample", "sonar.projectDate", "2016-12-31");
+    analyzeProject(projectKey1, "shared/xoo-sample");
+    // This project has only one analysis, so no leak period
+    String projectKey2 = newProjectKey();
+    analyzeProject(projectKey2, "shared/xoo-sample");
+    // This project is provisioned, so has no leak period
+    String projectKey3 = newProjectKey();
+    newAdminWsClient(orchestrator).projects().create(CreateRequest.builder().setKey(projectKey3).setName(projectKey3).setOrganization(organizationKey).build());
+
+    SearchProjectsWsResponse response = searchProjects(
+      SearchProjectsRequest.builder().setAdditionalFields(singletonList("leakPeriodDate")).setOrganization(organizationKey).build());
+
+    assertThat(response.getComponentsList()).extracting(Component::getKey, Component::hasLeakPeriodDate)
+      .containsOnly(
+        tuple(projectKey1, true),
+        tuple(projectKey2, false),
+        tuple(projectKey3, false));
+    Component project1 = response.getComponentsList().stream().filter(component -> component.getKey().equals(projectKey1)).findFirst()
+      .orElseThrow(() -> new IllegalStateException("Project1 is not found"));
+    assertThat(sanitizeTimezones(project1.getLeakPeriodDate())).isEqualTo("2016-12-31T00:00:00+0000");
+  }
+
   @Test
   public void filter_by_text_query() throws IOException {
-    orchestrator.executeBuild(create(projectDir("shared/xoo-sample"), "sonar.projectKey", "project1", "sonar.projectName", "apachee"));
-    orchestrator.executeBuild(create(projectDir("shared/xoo-sample"), "sonar.projectKey", "project2", "sonar.projectName", "Apache"));
-    orchestrator.executeBuild(create(projectDir("shared/xoo-multi-modules-sample"), "sonar.projectKey", "project3", "sonar.projectName", "Apache Foundation"));
-    orchestrator.executeBuild(create(projectDir("shared/xoo-multi-modules-sample"), "sonar.projectKey", "project4", "sonar.projectName", "Windows"));
+    analyzeProject("project1", "shared/xoo-sample", "sonar.projectName", "apachee");
+    analyzeProject("project2", "shared/xoo-sample", "sonar.projectName", "Apache");
+    analyzeProject("project3", "shared/xoo-multi-modules-sample", "sonar.projectName", "Apache Foundation");
+    analyzeProject("project4", "shared/xoo-multi-modules-sample", "sonar.projectName", "Windows");
 
     // Search only by text query
     assertThat(searchProjects("query = \"apache\"").getComponentsList()).extracting(Component::getKey).containsExactly("project2", "project3", "project1");
@@ -133,16 +170,6 @@ public class SearchProjectsTest {
         .containsOnly(tuple("*-1000.0", 0L), tuple("1000.0-10000.0", 0L), tuple("10000.0-100000.0", 0L), tuple("100000.0-500000.0", 0L), tuple("500000.0-*", 0L));
   }
 
-  @Test
-  public void provisioned_projects_should_be_included_to_results() throws Exception {
-    String projectKey = newProjectKey();
-    newAdminWsClient(orchestrator).projects().create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organizationKey).build());
-
-    SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organizationKey).build());
-
-    assertThat(response.getComponentsList()).extracting(Component::getKey).contains(projectKey);
-  }
-
   @Test
   public void should_return_facets() throws Exception {
     analyzeProject(newProjectKey(), "shared/xoo-sample");
@@ -211,11 +238,11 @@ public class SearchProjectsTest {
     setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis");
     // This project has no duplication on new code
     String projectKey1 = newProjectKey();
-    analyzeProject(projectKey1, "shared/xoo-sample", "2016-12-31");
+    analyzeProject(projectKey1, "shared/xoo-sample", "sonar.projectDate", "2016-12-31");
     analyzeProject(projectKey1, "shared/xoo-sample");
     // This project has 0% duplication on new code
     String projectKey2 = newProjectKey();
-    analyzeProject(projectKey2, "projectSearch/xoo-history-v1", "2016-12-31");
+    analyzeProject(projectKey2, "projectSearch/xoo-history-v1", "sonar.projectDate", "2016-12-31");
     analyzeProject(projectKey2, "projectSearch/xoo-history-v2");
 
     SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organizationKey).setFacets(asList(
@@ -266,23 +293,14 @@ public class SearchProjectsTest {
     assertThat(facet.getValuesList()).extracting(Common.FacetValue::getVal, Common.FacetValue::getCount).containsExactlyInAnyOrder(values);
   }
 
-  private void analyzeProject(String projectKey, String relativePath) {
-    analyzeProject(projectKey, relativePath, null);
-  }
-
-  private void analyzeProject(String projectKey, String relativePath, @Nullable String analysisDate) {
+  private void analyzeProject(String projectKey, String relativePath, String... properties) {
     List<String> keyValueProperties = new ArrayList<>(asList(
       "sonar.projectKey", projectKey,
       "sonar.organization", organizationKey,
       "sonar.profile", "with-many-rules",
       "sonar.login", "admin", "sonar.password", "admin",
-      "sonar.scm.disabled", "false"
-    ));
-    if (analysisDate != null) {
-      keyValueProperties.add("sonar.projectDate");
-      keyValueProperties.add(analysisDate);
-    }
-    orchestrator.executeBuild(SonarScanner.create(projectDir(relativePath), keyValueProperties.toArray(new String[0])));
+      "sonar.scm.disabled", "false"));
+    orchestrator.executeBuild(SonarScanner.create(projectDir(relativePath), concat(keyValueProperties.toArray(new String[0]), properties)));
   }
 
   private SearchProjectsWsResponse searchProjects(String filter) throws IOException {
index 76827619b7c8d87719894cf7bd15473c90421fb4..6d515dc7074f376882cb550efad4c6b98410ebab 100644 (file)
@@ -38,7 +38,6 @@ 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.server.ws.WebService.Param;
-import org.sonar.api.utils.DateUtils;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -51,7 +50,6 @@ import org.sonar.server.component.ws.FilterParser.Criterion;
 import org.sonar.server.es.Facets;
 import org.sonar.server.es.SearchIdResult;
 import org.sonar.server.es.SearchOptions;
-import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.measure.index.ProjectMeasuresIndex;
 import org.sonar.server.measure.index.ProjectMeasuresQuery;
 import org.sonar.server.project.Visibility;
@@ -68,12 +66,15 @@ import static java.util.Collections.emptyMap;
 import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
 import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
 import static org.sonar.api.server.ws.WebService.Param.FIELDS;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.IS_FAVORITE_CRITERION;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
 import static org.sonar.server.measure.index.ProjectMeasuresIndex.SUPPORTED_FACETS;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
+import static org.sonar.server.ws.WsUtils.checkFound;
 import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
@@ -86,7 +87,8 @@ import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_TAGS;
 public class SearchProjectsAction implements ComponentsWsAction {
 
   private static final String ANALYSIS_DATE = "analysisDate";
-  private static final Set<String> POSSIBLE_FIELDS = newHashSet(ANALYSIS_DATE);
+  private static final String LEAK_PERIOD_DATE = "leakPeriodDate";
+  private static final Set<String> POSSIBLE_FIELDS = newHashSet(ANALYSIS_DATE, LEAK_PERIOD_DATE);
 
   private final DbClient dbClient;
   private final ProjectMeasuresIndex index;
@@ -112,8 +114,8 @@ public class SearchProjectsAction implements ComponentsWsAction {
         new Change("6.4", format("The '%s' parameter accepts '%s' to filter by language", FILTER_LANGUAGES, PARAM_FILTER)),
         new Change("6.4", "The 'visibility' field is added"),
         new Change("6.5", "The 'filter' parameter now allows 'NO_DATA' as value for numeric metrics"),
-        new Change("6.5", "Added the option 'analysisDate' for the 'sort' parameter")
-      )
+        new Change("6.5", "Added the option 'analysisDate' for the 'sort' parameter"),
+        new Change("6.5", format("Value '%s' is added to parameter '%s'", LEAK_PERIOD_DATE, FIELDS)))
       .setHandler(this);
 
     action.createFieldsParam(POSSIBLE_FIELDS)
@@ -272,7 +274,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
   }
 
   private Map<String, SnapshotDto> getSnapshots(DbSession dbSession, SearchProjectsRequest request, List<String> projectUuids) {
-    if (request.getAdditionalFields().contains(ANALYSIS_DATE)) {
+    if (request.getAdditionalFields().contains(ANALYSIS_DATE) || request.getAdditionalFields().contains(LEAK_PERIOD_DATE)) {
       return dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
         .stream()
         .collect(MoreCollectors.uniqueIndex(SnapshotDto::getComponentUuid));
@@ -291,14 +293,14 @@ public class SearchProjectsAction implements ComponentsWsAction {
     if (httpRequest.hasParam(Param.FACETS)) {
       request.setFacets(httpRequest.paramAsStrings(Param.FACETS));
     }
-    if (httpRequest.hasParam(Param.FIELDS)) {
+    if (httpRequest.hasParam(FIELDS)) {
       request.setAdditionalFields(httpRequest.paramAsStrings(FIELDS));
     }
     return request.build();
   }
 
   private SearchProjectsWsResponse buildResponse(SearchProjectsRequest request, SearchResults searchResults, Map<String, OrganizationDto> organizationsByUuid) {
-    Function<ComponentDto, Component> dbToWsComponent = new DbToWsComponent(organizationsByUuid, searchResults.favoriteProjectUuids, searchResults.analysisByProjectUuid,
+    Function<ComponentDto, Component> dbToWsComponent = new DbToWsComponent(request, organizationsByUuid, searchResults.favoriteProjectUuids, searchResults.analysisByProjectUuid,
       userSession.isLoggedIn());
 
     return Stream.of(SearchProjectsWsResponse.newBuilder())
@@ -392,14 +394,16 @@ public class SearchProjectsAction implements ComponentsWsAction {
   }
 
   private static class DbToWsComponent implements Function<ComponentDto, Component> {
+    private final SearchProjectsRequest request;
     private final Component.Builder wsComponent;
     private final Map<String, OrganizationDto> organizationsByUuid;
     private final Set<String> favoriteProjectUuids;
     private final boolean isUserLoggedIn;
     private final Map<String, SnapshotDto> analysisByProjectUuid;
 
-    private DbToWsComponent(Map<String, OrganizationDto> organizationsByUuid, Set<String> favoriteProjectUuids, Map<String, SnapshotDto> analysisByProjectUuid,
-      boolean isUserLoggedIn) {
+    private DbToWsComponent(SearchProjectsRequest request, Map<String, OrganizationDto> organizationsByUuid, Set<String> favoriteProjectUuids,
+      Map<String, SnapshotDto> analysisByProjectUuid, boolean isUserLoggedIn) {
+      this.request = request;
       this.analysisByProjectUuid = analysisByProjectUuid;
       this.wsComponent = Component.newBuilder();
       this.organizationsByUuid = organizationsByUuid;
@@ -409,10 +413,9 @@ public class SearchProjectsAction implements ComponentsWsAction {
 
     @Override
     public Component apply(ComponentDto dbComponent) {
-      OrganizationDto organizationDto = organizationsByUuid.get(dbComponent.getOrganizationUuid());
-      if (organizationDto == null) {
-        throw new NotFoundException(format("Organization with uuid '%s' not found", dbComponent.getOrganizationUuid()));
-      }
+      String organizationUuid = dbComponent.getOrganizationUuid();
+      OrganizationDto organizationDto = organizationsByUuid.get(organizationUuid);
+      checkFound(organizationDto, "Organization with uuid '%s' not found", organizationUuid);
       wsComponent
         .clear()
         .setOrganization(organizationDto.getKey())
@@ -424,7 +427,12 @@ public class SearchProjectsAction implements ComponentsWsAction {
 
       SnapshotDto snapshotDto = analysisByProjectUuid.get(dbComponent.uuid());
       if (snapshotDto != null) {
-        wsComponent.setAnalysisDate(DateUtils.formatDateTime(snapshotDto.getCreatedAt()));
+        if (request.getAdditionalFields().contains(ANALYSIS_DATE)) {
+          wsComponent.setAnalysisDate(formatDateTime(snapshotDto.getCreatedAt()));
+        }
+        if (request.getAdditionalFields().contains(LEAK_PERIOD_DATE)) {
+          setNullable(snapshotDto.getPeriodDate(), leakPeriodDate -> wsComponent.setLeakPeriodDate(formatDateTime(leakPeriodDate)));
+        }
       }
 
       if (isUserLoggedIn) {
index f39e7092cca78fc264c5f3a917562cd1fc2646fb..59bb204ac1cbe2430cb2d82fce19464746625395 100644 (file)
@@ -152,8 +152,8 @@ public class SearchProjectsActionTest {
       tuple("6.4", "The 'languages' parameter accepts 'filter' to filter by language"),
       tuple("6.4", "The 'visibility' field is added"),
       tuple("6.5", "The 'filter' parameter now allows 'NO_DATA' as value for numeric metrics"),
-      tuple("6.5", "Added the option 'analysisDate' for the 'sort' parameter")
-    );
+      tuple("6.5", "Added the option 'analysisDate' for the 'sort' parameter"),
+      tuple("6.5", "Value 'leakPeriodDate' is added to parameter 'f'"));
 
     Param organization = def.param("organization");
     assertThat(organization.isRequired()).isFalse();
@@ -171,7 +171,7 @@ public class SearchProjectsActionTest {
 
     Param additionalFields = def.param("f");
     assertThat(additionalFields.defaultValue()).isNull();
-    assertThat(additionalFields.possibleValues()).containsOnly("analysisDate");
+    assertThat(additionalFields.possibleValues()).containsOnly("analysisDate", "leakPeriodDate");
 
     Param facets = def.param("facets");
     assertThat(facets.defaultValue()).isNull();
@@ -996,8 +996,36 @@ public class SearchProjectsActionTest {
 
     SearchProjectsWsResponse result = call(request.setAdditionalFields(singletonList("analysisDate")));
 
-    assertThat(result.getComponentsList()).extracting(Component::getAnalysisDate)
-      .containsOnly(formatDateTime(new Date(20_000_000_000L)), formatDateTime(new Date(30_000_000_000L)), "");
+    assertThat(result.getComponentsList()).extracting(Component::getKey, Component::hasAnalysisDate, Component::getAnalysisDate)
+      .containsOnly(
+        tuple(project1.getKey(), true, formatDateTime(new Date(20_000_000_000L))),
+        tuple(project2.getKey(), true, formatDateTime(new Date(30_000_000_000L))),
+        tuple(project3.getKey(), false, ""));
+  }
+
+  @Test
+  public void return_leak_period_date() {
+    userSession.logIn();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project1 = db.components().insertPublicProject(organization);
+    db.components().insertSnapshot(project1, snapshot -> snapshot.setPeriodDate(10_000_000_000L));
+    authorizationIndexerTester.allowOnlyAnyone(project1);
+    // No leak period
+    ComponentDto project2 = db.components().insertPublicProject(organization);
+    db.components().insertSnapshot(project2, snapshot -> snapshot.setPeriodDate(null));
+    authorizationIndexerTester.allowOnlyAnyone(project2);
+    // No snapshot on project 3
+    ComponentDto project3 = db.components().insertPublicProject(organization);
+    authorizationIndexerTester.allowOnlyAnyone(project3);
+    projectMeasuresIndexer.indexOnStartup(null);
+
+    SearchProjectsWsResponse result = call(request.setAdditionalFields(singletonList("leakPeriodDate")));
+
+    assertThat(result.getComponentsList()).extracting(Component::getKey, Component::hasLeakPeriodDate, Component::getLeakPeriodDate)
+      .containsOnly(
+        tuple(project1.getKey(), true, formatDateTime(new Date(10_000_000_000L))),
+        tuple(project2.getKey(), false, ""),
+        tuple(project3.getKey(), false, ""));
   }
 
   @Test
index 3159ed3ba95a2a573a24ba2db55a659acf45ce3e..58b02d9687c265f9efbb7eb61e62c79444a033e4 100644 (file)
@@ -117,6 +117,7 @@ message Component {
   optional string analysisDate = 13;
   optional Tags tags = 14;
   optional string visibility = 15;
+  optional string leakPeriodDate = 16;
 
   message Tags {
     repeated string tags = 1;