]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5531 Fix sort and search by dates
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 23 Sep 2014 12:44:49 +0000 (14:44 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 23 Sep 2014 12:53:48 +0000 (14:53 +0200)
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueNormalizer.java
server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java

index 152f6440213a4cae2e19f62969d5f1da0b05b4a7..a2f2489fd73d64ae0759839fa99f55128e7737c8 100644 (file)
@@ -30,6 +30,9 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.index.query.*;
 import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.sort.FieldSortBuilder;
+import org.elasticsearch.search.sort.SortBuilders;
+import org.elasticsearch.search.sort.SortOrder;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.IssueQuery;
 import org.sonar.api.web.UserRole;
@@ -39,10 +42,7 @@ import org.sonar.server.search.*;
 import javax.annotation.Nullable;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import static com.google.common.collect.Lists.newArrayList;
 
@@ -131,15 +131,33 @@ public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
       .setTypes(this.getIndexType())
       .setIndices(this.getIndexName());
 
-    // Integrate Pagination
-    esSearch.setFrom(options.getOffset());
-    esSearch.setSize(options.getLimit());
-
     if (options.isScroll()) {
       esSearch.setSearchType(SearchType.SCAN);
       esSearch.setScroll(TimeValue.timeValueMinutes(3));
     }
 
+    setFacets(options, esSearch);
+    setSorting(query, esSearch);
+    setPagination(options, esSearch);
+
+    QueryBuilder esQuery = QueryBuilders.matchAllQuery();
+    BoolFilterBuilder esFilter = getFilter(query, options);
+    if (esFilter.hasClauses()) {
+      esSearch.setQuery(QueryBuilders.filteredQuery(esQuery, esFilter));
+    } else {
+      esSearch.setQuery(esQuery);
+    }
+
+    // Sample Functional aggregation
+    // esSearch.addAggregation(AggregationBuilders.sum("totalDuration")
+    // .field(IssueNormalizer.IssueField.DEBT.field()));
+
+    SearchResponse response = getClient().execute(esSearch);
+    return new Result<Issue>(this, response);
+  }
+
+  /* Build main filter (match based) */
+  protected BoolFilterBuilder getFilter(IssueQuery query, QueryContext options) {
     BoolFilterBuilder esFilter = FilterBuilders.boolFilter();
 
     // Authorization
@@ -158,7 +176,7 @@ public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
         FilterBuilders.boolFilter()
           .must(FilterBuilders.termFilter(IssueAuthorizationNormalizer.IssueAuthorizationField.PERMISSION.field(), UserRole.USER), groupsAndUser)
           .cache(true))
