]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9551 Allow searching issues by creation dates and projects
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 26 Jul 2017 09:22:50 +0000 (11:22 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 31 Jul 2017 09:27:51 +0000 (11:27 +0200)
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/test/java/org/sonar/server/issue/IssueDocTesting.java
server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java

index be38d598ec93c31d66e93475d69b4cfeabc66e7e..4805a61b58f4f154069958fa253579ef41282572 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -69,6 +70,7 @@ public class IssueQuery {
   private final Collection<String> languages;
   private final Collection<String> tags;
   private final Collection<String> types;
+  private final Map<String, Date> createdAfterByProjectUuids;
   private final Boolean onComponentOnly;
   private final Boolean assigned;
   private final Boolean resolved;
@@ -99,6 +101,7 @@ public class IssueQuery {
     this.languages = defaultCollection(builder.languages);
     this.tags = defaultCollection(builder.tags);
     this.types = defaultCollection(builder.types);
+    this.createdAfterByProjectUuids = defaultMap(builder.createdAfterByProjectUuids);
     this.onComponentOnly = builder.onComponentOnly;
     this.assigned = builder.assigned;
     this.resolved = builder.resolved;
@@ -180,6 +183,10 @@ public class IssueQuery {
     return types;
   }
 
+  public Map<String, Date> createdAfterByProjectUuids() {
+    return createdAfterByProjectUuids;
+  }
+
   @CheckForNull
   public Boolean onComponentOnly() {
     return onComponentOnly;
@@ -260,6 +267,7 @@ public class IssueQuery {
     private Collection<String> languages;
     private Collection<String> tags;
     private Collection<String> types;
+    private Map<String, Date> createdAfterByProjectUuids;
     private Boolean onComponentOnly = false;
     private Boolean assigned = null;
     private Boolean resolved = null;
@@ -361,6 +369,11 @@ public class IssueQuery {
       return this;
     }
 
+    public Builder createdAfterByProjectUuids(@Nullable Map<String, Date> createdAfterByProjectUuids) {
+      this.createdAfterByProjectUuids = createdAfterByProjectUuids;
+      return this;
+    }
+
     /**
      * If true, it will return only issues on the passed component(s)
      * If false, it will return all issues on the passed component(s) and their descendants
@@ -442,4 +455,9 @@ public class IssueQuery {
   private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
     return c == null ? Collections.emptyList() : Collections.unmodifiableCollection(c);
   }
+
+  private static <K,V> Map<K,V> defaultMap(@Nullable Map<K,V> map) {
+    return map == null ? Collections.emptyMap() : Collections.unmodifiableMap(map);
+  }
+
 }
index df819023a296e3f349980a3cec969862438c0f1b..3fb083766d93b99df926299c3364f4c2da637858 100644 (file)
@@ -239,7 +239,7 @@ public class IssueIndex {
   }
 
   private static void addSimpleStickyFacetIfNeeded(SearchOptions options, StickyFacetBuilder stickyFacetBuilder, SearchRequestBuilder esSearch,
-    String facetName, String fieldName, Object... selectedValues) {
+                                                   String facetName, String fieldName, Object... selectedValues) {
     if (options.getFacets().contains(facetName)) {
       esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(fieldName, facetName, DEFAULT_FACET_SIZE, selectedValues));
     }
@@ -397,7 +397,7 @@ public class IssueIndex {
     filters.put(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, createTermFilter(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid()));
 
     addDatesFilter(filters, query);
-
+    addCreatedAfterByProjectsFilter(filters, query);
     return filters;
   }
 
@@ -430,6 +430,15 @@ public class IssueIndex {
     }
   }
 
+  private static void addCreatedAfterByProjectsFilter(Map<String, QueryBuilder> filters, IssueQuery query) {
+    Map<String, Date> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
+    BoolQueryBuilder boolQueryBuilder = boolQuery();
+    createdAfterByProjectUuids.forEach((projectUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
+      .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid))
+      .filter(rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).gte(createdAfterDate))));
+    filters.put("createdAfterByProjectUuids", boolQueryBuilder);
+  }
+
   private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {
     Preconditions.checkArgument(createdAfter == null || createdAfter.before(new Date(system.now())),
       "Start bound cannot be in the future");
index 138e29114fc244f05cb4baea996bc5af74cf2237..2dcc8de454ff8cb28e33c865f41862c1775ae487 100644 (file)
 package org.sonar.server.issue;
 
 import com.google.common.collect.Maps;
-import org.sonar.api.issue.Issue;
+import java.util.Date;
+import org.apache.commons.lang.math.RandomUtils;
 import org.sonar.api.resources.Scopes;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.DateUtils;
+import org.sonar.core.util.Uuids;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.rule.RuleTesting;
 import org.sonar.server.issue.index.IssueDoc;
 
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+
 public class IssueDocTesting {
 
   public static IssueDoc newDoc() {
     IssueDoc doc = new IssueDoc(Maps.<String, Object>newHashMap());
-    doc.setKey("ABC");
+    doc.setKey(Uuids.createFast());
     doc.setRuleKey(RuleTesting.XOO_X1.toString());
     doc.setType(RuleType.CODE_SMELL);
-    doc.setAssignee("steve");
-    doc.setAuthorLogin("roger");
-    doc.setLanguage("xoo");
-    doc.setComponentUuid("FILE_1");
-    doc.setFilePath("src/Foo.xoo");
-    doc.setDirectoryPath("/src");
-    doc.setModuleUuid("MODULE_1");
-    doc.setModuleUuidPath("MODULE_1");
-    doc.setProjectUuid("PROJECT_1");
-    doc.setLine(42);
-    doc.setStatus(Issue.STATUS_OPEN);
+    doc.setAssignee("assignee_" + randomAlphabetic(5));
+    doc.setAuthorLogin("author_" + randomAlphabetic(5));
+    doc.setLanguage("language_" + randomAlphabetic(5));
+    doc.setComponentUuid(Uuids.createFast());
+    doc.setFilePath("filePath_" + randomAlphabetic(5));
+    doc.setDirectoryPath("directory_" + randomAlphabetic(5));
+    doc.setModuleUuid(Uuids.createFast());
+    doc.setModuleUuidPath(Uuids.createFast());
+    doc.setProjectUuid(Uuids.createFast());
+    doc.setLine(nextInt(1_000) + 1);
+    doc.setStatus(STATUS_OPEN);
     doc.setResolution(null);
-    doc.setSeverity(Severity.MAJOR);
-    doc.setEffort(10L);
-    doc.setFuncCreationDate(DateUtils.parseDate("2014-09-04"));
-    doc.setFuncUpdateDate(DateUtils.parseDate("2014-12-04"));
+    doc.setSeverity(Severity.ALL.get(nextInt(Severity.ALL.size())));
+    doc.setEffort((long) RandomUtils.nextInt(10));
+    doc.setFuncCreationDate(new Date(System.currentTimeMillis() - 2_000));
+    doc.setFuncUpdateDate(new Date(System.currentTimeMillis() - 1_000));
     doc.setFuncCloseDate(null);
     return doc;
   }
index cce5c009d9c17cbad38b5242a39800df80bf3965..a822662a26858193742668874d267c070f2836af 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.issue;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import java.util.Date;
 import org.junit.Test;
@@ -28,6 +29,7 @@ import org.sonar.api.rule.Severity;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
 
 public class IssueQueryTest {
 
@@ -38,6 +40,7 @@ public class IssueQueryTest {
       .severities(newArrayList(Severity.BLOCKER))
       .statuses(Lists.newArrayList(Issue.STATUS_RESOLVED))
       .resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE))
+      .projectUuids(newArrayList("PROJECT"))
       .componentUuids(newArrayList("org/struts/Action.java"))
       .moduleUuids(newArrayList("org.struts:core"))
       .rules(newArrayList(RuleKey.of("squid", "AvoidCycle")))
@@ -45,6 +48,7 @@ public class IssueQueryTest {
       .languages(newArrayList("xoo"))
       .tags(newArrayList("tag1", "tag2"))
       .types(newArrayList("RELIABILITY", "SECURITY"))
+      .createdAfterByProjectUuids(ImmutableMap.of("PROJECT", new Date(10_000_000_000L)))
       .assigned(true)
       .createdAfter(new Date())
       .createdBefore(new Date())
@@ -57,12 +61,14 @@ public class IssueQueryTest {
     assertThat(query.severities()).containsOnly(Severity.BLOCKER);
     assertThat(query.statuses()).containsOnly(Issue.STATUS_RESOLVED);
     assertThat(query.resolutions()).containsOnly(Issue.RESOLUTION_FALSE_POSITIVE);
+    assertThat(query.projectUuids()).containsOnly("PROJECT");
     assertThat(query.componentUuids()).containsOnly("org/struts/Action.java");
     assertThat(query.moduleUuids()).containsOnly("org.struts:core");
     assertThat(query.assignees()).containsOnly("gargantua");
     assertThat(query.languages()).containsOnly("xoo");
     assertThat(query.tags()).containsOnly("tag1", "tag2");
     assertThat(query.types()).containsOnly("RELIABILITY", "SECURITY");
+    assertThat(query.createdAfterByProjectUuids()).containsOnly(entry("PROJECT", new Date(10_000_000_000L)));
     assertThat(query.assigned()).isTrue();
     assertThat(query.rules()).containsOnly(RuleKey.of("squid", "AvoidCycle"));
     assertThat(query.createdAfter()).isNotNull();
@@ -103,6 +109,7 @@ public class IssueQueryTest {
   public void collection_params_should_not_be_null_but_empty() {
     IssueQuery query = IssueQuery.builder()
       .issueKeys(null)
+      .projectUuids(null)
       .componentUuids(null)
       .moduleUuids(null)
       .statuses(null)
@@ -113,8 +120,10 @@ public class IssueQueryTest {
       .languages(null)
       .tags(null)
       .types(null)
+      .createdAfterByProjectUuids(null)
       .build();
     assertThat(query.issueKeys()).isEmpty();
+    assertThat(query.projectUuids()).isEmpty();
     assertThat(query.componentUuids()).isEmpty();
     assertThat(query.moduleUuids()).isEmpty();
     assertThat(query.statuses()).isEmpty();
@@ -125,12 +134,14 @@ public class IssueQueryTest {
     assertThat(query.languages()).isEmpty();
     assertThat(query.tags()).isEmpty();
     assertThat(query.types()).isEmpty();
+    assertThat(query.createdAfterByProjectUuids()).isEmpty();
   }
 
   @Test
   public void test_default_query() throws Exception {
     IssueQuery query = IssueQuery.builder().build();
     assertThat(query.issueKeys()).isEmpty();
+    assertThat(query.projectUuids()).isEmpty();
     assertThat(query.componentUuids()).isEmpty();
     assertThat(query.moduleUuids()).isEmpty();
     assertThat(query.statuses()).isEmpty();
@@ -145,7 +156,7 @@ public class IssueQueryTest {
     assertThat(query.createdBefore()).isNull();
     assertThat(query.resolved()).isNull();
     assertThat(query.sort()).isNull();
-
+    assertThat(query.createdAfterByProjectUuids()).isEmpty();
   }
 
   @Test
index de3b0f85311da64026d0cc069bb4145ee940616f..71f737ce3c32c16b76eaf01b7a04a69eebb342b1 100644 (file)
@@ -104,11 +104,11 @@ public class IssueIndexDebtTest {
     ComponentDto file3 = ComponentTesting.newFileDto(project, null, "CDEF");
 
     indexIssues(
-      IssueDocTesting.newDoc("I1", project),
-      IssueDocTesting.newDoc("I2", file1),
-      IssueDocTesting.newDoc("I3", file2),
-      IssueDocTesting.newDoc("I4", file2),
-      IssueDocTesting.newDoc("I5", file3));
+      IssueDocTesting.newDoc("I1", project).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file1).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file2).setEffort(10L),
+      IssueDocTesting.newDoc("I4", file2).setEffort(10L),
+      IssueDocTesting.newDoc("I5", file3).setEffort(10L));
 
     Facets facets = search("fileUuids");
     assertThat(facets.getNames()).containsOnly("fileUuids", FACET_MODE_EFFORT);
@@ -124,8 +124,8 @@ public class IssueIndexDebtTest {
     ComponentDto file2 = ComponentTesting.newFileDto(project, null).setPath("F2.xoo");
 
     indexIssues(
-      IssueDocTesting.newDoc("I1", file1).setDirectoryPath("/src/main/xoo"),
-      IssueDocTesting.newDoc("I2", file2).setDirectoryPath("/"));
+      IssueDocTesting.newDoc("I1", file1).setDirectoryPath("/src/main/xoo").setEffort(10L),
+      IssueDocTesting.newDoc("I2", file2).setDirectoryPath("/").setEffort(10L));
 
     Facets facets = search("directories");
     assertThat(facets.getNames()).containsOnly("directories", FACET_MODE_EFFORT);
@@ -139,9 +139,9 @@ public class IssueIndexDebtTest {
     ComponentDto file = ComponentTesting.newFileDto(project, null);
 
     indexIssues(
-      IssueDocTesting.newDoc("I1", file).setSeverity(Severity.INFO),
-      IssueDocTesting.newDoc("I2", file).setSeverity(Severity.INFO),
-      IssueDocTesting.newDoc("I3", file).setSeverity(Severity.MAJOR));
+      IssueDocTesting.newDoc("I1", file).setSeverity(Severity.INFO).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setSeverity(Severity.INFO).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setSeverity(Severity.MAJOR).setEffort(10L));
 
     Facets facets = search("severities");
     assertThat(facets.getNames()).containsOnly("severities", FACET_MODE_EFFORT);
@@ -155,9 +155,9 @@ public class IssueIndexDebtTest {
     ComponentDto file = ComponentTesting.newFileDto(project, null);
 
     indexIssues(
-      IssueDocTesting.newDoc("I1", file).setStatus(Issue.STATUS_CLOSED),
-      IssueDocTesting.newDoc("I2", file).setStatus(Issue.STATUS_CLOSED),
-      IssueDocTesting.newDoc("I3", file).setStatus(Issue.STATUS_OPEN));
+      IssueDocTesting.newDoc("I1", file).setStatus(Issue.STATUS_CLOSED).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setStatus(Issue.STATUS_CLOSED).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setStatus(Issue.STATUS_OPEN).setEffort(10L));
 
     Facets facets = search("statuses");
     assertThat(facets.getNames()).containsOnly("statuses", FACET_MODE_EFFORT);
@@ -171,9 +171,9 @@ public class IssueIndexDebtTest {
     ComponentDto file = ComponentTesting.newFileDto(project, null);
 
     indexIssues(
-      IssueDocTesting.newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
-      IssueDocTesting.newDoc("I2", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
-      IssueDocTesting.newDoc("I3", file).setResolution(Issue.RESOLUTION_FIXED));
+      IssueDocTesting.newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setResolution(Issue.RESOLUTION_FIXED).setEffort(10L));
 
     Facets facets = search("resolutions");
     assertThat(facets.getNames()).containsOnly("resolutions", FACET_MODE_EFFORT);
@@ -205,10 +205,10 @@ public class IssueIndexDebtTest {
     ComponentDto file = ComponentTesting.newFileDto(project, null);
 
     indexIssues(
-      IssueDocTesting.newDoc("I1", file).setAssignee("steph"),
-      IssueDocTesting.newDoc("I2", file).setAssignee("simon"),
-      IssueDocTesting.newDoc("I3", file).setAssignee("simon"),
-      IssueDocTesting.newDoc("I4", file).setAssignee(null));
+      IssueDocTesting.newDoc("I1", file).setAssignee("steph").setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setAssignee("simon").setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setAssignee("simon").setEffort(10L),
+      IssueDocTesting.newDoc("I4", file).setAssignee(null).setEffort(10L));
 
     Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("assignees"))));
     assertThat(facets.getNames()).containsOnly("assignees", FACET_MODE_EFFORT);
@@ -222,10 +222,10 @@ public class IssueIndexDebtTest {
     ComponentDto file = ComponentTesting.newFileDto(project, null);
 
     indexIssues(
-      IssueDocTesting.newDoc("I1", file).setAuthorLogin("steph"),
-      IssueDocTesting.newDoc("I2", file).setAuthorLogin("simon"),
-      IssueDocTesting.newDoc("I3", file).setAuthorLogin("simon"),
-      IssueDocTesting.newDoc("I4", file).setAuthorLogin(null));
+      IssueDocTesting.newDoc("I1", file).setAuthorLogin("steph").setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setAuthorLogin("simon").setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setAuthorLogin("simon").setEffort(10L),
+      IssueDocTesting.newDoc("I4", file).setAuthorLogin(null).setEffort(10L));
 
     Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("authors"))));
     assertThat(facets.getNames()).containsOnly("authors", FACET_MODE_EFFORT);
@@ -269,13 +269,13 @@ public class IssueIndexDebtTest {
     ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
     ComponentDto file = ComponentTesting.newFileDto(project, null);
 
-    IssueDoc issue0 = IssueDocTesting.newDoc("ISSUE0", file).setFuncCreationDate(DateUtils.parseDateTime("2011-04-25T01:05:13+0100"));
-    IssueDoc issue1 = IssueDocTesting.newDoc("I1", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T12:34:56+0100"));
-    IssueDoc issue2 = IssueDocTesting.newDoc("I2", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T23:46:00+0100"));
-    IssueDoc issue3 = IssueDocTesting.newDoc("I3", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-02T12:34:56+0100"));
-    IssueDoc issue4 = IssueDocTesting.newDoc("I4", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-05T12:34:56+0100"));
-    IssueDoc issue5 = IssueDocTesting.newDoc("I5", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-20T12:34:56+0100"));
-    IssueDoc issue6 = IssueDocTesting.newDoc("I6", file).setFuncCreationDate(DateUtils.parseDateTime("2015-01-18T12:34:56+0100"));
+    IssueDoc issue0 = IssueDocTesting.newDoc("ISSUE0", file).setEffort(10L).setFuncCreationDate(DateUtils.parseDateTime("2011-04-25T01:05:13+0100"));
+    IssueDoc issue1 = IssueDocTesting.newDoc("I1", file).setEffort(10L).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T12:34:56+0100"));
+    IssueDoc issue2 = IssueDocTesting.newDoc("I2", file).setEffort(10L).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T23:46:00+0100"));
+    IssueDoc issue3 = IssueDocTesting.newDoc("I3", file).setEffort(10L).setFuncCreationDate(DateUtils.parseDateTime("2014-09-02T12:34:56+0100"));
+    IssueDoc issue4 = IssueDocTesting.newDoc("I4", file).setEffort(10L).setFuncCreationDate(DateUtils.parseDateTime("2014-09-05T12:34:56+0100"));
+    IssueDoc issue5 = IssueDocTesting.newDoc("I5", file).setEffort(10L).setFuncCreationDate(DateUtils.parseDateTime("2014-09-20T12:34:56+0100"));
+    IssueDoc issue6 = IssueDocTesting.newDoc("I6", file).setEffort(10L).setFuncCreationDate(DateUtils.parseDateTime("2015-01-18T12:34:56+0100"));
 
     indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
 
index 5479f94e56a9257366217c6702c9353edf6a7f96..3e47b6a916fbb540e40d6df56a5d609e4509c401 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.issue.index;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -66,6 +67,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.sonar.api.utils.DateUtils.addDays;
 import static org.sonar.api.utils.DateUtils.parseDate;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
@@ -296,6 +298,46 @@ public class IssueIndexTest {
     assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(asList("unknown")));
   }
 
+  @Test
+  public void filter_by_created_after_by_projects() {
+    Date now = new Date();
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(organizationDto);
+    IssueDoc project1Issue1 = newDoc().setProjectUuid(project1.uuid()).setFuncCreationDate(addDays(now, -10));
+    IssueDoc project1Issue2 = newDoc().setProjectUuid(project1.uuid()).setFuncCreationDate(addDays(now, -20));
+    ComponentDto project2 = newPrivateProjectDto(organizationDto);
+    IssueDoc project2Issue1 = newDoc().setProjectUuid(project2.uuid()).setFuncCreationDate(addDays(now, -15));
+    IssueDoc project2Issue2 = newDoc().setProjectUuid(project2.uuid()).setFuncCreationDate(addDays(now, -30));
+    indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2);
+
+    // Search for issues of project 1 having less than 15 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+        .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), addDays(now, -15))),
+      project1Issue1.key());
+
+    // Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(
+        project1.uuid(), addDays(now, -14),
+        project2.uuid(), addDays(now, -25)
+      )),
+      project1Issue1.key(), project2Issue1.key());
+
+    // Search for issues of project 1 having less than 30 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+        .createdAfterByProjectUuids(ImmutableMap.of(
+          project1.uuid(), addDays(now, -30)
+        )),
+      project1Issue1.key(), project1Issue2.key());
+
+    // Search for issues of project 1 and project 2 having less than 5 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+        .createdAfterByProjectUuids(ImmutableMap.of(
+          project1.uuid(), addDays(now, -5),
+          project2.uuid(), addDays(now, -5)
+        )));
+  }
+
   @Test
   public void filter_by_severities() {
     ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
@@ -668,8 +710,8 @@ public class IssueIndexTest {
     SearchOptions SearchOptions = fixtureForCreatedAtFacet();
 
     SearchResponse result = underTest.search(IssueQuery.builder()
-        .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
-        .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
+      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
       SearchOptions);
     Map<String, Long> createdAt = new Facets(result).get("createdAt");
     assertThat(createdAt).containsOnly(
@@ -684,8 +726,8 @@ public class IssueIndexTest {
     SearchOptions SearchOptions = fixtureForCreatedAtFacet();
 
     SearchResponse result = underTest.search(IssueQuery.builder()
-        .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
-        .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
+      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
       SearchOptions);
     Map<String, Long> createdAt = new Facets(result).get("createdAt");
     assertThat(createdAt).containsOnly(
@@ -702,8 +744,8 @@ public class IssueIndexTest {
     SearchOptions SearchOptions = fixtureForCreatedAtFacet();
 
     SearchResponse result = underTest.search(IssueQuery.builder()
-        .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
-        .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
+      .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
       SearchOptions);
     Map<String, Long> createdAt = new Facets(result).get("createdAt");
     assertThat(createdAt).containsOnly(
@@ -721,8 +763,8 @@ public class IssueIndexTest {
     SearchOptions SearchOptions = fixtureForCreatedAtFacet();
 
     SearchResponse result = underTest.search(IssueQuery.builder()
-        .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
-        .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
+      .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
+      .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
       SearchOptions);
     Map<String, Long> createdAt = new Facets(result).get("createdAt");
     assertThat(createdAt).containsOnly(
@@ -754,7 +796,7 @@ public class IssueIndexTest {
     SearchOptions searchOptions = fixtureForCreatedAtFacet();
 
     SearchResponse result = underTest.search(IssueQuery.builder()
-        .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
+      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
       searchOptions);
     Map<String, Long> createdAt = new Facets(result).get("createdAt");
     assertThat(createdAt).containsOnly(
@@ -823,7 +865,7 @@ public class IssueIndexTest {
       String key = "I" + i;
       issues.add(newDoc(key, file));
     }
-    indexIssues(issues.toArray(new IssueDoc[]{}));
+    indexIssues(issues.toArray(new IssueDoc[] {}));
 
     IssueQuery.Builder query = IssueQuery.builder();
     SearchResponse result = underTest.search(query.build(), new SearchOptions().setLimit(Integer.MAX_VALUE));