]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13196 Search issues since leak period returns all issues if no leak period...
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 13 Apr 2020 20:37:35 +0000 (15:37 -0500)
committersonartech <sonartech@sonarsource.com>
Fri, 17 Apr 2020 20:03:43 +0000 (20:03 +0000)
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period.json [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period_has_no_effect_on_prs.json [new file with mode: 0644]

index d9b1ecadc966f52f28cbbc5a1e93e87763d3ae8e..58c00a5fa8da605382af2bb233a22eff675ee12b 100644 (file)
@@ -638,7 +638,7 @@ public class IssueIndex {
   }
 
   private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {
-    Preconditions.checkArgument(createdAfter == null || createdAfter.before(new Date(system.now())),
+    Preconditions.checkArgument(createdAfter == null || createdAfter.compareTo(new Date(system.now())) <= 0,
       "Start bound cannot be in the future");
     Preconditions.checkArgument(createdAfter == null || createdBefore == null || createdAfter.before(createdBefore),
       "Start bound cannot be larger or equal to end bound");
index 7dd6221826f44924f909ed61a57b5ad3fcb47f0a..d6c7f69d8c3f54c117ceab506373825bb541a028 100644 (file)
@@ -95,7 +95,7 @@ public class IssueQueryFactory {
     .map(Enum::name)
     .collect(MoreCollectors.toSet(RuleType.values().length - 1));
   private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setProjectUuid(UNKNOWN);
-
+  private static final Set<String> QUALIFIERS_WITHOUT_LEAK_PERIOD = new HashSet<>(Arrays.asList(Qualifiers.APP, Qualifiers.VIEW, Qualifiers.SUBVIEW));
   private final DbClient dbClient;
   private final Clock clock;
   private final UserSession userSession;
@@ -145,8 +145,6 @@ public class IssueQueryFactory {
   }
 
   private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) {
-    checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
-
     Date actualCreatedAfter = createdAfter;
     if (createdInLast != null) {
       actualCreatedAfter = Date.from(
@@ -171,20 +169,26 @@ public class IssueQueryFactory {
     String createdInLast = request.getCreatedInLast();
 
     if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
+      checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
       setCreatedAfterFromDates(builder, createdAfter, createdInLast, true);
     } else {
       checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
+      checkArgument(createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_IN_LAST, PARAM_SINCE_LEAK_PERIOD));
+
       checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
       ComponentDto component = componentUuids.iterator().next();
-      Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
-      setCreatedAfterFromDates(builder, createdAfterFromSnapshot, createdInLast, false);
+
+      if (!QUALIFIERS_WITHOUT_LEAK_PERIOD.contains(component.qualifier()) && request.getPullRequest() == null) {
+        Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
+        setCreatedAfterFromDates(builder, createdAfterFromSnapshot, null, false);
+      }
     }
   }
 
-  @CheckForNull
   private Date findCreatedAfterFromComponentUuid(DbSession dbSession, ComponentDto component) {
     Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
-    return snapshot.map(s -> longToDate(s.getPeriodDate())).orElse(null);
+    // if last analysis has no period date, then no issue should be considered new.
+    return snapshot.map(s -> longToDate(s.getPeriodDate())).orElseGet(() -> new Date(clock.millis()));
   }
 
   private boolean mergeDeprecatedComponentParameters(DbSession session, SearchRequest request, List<ComponentDto> allComponents) {
@@ -319,7 +323,7 @@ public class IssueQueryFactory {
   }
 
   private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request, Set<String> applicationUuids) {
-    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
+    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod() || request.getPullRequest() != null) {
       return;
     }
 
@@ -331,6 +335,7 @@ public class IssueQueryFactory {
       .stream()
       .filter(s -> s.getPeriodDate() != null)
       .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> new PeriodStart(longToDate(s.getPeriodDate()), false)));
+
     builder.createdAfterByProjectUuids(leakByProjects);
   }
 
index 050eb283ae7fe1e3358a3486688ad5c6c8051a0d..c86fb9385711d89a9551bf1f244fe2c9e5e25e89 100644 (file)
@@ -24,6 +24,7 @@ import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Map;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -43,6 +44,7 @@ import org.sonar.server.tester.UserSessionRule;
 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.api.Assertions.tuple;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -276,6 +278,7 @@ public class IssueQueryFactoryTest {
   @Test
   public void application_search_project_issues_on_leak() {
     Date now = new Date();
+    when(clock.millis()).thenReturn(now.getTime());
     ComponentDto project1 = db.components().insertPublicProject();
     SnapshotDto analysis1 = db.components().insertSnapshot(project1, s -> s.setPeriodDate(addDays(now, -14).getTime()));
     ComponentDto project2 = db.components().insertPublicProject();
@@ -292,8 +295,8 @@ public class IssueQueryFactoryTest {
       .setSinceLeakPeriod(true));
 
     assertThat(result.createdAfterByProjectUuids()).hasSize(1);
-    assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).date().getTime()).isEqualTo(analysis1.getPeriodDate());
-    assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).inclusive()).isFalse();
+    assertThat(result.createdAfterByProjectUuids().entrySet()).extracting(Map.Entry::getKey, e -> e.getValue().date(), e -> e.getValue().inclusive()).containsOnly(
+      tuple(project1.uuid(), new Date(analysis1.getPeriodDate()), false));
     assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
   }
 
