diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2014-09-23 14:44:49 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2014-09-23 14:53:48 +0200 |
commit | 3c8726bcaeb9898d995452938d702d9b3543a39e (patch) | |
tree | 0ecf8bdc3ec36240d6a1d0acce5a350be0f2ccad | |
parent | 322d6155f57a92ca914b356c2c7bfa1a66ec007a (diff) | |
download | sonarqube-3c8726bcaeb9898d995452938d702d9b3543a39e.tar.gz sonarqube-3c8726bcaeb9898d995452938d702d9b3543a39e.zip |
SONAR-5531 Fix sort and search by dates
5 files changed, 135 insertions, 35 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java index 152f6440213..a2f2489fd73 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -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) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueNormalizer.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueNormalizer.java index ba91903c0e9..f8c6cb2048a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueNormalizer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueNormalizer.java @@ -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"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java b/server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java index 4ccc0d56aca..e4104142fd0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java @@ -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), diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java index af9779d1a27..6b9d6a3ef79 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java @@ -20,7 +20,10 @@ 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; @@ -212,6 +215,42 @@ public class IssueIndexMediumTest { } @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++) { IssueDto issue = createIssue(); @@ -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"); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java index fc4458e49a5..015b1ef7930 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java @@ -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; |