-      ));
+    ));
 
     // Issue is assigned Filter
     if (BooleanUtils.isTrue(query.assigned())) {
@@ -189,27 +207,27 @@ public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
     matchFilter(esFilter, IssueNormalizer.IssueField.STATUS, query.statuses());
 
     // Date filters
-    if (query.createdAfter() != null) {
+    Date createdAfter = query.createdAfter();
+    if (createdAfter != null) {
       esFilter.must(FilterBuilders
         .rangeFilter(IssueNormalizer.IssueField.ISSUE_CREATED_AT.field())
-        .gte(query.createdAfter()));
+        .gte(createdAfter));
     }
-    if (query.createdBefore() != null) {
+    Date createdBefore = query.createdBefore();
+    if (createdBefore != null) {
       esFilter.must(FilterBuilders
         .rangeFilter(IssueNormalizer.IssueField.ISSUE_CREATED_AT.field())
-        .lte(query.createdBefore()));
+        .lte(createdBefore));
     }
-    // TODO match day bracket for day on createdAt
-    // query.createdAt();
-
-    QueryBuilder esQuery = QueryBuilders.matchAllQuery();
-
-    if (esFilter.hasClauses()) {
-      esSearch.setQuery(QueryBuilders.filteredQuery(esQuery, esFilter));
-    } else {
-      esSearch.setQuery(esQuery);
+    Date createdAt = query.createdAt();
+    if (createdAt != null) {
+      esFilter.must(FilterBuilders.termFilter(IssueNormalizer.IssueField.ISSUE_CREATED_AT.field(), createdAt));
     }
 
+    return esFilter;
+  }
+
+  private void setFacets(QueryContext options, SearchRequestBuilder esSearch) {
     if (options.isFacet()) {
       // Execute Term aggregations
       esSearch.addAggregation(AggregationBuilders.terms(IssueNormalizer.IssueField.SEVERITY.field())
@@ -221,13 +239,37 @@ public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
       esSearch.addAggregation(AggregationBuilders.terms(IssueNormalizer.IssueField.ACTION_PLAN.field())
         .field(IssueNormalizer.IssueField.ACTION_PLAN.field()));
     }
+  }
 
-    // Sample Functional aggregation
-    // esSearch.addAggregation(AggregationBuilders.sum("totalDuration")
-    // .field(IssueNormalizer.IssueField.DEBT.field()));
+  private void setSorting(IssueQuery query, SearchRequestBuilder esSearch) {
+    /* integrate Query Sort */
+    String sortField = query.sort();
+    Boolean asc = query.asc();
+    if (sortField != null) {
+      FieldSortBuilder sort = SortBuilders.fieldSort(toIndexField(sortField).sortField());
+      if (asc != null && asc) {
+        sort.order(SortOrder.ASC);
+      } else {
+        sort.order(SortOrder.DESC);
+      }
+      esSearch.addSort(sort);
+    } else {
+      esSearch.addSort(IssueNormalizer.IssueField.ISSUE_UPDATED_AT.sortField(), SortOrder.DESC);
+      // deterministic sort when exactly the same updated_at (same millisecond)
+      esSearch.addSort(IssueNormalizer.IssueField.KEY.sortField(), SortOrder.ASC);
+    }
+  }
 
-    SearchResponse response = getClient().execute(esSearch);
-    return new Result<Issue>(this, response);
+  private IndexField toIndexField(String sort){
+    if (IssueQuery.SORT_BY_ASSIGNEE.equals(sort)) {
+      return IssueNormalizer.IssueField.ASSIGNEE;
+    }
+    throw new IllegalStateException("Unknown sort field : " + sort);
+  }
+
+  protected void setPagination(QueryContext options, SearchRequestBuilder esSearch) {
+    esSearch.setFrom(options.getOffset());
+    esSearch.setSize(options.getLimit());
   }
 
   private void matchFilter(BoolFilterBuilder filter, IndexField field, @Nullable Collection<?> values) {
index ba91903c0e9007c1338f82d33f3b460e5ba1271f..f8c6cb2048abe3496ed520bf3ac3af37b1b17d87 100644 (file)
@@ -44,21 +44,21 @@ public class IssueNormalizer extends BaseNormalizer<IssueDto, String> {
     public static final IndexField UPDATED_AT = add(IndexField.Type.DATE, "updatedAt");
 
     public static final IndexField ACTION_PLAN = add(IndexField.Type.STRING, "actionPlan");
-    public static final IndexField ASSIGNEE = add(IndexField.Type.STRING, "assignee");
+    public static final IndexField ASSIGNEE = addSortable(IndexField.Type.STRING, "assignee");
     public static final IndexField ATTRIBUTE = add(IndexField.Type.OBJECT, "attributes");
     public static final IndexField AUTHOR_LOGIN = add(IndexField.Type.STRING, "authorLogin");
     public static final IndexField COMPONENT = add(IndexField.Type.STRING, "component");
     public static final IndexField DEBT = add(IndexField.Type.NUMERIC, "debt");
     public static final IndexField EFFORT = add(IndexField.Type.NUMERIC, "effort");
-    public static final IndexField ISSUE_CREATED_AT = add(IndexField.Type.DATE, "issueCreatedAt");
-    public static final IndexField ISSUE_UPDATED_AT = add(IndexField.Type.DATE, "issueUpdatedAt");
-    public static final IndexField ISSUE_CLOSE_DATE = add(IndexField.Type.DATE, "issueClosedAt");
+    public static final IndexField ISSUE_CREATED_AT = addSortable(IndexField.Type.DATE, "issueCreatedAt");
+    public static final IndexField ISSUE_UPDATED_AT = addSortable(IndexField.Type.DATE, "issueUpdatedAt");
+    public static final IndexField ISSUE_CLOSE_DATE = addSortable(IndexField.Type.DATE, "issueClosedAt");
     public static final IndexField LINE = add(IndexField.Type.NUMERIC, "line");
     public static final IndexField MESSAGE = add(IndexField.Type.STRING, "message");
     public static final IndexField PROJECT = add(IndexField.Type.STRING, "project");
     public static final IndexField RESOLUTION = add(IndexField.Type.STRING, "resolution");
     public static final IndexField REPORTER = add(IndexField.Type.STRING, "reporter");
-    public static final IndexField STATUS = add(IndexField.Type.STRING, "status");
+    public static final IndexField STATUS = addSortable(IndexField.Type.STRING, "status");
     public static final IndexField SEVERITY = add(IndexField.Type.STRING, "severity");
     public static final IndexField LANGUAGE = add(IndexField.Type.STRING, "language");
     public static final IndexField RULE_KEY = addSearchable(IndexField.Type.STRING, "ruleKey");
index 4ccc0d56aca088858deadfef75966569a6ecd938..e4104142fd035b3a95a0fce67b51e426c5d1070a 100644 (file)
@@ -124,7 +124,7 @@ public abstract class SearchRequestHandler<QUERY, DOMAIN> implements RequestHand
     json.endObject().close();
   }
 
-  private final QueryContext getQueryContext(Request request) {
+  private QueryContext getQueryContext(Request request) {
     return new QueryContext().addFieldsToReturn(request.paramAsStrings(PARAM_FIELDS))
       .setFacet(request.mandatoryParamAsBoolean(PARAM_FACETS))
       .setPage(request.mandatoryParamAsInt(PARAM_PAGE),
index af9779d1a27e8897bc08c142bdb63131019dd759..6b9d6a3ef79d755d1e9a39c598b19b063c9b91f0 100644 (file)
 package org.sonar.server.issue.index;
 
 import com.google.common.collect.ImmutableList;
-import org.junit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.IssueQuery;
 import org.sonar.api.rule.RuleKey;
@@ -211,6 +214,42 @@ public class IssueIndexMediumTest {
     assertThat(result.getHits()).hasSize(2);
   }
 
+  @Test
+  public void filter_created_after() throws Exception {
+    IssueDto issue1 = createIssue().setIssueCreationDate(DateUtils.parseDate("2014-09-20"));
+    IssueDto issue2 = createIssue().setIssueCreationDate(DateUtils.parseDate("2014-09-23"));
+    db.issueDao().insert(session, issue1, issue2);
+    session.commit();
+
+    assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-19")).build(), new QueryContext()).getHits()).hasSize(2);
+    assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-20")).build(), new QueryContext()).getHits()).hasSize(2);
+    assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-21")).build(), new QueryContext()).getHits()).hasSize(1);
+    assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-25")).build(), new QueryContext()).getHits()).isEmpty();
+  }
+
+  @Test
+  public void filter_created_before() throws Exception {
+    IssueDto issue1 = createIssue().setIssueCreationDate(DateUtils.parseDate("2014-09-20"));
+    IssueDto issue2 = createIssue().setIssueCreationDate(DateUtils.parseDate("2014-09-23"));
+    db.issueDao().insert(session, issue1, issue2);
+    session.commit();
+
+    assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-19")).build(), new QueryContext()).getHits()).isEmpty();
+    assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-20")).build(), new QueryContext()).getHits()).hasSize(1);
+    assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-21")).build(), new QueryContext()).getHits()).hasSize(1);
+    assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-25")).build(), new QueryContext()).getHits()).hasSize(2);
+  }
+
+  @Test
+  public void filter_created_at() throws Exception {
+    IssueDto issue = createIssue().setIssueCreationDate(DateUtils.parseDate("2014-09-20"));
+    db.issueDao().insert(session, issue);
+    session.commit();
+
+    assertThat(index.search(IssueQuery.builder().createdAt(DateUtils.parseDate("2014-09-20")).build(), new QueryContext()).getHits()).hasSize(1);
+    assertThat(index.search(IssueQuery.builder().createdAt(DateUtils.parseDate("2014-09-21")).build(), new QueryContext()).getHits()).isEmpty();
+  }
+
   @Test
   public void paging() throws Exception {
     for (int i=0; i<12; i++) {
@@ -260,7 +299,6 @@ public class IssueIndexMediumTest {
   }
 
   @Test
-  @Ignore("TODO")
   public void sort_by_assignee() throws Exception {
     IssueDto issue1 = createIssue().setAssignee("steph");
     IssueDto issue2 = createIssue().setAssignee("simon");
index fc4458e49a5d1a78d69cbb5e5e8679685e1000de..015b1ef79307a8a590554d1b38db0b09f63fec70 100644 (file)
@@ -202,14 +202,26 @@ public class IssueQuery {
     return asc;
   }
 
+  /**
+   * @deprecated since 5.0, use {@link org.sonar.server.search.QueryContext} instead
+   */
+  @Deprecated
   public int pageSize() {
     return pageSize;
   }
 
+  /**
+   * @deprecated since 5.0, use {@link org.sonar.server.search.QueryContext} instead
+   */
+  @Deprecated
   public int pageIndex() {
     return pageIndex;
   }
 
+  /**
+   * @deprecated since 5.0, now useless with the usage of E/S
+   */
+  @Deprecated
   public int maxResults() {
     return MAX_RESULTS;
   }
@@ -377,11 +389,19 @@ public class IssueQuery {
       return this;
     }
 
+    /**
+     * @deprecated since 5.0, use {@link org.sonar.server.search.QueryContext} instead
+     */
+    @Deprecated
     public Builder pageSize(@Nullable Integer i) {
       this.pageSize = i;
       return this;
     }
 
+    /**
+     * @deprecated since 5.0, use {@link org.sonar.server.search.QueryContext} instead
+     */
+    @Deprecated
     public Builder pageIndex(@Nullable Integer i) {
       this.pageIndex = i;
       return this;