@@ -392,14 +395,14 @@ public class IssueQueryFactoryTest {
     assertThat(underTest.create(new SearchRequest()
       .setProjectKeys(singletonList(branch.getKey()))
       .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+      .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
 
     assertThat(underTest.create(new SearchRequest()
       .setComponentKeys(singletonList(branch.getKey()))
       .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+      .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
   }
 
   @Test
@@ -411,22 +414,22 @@ public class IssueQueryFactoryTest {
     assertThat(underTest.create(new SearchRequest()
       .setComponentKeys(singletonList(file.getKey()))
       .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
+      .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
 
     assertThat(underTest.create(new SearchRequest()
       .setComponentKeys(singletonList(branch.getKey()))
       .setFileUuids(singletonList(file.uuid()))
       .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
+      .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
 
     assertThat(underTest.create(new SearchRequest()
       .setProjectKeys(singletonList(branch.getKey()))
       .setFileUuids(singletonList(file.uuid()))
       .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
+      .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
   }
 
   @Test
@@ -439,8 +442,8 @@ public class IssueQueryFactoryTest {
       .setComponentKeys(singletonList(file.getKey()))
       .setBranch(branch.getBranch())
       .setOnComponentOnly(true)))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch)
+      .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
   }
 
   @Test
@@ -451,13 +454,13 @@ public class IssueQueryFactoryTest {
     assertThat(underTest.create(new SearchRequest()
       .setProjectKeys(singletonList(project.getKey()))
       .setBranch("master")))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(project.uuid(), singletonList(project.uuid()), true);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+      .containsOnly(project.uuid(), singletonList(project.uuid()), true);
     assertThat(underTest.create(new SearchRequest()
       .setComponentKeys(singletonList(project.getKey()))
       .setBranch("master")))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(project.uuid(), singletonList(project.uuid()), true);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+      .containsOnly(project.uuid(), singletonList(project.uuid()), true);
   }
 
   @Test
@@ -492,16 +495,16 @@ public class IssueQueryFactoryTest {
     assertThat(underTest.create(new SearchRequest()
       .setComponentKeys(singletonList(applicationBranch1.getKey()))
       .setBranch(applicationBranch1.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(applicationBranch1.uuid(), Collections.emptyList(), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+      .containsOnly(applicationBranch1.uuid(), Collections.emptyList(), false);
 
     // Search on project1Branch1
     assertThat(underTest.create(new SearchRequest()
       .setComponentKeys(singletonList(applicationBranch1.getKey()))
       .setProjectKeys(singletonList(project1.getKey()))
       .setBranch(applicationBranch1.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(applicationBranch1.uuid(), singletonList(project1.uuid()), false);
+      .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+      .containsOnly(applicationBranch1.uuid(), singletonList(project1.uuid()), false);
   }
 
   @Test
index 3676f4abfa46fe53d7b6e7fa0ef68fd3abbf0814..1a18e72dcde6a725a6c939d8a0bb5662d4bfcc4c 100644 (file)
@@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableSet;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -44,6 +45,7 @@ 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.api.utils.System2;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
@@ -101,13 +103,14 @@ public class SearchAction implements HotspotsWsAction {
   private final UserSession userSession;
   private final IssueIndex issueIndex;
   private final HotspotWsResponseFormatter responseFormatter;
+  private System2 system2;
 
-  public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex,
-    HotspotWsResponseFormatter responseFormatter) {
+  public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex, HotspotWsResponseFormatter responseFormatter, System2 system2) {
     this.dbClient = dbClient;
     this.userSession = userSession;
     this.issueIndex = issueIndex;
     this.responseFormatter = responseFormatter;
+    this.system2 = system2;
   }
 
   @Override
@@ -292,10 +295,11 @@ public class SearchAction implements HotspotsWsAction {
         builder.mainBranch(false);
       }
 
-      if (wsRequest.isSinceLeakPeriod()) {
-        dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, p.uuid())
+      if (wsRequest.isSinceLeakPeriod() && !wsRequest.getPullRequest().isPresent()) {
+        Date sinceDate = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, p.uuid())
           .map(s -> longToDate(s.getPeriodDate()))
-          .ifPresent(d -> builder.createdAfter(d, false));
+          .orElseGet(() -> new Date(system2.now()));
+        builder.createdAfter(sinceDate, false);
       }
     });
     if (!hotspotKeys.isEmpty()) {
index 83a3ed8dc031029c85a11af22136b574fb6935d5..36e261558a8257604195dc9c5e0bd62c03066cf9 100644 (file)
@@ -307,7 +307,7 @@ public class SearchAction implements IssuesWsAction {
       .setExampleValue("1m2w (1 month 2 weeks)");
     action.createParam(PARAM_SINCE_LEAK_PERIOD)
       .setDescription("To retrieve issues created since the leak period.<br>" +
-        "If this parameter is set to a truthy value, createdAfter must not be set and one component id or key must be provided.")
+        "If this parameter is set to a truthy value, createdAfter must not be set and one component uuid or key must be provided.")
       .setBooleanPossibleValues()
       .setDefaultValue("false");
   }
index dfa747882ced2361a6973a3932b611a2cf197768..c4f2c6a6cc506fdbbbf8d3d5318361b30b9c1145 100644 (file)
@@ -40,6 +40,7 @@ import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.System2;
@@ -50,7 +51,6 @@ import org.sonar.db.component.BranchType;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.issue.IssueDto;
-import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleTesting;
 import org.sonar.server.es.EsTester;
@@ -104,6 +104,7 @@ public class SearchActionTest {
   @Rule
   public UserSessionRule userSessionRule = UserSessionRule.standalone();
 
+  private TestSystem2 system2 = new TestSystem2();
   private DbClient dbClient = dbTester.getDbClient();
   private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester);
 
@@ -113,7 +114,7 @@ public class SearchActionTest {
   private PermissionIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer);
   private HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter(defaultOrganizationProvider);
 
-  private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, responseFormatter);
+  private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, responseFormatter, system2);
   private WsActionTester actionTester = new WsActionTester(underTest);
 
   @Test
@@ -724,8 +725,8 @@ public class SearchActionTest {
       .setParam("hotspots", IntStream.range(2, 10).mapToObj(String::valueOf).collect(joining(",")))
       .setParam("onlyMine", "true")
       .execute())
-        .isInstanceOf(IllegalArgumentException.class)
-        .hasMessage("Parameter 'onlyMine' can be used with parameter 'projectKey' only");
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Parameter 'onlyMine' can be used with parameter 'projectKey' only");
   }
 
   @Test
@@ -738,8 +739,8 @@ public class SearchActionTest {
       .setParam("projectKey", project.getKey())
       .setParam("onlyMine", "true")
       .execute())
-        .isInstanceOf(IllegalArgumentException.class)
-        .hasMessage("Parameter 'onlyMine' requires user to be logged in");
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Parameter 'onlyMine' requires user to be logged in");
   }
 
   @Test
@@ -1340,7 +1341,7 @@ public class SearchActionTest {
 
     SearchWsResponse responseOnLeak = newRequest(project,
       t -> t.setParam("sinceLeakPeriod", "true"))
-        .executeProtobuf(SearchWsResponse.class);
+      .executeProtobuf(SearchWsResponse.class);
     assertThat(responseOnLeak.getHotspotsList())
       .extracting(SearchWsResponse.Hotspot::getKey)
       .containsExactlyInAnyOrder(Stream.concat(
@@ -1350,6 +1351,69 @@ public class SearchActionTest {
         .toArray(String[]::new));
   }
 
+  @Test
+  public void returns_nothing_when_sinceLeakPeriod_is_true_and_no_period_exists() {
+    long referenceDate = 800_996_999_332L;
+
+    system2.setNow(referenceDate + 10_000);
+    ComponentDto project = dbTester.components().insertPublicProject();
+    userSessionRule.registerComponents(project);
+    indexPermissions();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(referenceDate).setLast(false));
+    dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(null).setLast(true));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto afterRef = dbTester.issues().insertHotspot(rule, project, file, t -> t.setIssueCreationTime(referenceDate + 1000));
+    IssueDto atRef = dbTester.issues().insertHotspot(rule, project, file, t -> t.setType(SECURITY_HOTSPOT).setIssueCreationTime(referenceDate));
+    IssueDto beforeRef = dbTester.issues().insertHotspot(rule, project, file, t -> t.setIssueCreationTime(referenceDate - 1000));
+    indexIssues();
+
+    SearchWsResponse responseAll = newRequest(project)
+      .executeProtobuf(SearchWsResponse.class);
+    assertThat(responseAll.getHotspotsList())
+      .extracting(SearchWsResponse.Hotspot::getKey)
+      .containsExactlyInAnyOrder(Stream.of(afterRef, atRef, beforeRef)
+        .map(IssueDto::getKey)
+        .toArray(String[]::new));
+
+    SearchWsResponse responseOnLeak = newRequest(project,
+      t -> t.setParam("sinceLeakPeriod", "true"))
+      .executeProtobuf(SearchWsResponse.class);
+    assertThat(responseOnLeak.getHotspotsList()).isEmpty();
+  }
+
+  @Test
+  public void returnsall_issues_when_sinceLeakPeriod_is_true_and_is_pr() {
+    long referenceDate = 800_996_999_332L;
+
+    system2.setNow(referenceDate + 10_000);
+    ComponentDto project = dbTester.components().insertPublicProject();
+    ComponentDto pr = dbTester.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST).setKey("pr"));
+    userSessionRule.registerComponents(project);
+    indexPermissions();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(pr));
+    dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(referenceDate).setLast(true));
+    dbTester.components().insertSnapshot(pr, t -> t.setPeriodDate(null).setLast(true));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto afterRef = dbTester.issues().insertHotspot(rule, pr, file, t -> t.setIssueCreationTime(referenceDate + 1000));
+    IssueDto atRef = dbTester.issues().insertHotspot(rule, pr, file, t -> t.setType(SECURITY_HOTSPOT).setIssueCreationTime(referenceDate));
+    IssueDto beforeRef = dbTester.issues().insertHotspot(rule, pr, file, t -> t.setIssueCreationTime(referenceDate - 1000));
+    indexIssues();
+
+    SearchWsResponse responseAll = newRequest(project).setParam("pullRequest", "pr")
+      .executeProtobuf(SearchWsResponse.class);
+    assertThat(responseAll.getHotspotsList())
+      .extracting(SearchWsResponse.Hotspot::getKey)
+      .containsExactlyInAnyOrder(Stream.of(afterRef, atRef, beforeRef)
+        .map(IssueDto::getKey)
+        .toArray(String[]::new));
+
+    SearchWsResponse responseOnLeak = newRequest(project,
+      t -> t.setParam("sinceLeakPeriod", "true").setParam("pullRequest", "pr"))
+      .executeProtobuf(SearchWsResponse.class);
+    assertThat(responseOnLeak.getHotspotsList()).hasSize(3);
+  }
+
   @Test
   public void verify_response_example() {
     ComponentDto project = dbTester.components().insertPublicProject(componentDto -> componentDto
@@ -1375,7 +1439,7 @@ public class SearchActionTest {
         return insertHotspot(rule, project, fileWithHotspot, issueDto -> issueDto.setKee("hotspot-" + i)
           .setAssigneeUuid("assignee-uuid")
           .setAuthorLogin("joe")
-          .setMessage("message-" +i)
+          .setMessage("message-" + i)
           .setLine(10 + i)
           .setIssueCreationTime(time)
           .setIssueUpdateTime(time)
index e61776b8b6a4db292a6916e1d6890306eb98f208..1d6314dcb9354132691b2e8625a3333fc3bbd059 100644 (file)
@@ -168,12 +168,12 @@ public class SearchActionComponentsTest {
     assertThat(ws.newRequest()
       .setParam(PARAM_COMPONENT_KEYS, module1.getKey())
       .executeProtobuf(SearchWsResponse.class).getIssuesList()).extracting(Issue::getKey)
-        .containsExactlyInAnyOrder(issue1.getKey());
+      .containsExactlyInAnyOrder(issue1.getKey());
 
     assertThat(ws.newRequest()
       .setParam(PARAM_MODULE_UUIDS, module1.uuid())
       .executeProtobuf(SearchWsResponse.class).getIssuesList()).extracting(Issue::getKey)
-        .containsExactlyInAnyOrder(issue1.getKey());
+      .containsExactlyInAnyOrder(issue1.getKey());
   }
 
   @Test
@@ -484,11 +484,11 @@ public class SearchActionComponentsTest {
       .setParam(PARAM_COMPONENT_KEYS, applicationBranch1.getKey())
       .setParam(PARAM_BRANCH, applicationBranch1.getBranch())
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-        .extracting(Issue::getKey, Issue::getComponent, Issue::getProject, Issue::getBranch, Issue::hasBranch)
-        .containsExactlyInAnyOrder(
-          tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true),
-          tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true),
-          tuple(issueOnProject2.getKey(), project2.getKey(), project2.getKey(), "", false));
+      .extracting(Issue::getKey, Issue::getComponent, Issue::getProject, Issue::getBranch, Issue::hasBranch)
+      .containsExactlyInAnyOrder(
+        tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true),
+        tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true),
+        tuple(issueOnProject2.getKey(), project2.getKey(), project2.getKey(), "", false));
 
     // Issues on project1Branch1
     assertThat(ws.newRequest()
@@ -496,10 +496,10 @@ public class SearchActionComponentsTest {
       .setParam(PARAM_PROJECTS, project1.getKey())
       .setParam(PARAM_BRANCH, applicationBranch1.getBranch())
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-        .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
-        .containsExactlyInAnyOrder(
-          tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch()),
-          tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getBranch()));
+      .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
+      .containsExactlyInAnyOrder(
+        tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch()),
+        tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getBranch()));
   }
 
   @Test
@@ -670,24 +670,24 @@ public class SearchActionComponentsTest {
       .setParam(PARAM_COMPONENT_KEYS, project.getKey())
       .setParam(PARAM_BRANCH, branch.getBranch())
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-        .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
-        .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch()));
+      .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
+      .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch()));
 
     // On project key + branch
     assertThat(ws.newRequest()
       .setParam(PARAM_PROJECT_KEYS, project.getKey())
       .setParam(PARAM_BRANCH, branch.getBranch())
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-        .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
-        .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch()));
+      .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
+      .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch()));
 
     // On file key + branch
     assertThat(ws.newRequest()
       .setParam(PARAM_COMPONENT_KEYS, branchFile.getKey())
       .setParam(PARAM_BRANCH, branch.getBranch())
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-        .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
-        .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch()));
+      .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch)
+      .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch()));
   }
 
   @Test
index 68ba8cc80370b1e59ac4d4a9354556e35a92664e..ed1c329999ca245a8e19e2ea672cf62b7d5c0836 100644 (file)
@@ -44,8 +44,10 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.SnapshotDto;
 import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.organization.OrganizationDto;
@@ -94,6 +96,7 @@ import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.api.utils.DateUtils.parseDate;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
+import static org.sonar.db.component.ComponentDto.PULL_REQUEST_SEPARATOR;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.issue.IssueTesting.newDto;
 import static org.sonar.server.tester.UserSessionRule.standalone;
@@ -106,7 +109,9 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
 
 public class SearchActionTest {
 
@@ -508,6 +513,126 @@ public class SearchActionTest {
       .assertJson(this.getClass(), "filter_by_assigned_to_me.json");
   }
 
+  @Test
+  public void filter_by_leak_period() {
+    UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com"));
+    UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com"));
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertComponent(ComponentTesting.newPublicProjectDto(organization, "PROJECT_ID").setDbKey("PROJECT_KEY"));
+    SnapshotDto snapshotDto = db.components().insertSnapshot(project, s -> s.setLast(true).setPeriodDate(parseDateTime("2014-09-05T00:00:00+0100").getTime()));
+    indexPermissions();
+
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+    RuleDto rule = newIssueRule();
+    IssueDto issue1 = newDto(rule, file, project)
+      .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100"))
+      .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")
+      .setSeverity("MAJOR")
+      .setAssigneeUuid(john.getUuid());
+    IssueDto issue2 = newDto(rule, file, project)
+      .setIssueCreationDate(parseDateTime("2014-09-06T00:00:00+0100"))
+      .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2")
+      .setSeverity("MAJOR")
+      .setAssigneeUuid(alice.getUuid());
+    dbClient.issueDao().insert(session, issue1, issue2);
+    session.commit();
+    indexIssues();
+
+    userSession.logIn(john);
+
+    ws.newRequest()
+      .setParam(PARAM_SINCE_LEAK_PERIOD, "true")
+      .setParam(PARAM_COMPONENT_KEYS, "PROJECT_KEY")
+      .execute()
+      .assertJson(this.getClass(), "filter_by_leak_period.json");
+  }
+
+  @Test
+  public void filter_by_leak_period_without_a_period() {
+    UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com"));
+    UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com"));
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertComponent(ComponentTesting.newPublicProjectDto(organization, "PROJECT_ID").setDbKey("PROJECT_KEY"));
+    SnapshotDto snapshotDto = db.components().insertSnapshot(project);
+    indexPermissions();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+    RuleDto rule = newIssueRule();
+    IssueDto issue1 = newDto(rule, file, project)
+      .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100"))
+      .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")
+      .setSeverity("MAJOR")
+      .setAssigneeUuid(john.getUuid());
+    IssueDto issue2 = newDto(rule, file, project)
+      .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100"))
+      .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2")
+      .setSeverity("MAJOR")
+      .setAssigneeUuid(alice.getUuid());
+    dbClient.issueDao().insert(session, issue1, issue2);
+    session.commit();
+    indexIssues();
+
+    userSession.logIn(john);
+
+    ws.newRequest()
+      .setParam(PARAM_COMPONENT_KEYS, "PROJECT_KEY")
+      .setParam(PARAM_SINCE_LEAK_PERIOD, "true")
+      .execute()
+      .assertJson(this.getClass(), "empty_result.json");
+  }
+
+  @Test
+  public void filter_by_leak_period_has_no_effect_on_prs() {
+    UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com"));
+    UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com"));
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPublicProject(organization, c -> c.setUuid("PROJECT_ID").setDbKey("PROJECT_KEY"));
+    ComponentDto pr = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST).setKey("pr"));
+    SnapshotDto snapshotDto = db.components().insertSnapshot(pr);
+    indexPermissions();
+    ComponentDto file = db.components().insertComponent(newFileDto(pr, null, "FILE_ID").setDbKey("FILE_KEY" + PULL_REQUEST_SEPARATOR + "pr"));
+    RuleDto rule = newIssueRule();
+    IssueDto issue1 = newDto(rule, file, pr)
+      .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100"))
+      .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")
+      .setSeverity("MAJOR")
+      .setAssigneeUuid(john.getUuid());
+    IssueDto issue2 = newDto(rule, file, pr)
+      .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100"))
+      .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2")
+      .setSeverity("MAJOR")
+      .setAssigneeUuid(alice.getUuid());
+    dbClient.issueDao().insert(session, issue1, issue2);
+    session.commit();
+    indexIssues();
+
+    userSession.logIn(john);
+
+    ws.newRequest()
+      .setParam(PARAM_COMPONENT_KEYS, "PROJECT_KEY")
+      .setParam(PARAM_PULL_REQUEST, "pr")
+      .setParam(PARAM_SINCE_LEAK_PERIOD, "true")
+      .execute()
+      .assertJson(this.getClass(), "filter_by_leak_period_has_no_effect_on_prs.json");
+  }
+
   @Test
   public void return_empty_when_login_is_unknown() {
     UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com"));
@@ -620,7 +745,7 @@ public class SearchActionTest {
     assertThat(ws.newRequest()
       .setMultiParam("author", singletonList("unknown"))
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-        .isEmpty();
+      .isEmpty();
   }
 
   @Test
@@ -652,8 +777,8 @@ public class SearchActionTest {
       // This parameter will be ignored
       .setParam("authors", "leia")
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-        .extracting(Issue::getKey)
-        .containsExactlyInAnyOrder(issue2.getKey());
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issue2.getKey());
   }
 
   @Test
@@ -829,8 +954,8 @@ public class SearchActionTest {
     assertThatThrownBy(() -> ws.newRequest()
       .setParam("types", RuleType.SECURITY_HOTSPOT.toString())
       .execute())
-        .isInstanceOf(IllegalArgumentException.class)
-        .hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]");
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]");
   }
 
   @Test
diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period.json
new file mode 100644 (file)
index 0000000..0b868a7
--- /dev/null
@@ -0,0 +1,51 @@
+{
+  "total": 1,
+  "p": 1,
+  "ps": 100,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 1
+  },
+  "effortTotal": 10,
+  "debtTotal": 10,
+  "issues": [
+    {
+      "key": "7b112bd4-b650-4037-80bc-82fd47d4eac2",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "message": "",
+      "effort": "10min",
+      "debt": "10min",
+      "assignee": "alice",
+      "tags": [],
+      "creationDate": "2014-09-06T00:00:00+0100",
+      "updateDate": "2017-12-04T00:00:00+0100",
+      "type": "CODE_SMELL",
+    }
+  ],
+  "components": [
+    {
+      "key": "FILE_KEY",
+      "uuid": "FILE_ID",
+      "enabled": true,
+      "qualifier": "FIL",
+      "name": "NAME_FILE_ID",
+      "longName": "null/NAME_FILE_ID",
+      "path": "null/NAME_FILE_ID"
+    },
+    {
+      "key": "PROJECT_KEY",
+      "uuid": "PROJECT_ID",
+      "enabled": true,
+      "qualifier": "TRK",
+      "name": "NAME_PROJECT_ID",
+      "longName": "LONG_NAME_PROJECT_ID"
+    }
+  ],
+  "facets": []
+}
diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period_has_no_effect_on_prs.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period_has_no_effect_on_prs.json
new file mode 100644 (file)
index 0000000..75400d0
--- /dev/null
@@ -0,0 +1,69 @@
+{
+  "total": 2,
+  "p": 1,
+  "ps": 100,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2
+  },
+  "effortTotal": 20,
+  "debtTotal": 20,
+  "issues": [
+    {
+      "key": "7b112bd4-b650-4037-80bc-82fd47d4eac2",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "message": "",
+      "effort": "10min",
+      "debt": "10min",
+      "assignee": "alice",
+      "tags": [],
+      "creationDate": "2014-09-04T00:00:00+0100",
+      "updateDate": "2017-12-04T00:00:00+0100",
+      "type": "CODE_SMELL",
+      "pullRequest": "pr"
+    },
+    {
+      "key": "82fd47d4-b650-4037-80bc-7b112bd4eac2",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "message": "",
+      "effort": "10min",
+      "debt": "10min",
+      "assignee": "john",
+      "tags": [],
+      "creationDate": "2014-09-04T00:00:00+0100",
+      "updateDate": "2017-12-04T00:00:00+0100",
+      "type": "CODE_SMELL",
+      "pullRequest": "pr"
+    }
+  ],
+  "components": [
+    {
+      "key": "FILE_KEY",
+      "uuid": "FILE_ID",
+      "enabled": true,
+      "qualifier": "FIL",
+      "name": "NAME_FILE_ID",
+      "longName": "null/NAME_FILE_ID",
+      "path": "null/NAME_FILE_ID",
+      "pullRequest": "pr"
+    },
+    {
+      "key": "PROJECT_KEY",
+      "enabled": true,
+      "qualifier": "TRK",
+      "pullRequest": "pr"
+    }
+  ],
+  "facets": []
+}