From 0ea7c0cedcadefe53e5ea7aab1e66592c0cb9279 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 5 Feb 2015 13:37:25 +0100 Subject: [PATCH] Refactor issue search stack in order to remove dependency with SearchRequestHandler --- .../benchmark/IssueIndexBenchmarkTest.java | 6 +- .../computation/dbcleaner/ProjectCleaner.java | 15 +- .../java/org/sonar/server/db/DbClient.java | 10 +- .../java/org/sonar/server/es/BaseIndex.java | 35 + .../java/org/sonar/server/es/EsUtils.java | 65 ++ .../main/java/org/sonar/server/es/Facets.java | 153 ++++ .../org/sonar/server/es/SearchOptions.java | 179 +++++ .../org/sonar/server/es/SearchResult.java | 58 ++ .../issue/AbstractChangeTagsAction.java | 6 +- .../java/org/sonar/server/issue/Action.java | 3 +- .../org/sonar/server/issue/AssignAction.java | 4 +- .../org/sonar/server/issue/CommentAction.java | 7 +- .../issue/InternalRubyIssueService.java | 13 +- .../server/issue/IssueBulkChangeService.java | 55 +- .../org/sonar/server/issue/IssueQuery.java | 42 +- .../org/sonar/server/issue/IssueService.java | 65 +- .../org/sonar/server/issue/PlanAction.java | 9 +- .../sonar/server/issue/SetSeverityAction.java | 6 +- .../sonar/server/issue/TransitionAction.java | 4 +- .../org/sonar/server/issue/db/IssueDao.java | 4 +- .../issue/filter/IssueFilterService.java | 30 +- .../sonar/server/issue/index/IssueIndex.java | 612 ++++++++-------- .../sonar/server/issue/ws/AuthorsAction.java | 19 +- .../BaseIssuesWsAction.java} | 15 +- .../server/issue/ws/ComponentTagsAction.java | 6 +- .../server/issue/ws/IssueShowAction.java | 7 +- .../org/sonar/server/issue/ws/IssuesWs.java | 36 +- .../sonar/server/issue/ws/SearchAction.java | 248 ++++--- .../sonar/server/issue/ws/SetTagsAction.java | 21 +- .../org/sonar/server/issue/ws/TagsAction.java | 9 +- .../server/search/StickyFacetBuilder.java | 6 +- .../server/source/index/SourceLineIndex.java | 12 +- .../sonar/server/issue/ws/example-search.json | 11 +- .../dbcleaner/ProjectCleanerTest.java | 41 +- .../java/org/sonar/server/es/EsUtilsTest.java | 69 ++ .../sonar/server/es/SearchOptionsTest.java | 156 ++++ .../org/sonar/server/issue/ActionTest.java | 9 +- .../issue/InternalRubyIssueServiceTest.java | 40 +- .../issue/IssueBulkChangeServiceTest.java | 682 +++++++++--------- .../server/issue/IssueServiceMediumTest.java | 81 ++- .../issue/filter/IssueFilterServiceTest.java | 17 +- .../issue/index/IssueIndexMediumTest.java | 608 ++++++++-------- .../server/issue/ws/AuthorsActionTest.java | 5 +- .../issue/ws/ComponentTagsActionTest.java | 5 +- .../server/issue/ws/IssueShowActionTest.java | 14 +- .../server/issue/ws/IssueTagsActionTest.java | 5 +- .../sonar/server/issue/ws/IssuesWsTest.java | 236 +----- .../ws/SearchActionComponentsMediumTest.java | 13 +- .../issue/ws/SearchActionMediumTest.java | 14 +- .../server/issue/ws/SetTagsActionTest.java | 22 +- .../view/index/ViewIndexerMediumTest.java | 10 +- .../default_page_size_is_100.json | 1 - .../deprecated_paging.json | 1 - .../SearchActionMediumTest/empty_result.json | 1 - .../ws/SearchActionMediumTest/issue.json | 1 - .../sonar/core/issue/db/IssueChangeDao.java | 3 +- .../org/sonar/core/issue/db/IssueMapper.java | 3 +- .../org/sonar/api/server/ws/WebService.java | 72 +- .../sonar/api/server/ws/WebServiceTest.java | 22 +- 59 files changed, 2247 insertions(+), 1655 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/es/BaseIndex.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/es/Facets.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/es/SearchOptions.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/es/SearchResult.java rename server/sonar-server/src/main/java/org/sonar/server/issue/{index/FakeIssueDto.java => ws/BaseIssuesWsAction.java} (79%) create mode 100644 server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/es/SearchOptionsTest.java diff --git a/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/IssueIndexBenchmarkTest.java b/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/IssueIndexBenchmarkTest.java index 64950c865d7..4d2a9de9f5b 100644 --- a/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/IssueIndexBenchmarkTest.java +++ b/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/IssueIndexBenchmarkTest.java @@ -34,14 +34,14 @@ import org.sonar.api.issue.Issue; import org.sonar.api.rule.Severity; import org.sonar.api.utils.internal.Uuids; import org.sonar.server.es.EsClient; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.issue.IssueQuery; import org.sonar.server.issue.index.IssueAuthorizationDao; import org.sonar.server.issue.index.IssueAuthorizationIndexer; import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.issue.index.IssueIndexer; -import org.sonar.server.search.QueryContext; -import org.sonar.server.search.Result; import org.sonar.server.tester.ServerTester; import org.sonar.server.user.MockUserSession; @@ -138,7 +138,7 @@ public class IssueIndexBenchmarkTest { IssueIndex index = tester.get(IssueIndex.class); for (int i = 0; i < 10; i++) { long start = System.currentTimeMillis(); - Result result = index.search(query, new QueryContext()); + SearchResult result = index.search(query, new SearchOptions()); long end = System.currentTimeMillis(); LOGGER.info("Request (" + label + "): {} docs in {} ms", result.getTotal(), end - start); } diff --git a/server/sonar-server/src/main/java/org/sonar/core/computation/dbcleaner/ProjectCleaner.java b/server/sonar-server/src/main/java/org/sonar/core/computation/dbcleaner/ProjectCleaner.java index 021f88ebdaf..dd9a2d955ec 100644 --- a/server/sonar-server/src/main/java/org/sonar/core/computation/dbcleaner/ProjectCleaner.java +++ b/server/sonar-server/src/main/java/org/sonar/core/computation/dbcleaner/ProjectCleaner.java @@ -28,10 +28,13 @@ import org.sonar.api.config.Settings; import org.sonar.api.utils.TimeUtils; import org.sonar.core.computation.dbcleaner.period.DefaultPeriodCleaner; import org.sonar.core.persistence.DbSession; -import org.sonar.core.purge.*; +import org.sonar.core.purge.IdUuidPair; +import org.sonar.core.purge.PurgeConfiguration; +import org.sonar.core.purge.PurgeDao; +import org.sonar.core.purge.PurgeListener; +import org.sonar.core.purge.PurgeProfiler; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.properties.ProjectSettingsFactory; -import org.sonar.server.search.IndexClient; import javax.annotation.Nullable; import java.util.Date; @@ -46,16 +49,16 @@ public class ProjectCleaner implements ServerComponent { private final PurgeDao purgeDao; private final DefaultPeriodCleaner periodCleaner; private final ProjectSettingsFactory projectSettingsFactory; - private final IndexClient indexClient; + private final IssueIndex issueIndex; public ProjectCleaner(PurgeDao purgeDao, DefaultPeriodCleaner periodCleaner, PurgeProfiler profiler, PurgeListener purgeListener, - ProjectSettingsFactory projectSettingsFactory, IndexClient indexClient) { + ProjectSettingsFactory projectSettingsFactory, IssueIndex issueIndex) { this.purgeDao = purgeDao; this.periodCleaner = periodCleaner; this.profiler = profiler; this.purgeListener = purgeListener; this.projectSettingsFactory = projectSettingsFactory; - this.indexClient = indexClient; + this.issueIndex = issueIndex; } public ProjectCleaner purge(DbSession session, IdUuidPair idUuidPair) { @@ -77,7 +80,7 @@ public class ProjectCleaner implements ServerComponent { private void deleteIndexedIssuesBefore(String uuid, @Nullable Date lastDateWithClosedIssues) { if (lastDateWithClosedIssues != null) { - indexClient.get(IssueIndex.class).deleteClosedIssuesOfProjectBefore(uuid, lastDateWithClosedIssues); + issueIndex.deleteClosedIssuesOfProjectBefore(uuid, lastDateWithClosedIssues); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java index 22cbf6fe36e..ea6ac8f2980 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java @@ -19,10 +19,9 @@ */ package org.sonar.server.db; -import org.sonar.core.user.AuthorDao; - import org.sonar.api.ServerComponent; import org.sonar.core.issue.db.ActionPlanDao; +import org.sonar.core.issue.db.IssueChangeDao; import org.sonar.core.persistence.DaoComponent; import org.sonar.core.persistence.Database; import org.sonar.core.persistence.DbSession; @@ -33,6 +32,7 @@ import org.sonar.core.resource.ResourceDao; import org.sonar.core.source.db.FileSourceDao; import org.sonar.core.technicaldebt.db.CharacteristicDao; import org.sonar.core.template.LoadedTemplateDao; +import org.sonar.core.user.AuthorDao; import org.sonar.core.user.AuthorizationDao; import org.sonar.server.activity.db.ActivityDao; import org.sonar.server.component.db.ComponentDao; @@ -79,6 +79,7 @@ public class DbClient implements ServerComponent { private final UserDao userDao; private final GroupDao groupDao; private final IssueDao issueDao; + private final IssueChangeDao issueChangeDao; private final ActionPlanDao actionPlanDao; private final AnalysisReportDao analysisReportDao; private final DashboardDao dashboardDao; @@ -111,6 +112,7 @@ public class DbClient implements ServerComponent { userDao = getDao(map, UserDao.class); groupDao = getDao(map, GroupDao.class); issueDao = getDao(map, IssueDao.class); + issueChangeDao = getDao(map, IssueChangeDao.class); actionPlanDao = getDao(map, ActionPlanDao.class); analysisReportDao = getDao(map, AnalysisReportDao.class); dashboardDao = getDao(map, DashboardDao.class); @@ -140,6 +142,10 @@ public class DbClient implements ServerComponent { return issueDao; } + public IssueChangeDao issueChangeDao() { + return issueChangeDao; + } + public QualityProfileDao qualityProfileDao() { return qualityProfileDao; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/BaseIndex.java b/server/sonar-server/src/main/java/org/sonar/server/es/BaseIndex.java new file mode 100644 index 00000000000..45d253555b3 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/es/BaseIndex.java @@ -0,0 +1,35 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.es; + +import org.sonar.api.ServerComponent; + +public abstract class BaseIndex implements ServerComponent { + private final EsClient client; + + public BaseIndex(EsClient client) { + this.client = client; + } + + public EsClient getClient() { + return client; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java b/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java new file mode 100644 index 00000000000..837754cf20a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java @@ -0,0 +1,65 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.es; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchHits; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; +import org.sonar.server.search.BaseDoc; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class EsUtils { + + private EsUtils() { + // only static methods + } + + public static List convertToDocs(SearchHits hits, Function, D> converter) { + List docs = new ArrayList<>(); + for (SearchHit hit : hits.getHits()) { + docs.add(converter.apply(hit.getSource())); + } + return docs; + } + + public static LinkedHashMap termsToMap(Terms terms) { + LinkedHashMap map = new LinkedHashMap<>(); + List buckets = terms.getBuckets(); + for (Terms.Bucket bucket : buckets) { + map.put(bucket.getKey(), bucket.getDocCount()); + } + return map; + } + + public static List termsKeys(Terms terms) { + return Lists.transform(terms.getBuckets(), new Function() { + @Override + public String apply(Terms.Bucket bucket) { + return bucket.getKey(); + } + }); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/Facets.java b/server/sonar-server/src/main/java/org/sonar/server/es/Facets.java new file mode 100644 index 00000000000..a22131e18f3 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/es/Facets.java @@ -0,0 +1,153 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.es; + +import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.HasAggregations; +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram; +import org.elasticsearch.search.aggregations.bucket.missing.Missing; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; + +import javax.annotation.CheckForNull; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +public class Facets { + + private final Map> facetsByName = new LinkedHashMap<>(); + + public Facets(SearchResponse response) { + if (response.getAggregations() != null) { + for (Aggregation facet : response.getAggregations()) { + processAggregation(facet); + } + } + } + + public Facets(Terms terms) { + processTermsAggregation(terms); + } + + private void processAggregation(Aggregation aggregation) { + if (Missing.class.isAssignableFrom(aggregation.getClass())) { + processMissingAggregation((Missing) aggregation); + } else if (Terms.class.isAssignableFrom(aggregation.getClass())) { + processTermsAggregation((Terms) aggregation); + } else if (HasAggregations.class.isAssignableFrom(aggregation.getClass())) { + processSubAggregations((HasAggregations) aggregation); + } else if (DateHistogram.class.isAssignableFrom(aggregation.getClass())) { + processDateHistogram((DateHistogram) aggregation); + } else { + throw new IllegalArgumentException("Aggregation type not supported yet: " + aggregation.getClass()); + } + } + + private void processMissingAggregation(Missing aggregation) { + long docCount = aggregation.getDocCount(); + if (docCount > 0L) { + getOrCreateFacet(aggregation.getName().replace("_missing", "")).put("", docCount); + } + } + + private void processTermsAggregation(Terms aggregation) { + String facetName = aggregation.getName(); + // TODO document this naming convention + if (facetName.contains("__") && !facetName.startsWith("__")) { + facetName = facetName.substring(0, facetName.indexOf("__")); + } + facetName = facetName.replace("_selected", ""); + LinkedHashMap facet = getOrCreateFacet(facetName); + for (Terms.Bucket value : aggregation.getBuckets()) { + facet.put(value.getKey(), value.getDocCount()); + } + } + + private void processSubAggregations(HasAggregations aggregation) { + for (Aggregation sub : aggregation.getAggregations()) { + processAggregation(sub); + } + } + + private void processDateHistogram(DateHistogram aggregation) { + LinkedHashMap facet = getOrCreateFacet(aggregation.getName()); + for (DateHistogram.Bucket value : aggregation.getBuckets()) { + facet.put(value.getKeyAsText().toString(), value.getDocCount()); + } + } + + public boolean contains(String facetName) { + return facetsByName.containsKey(facetName); + } + + /** + * The buckets of the given facet. Null if the facet does not exist + */ + @CheckForNull + public LinkedHashMap get(String facetName) { + return facetsByName.get(facetName); + } + + public Map> getAll() { + return facetsByName; + } + + /** + * Value of the facet bucket. Null if the facet or the bucket do not exist. + */ + @CheckForNull + public Long getBucketValue(String facetName, String bucketKey) { + LinkedHashMap facet = facetsByName.get(facetName); + if (facet != null) { + return facet.get(bucketKey); + } + return null; + } + + public Set getBucketKeys(String facetName) { + LinkedHashMap facet = facetsByName.get(facetName); + if (facet != null) { + return facet.keySet(); + } + return Collections.emptySet(); + } + + public Set getNames() { + return facetsByName.keySet(); + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE); + } + + private LinkedHashMap getOrCreateFacet(String facetName) { + LinkedHashMap facet = facetsByName.get(facetName); + if (facet == null) { + facet = new LinkedHashMap<>(); + facetsByName.put(facetName, facet); + } + return facet; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/SearchOptions.java b/server/sonar-server/src/main/java/org/sonar/server/es/SearchOptions.java new file mode 100644 index 00000000000..57cf4904cef --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/es/SearchOptions.java @@ -0,0 +1,179 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.es; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.text.JsonWriter; + +import javax.annotation.Nullable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Various Elasticsearch request options: paging, fields and facets + */ +public class SearchOptions { + + public static final int DEFAULT_OFFSET = 0; + public static final int DEFAULT_LIMIT = 10; + public static final int MAX_LIMIT = 500; + + private int offset = DEFAULT_OFFSET; + private int limit = DEFAULT_LIMIT; + private final Set facets = new LinkedHashSet<>(); + private final Set fieldsToReturn = new HashSet<>(); + + /** + * Offset of the first result to return. Defaults to {@link #DEFAULT_OFFSET} + */ + public int getOffset() { + return offset; + } + + /** + * Sets the offset of the first result to return (zero-based). + */ + public SearchOptions setOffset(int offset) { + Preconditions.checkArgument(offset >= 0, "Offset must be positive"); + this.offset = offset; + return this; + } + + /** + * Set offset and limit according to page approach. If pageSize is negative, then + * {@link #MAX_LIMIT} is used. + */ + public SearchOptions setPage(int page, int pageSize) { + Preconditions.checkArgument(page >= 1, "Page must be greater or equal to 1 (got " + page + ")"); + setLimit(pageSize); + setOffset((page * this.limit) - this.limit); + return this; + } + + public int getPage() { + return limit > 0 ? (int) Math.ceil((double) (offset + 1) / (double) limit) : 0; + } + + /** + * Limit on the number of results to return. Defaults to {@link #DEFAULT_LIMIT}. + */ + public int getLimit() { + return limit; + } + + /** + * Sets the limit on the number of results to return. + */ + public SearchOptions setLimit(int limit) { + if (limit < 0) { + this.limit = MAX_LIMIT; + } else { + this.limit = Math.min(limit, MAX_LIMIT); + } + return this; + } + + /** + * WARNING - dangerous + */ + @Deprecated + public SearchOptions disableLimit() { + this.limit = 999999; + return this; + } + + /** + * Lists selected facets. + */ + public Collection getFacets() { + return facets; + } + + /** + * Selects facets to return for the domain. + */ + public SearchOptions addFacets(@Nullable Collection f) { + if (f != null) { + this.facets.addAll(f); + } + return this; + } + + public SearchOptions addFacets(String... array) { + Collections.addAll(facets, array); + return this; + } + + public Set getFields() { + return fieldsToReturn; + } + + public boolean hasField(String key) { + return fieldsToReturn.isEmpty() || fieldsToReturn.contains(key); + } + + public SearchOptions addFields(@Nullable Collection c) { + if (c != null) { + for (String s : c) { + if (StringUtils.isNotBlank(s)) { + fieldsToReturn.add(s); + } + } + } + return this; + } + + public SearchOptions addFields(String... array) { + return addFields(Arrays.asList(array)); + } + + public SearchOptions writeJson(JsonWriter json, long totalHits) { + json.prop("total", totalHits); + json.prop(WebService.Param.PAGE, getPage()); + json.prop(WebService.Param.PAGE_SIZE, getLimit()); + return this; + } + + @Deprecated + public SearchOptions writeDeprecatedJson(JsonWriter json, long totalHits) { + int pages = 0; + if (limit > 0) { + pages = (int) (totalHits / limit); + if (totalHits % limit > 0) { + pages++; + } + } + json.name("paging").beginObject() + .prop("pageIndex", getPage()) + .prop("pageSize", getLimit()) + .prop("total", totalHits) + .prop("fTotal", String.valueOf(totalHits)) + .prop("pages", pages) + .endObject(); + return this; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/SearchResult.java b/server/sonar-server/src/main/java/org/sonar/server/es/SearchResult.java new file mode 100644 index 00000000000..731f48730cc --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/es/SearchResult.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.es; + +import com.google.common.base.Function; +import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.elasticsearch.action.search.SearchResponse; +import org.sonar.server.search.BaseDoc; + +import java.util.List; +import java.util.Map; + +public class SearchResult { + + private final List docs; + private final Facets facets; + private final long total; + + public SearchResult(SearchResponse response, Function, DOC> converter) { + this.facets = new Facets(response); + this.total = response.getHits().totalHits(); + this.docs = EsUtils.convertToDocs(response.getHits(), converter); + } + + public List getDocs() { + return docs; + } + + public long getTotal() { + return total; + } + + public Facets getFacets() { + return this.facets; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java index d5a0c629341..7aa68b28e11 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java @@ -32,11 +32,9 @@ import org.sonar.core.issue.IssueUpdater; import org.sonar.server.user.UserSession; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Set; - public abstract class AbstractChangeTagsAction extends Action implements ServerComponent { private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); @@ -50,7 +48,7 @@ public abstract class AbstractChangeTagsAction extends Action implements ServerC } @Override - public boolean verify(Map properties, List issues, UserSession userSession){ + public boolean verify(Map properties, Collection issues, UserSession userSession) { parseTags(properties); return true; } @@ -67,7 +65,7 @@ public abstract class AbstractChangeTagsAction extends Action implements ServerC Set result = Sets.newHashSet(); String tagsString = (String) properties.get("tags"); if (!Strings.isNullOrEmpty(tagsString)) { - for(String tag: TAGS_SPLITTER.split(tagsString)) { + for (String tag : TAGS_SPLITTER.split(tagsString)) { RuleTagFormat.validate(tag); result.add(tag); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java b/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java index e7224a79129..715e725df6e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java @@ -29,6 +29,7 @@ import org.sonar.api.issue.condition.Condition; import org.sonar.api.issue.internal.IssueChangeContext; import org.sonar.server.user.UserSession; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -70,7 +71,7 @@ public abstract class Action implements ServerComponent { return true; } - abstract boolean verify(Map properties, List issues, UserSession userSession); + abstract boolean verify(Map properties, Collection issues, UserSession userSession); abstract boolean execute(Map properties, Context context); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java index d6ead7dd524..8beab29d430 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java @@ -30,7 +30,7 @@ import org.sonar.api.user.UserFinder; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.user.UserSession; -import java.util.List; +import java.util.Collection; import java.util.Map; @@ -50,7 +50,7 @@ public class AssignAction extends Action implements ServerComponent { } @Override - public boolean verify(Map properties, List issues, UserSession userSession){ + public boolean verify(Map properties, Collection issues, UserSession userSession){ String assignee = assigneeValue(properties); if(!Strings.isNullOrEmpty(assignee)) { User user = selectUser(assignee); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java index e50eba29d08..d8700f55600 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java @@ -27,10 +27,9 @@ import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.user.UserSession; -import java.util.List; +import java.util.Collection; import java.util.Map; - public class CommentAction extends Action implements ServerComponent { public static final String KEY = "comment"; @@ -44,7 +43,7 @@ public class CommentAction extends Action implements ServerComponent { } @Override - public boolean verify(Map properties, List issues, UserSession userSession) { + public boolean verify(Map properties, Collection issues, UserSession userSession) { comment(properties); return true; } @@ -58,7 +57,7 @@ public class CommentAction extends Action implements ServerComponent { private String comment(Map properties) { String param = (String) properties.get(COMMENT_PROPERTY); if (Strings.isNullOrEmpty(param)) { - throw new IllegalArgumentException("Missing parameter : '"+ COMMENT_PROPERTY +"'"); + throw new IllegalArgumentException("Missing parameter : '" + COMMENT_PROPERTY + "'"); } return param; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index e60b31e6323..d20fc825c44 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -43,6 +43,7 @@ import org.sonar.core.issue.workflow.Transition; import org.sonar.core.resource.ResourceDao; import org.sonar.core.resource.ResourceDto; import org.sonar.core.resource.ResourceQuery; +import org.sonar.server.es.SearchOptions; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.issue.filter.IssueFilterParameters; @@ -477,7 +478,7 @@ public class InternalRubyIssueService implements ServerComponent { * Execute issue filter from parameters */ public IssueFilterService.IssueFilterResult execute(Map props) { - return issueFilterService.execute(issueQueryService.createFromMap(props), toContext(props)); + return issueFilterService.execute(issueQueryService.createFromMap(props), toSearchOptions(props)); } /** @@ -636,16 +637,16 @@ public class InternalRubyIssueService implements ServerComponent { } @VisibleForTesting - static QueryContext toContext(Map props) { - QueryContext context = new QueryContext(); + static SearchOptions toSearchOptions(Map props) { + SearchOptions options = new SearchOptions(); Integer pageIndex = RubyUtils.toInteger(props.get(IssueFilterParameters.PAGE_INDEX)); Integer pageSize = RubyUtils.toInteger(props.get(IssueFilterParameters.PAGE_SIZE)); if (pageSize != null && pageSize < 0) { - context.setMaxLimit(); + options.setLimit(SearchOptions.MAX_LIMIT); } else { - context.setPage(pageIndex != null ? pageIndex : 1, pageSize != null ? pageSize : 100); + options.setPage(pageIndex != null ? pageIndex : 1, pageSize != null ? pageSize : 100); } - return context; + return options; } public Collection listTags() { diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java index f235a36e71d..d29e4a4eabe 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java @@ -22,7 +22,9 @@ package org.sonar.server.issue; import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.issue.Issue; @@ -35,16 +37,20 @@ import org.sonar.core.component.ComponentDto; import org.sonar.core.issue.db.IssueDto; import org.sonar.core.issue.db.IssueStorage; import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; import org.sonar.server.db.DbClient; +import org.sonar.server.es.SearchOptions; import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.notification.IssueChangeNotification; import org.sonar.server.rule.DefaultRuleFinder; -import org.sonar.server.search.QueryContext; import org.sonar.server.user.UserSession; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -83,12 +89,12 @@ public class IssueBulkChangeService { IssueBulkChangeResult result = new IssueBulkChangeResult(); - List issues = getByKeysForUpdate(issueBulkChangeQuery.issues()); + Collection issues = getByKeysForUpdate(issueBulkChangeQuery.issues()); Repository repository = new Repository(issues); List bulkActions = getActionsToApply(issueBulkChangeQuery, issues, userSession); IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(), userSession.login()); - Set concernedProjects = new HashSet(); + Set concernedProjects = new HashSet<>(); for (Issue issue : issues) { ActionContext actionContext = new ActionContext(issue, issueChangeContext); for (Action action : bulkActions) { @@ -119,31 +125,36 @@ public class IssueBulkChangeService { return result; } - private List getByKeysForUpdate(List issueKeys) { + private Collection getByKeysForUpdate(List issueKeys) { // Load from index to check permission - List authorizedIndexIssues = issueService.search(IssueQuery.builder().issueKeys(issueKeys).build(), new QueryContext().setMaxLimit()).getHits(); - List authorizedIssueKeys = newArrayList(Iterables.transform(authorizedIndexIssues, new Function() { + SearchOptions options = new SearchOptions().setLimit(SearchOptions.MAX_LIMIT); + // TODO restrict fields to issue key, in order to not load all other fields; + List authorizedIssues = issueService.search(IssueQuery.builder().issueKeys(issueKeys).build(), options).getDocs(); + Collection authorizedKeys = Collections2.transform(authorizedIssues, new Function() { @Override - public String apply(@Nullable Issue input) { - return input != null ? input.key() : null; + public String apply(IssueDoc input) { + return input.key(); } - })); + }); - DbSession session = dbClient.openSession(false); - try { - List issueDtos = dbClient.issueDao().selectByKeys(session, authorizedIssueKeys); - return newArrayList(Iterables.transform(issueDtos, new Function() { - @Override - public Issue apply(@Nullable IssueDto input) { - return input != null ? input.toDefaultIssue() : null; - } - })); - } finally { - session.close(); + if (!authorizedKeys.isEmpty()) { + DbSession session = dbClient.openSession(false); + try { + List dtos = dbClient.issueDao().selectByKeys(session, Lists.newArrayList(authorizedKeys)); + return Collections2.transform(dtos, new Function() { + @Override + public Issue apply(@Nullable IssueDto input) { + return input != null ? input.toDefaultIssue() : null; + } + }); + } finally { + MyBatis.closeQuietly(session); + } } + return Collections.emptyList(); } - private List getActionsToApply(IssueBulkChangeQuery issueBulkChangeQuery, List issues, UserSession userSession) { + private List getActionsToApply(IssueBulkChangeQuery issueBulkChangeQuery, Collection issues, UserSession userSession) { List bulkActions = newArrayList(); for (String actionKey : issueBulkChangeQuery.actions()) { Action action = getAction(actionKey); @@ -207,7 +218,7 @@ public class IssueBulkChangeService { private final Map components = newHashMap(); private final Map projects = newHashMap(); - public Repository(List issues) { + public Repository(Collection issues) { Set ruleKeys = newHashSet(); Set componentKeys = newHashSet(); Set projectKeys = newHashSet(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java index 8067ca37021..94ff60ae58f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java @@ -24,15 +24,17 @@ import com.google.common.collect.ImmutableSet; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.sonar.api.rule.RuleKey; import org.sonar.server.search.QueryContext; +import org.sonar.server.user.UserSession; import javax.annotation.CheckForNull; import javax.annotation.Nullable; - import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Set; +import static com.google.common.collect.Sets.newHashSet; + /** * @since 3.6 */ @@ -84,6 +86,10 @@ public class IssueQuery { private final Boolean ignorePaging; private final Boolean contextualized; + private final String userLogin; + private final Set userGroups; + private final boolean checkAuthorization; + private IssueQuery(Builder builder) { this.issueKeys = defaultCollection(builder.issueKeys); this.severities = defaultCollection(builder.severities); @@ -114,6 +120,9 @@ public class IssueQuery { this.sort = builder.sort; this.asc = builder.asc; this.ignorePaging = builder.ignorePaging; + this.userLogin = builder.userLogin; + this.userGroups = builder.userGroups; + this.checkAuthorization = builder.checkAuthorization; this.contextualized = builder.contextualized; } @@ -247,6 +256,19 @@ public class IssueQuery { return ignorePaging; } + @CheckForNull + public String userLogin() { + return userLogin; + } + + public Set userGroups() { + return newHashSet(defaultCollection(userGroups)); + } + + public boolean checkAuthorization() { + return checkAuthorization; + } + @CheckForNull public Boolean isContextualized() { return contextualized; @@ -292,6 +314,9 @@ public class IssueQuery { private Boolean asc = false; private Boolean ignorePaging = false; private boolean contextualized; + private String userLogin = UserSession.get().login(); + private Set userGroups = UserSession.get().userGroups(); + private boolean checkAuthorization = true; private Builder() { } @@ -475,6 +500,21 @@ public class IssueQuery { return this; } + public Builder userLogin(@Nullable String userLogin) { + this.userLogin = userLogin; + return this; + } + + public Builder userGroups(@Nullable Set userGroups) { + this.userGroups = userGroups; + return this; + } + + public Builder checkAuthorization(boolean checkAuthorization) { + this.checkAuthorization = checkAuthorization; + return this; + } + public Builder setContextualized(boolean b) { this.contextualized = b; return this; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java index 0c893e2fe79..6379acc899f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java @@ -48,13 +48,13 @@ import org.sonar.core.issue.workflow.Transition; import org.sonar.core.persistence.DbSession; import org.sonar.core.rule.RuleDto; import org.sonar.server.db.DbClient; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.issue.actionplan.ActionPlanService; +import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.issue.notification.IssueChangeNotification; -import org.sonar.server.search.FacetValue; -import org.sonar.server.search.IndexClient; -import org.sonar.server.search.QueryContext; import org.sonar.server.source.index.SourceLineDoc; import org.sonar.server.source.index.SourceLineIndex; import org.sonar.server.user.UserSession; @@ -64,14 +64,20 @@ import org.sonar.server.user.index.UserIndex; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; import static com.google.common.collect.Maps.newLinkedHashMap; public class IssueService implements ServerComponent { private final DbClient dbClient; - private final IndexClient indexClient; + private final IssueIndex issueIndex; private final IssueWorkflow workflow; private final IssueUpdater issueUpdater; @@ -84,7 +90,7 @@ public class IssueService implements ServerComponent { private final UserIndex userIndex; private final SourceLineIndex sourceLineIndex; - public IssueService(DbClient dbClient, IndexClient indexClient, + public IssueService(DbClient dbClient, IssueIndex issueIndex, IssueWorkflow workflow, IssueStorage issueStorage, IssueUpdater issueUpdater, @@ -95,7 +101,7 @@ public class IssueService implements ServerComponent { UserFinder userFinder, UserIndex userIndex, SourceLineIndex sourceLineIndex) { this.dbClient = dbClient; - this.indexClient = indexClient; + this.issueIndex = issueIndex; this.workflow = workflow; this.issueStorage = issueStorage; this.issueUpdater = issueUpdater; @@ -301,19 +307,20 @@ public class IssueService implements ServerComponent { public Map findIssueAssignees(IssueQuery query) { Map result = newLinkedHashMap(); - List facetValues = indexClient.get(IssueIndex.class).listAssignees(query); - for (FacetValue facetValue : facetValues) { - if ("_notAssigned_".equals(facetValue.getKey())) { - result.put(null, facetValue.getValue()); + Map buckets = issueIndex.searchForAssignees(query); + for (Map.Entry bucket : buckets.entrySet()) { + if ("_notAssigned_".equals(bucket.getKey())) { + // TODO null key ? + result.put(null, bucket.getValue()); } else { - result.put(facetValue.getKey(), facetValue.getValue()); + result.put(bucket.getKey(), bucket.getValue()); } } return result; } public Issue getByKey(String key) { - return indexClient.get(IssueIndex.class).getByKey(key); + return issueIndex.getByKey(key); } IssueDto getByKeyForUpdate(DbSession session, String key) { @@ -346,20 +353,29 @@ public class IssueService implements ServerComponent { return ruleFinder.findByKey(ruleKey); } - public org.sonar.server.search.Result search(IssueQuery query, QueryContext options) { - return indexClient.get(IssueIndex.class).search(query, options); + public SearchResult search(IssueQuery query, SearchOptions options) { + return issueIndex.search(query, options); } private void verifyLoggedIn() { UserSession.get().checkLoggedIn(); } - public Collection listTags(@Nullable String query, int pageSize) { - return indexClient.get(IssueIndex.class).listTagsMatching(query, pageSize); + /** + * Search for all tags, whatever issue resolution or user access rights + */ + public List listTags(@Nullable String textQuery, int pageSize) { + IssueQuery query = IssueQuery.builder() + .checkAuthorization(false) + .build(); + return issueIndex.listTags(query, textQuery, pageSize); } - public Collection listAuthors(@Nullable String query, int pageSize) { - return indexClient.get(IssueIndex.class).listAuthorsMatching(query, pageSize); + public List listAuthors(@Nullable String textQuery, int pageSize) { + IssueQuery query = IssueQuery.builder() + .checkAuthorization(false) + .build(); + return issueIndex.listAuthors(query, textQuery, pageSize); } public Collection setTags(String issueKey, Collection tags) { @@ -379,8 +395,17 @@ public class IssueService implements ServerComponent { } } + // TODO check compatibility with Views, projects, etc. public Map listTagsForComponent(String componentUuid, int pageSize) { - return indexClient.get(IssueIndex.class).listTagsForComponent(componentUuid, pageSize); + IssueQuery query = IssueQuery.builder() + .userLogin(UserSession.get().login()) + .userGroups(UserSession.get().userGroups()) + .moduleRootUuids(Arrays.asList(componentUuid)) + .onComponentOnly(false) + .setContextualized(true) + .resolved(false) + .build(); + return issueIndex.countTags(query, pageSize); } @CheckForNull diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java index 49aa95a09ac..1bf7d3ee49b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java @@ -30,10 +30,9 @@ import org.sonar.core.issue.IssueUpdater; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.user.UserSession; -import java.util.List; +import java.util.Collection; import java.util.Map; - public class PlanAction extends Action implements ServerComponent { public static final String KEY = "plan"; @@ -50,7 +49,7 @@ public class PlanAction extends Action implements ServerComponent { } @Override - public boolean verify(Map properties, List issues, UserSession userSession) { + public boolean verify(Map properties, Collection issues, UserSession userSession) { String actionPlanValue = planValue(properties); if (!Strings.isNullOrEmpty(actionPlanValue)) { ActionPlan actionPlan = selectActionPlan(actionPlanValue, userSession); @@ -67,7 +66,7 @@ public class PlanAction extends Action implements ServerComponent { @Override public boolean execute(Map properties, Context context) { - if(!properties.containsKey(VERIFIED_ACTION_PLAN)) { + if (!properties.containsKey(VERIFIED_ACTION_PLAN)) { throw new IllegalArgumentException("Action plan is missing from the execution parameters"); } ActionPlan actionPlan = (ActionPlan) properties.get(VERIFIED_ACTION_PLAN); @@ -78,7 +77,7 @@ public class PlanAction extends Action implements ServerComponent { return (String) properties.get("plan"); } - private void verifyIssuesAreAllRelatedOnActionPlanProject(List issues, ActionPlan actionPlan) { + private void verifyIssuesAreAllRelatedOnActionPlanProject(Collection issues, ActionPlan actionPlan) { String projectKey = actionPlan.projectKey(); for (Issue issue : issues) { DefaultIssue defaultIssue = (DefaultIssue) issue; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java index a48f22c4e2a..7a8efdc62b4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java @@ -30,7 +30,7 @@ import org.sonar.api.web.UserRole; import org.sonar.core.issue.IssueUpdater; import org.sonar.server.user.UserSession; -import java.util.List; +import java.util.Collection; import java.util.Map; public class SetSeverityAction extends Action implements ServerComponent { @@ -45,7 +45,7 @@ public class SetSeverityAction extends Action implements ServerComponent { super.setConditions(new IsUnResolved(), new Condition() { @Override public boolean matches(Issue issue) { - return isCurrentUserIssueAdmin(((DefaultIssue) issue).projectKey()); + return isCurrentUserIssueAdmin(issue.projectKey()); } }); } @@ -55,7 +55,7 @@ public class SetSeverityAction extends Action implements ServerComponent { } @Override - public boolean verify(Map properties, List issues, UserSession userSession) { + public boolean verify(Map properties, Collection issues, UserSession userSession) { severity(properties); return true; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java index b2c39ccaa9b..bb18afbfdda 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java @@ -31,7 +31,7 @@ import org.sonar.core.issue.workflow.IssueWorkflow; import org.sonar.core.issue.workflow.Transition; import org.sonar.server.user.UserSession; -import java.util.List; +import java.util.Collection; import java.util.Map; public class TransitionAction extends Action implements ServerComponent { @@ -46,7 +46,7 @@ public class TransitionAction extends Action implements ServerComponent { } @Override - public boolean verify(Map properties, List issues, UserSession userSession) { + public boolean verify(Map properties, Collection issues, UserSession userSession) { transition(properties); return true; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java b/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java index f8bd5f91d53..958135fb50d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java @@ -28,8 +28,6 @@ import org.sonar.core.persistence.MyBatis; import org.sonar.server.exceptions.NotFoundException; import javax.annotation.CheckForNull; - -import java.util.Collection; import java.util.List; public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoComponent { @@ -55,7 +53,7 @@ public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoCom return mapper(session).selectByActionPlan(actionPlan); } - public List selectByKeys(DbSession session, Collection keys) { + public List selectByKeys(DbSession session, List keys) { return mapper(session).selectByKeys(keys); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/filter/IssueFilterService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/filter/IssueFilterService.java index bf53ae3f6c3..df6025a9b34 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/filter/IssueFilterService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/filter/IssueFilterService.java @@ -25,7 +25,6 @@ import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import org.sonar.api.ServerComponent; -import org.sonar.api.issue.Issue; import org.sonar.api.utils.Paging; import org.sonar.core.issue.DefaultIssueFilter; import org.sonar.core.issue.IssueFilterSerializer; @@ -35,14 +34,15 @@ import org.sonar.core.issue.db.IssueFilterFavouriteDao; import org.sonar.core.issue.db.IssueFilterFavouriteDto; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.user.AuthorizationDao; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.issue.IssueQuery; +import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.index.IssueIndex; -import org.sonar.server.search.QueryContext; -import org.sonar.server.search.Result; import org.sonar.server.user.UserSession; import javax.annotation.CheckForNull; @@ -61,8 +61,8 @@ public class IssueFilterService implements ServerComponent { private final IssueFilterSerializer serializer; public IssueFilterService(IssueFilterDao filterDao, IssueFilterFavouriteDao favouriteDao, - IssueIndex issueIndex, AuthorizationDao authorizationDao, - IssueFilterSerializer serializer) { + IssueIndex issueIndex, AuthorizationDao authorizationDao, + IssueFilterSerializer serializer) { this.filterDao = filterDao; this.favouriteDao = favouriteDao; this.issueIndex = issueIndex; @@ -70,8 +70,8 @@ public class IssueFilterService implements ServerComponent { this.serializer = serializer; } - public IssueFilterResult execute(IssueQuery issueQuery, QueryContext context) { - return createIssueFilterResult(issueIndex.search(issueQuery, context), context); + public IssueFilterResult execute(IssueQuery issueQuery, SearchOptions options) { + return createIssueFilterResult(issueIndex.search(issueQuery, options), options); } public DefaultIssueFilter find(Long id, UserSession userSession) { @@ -335,8 +335,8 @@ public class IssueFilterService implements ServerComponent { return authorizationDao.selectGlobalPermissions(user).contains(GlobalPermissions.SYSTEM_ADMIN); } - private IssueFilterResult createIssueFilterResult(Result issues, QueryContext context) { - return new IssueFilterResult(issues.getHits(), Paging.create(context.getLimit(), context.getPage(), ((Long) issues.getTotal()).intValue())); + private IssueFilterResult createIssueFilterResult(SearchResult issues, SearchOptions options) { + return new IssueFilterResult(issues.getDocs(), Paging.create(options.getLimit(), options.getPage(), (int) issues.getTotal())); } private boolean hasUserSharingPermission(String user) { @@ -345,21 +345,21 @@ public class IssueFilterService implements ServerComponent { public static class IssueFilterResult { - private final List issues; + private final List issues; private final Paging paging; - public IssueFilterResult(List issues, Paging paging) { + public IssueFilterResult(List issues, Paging paging) { this.issues = issues; this.paging = paging; } - public List issues(){ + public List issues() { return issues; } - public Paging paging(){ + public Paging paging() { return paging; } - } - + } + } 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 42064038b10..6e42d8c5b21 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 @@ -20,24 +20,25 @@ package org.sonar.server.issue.index; import com.google.common.base.Function; -import com.google.common.base.Preconditions; -import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.lang.BooleanUtils; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.index.query.*; +import org.elasticsearch.index.query.BoolFilterBuilder; +import org.elasticsearch.index.query.FilterBuilder; +import org.elasticsearch.index.query.FilterBuilders; +import org.elasticsearch.index.query.OrFilterBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder; -import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram.Interval; +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram; import org.elasticsearch.search.aggregations.bucket.missing.InternalMissing; import org.elasticsearch.search.aggregations.bucket.terms.Terms; -import org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket; -import org.elasticsearch.search.aggregations.bucket.terms.Terms.Order; import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder; import org.elasticsearch.search.aggregations.metrics.min.Min; import org.joda.time.Duration; @@ -45,10 +46,16 @@ import org.sonar.api.issue.Issue; import org.sonar.api.rule.Severity; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; +import org.sonar.server.es.BaseIndex; +import org.sonar.server.es.EsClient; +import org.sonar.server.es.EsUtils; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.es.Sorting; +import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.issue.IssueQuery; import org.sonar.server.issue.filter.IssueFilterParameters; -import org.sonar.server.search.*; +import org.sonar.server.search.StickyFacetBuilder; import org.sonar.server.user.UserSession; import org.sonar.server.view.index.ViewIndexDefinition; @@ -56,179 +63,155 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; import static com.google.common.collect.Lists.newArrayList; -public class IssueIndex extends BaseIndex { - +/** + * The unique entry-point to interact with Elasticsearch index "issues". + * All the requests are listed here. + */ +public class IssueIndex extends BaseIndex { + + public static final List SUPPORTED_FACETS = ImmutableList.of( + IssueFilterParameters.SEVERITIES, + IssueFilterParameters.STATUSES, + IssueFilterParameters.RESOLUTIONS, + IssueFilterParameters.ACTION_PLANS, + IssueFilterParameters.PROJECT_UUIDS, + IssueFilterParameters.RULES, + IssueFilterParameters.ASSIGNEES, + IssueFilterParameters.REPORTERS, + IssueFilterParameters.AUTHORS, + IssueFilterParameters.MODULE_UUIDS, + IssueFilterParameters.FILE_UUIDS, + IssueFilterParameters.DIRECTORIES, + IssueFilterParameters.LANGUAGES, + IssueFilterParameters.TAGS, + IssueFilterParameters.CREATED_AT); + + // TODO to be documented private static final String FILTER_COMPONENT_ROOT = "__componentRoot"; + // TODO to be documented + // TODO move to Facets ? private static final String FACET_SUFFIX_MISSING = "_missing"; - private static final int DEFAULT_ISSUE_FACET_SIZE = 15; - + private static final int DEFAULT_FACET_SIZE = 15; private static final Duration TWENTY_DAYS = Duration.standardDays(20L); private static final Duration TWENTY_WEEKS = Duration.standardDays(20L * 7L); private static final Duration TWENTY_MONTHS = Duration.standardDays(20L * 30L); - private final Sorting sorting; + /** + * Convert an Elasticsearch result (a map) to an {@link org.sonar.server.issue.index.IssueDoc}. It's + * used for {@link org.sonar.server.es.SearchResult}. + */ + private static final Function, IssueDoc> DOC_CONVERTER = new Function, IssueDoc>() { + @Override + public IssueDoc apply(Map input) { + return new IssueDoc(input); + } + }; + private final Sorting sorting; private final System2 system; - public IssueIndex(SearchClient client, System2 system) { - super(IndexDefinition.ISSUES, null, client); - this.system = system; + public IssueIndex(EsClient client, System2 system) { + super(client); - sorting = new Sorting(); - sorting.add(IssueQuery.SORT_BY_ASSIGNEE, IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE); - sorting.add(IssueQuery.SORT_BY_STATUS, IssueIndexDefinition.FIELD_ISSUE_STATUS); - sorting.add(IssueQuery.SORT_BY_SEVERITY, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE); - sorting.add(IssueQuery.SORT_BY_CREATION_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT); - sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT); - sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT); - sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID); - sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_FILE_PATH); - sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_LINE); - sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE).reverse(); - sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_KEY); + this.system = system; + this.sorting = new Sorting(); + this.sorting.add(IssueQuery.SORT_BY_ASSIGNEE, IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE); + this.sorting.add(IssueQuery.SORT_BY_STATUS, IssueIndexDefinition.FIELD_ISSUE_STATUS); + this.sorting.add(IssueQuery.SORT_BY_SEVERITY, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE); + this.sorting.add(IssueQuery.SORT_BY_CREATION_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT); + this.sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT); + this.sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT); + this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID); + this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_FILE_PATH); + this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_LINE); + this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE).reverse(); + this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_KEY); // by default order by updated date and issue key (in order to be deterministic when same ms) - sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT).reverse(); - sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_KEY); - } - - @Override - protected void initializeIndex() { - // replaced by IssueIndexDefinition - } - - @Override - protected String getKeyValue(String keyString) { - return keyString; - } - - @Override - protected Map mapProperties() { - throw new UnsupportedOperationException("Being refactored"); + this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT).reverse(); + this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_KEY); } - @Override - protected Map mapKey() { - throw new UnsupportedOperationException("Being refactored"); - } - - @Override - protected IssueDoc toDoc(Map fields) { - Preconditions.checkNotNull(fields, "Cannot construct Issue with null response"); - return new IssueDoc(fields); - } - - @Override - public Issue getNullableByKey(String key) { - Result result = search(IssueQuery.builder().issueKeys(newArrayList(key)).build(), new QueryContext()); + /** + * Warning, this method is not efficient as routing (the project uuid) is not known. + * All the ES cluster nodes are involved. + */ + @CheckForNull + public IssueDoc getNullableByKey(String key) { + SearchResult result = search(IssueQuery.builder().issueKeys(newArrayList(key)).build(), new SearchOptions()); if (result.getTotal() == 1) { - return result.getHits().get(0); + return result.getDocs().get(0); } return null; } - public List listAssignees(IssueQuery query) { - QueryContext queryContext = new QueryContext().setPage(1, 0); - - SearchRequestBuilder esSearch = getClient() - .prepareSearch(IssueIndexDefinition.INDEX) - .setTypes(IssueIndexDefinition.TYPE_ISSUE); - - QueryBuilder esQuery = QueryBuilders.matchAllQuery(); - BoolFilterBuilder esFilter = getFilter(query, queryContext); - if (esFilter.hasClauses()) { - esSearch.setQuery(QueryBuilders.filteredQuery(esQuery, esFilter)); - } else { - esSearch.setQuery(esQuery); + /** + * Warning, see {@link #getNullableByKey(String)}. + * A {@link org.sonar.server.exceptions.NotFoundException} is thrown if key does not exist. + */ + public IssueDoc getByKey(String key) { + IssueDoc value = getNullableByKey(key); + if (value == null) { + throw new NotFoundException(String.format("Issue with key '%s' does not exist", key)); } - esSearch.addAggregation(AggregationBuilders.terms(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE) - .size(Integer.MAX_VALUE) - .field(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE)); - esSearch.addAggregation(AggregationBuilders.missing("notAssigned") - .field(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE)); - - SearchResponse response = esSearch.get(); - Terms aggregation = (Terms) response.getAggregations().getAsMap().get(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE); - List facetValues = newArrayList(); - for (Terms.Bucket value : aggregation.getBuckets()) { - facetValues.add(new FacetValue(value.getKey(), value.getDocCount())); - } - facetValues.add(new FacetValue("_notAssigned_", ((InternalMissing) response.getAggregations().get("notAssigned")).getDocCount())); - - return facetValues; + return value; } - public Result search(IssueQuery query, QueryContext options) { - SearchRequestBuilder esSearch = getClient() + public SearchResult search(IssueQuery query, SearchOptions options) { + SearchRequestBuilder requestBuilder = getClient() .prepareSearch(IssueIndexDefinition.INDEX) .setTypes(IssueIndexDefinition.TYPE_ISSUE); - if (options.isScroll()) { - esSearch.setSearchType(SearchType.SCAN); - esSearch.setScroll(TimeValue.timeValueMinutes(3)); - } - - setSorting(query, esSearch); - setPagination(options, esSearch); + configureSorting(query, requestBuilder); + configurePagination(options, requestBuilder); QueryBuilder esQuery = QueryBuilders.matchAllQuery(); - Map filters = getFilters(query, options); - setQueryFilter(esSearch, esQuery, filters); - - setFacets(query, options, filters, esQuery, esSearch); - - SearchResponse response = esSearch.get(); - return new Result<>(this, response); - } - - protected void setQueryFilter(SearchRequestBuilder esSearch, QueryBuilder esQuery, Map filters) { BoolFilterBuilder esFilter = FilterBuilders.boolFilter(); + Map filters = createFilters(query); for (FilterBuilder filter : filters.values()) { if (filter != null) { esFilter.must(filter); } } - if (esFilter.hasClauses()) { - esSearch.setQuery(QueryBuilders.filteredQuery(esQuery, esFilter)); + requestBuilder.setQuery(QueryBuilders.filteredQuery(esQuery, esFilter)); } else { - esSearch.setQuery(esQuery); + requestBuilder.setQuery(esQuery); } + + configureStickyFacets(query, options, filters, esQuery, requestBuilder); + return new SearchResult<>(requestBuilder.get(), DOC_CONVERTER); } - private BoolFilterBuilder getFilter(IssueQuery query, QueryContext options) { - BoolFilterBuilder esFilter = FilterBuilders.boolFilter(); - for (FilterBuilder filter : getFilters(query, options).values()) { - if (filter != null) { - esFilter.must(filter); - } + private void configureSorting(IssueQuery query, SearchRequestBuilder esRequest) { + String sortField = query.sort(); + if (sortField != null) { + boolean asc = BooleanUtils.isTrue(query.asc()); + sorting.fill(esRequest, sortField, asc); + } else { + sorting.fillDefault(esRequest); } - return esFilter; } - public void deleteClosedIssuesOfProjectBefore(String uuid, Date beforeDate) { - FilterBuilder projectFilter = FilterBuilders.boolFilter().must(FilterBuilders.termsFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, uuid)); - FilterBuilder dateFilter = FilterBuilders.rangeFilter(IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT).lt(beforeDate.getTime()); - QueryBuilder queryBuilder = QueryBuilders.filteredQuery( - QueryBuilders.matchAllQuery(), - FilterBuilders.andFilter(projectFilter, dateFilter) - ); - - getClient().prepareDeleteByQuery(IssueIndexDefinition.INDEX).setQuery(queryBuilder).get(); + protected void configurePagination(SearchOptions options, SearchRequestBuilder esSearch) { + esSearch.setFrom(options.getOffset()).setSize(options.getLimit()); } - /* Build main filter (match based) */ - protected Map getFilters(IssueQuery query, QueryContext options) { - - Map filters = Maps.newHashMap(); - - filters.put("__authorization", getAuthorizationFilter(options)); + private Map createFilters(IssueQuery query) { + Map filters = new HashMap<>(); + filters.put("__authorization", createAuthorizationFilter(query)); // Issue is assigned Filter String isAssigned = "__isAssigned"; @@ -255,20 +238,20 @@ public class IssueIndex extends BaseIndex { } // Field Filters - filters.put(IssueIndexDefinition.FIELD_ISSUE_KEY, matchFilter(IssueIndexDefinition.FIELD_ISSUE_KEY, query.issueKeys())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_ACTION_PLAN, matchFilter(IssueIndexDefinition.FIELD_ISSUE_ACTION_PLAN, query.actionPlans())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE, matchFilter(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE, query.assignees())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_KEY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_KEY, query.issueKeys())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_ACTION_PLAN, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_ACTION_PLAN, query.actionPlans())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE, query.assignees())); addComponentRelatedFilters(query, filters); - filters.put(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, matchFilter(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_TAGS, matchFilter(IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, matchFilter(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, query.resolutions())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_REPORTER, matchFilter(IssueIndexDefinition.FIELD_ISSUE_REPORTER, query.reporters())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, matchFilter(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_RULE_KEY, matchFilter(IssueIndexDefinition.FIELD_ISSUE_RULE_KEY, query.rules())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, matchFilter(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, query.severities())); - filters.put(IssueIndexDefinition.FIELD_ISSUE_STATUS, matchFilter(IssueIndexDefinition.FIELD_ISSUE_STATUS, query.statuses())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_TAGS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, query.resolutions())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_REPORTER, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_REPORTER, query.reporters())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_RULE_KEY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_RULE_KEY, query.rules())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, query.severities())); + filters.put(IssueIndexDefinition.FIELD_ISSUE_STATUS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_STATUS, query.statuses())); addDatesFilter(filters, query); @@ -276,14 +259,14 @@ public class IssueIndex extends BaseIndex { } private void addComponentRelatedFilters(IssueQuery query, Map filters) { - FilterBuilder viewFilter = viewFilter(query.viewUuids()); - FilterBuilder componentFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.componentUuids()); - FilterBuilder projectFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, query.projectUuids()); - FilterBuilder moduleRootFilter = moduleRootFilter(query.moduleRootUuids()); - FilterBuilder moduleFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids()); - FilterBuilder directoryRootFilter = directoryRootFilter(query.moduleUuids(), query.directories()); - FilterBuilder directoryFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories()); - FilterBuilder fileFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids()); + FilterBuilder viewFilter = createViewFilter(query.viewUuids()); + FilterBuilder componentFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.componentUuids()); + FilterBuilder projectFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, query.projectUuids()); + FilterBuilder moduleRootFilter = createModuleRootFilter(query.moduleRootUuids()); + FilterBuilder moduleFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids()); + FilterBuilder directoryRootFilter = createDirectoryRootFilter(query.moduleUuids(), query.directories()); + FilterBuilder directoryFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories()); + FilterBuilder fileFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids()); if (BooleanUtils.isTrue(query.isContextualized())) { if (viewFilter != null) { @@ -325,12 +308,12 @@ public class IssueIndex extends BaseIndex { } @CheckForNull - private FilterBuilder moduleRootFilter(Collection componentUuids) { + private FilterBuilder createModuleRootFilter(Collection componentUuids) { if (componentUuids.isEmpty()) { return null; } - FilterBuilder componentFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, componentUuids); - FilterBuilder modulePathFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_PATH, componentUuids); + FilterBuilder componentFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, componentUuids); + FilterBuilder modulePathFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_PATH, componentUuids); FilterBuilder compositeFilter = null; if (componentFilter != null) { if (modulePathFilter != null) { @@ -341,14 +324,15 @@ public class IssueIndex extends BaseIndex { } else if (modulePathFilter != null) { compositeFilter = modulePathFilter; } + System.out.println("compositeFilter:" + compositeFilter); return compositeFilter; } @CheckForNull - private FilterBuilder directoryRootFilter(Collection moduleUuids, Collection directoryPaths) { + private FilterBuilder createDirectoryRootFilter(Collection moduleUuids, Collection directoryPaths) { BoolFilterBuilder directoryTop = null; - FilterBuilder moduleFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, moduleUuids); - FilterBuilder directoryFilter = matchFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, directoryPaths); + FilterBuilder moduleFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, moduleUuids); + FilterBuilder directoryFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, directoryPaths); if (moduleFilter != null) { directoryTop = FilterBuilders.boolFilter(); directoryTop.must(moduleFilter); @@ -363,7 +347,7 @@ public class IssueIndex extends BaseIndex { } @CheckForNull - private FilterBuilder viewFilter(Collection viewUuids) { + private FilterBuilder createViewFilter(Collection viewUuids) { if (viewUuids.isEmpty()) { return null; } @@ -381,26 +365,29 @@ public class IssueIndex extends BaseIndex { } public static String viewsLookupCacheKey(String viewUuid) { - return IssueIndexDefinition.TYPE_ISSUE + viewUuid + ViewIndexDefinition.TYPE_VIEW; + return String.format("%s%s%s", IssueIndexDefinition.TYPE_ISSUE, viewUuid, ViewIndexDefinition.TYPE_VIEW); } - private FilterBuilder getAuthorizationFilter(QueryContext options) { - String user = options.getUserLogin(); - Set groups = options.getUserGroups(); - OrFilterBuilder groupsAndUser = FilterBuilders.orFilter(); - if (user != null) { - groupsAndUser.add(FilterBuilders.termFilter(IssueIndexDefinition.FIELD_AUTHORIZATION_USERS, user)); - } - for (String group : groups) { - groupsAndUser.add(FilterBuilders.termFilter(IssueIndexDefinition.FIELD_AUTHORIZATION_GROUPS, group)); + private FilterBuilder createAuthorizationFilter(IssueQuery query) { + if (query.checkAuthorization()) { + String user = query.userLogin(); + OrFilterBuilder groupsAndUser = FilterBuilders.orFilter(); + if (user != null) { + groupsAndUser.add(FilterBuilders.termFilter(IssueIndexDefinition.FIELD_AUTHORIZATION_USERS, user)); + } + for (String group : query.userGroups()) { + groupsAndUser.add(FilterBuilders.termFilter(IssueIndexDefinition.FIELD_AUTHORIZATION_GROUPS, group)); + } + return FilterBuilders.hasParentFilter(IssueIndexDefinition.TYPE_AUTHORIZATION, + QueryBuilders.filteredQuery( + QueryBuilders.matchAllQuery(), + FilterBuilders.boolFilter() + .must(groupsAndUser) + .cache(true)) + ); + } else { + return FilterBuilders.matchAllFilter(); } - return FilterBuilders.hasParentFilter(IssueIndexDefinition.TYPE_AUTHORIZATION, - QueryBuilders.filteredQuery( - QueryBuilders.matchAllQuery(), - FilterBuilders.boolFilter() - .must(groupsAndUser) - .cache(true)) - ); } private void addDatesFilter(Map filters, IssueQuery query) { @@ -424,9 +411,9 @@ public class IssueIndex extends BaseIndex { } } - private void setFacets(IssueQuery query, QueryContext options, Map filters, QueryBuilder esQuery, SearchRequestBuilder esSearch) { - if (options.isFacet()) { - StickyFacetBuilder stickyFacetBuilder = stickyFacetBuilder(esQuery, filters); + private void configureStickyFacets(IssueQuery query, SearchOptions options, Map filters, QueryBuilder esQuery, SearchRequestBuilder esSearch) { + if (!options.getFacets().isEmpty()) { + StickyFacetBuilder stickyFacetBuilder = new StickyFacetBuilder(esQuery, filters); // Execute Term aggregations addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, IssueFilterParameters.SEVERITIES, IssueIndexDefinition.FIELD_ISSUE_SEVERITY, Severity.ALL.toArray()); @@ -450,33 +437,85 @@ public class IssueIndex extends BaseIndex { addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, IssueFilterParameters.AUTHORS, IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors().toArray()); - if (options.facets().contains(IssueFilterParameters.TAGS)) { + if (options.getFacets().contains(IssueFilterParameters.TAGS)) { esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(IssueIndexDefinition.FIELD_ISSUE_TAGS, IssueFilterParameters.TAGS, query.tags().toArray())); } - if (options.facets().contains(IssueFilterParameters.RESOLUTIONS)) { - esSearch.addAggregation(getResolutionFacet(filters, esQuery)); + if (options.getFacets().contains(IssueFilterParameters.RESOLUTIONS)) { + esSearch.addAggregation(createResolutionFacet(filters, esQuery)); } - if (options.facets().contains(IssueFilterParameters.ASSIGNEES)) { - esSearch.addAggregation(getAssigneesFacet(query, filters, esQuery)); + if (options.getFacets().contains(IssueFilterParameters.ASSIGNEES)) { + esSearch.addAggregation(createAssigneesFacet(query, filters, esQuery)); } - if (options.facets().contains(IssueFilterParameters.ACTION_PLANS)) { - esSearch.addAggregation(getActionPlansFacet(query, filters, esQuery)); + if (options.getFacets().contains(IssueFilterParameters.ACTION_PLANS)) { + esSearch.addAggregation(createActionPlansFacet(query, filters, esQuery)); } - if (options.facets().contains(IssueFilterParameters.CREATED_AT)) { + if (options.getFacets().contains(IssueFilterParameters.CREATED_AT)) { esSearch.addAggregation(getCreatedAtFacet(query, filters, esQuery)); } } } - private void addSimpleStickyFacetIfNeeded(QueryContext options, StickyFacetBuilder stickyFacetBuilder, SearchRequestBuilder esSearch, + private void addSimpleStickyFacetIfNeeded(SearchOptions options, StickyFacetBuilder stickyFacetBuilder, SearchRequestBuilder esSearch, String facetName, String fieldName, Object... selectedValues) { - if (options.facets().contains(facetName)) { - esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(fieldName, facetName, DEFAULT_ISSUE_FACET_SIZE, selectedValues)); + if (options.getFacets().contains(facetName)) { + esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(fieldName, facetName, DEFAULT_FACET_SIZE, selectedValues)); + } + } + + private AggregationBuilder getCreatedAtFacet(IssueQuery query, Map filters, QueryBuilder esQuery) { + Date now = system.newDate(); + SimpleDateFormat tzFormat = new SimpleDateFormat("XX"); + tzFormat.setTimeZone(TimeZone.getDefault()); + String timeZoneString = tzFormat.format(now); + + DateHistogram.Interval bucketSize = DateHistogram.Interval.YEAR; + Date createdAfter = query.createdAfter(); + long startTime = createdAfter == null ? getMinCreatedAt(filters, esQuery) : createdAfter.getTime(); + Date createdBefore = query.createdBefore(); + long endTime = createdBefore == null ? now.getTime() : createdBefore.getTime(); + Duration timeSpan = new Duration(startTime, endTime); + if (timeSpan.isShorterThan(TWENTY_DAYS)) { + bucketSize = DateHistogram.Interval.DAY; + } else if (timeSpan.isShorterThan(TWENTY_WEEKS)) { + bucketSize = DateHistogram.Interval.WEEK; + } else if (timeSpan.isShorterThan(TWENTY_MONTHS)) { + bucketSize = DateHistogram.Interval.MONTH; + } + + return AggregationBuilders.dateHistogram(IssueFilterParameters.CREATED_AT) + .field(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT) + .interval(bucketSize) + .minDocCount(0L) + .format(DateUtils.DATETIME_FORMAT) + .preZone(timeZoneString) + .postZone(timeZoneString) + .extendedBounds(startTime, endTime); + } + + private long getMinCreatedAt(Map filters, QueryBuilder esQuery) { + String facetNameAndField = IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT; + SearchRequestBuilder esRequest = getClient() + .prepareSearch(IssueIndexDefinition.INDEX) + .setTypes(IssueIndexDefinition.TYPE_ISSUE) + .setSearchType(SearchType.COUNT); + BoolFilterBuilder esFilter = FilterBuilders.boolFilter(); + for (FilterBuilder filter : filters.values()) { + if (filter != null) { + esFilter.must(filter); + } } + if (esFilter.hasClauses()) { + esRequest.setQuery(QueryBuilders.filteredQuery(esQuery, esFilter)); + } else { + esRequest.setQuery(esQuery); + } + esRequest.addAggregation(AggregationBuilders.min(facetNameAndField).field(facetNameAndField)); + Min minValue = esRequest.get().getAggregations().get(facetNameAndField); + return (long) minValue.getValue(); } - private AggregationBuilder getAssigneesFacet(IssueQuery query, Map filters, QueryBuilder esQuery) { + private AggregationBuilder createAssigneesFacet(IssueQuery query, Map filters, QueryBuilder queryBuilder) { String fieldName = IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE; String facetName = IssueFilterParameters.ASSIGNEES; @@ -484,9 +523,9 @@ public class IssueIndex extends BaseIndex { Map assigneeFilters = Maps.newHashMap(filters); assigneeFilters.remove("__isAssigned"); assigneeFilters.remove(fieldName); - StickyFacetBuilder assigneeFacetBuilder = new StickyFacetBuilder(esQuery, assigneeFilters); + StickyFacetBuilder assigneeFacetBuilder = new StickyFacetBuilder(queryBuilder, assigneeFilters); BoolFilterBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName); - FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_ISSUE_FACET_SIZE); + FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_FACET_SIZE); List assignees = Lists.newArrayList(query.assignees()); UserSession session = UserSession.get(); @@ -507,7 +546,7 @@ public class IssueIndex extends BaseIndex { .subAggregation(facetTopAggregation); } - private AggregationBuilder getResolutionFacet(Map filters, QueryBuilder esQuery) { + private AggregationBuilder createResolutionFacet(Map filters, QueryBuilder esQuery) { String fieldName = IssueIndexDefinition.FIELD_ISSUE_RESOLUTION; String facetName = IssueFilterParameters.RESOLUTIONS; @@ -517,7 +556,7 @@ public class IssueIndex extends BaseIndex { resolutionFilters.remove(fieldName); StickyFacetBuilder assigneeFacetBuilder = new StickyFacetBuilder(esQuery, resolutionFilters); BoolFilterBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName); - FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_ISSUE_FACET_SIZE); + FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_FACET_SIZE); facetTopAggregation = assigneeFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, Issue.RESOLUTIONS.toArray()); // Add missing facet for unresolved issues @@ -532,7 +571,7 @@ public class IssueIndex extends BaseIndex { .subAggregation(facetTopAggregation); } - private AggregationBuilder getActionPlansFacet(IssueQuery query, Map filters, QueryBuilder esQuery) { + private AggregationBuilder createActionPlansFacet(IssueQuery query, Map filters, QueryBuilder esQuery) { String fieldName = IssueIndexDefinition.FIELD_ISSUE_ACTION_PLAN; String facetName = IssueFilterParameters.ACTION_PLANS; @@ -542,7 +581,7 @@ public class IssueIndex extends BaseIndex { actionPlanFilters.remove(fieldName); StickyFacetBuilder actionPlanFacetBuilder = new StickyFacetBuilder(esQuery, actionPlanFilters); BoolFilterBuilder facetFilter = actionPlanFacetBuilder.getStickyFacetFilter(fieldName); - FilterAggregationBuilder facetTopAggregation = actionPlanFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_ISSUE_FACET_SIZE); + FilterAggregationBuilder facetTopAggregation = actionPlanFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_FACET_SIZE); facetTopAggregation = actionPlanFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, query.actionPlans().toArray()); // Add missing facet for unresolved issues @@ -557,66 +596,8 @@ public class IssueIndex extends BaseIndex { .subAggregation(facetTopAggregation); } - private AggregationBuilder getCreatedAtFacet(IssueQuery query, Map filters, QueryBuilder esQuery) { - Date now = system.newDate(); - SimpleDateFormat tzFormat = new SimpleDateFormat("XX"); - tzFormat.setTimeZone(TimeZone.getDefault()); - String timeZoneString = tzFormat.format(now); - - Interval bucketSize = Interval.YEAR; - Date createdAfter = query.createdAfter(); - long startTime = createdAfter == null ? getMinCreatedAt(filters, esQuery) : createdAfter.getTime(); - Date createdBefore = query.createdBefore(); - long endTime = createdBefore == null ? now.getTime() : createdBefore.getTime(); - Duration timeSpan = new Duration(startTime, endTime); - if (timeSpan.isShorterThan(TWENTY_DAYS)) { - bucketSize = Interval.DAY; - } else if (timeSpan.isShorterThan(TWENTY_WEEKS)) { - bucketSize = Interval.WEEK; - } else if (timeSpan.isShorterThan(TWENTY_MONTHS)) { - bucketSize = Interval.MONTH; - } - - - return AggregationBuilders.dateHistogram(IssueFilterParameters.CREATED_AT) - .field(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT) - .interval(bucketSize) - .minDocCount(0L) - .format(DateUtils.DATETIME_FORMAT) - .preZone(timeZoneString) - .postZone(timeZoneString) - .extendedBounds(startTime, endTime); - } - - private long getMinCreatedAt(Map filters, QueryBuilder esQuery) { - String facetNameAndField = IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT; - SearchRequestBuilder minCount = getClient() - .prepareSearch(IssueIndexDefinition.INDEX) - .setTypes(IssueIndexDefinition.TYPE_ISSUE) - .setSearchType(SearchType.COUNT); - setQueryFilter(minCount, esQuery, filters); - minCount.addAggregation(AggregationBuilders.min(facetNameAndField).field(facetNameAndField)); - Min minValue = minCount.get().getAggregations().get(facetNameAndField); - return (long) minValue.getValue(); - } - - private void setSorting(IssueQuery query, SearchRequestBuilder esRequest) { - String sortField = query.sort(); - if (sortField != null) { - boolean asc = BooleanUtils.isTrue(query.asc()); - sorting.fill(esRequest, sortField, asc); - } else { - sorting.fillDefault(esRequest); - } - } - - protected void setPagination(QueryContext options, SearchRequestBuilder esSearch) { - esSearch.setFrom(options.getOffset()); - esSearch.setSize(options.getLimit()); - } - @CheckForNull - private FilterBuilder matchFilter(String field, Collection values) { + private FilterBuilder createTermsFilter(String field, Collection values) { if (!values.isEmpty()) { return FilterBuilders.termsFilter(field, values); } else { @@ -624,55 +605,100 @@ public class IssueIndex extends BaseIndex { } } - public Collection listTagsMatching(@Nullable String query, int pageSize) { - return listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_TAGS, query, pageSize); + public List listTags(IssueQuery query, @Nullable String textQuery, int maxNumberOfTags) { + Terms terms = listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_TAGS, query, textQuery, Terms.Order.term(true), maxNumberOfTags); + return EsUtils.termsKeys(terms); } - public Collection listAuthorsMatching(@Nullable String query, int pageSize) { - return listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query, pageSize); + public Map countTags(IssueQuery query, int maxNumberOfTags) { + Terms terms = listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_TAGS, query, null, Terms.Order.count(false), maxNumberOfTags); + return EsUtils.termsToMap(terms); } - private Collection listTermsMatching(String fieldName, @Nullable String query, int pageSize) { - SearchRequestBuilder count = getClient().prepareSearch(IssueIndexDefinition.INDEX) - .setTypes(IssueIndexDefinition.TYPE_ISSUE) - .setQuery(QueryBuilders.matchAllQuery()); + public List listAuthors(IssueQuery query, @Nullable String textQuery, int maxNumberOfAuthors) { + Terms terms = listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query, textQuery, Terms.Order.term(true), maxNumberOfAuthors); + return EsUtils.termsKeys(terms); + } + + private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, Terms.Order termsOrder, int maxNumberOfTags) { + SearchRequestBuilder requestBuilder = getClient() + .prepareSearch(IssueIndexDefinition.INDEX) + .setTypes(IssueIndexDefinition.TYPE_ISSUE); + + requestBuilder.setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), + createBoolFilter(query))); + + // TODO do not return hits + TermsBuilder aggreg = AggregationBuilders.terms("_ref") .field(fieldName) - .size(pageSize) - .order(Order.term(true)) + .size(maxNumberOfTags) + .order(termsOrder) .minDocCount(1L); - if (query != null) { - aggreg.include(".*" + query + ".*"); + if (textQuery != null) { + aggreg.include(String.format(".*%s.*", textQuery)); } - Terms result = count.addAggregation(aggreg).get().getAggregations().get("_ref"); - return Collections2.transform(result.getBuckets(), new Function() { - @Override - public String apply(Bucket bucket) { - return bucket.getKey(); - } - }); + SearchResponse searchResponse = requestBuilder.addAggregation(aggreg).get(); + return searchResponse.getAggregations().get("_ref"); } - public Map listTagsForComponent(String componentUuid, int pageSize) { - SearchRequestBuilder count = getClient().prepareSearch(IssueIndexDefinition.INDEX) - .setTypes(IssueIndexDefinition.TYPE_ISSUE) - .setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), - FilterBuilders.boolFilter() - .must(getAuthorizationFilter(new QueryContext())) - .must(FilterBuilders.missingFilter(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)) - .must(moduleRootFilter(Arrays.asList(componentUuid))))); - TermsBuilder aggreg = AggregationBuilders.terms("_ref") - .field(IssueIndexDefinition.FIELD_ISSUE_TAGS) - .size(pageSize) - .order(Order.count(false)) - .minDocCount(1L); - Terms result = count.addAggregation(aggreg).get().getAggregations().get("_ref"); + public void deleteClosedIssuesOfProjectBefore(String projectUuid, Date beforeDate) { + FilterBuilder projectFilter = FilterBuilders.boolFilter().must(FilterBuilders.termsFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid)); + FilterBuilder dateFilter = FilterBuilders.rangeFilter(IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT).lt(beforeDate.getTime()); + QueryBuilder queryBuilder = QueryBuilders.filteredQuery( + QueryBuilders.matchAllQuery(), + FilterBuilders.andFilter(projectFilter, dateFilter) + ); + + getClient().prepareDeleteByQuery(IssueIndexDefinition.INDEX).setQuery(queryBuilder).get(); + } - Map map = Maps.newHashMap(); - for (Bucket bucket : result.getBuckets()) { - map.put(bucket.getKey(), bucket.getDocCount()); + public LinkedHashMap searchForAssignees(IssueQuery query) { + // TODO do not return hits + // TODO what's max size ? + + SearchRequestBuilder esSearch = getClient() + .prepareSearch(IssueIndexDefinition.INDEX) + .setTypes(IssueIndexDefinition.TYPE_ISSUE); + + QueryBuilder esQuery = QueryBuilders.matchAllQuery(); + BoolFilterBuilder esFilter = createBoolFilter(query); + if (esFilter.hasClauses()) { + esSearch.setQuery(QueryBuilders.filteredQuery(esQuery, esFilter)); + } else { + esSearch.setQuery(esQuery); + } + esSearch.addAggregation(AggregationBuilders.terms(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE) + .size(Integer.MAX_VALUE) + .field(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE)); + esSearch.addAggregation(AggregationBuilders.missing("notAssigned") + .field(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE)); + + SearchResponse response = esSearch.get(); + Terms aggregation = (Terms) response.getAggregations().getAsMap().get(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE); + LinkedHashMap result = EsUtils.termsToMap(aggregation); + result.put("_notAssigned_", ((InternalMissing) response.getAggregations().get("notAssigned")).getDocCount()); + return result; + } + + private BoolFilterBuilder createBoolFilter(IssueQuery query) { + BoolFilterBuilder boolFilter = FilterBuilders.boolFilter(); + for (FilterBuilder filter : createFilters(query).values()) { + // TODO Can it be null ? + if (filter != null) { + boolFilter.must(filter); + } } - return map; + return boolFilter; + } + + /** + * TODO used only by tests, so must be replaced by EsTester#countDocuments() + */ + public long countAll() { + return getClient().prepareCount(IssueIndexDefinition.INDEX) + .setTypes(IssueIndexDefinition.TYPE_ISSUE) + .get().getCount(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AuthorsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AuthorsAction.java index 3d500c0927c..8096dc5dd62 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AuthorsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AuthorsAction.java @@ -20,15 +20,15 @@ package org.sonar.server.issue.ws; import com.google.common.io.Resources; -import org.sonar.api.server.ws.*; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.NewAction; import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.issue.IssueService; -public class AuthorsAction implements RequestHandler { +public class AuthorsAction implements BaseIssuesWsAction { - private static final String PARAM_PAGE_SIZE = "ps"; - private static final String PARAM_QUERY = "q"; private final IssueService service; public AuthorsAction(IssueService service) { @@ -37,8 +37,8 @@ public class AuthorsAction implements RequestHandler { @Override public void handle(Request request, Response response) throws Exception { - String query = request.param(PARAM_QUERY); - int pageSize = request.mandatoryParamAsInt(PARAM_PAGE_SIZE); + String query = request.param(WebService.Param.TEXT_QUERY); + int pageSize = request.mandatoryParamAsInt(WebService.Param.PAGE_SIZE); JsonWriter json = response.newJsonWriter() .beginObject() @@ -52,17 +52,18 @@ public class AuthorsAction implements RequestHandler { json.endArray().endObject().close(); } - void define(WebService.NewController controller) { + @Override + public void define(WebService.NewController controller) { NewAction action = controller.createAction("authors") .setSince("5.1") .setDescription("Search SCM accounts which match a given query") .setResponseExample(Resources.getResource(this.getClass(), "example-authors.json")) .setHandler(this); - action.createParam(PARAM_QUERY) + action.createParam(WebService.Param.TEXT_QUERY) .setDescription("A pattern to match SCM accounts against") .setExampleValue("luke"); - action.createParam(PARAM_PAGE_SIZE) + action.createParam(WebService.Param.PAGE_SIZE) .setDescription("The size of the list to return") .setExampleValue("25") .setDefaultValue("10"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/FakeIssueDto.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/BaseIssuesWsAction.java similarity index 79% rename from server/sonar-server/src/main/java/org/sonar/server/issue/index/FakeIssueDto.java rename to server/sonar-server/src/main/java/org/sonar/server/issue/ws/BaseIssuesWsAction.java index 0db9d5d5b24..051a34613c0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/FakeIssueDto.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/BaseIssuesWsAction.java @@ -17,13 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.issue.index; +package org.sonar.server.issue.ws; -import org.sonar.core.persistence.Dto; +import org.sonar.api.server.ws.RequestHandler; +import org.sonar.api.server.ws.WebService; + +interface BaseIssuesWsAction extends RequestHandler { + + void define(WebService.NewController controller); -public class FakeIssueDto extends Dto { - @Override - public String getKey() { - throw new UnsupportedOperationException(); - } } + diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ComponentTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ComponentTagsAction.java index e642686d0bb..c68e9d00b78 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ComponentTagsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ComponentTagsAction.java @@ -21,7 +21,6 @@ package org.sonar.server.issue.ws; import com.google.common.io.Resources; import org.sonar.api.server.ws.Request; -import org.sonar.api.server.ws.RequestHandler; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.NewAction; @@ -34,7 +33,7 @@ import java.util.Map; * List issue tags matching a given query. * @since 5.1 */ -public class ComponentTagsAction implements RequestHandler { +public class ComponentTagsAction implements BaseIssuesWsAction { private final IssueService service; @@ -42,7 +41,8 @@ public class ComponentTagsAction implements RequestHandler { this.service = service; } - void define(WebService.NewController controller) { + @Override + public void define(WebService.NewController controller) { NewAction action = controller.createAction("component_tags") .setHandler(this) .setSince("5.1") diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueShowAction.java index 87340b7eaf1..1c0604c0283 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueShowAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueShowAction.java @@ -29,7 +29,6 @@ import org.sonar.api.issue.internal.FieldDiffs; import org.sonar.api.server.debt.DebtCharacteristic; import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic; import org.sonar.api.server.ws.Request; -import org.sonar.api.server.ws.RequestHandler; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.user.User; @@ -54,14 +53,13 @@ import org.sonar.server.user.UserSession; import javax.annotation.CheckForNull; import javax.annotation.Nullable; - import java.util.Date; import java.util.List; import java.util.Map; import static com.google.common.collect.Maps.newHashMap; -public class IssueShowAction implements RequestHandler { +public class IssueShowAction implements BaseIssuesWsAction { public static final String SHOW_ACTION = "show"; @@ -94,7 +92,8 @@ public class IssueShowAction implements RequestHandler { this.durations = durations; } - void define(WebService.NewController controller) { + @Override + public void define(WebService.NewController controller) { WebService.NewAction action = controller.createAction(SHOW_ACTION) .setDescription("Detail of issue") .setSince("4.2") diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java index 8efe7e1f039..df9e92da850 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java @@ -42,22 +42,10 @@ public class IssuesWs implements WebService { public static final String DO_ACTION_ACTION = "do_action"; public static final String BULK_CHANGE_ACTION = "bulk_change"; - private final IssueShowAction showAction; - private final SearchAction esSearchAction; - private final TagsAction tagsAction; - private final SetTagsAction setTagsAction; - private final ComponentTagsAction componentTagsAction; - private final AuthorsAction authorsAction; - - - public IssuesWs(IssueShowAction showAction, SearchAction searchAction, TagsAction tagsAction, SetTagsAction setTagsAction, ComponentTagsAction componentTagsAction, - AuthorsAction authorsAction) { - this.showAction = showAction; - this.esSearchAction = searchAction; - this.tagsAction = tagsAction; - this.setTagsAction = setTagsAction; - this.componentTagsAction = componentTagsAction; - this.authorsAction = authorsAction; + private final BaseIssuesWsAction[] actions; + + public IssuesWs(BaseIssuesWsAction... actions) { + this.actions = actions; } @Override @@ -65,14 +53,14 @@ public class IssuesWs implements WebService { NewController controller = context.createController(API_ENDPOINT); controller.setDescription("Coding rule issues"); controller.setSince("3.6"); + for (BaseIssuesWsAction action : actions) { + action.define(controller); + } + defineRailsActions(controller); + controller.done(); + } - showAction.define(controller); - esSearchAction.define(controller); - tagsAction.define(controller); - setTagsAction.define(controller); - componentTagsAction.define(controller); - authorsAction.define(controller); - + private void defineRailsActions(NewController controller) { defineChangelogAction(controller); defineAssignAction(controller); defineAddCommentAction(controller); @@ -85,8 +73,6 @@ public class IssuesWs implements WebService { defineCreateAction(controller); defineDoActionAction(controller); defineBulkChangeAction(controller); - - controller.done(); } private void defineChangelogAction(NewController controller) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 6960a88c93e..528aa02d5af 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -22,6 +22,7 @@ package org.sonar.server.issue.ws; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import com.google.common.io.Resources; import org.apache.commons.lang.BooleanUtils; import org.sonar.api.i18n.I18n; @@ -34,6 +35,7 @@ import org.sonar.api.resources.Languages; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; 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.user.User; import org.sonar.api.user.UserFinder; @@ -42,35 +44,37 @@ import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.component.ComponentDto; -import org.sonar.core.issue.db.IssueChangeDao; import org.sonar.core.persistence.DbSession; import org.sonar.markdown.Markdown; import org.sonar.server.db.DbClient; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.issue.IssueQuery; import org.sonar.server.issue.IssueQueryService; import org.sonar.server.issue.IssueService; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.issue.filter.IssueFilterParameters; import org.sonar.server.issue.index.IssueDoc; +import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.rule.Rule; import org.sonar.server.rule.RuleService; -import org.sonar.server.search.FacetValue; -import org.sonar.server.search.QueryContext; -import org.sonar.server.search.Result; -import org.sonar.server.search.ws.SearchRequestHandler; import org.sonar.server.user.UserSession; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; import static com.google.common.collect.Sets.newHashSet; -public class SearchAction extends SearchRequestHandler { - +public class SearchAction implements BaseIssuesWsAction { public static final String SEARCH_ACTION = "search"; @@ -84,7 +88,6 @@ public class SearchAction extends SearchRequestHandler { private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. "; - private final IssueChangeDao issueChangeDao; private final IssueService service; private final IssueActionsWriter actionsWriter; @@ -97,11 +100,9 @@ public class SearchAction extends SearchRequestHandler { private final Durations durations; private final Languages languages; - public SearchAction(DbClient dbClient, IssueChangeDao issueChangeDao, IssueService service, IssueActionsWriter actionsWriter, IssueQueryService issueQueryService, + public SearchAction(DbClient dbClient, IssueService service, IssueActionsWriter actionsWriter, IssueQueryService issueQueryService, RuleService ruleService, ActionPlanService actionPlanService, UserFinder userFinder, I18n i18n, Durations durations, Languages languages) { - super(SEARCH_ACTION); this.dbClient = dbClient; - this.issueChangeDao = issueChangeDao; this.service = service; this.actionsWriter = actionsWriter; this.issueQueryService = issueQueryService; @@ -114,12 +115,22 @@ public class SearchAction extends SearchRequestHandler { } @Override - protected void doDefinition(WebService.NewAction action) { - action.setDescription("Get a list of issues. If the number of issues is greater than 10,000, only the first 10,000 ones are returned by the web service. " + - "Requires Browse permission on project(s)") + public void define(WebService.NewController controller) { + WebService.NewAction action = controller + .createAction(SEARCH_ACTION) + .setHandler(this) + .setDescription( + "Get a list of issues. If the number of issues is greater than 10,000, only the first 10,000 ones are returned by the web service. Requires Browse permission on project(s)") .setSince("3.6") .setResponseExample(Resources.getResource(this.getClass(), "example-search.json")); + action.addPagingParams(100); + action.createParam(WebService.Param.FACETS) + .setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.") + .setPossibleValues(IssueIndex.SUPPORTED_FACETS); + action.addSortParams(IssueQuery.SORTS, null, true); + // TODO support param "f" + addComponentRelatedParams(action); action.createParam(IssueFilterParameters.ISSUES) .setDescription("Comma-separated list of issue keys") @@ -182,14 +193,6 @@ public class SearchAction extends SearchRequestHandler { action.createParam(IssueFilterParameters.CREATED_BEFORE) .setDescription("To retrieve issues created before the given date (exclusive). Format: date or datetime ISO formats") .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)"); - action.createParam(SearchRequestHandler.PARAM_SORT) - .setDescription("Sort field") - .setDeprecatedKey(IssueFilterParameters.SORT) - .setPossibleValues(IssueQuery.SORTS); - action.createParam(SearchRequestHandler.PARAM_ASCENDING) - .setDeprecatedKey(IssueFilterParameters.ASC) - .setDescription("Ascending sort") - .setBooleanPossibleValues(); action.createParam(IssueFilterParameters.IGNORE_PAGING) .setDescription("Return the full list of issues, regardless of paging. For internal use only") .setBooleanPossibleValues() @@ -199,7 +202,6 @@ public class SearchAction extends SearchRequestHandler { } private void addComponentRelatedParams(WebService.NewAction action) { - action.createParam(IssueFilterParameters.ON_COMPONENT_ONLY) .setDescription("Return only issues at a component's level, not on its descendants (modules, directories, files, etc). " + "This parameter is only considered when componentKeys or componentUuids is set. " + @@ -260,49 +262,34 @@ public class SearchAction extends SearchRequestHandler { } @Override - protected IssueQuery doQuery(Request request) { - return issueQueryService.createFromRequest(request); + public final void handle(Request request, Response response) throws Exception { + SearchOptions options = new SearchOptions(); + options.setPage(request.mandatoryParamAsInt(IssuesWs.Param.PAGE), request.mandatoryParamAsInt(IssuesWs.Param.PAGE_SIZE)); + options.addFacets(request.paramAsStrings(WebService.Param.FACETS)); + + IssueQuery query = issueQueryService.createFromRequest(request); + SearchResult result = execute(query, options); + + JsonWriter json = response.newJsonWriter().beginObject(); + options.writeJson(json, result.getTotal()); + options.writeDeprecatedJson(json, result.getTotal()); + + writeResponse(request, result, json); + if (!options.getFacets().isEmpty()) { + writeFacets(request, options, result, json); + } + json.endObject().close(); } - @Override - protected Result doSearch(IssueQuery query, QueryContext context) { + private SearchResult execute(IssueQuery query, SearchOptions options) { Collection components = query.componentUuids(); if (components != null && components.size() == 1 && BooleanUtils.isTrue(query.ignorePaging())) { - context.setShowFullResult(true); + options.disableLimit(); } - return service.search(query, context); + return service.search(query, options); } - @Override - @CheckForNull - protected Collection possibleFields() { - return Collections.emptyList(); - } - - @Override - @CheckForNull - protected Collection possibleFacets() { - return Arrays.asList(new String[]{ - IssueFilterParameters.SEVERITIES, - IssueFilterParameters.STATUSES, - IssueFilterParameters.RESOLUTIONS, - IssueFilterParameters.ACTION_PLANS, - IssueFilterParameters.PROJECT_UUIDS, - IssueFilterParameters.RULES, - IssueFilterParameters.ASSIGNEES, - IssueFilterParameters.REPORTERS, - IssueFilterParameters.AUTHORS, - IssueFilterParameters.MODULE_UUIDS, - IssueFilterParameters.FILE_UUIDS, - IssueFilterParameters.DIRECTORIES, - IssueFilterParameters.LANGUAGES, - IssueFilterParameters.TAGS, - IssueFilterParameters.CREATED_AT, - }); - } - - @Override - protected void doContextResponse(Request request, QueryContext context, Result result, JsonWriter json) { + private void writeResponse(Request request, SearchResult result, JsonWriter json) { List issueKeys = newArrayList(); Set ruleKeys = newHashSet(); Set projectUuids = newHashSet(); @@ -313,21 +300,19 @@ public class SearchAction extends SearchRequestHandler { Map componentsByUuid = newHashMap(); Multimap commentsByIssues = ArrayListMultimap.create(); Collection componentDtos = newHashSet(); - List projectDtos = newArrayList(); Map projectsByComponentUuid = newHashMap(); - for (Issue issue : result.getHits()) { - IssueDoc issueDoc = (IssueDoc) issue; - issueKeys.add(issue.key()); - ruleKeys.add(issue.ruleKey()); + for (IssueDoc issueDoc : result.getDocs()) { + issueKeys.add(issueDoc.key()); + ruleKeys.add(issueDoc.ruleKey()); projectUuids.add(issueDoc.projectUuid()); componentUuids.add(issueDoc.componentUuid()); - actionPlanKeys.add(issue.actionPlanKey()); - if (issue.reporter() != null) { - userLogins.add(issue.reporter()); + actionPlanKeys.add(issueDoc.actionPlanKey()); + if (issueDoc.reporter() != null) { + userLogins.add(issueDoc.reporter()); } - if (issue.assignee() != null) { - userLogins.add(issue.assignee()); + if (issueDoc.assignee() != null) { + userLogins.add(issueDoc.assignee()); } } @@ -342,7 +327,7 @@ public class SearchAction extends SearchRequestHandler { DbSession session = dbClient.openSession(false); try { - List comments = issueChangeDao.selectCommentsByIssues(session, issueKeys); + List comments = dbClient.issueChangeDao().selectCommentsByIssues(session, issueKeys); for (DefaultIssueComment issueComment : comments) { userLogins.add(issueComment.userLogin()); commentsByIssues.put(issueComment.issueKey(), issueComment); @@ -357,7 +342,7 @@ public class SearchAction extends SearchRequestHandler { projectUuids.add(component.projectUuid()); } - projectDtos = dbClient.componentDao().getByUuids(session, projectUuids); + List projectDtos = dbClient.componentDao().getByUuids(session, projectUuids); componentDtos.addAll(projectDtos); for (ComponentDto componentDto : componentDtos) { componentsByUuid.put(componentDto.uuid(), componentDto); @@ -378,28 +363,24 @@ public class SearchAction extends SearchRequestHandler { writeUsers(json, usersByLogin); writeActionPlans(json, actionPlanByKeys.values()); writeLanguages(json); - - // TODO remove legacy paging. Handled by the SearchRequestHandler - writeLegacyPaging(context, json, result); } - private void collectRuleKeys(Request request, Result result, Set ruleKeys) { - Collection facetRules = result.getFacetValues(IssueFilterParameters.RULES); + private void collectRuleKeys(Request request, SearchResult result, Set ruleKeys) { + Set facetRules = result.getFacets().getBucketKeys(IssueFilterParameters.RULES); if (facetRules != null) { - for (FacetValue rule: facetRules) { - ruleKeys.add(RuleKey.parse(rule.getKey())); + for (String rule : facetRules) { + ruleKeys.add(RuleKey.parse(rule)); } } List rulesFromRequest = request.paramAsStrings(IssueFilterParameters.RULES); - if (rulesFromRequest != null ) { - for (String ruleKey: rulesFromRequest) { + if (rulesFromRequest != null) { + for (String ruleKey : rulesFromRequest) { ruleKeys.add(RuleKey.parse(ruleKey)); } } } - @Override - protected void writeFacets(Request request, QueryContext context, Result results, JsonWriter json) { + protected void writeFacets(Request request, SearchOptions options, SearchResult results, JsonWriter json) { addMandatoryFacetValues(results, IssueFilterParameters.SEVERITIES, Severity.ALL); addMandatoryFacetValues(results, IssueFilterParameters.STATUSES, Issue.STATUSES); List resolutions = Lists.newArrayList(""); @@ -429,37 +410,51 @@ public class SearchAction extends SearchRequestHandler { addMandatoryFacetValues(results, IssueFilterParameters.ACTION_PLANS, actionPlans); addMandatoryFacetValues(results, IssueFilterParameters.COMPONENT_UUIDS, request.paramAsStrings(IssueFilterParameters.COMPONENT_UUIDS)); - super.writeFacets(request, context, results, json); + json.name("facets").beginArray(); + for (String facetName : options.getFacets()) { + json.beginObject(); + json.prop("property", facetName); + json.name("values").beginArray(); + if (results.getFacets().contains(facetName)) { + Set itemsFromFacets = Sets.newHashSet(); + for (Map.Entry bucket : results.getFacets().get(facetName).entrySet()) { + itemsFromFacets.add(bucket.getKey()); + json.beginObject(); + json.prop("val", bucket.getKey()); + json.prop("count", bucket.getValue()); + json.endObject(); + } + addZeroFacetsForSelectedItems(request, facetName, itemsFromFacets, json); + } + json.endArray().endObject(); + } + json.endArray(); } - private void collectFacetsData(Request request, Result result, Set projectUuids, Set componentUuids, List userLogins, Set actionPlanKeys) { - collectFacetKeys(result, IssueFilterParameters.PROJECT_UUIDS, projectUuids); + private void collectFacetsData(Request request, SearchResult result, Set projectUuids, Set componentUuids, List userLogins, + Set actionPlanKeys) { + collectBucketKeys(result, IssueFilterParameters.PROJECT_UUIDS, projectUuids); collectParameterValues(request, IssueFilterParameters.PROJECT_UUIDS, projectUuids); - collectFacetKeys(result, IssueFilterParameters.COMPONENT_UUIDS, componentUuids); + collectBucketKeys(result, IssueFilterParameters.COMPONENT_UUIDS, componentUuids); collectParameterValues(request, IssueFilterParameters.COMPONENT_UUIDS, componentUuids); - collectFacetKeys(result, IssueFilterParameters.FILE_UUIDS, componentUuids); + collectBucketKeys(result, IssueFilterParameters.FILE_UUIDS, componentUuids); collectParameterValues(request, IssueFilterParameters.FILE_UUIDS, componentUuids); - collectFacetKeys(result, IssueFilterParameters.MODULE_UUIDS, componentUuids); + collectBucketKeys(result, IssueFilterParameters.MODULE_UUIDS, componentUuids); collectParameterValues(request, IssueFilterParameters.MODULE_UUIDS, componentUuids); collectParameterValues(request, IssueFilterParameters.COMPONENT_ROOT_UUIDS, componentUuids); - collectFacetKeys(result, IssueFilterParameters.ASSIGNEES, userLogins); + collectBucketKeys(result, IssueFilterParameters.ASSIGNEES, userLogins); collectParameterValues(request, IssueFilterParameters.ASSIGNEES, userLogins); - collectFacetKeys(result, IssueFilterParameters.REPORTERS, userLogins); + collectBucketKeys(result, IssueFilterParameters.REPORTERS, userLogins); collectParameterValues(request, IssueFilterParameters.REPORTERS, userLogins); - collectFacetKeys(result, IssueFilterParameters.ACTION_PLANS, actionPlanKeys); + collectBucketKeys(result, IssueFilterParameters.ACTION_PLANS, actionPlanKeys); collectParameterValues(request, IssueFilterParameters.ACTION_PLANS, actionPlanKeys); } - private void collectFacetKeys(Result result, String facetName, Collection facetKeys) { - Collection facetValues = result.getFacetValues(facetName); - if (facetValues != null) { - for (FacetValue project : facetValues) { - facetKeys.add(project.getKey()); - } - } + private void collectBucketKeys(SearchResult result, String facetName, Collection bucketKeys) { + bucketKeys.addAll(result.getFacets().getBucketKeys(facetName)); } private void collectParameterValues(Request request, String facetName, Collection facetKeys) { @@ -469,28 +464,6 @@ public class SearchAction extends SearchRequestHandler { } } - private void writeLegacyPaging(QueryContext context, JsonWriter json, Result result) { - // TODO remove with stas on HTML side - json.prop("maxResultsReached", false); - - long pages = context.getLimit(); - if (pages > 0) { - pages = result.getTotal() / context.getLimit(); - if (result.getTotal() % context.getLimit() > 0) { - pages++; - } - } - - json.name("paging").beginObject() - .prop("pageIndex", context.getPage()) - .prop("pageSize", context.getLimit()) - .prop("total", result.getTotal()) - // TODO Remove as part of Front-end rework on Issue Domain - .prop("fTotal", i18n.formatInteger(UserSession.get().locale(), (int) result.getTotal())) - .prop("pages", pages) - .endObject(); - } - // TODO change to use the RuleMapper private void writeRules(JsonWriter json, Collection rules) { json.name("rules").beginArray(); @@ -508,11 +481,12 @@ public class SearchAction extends SearchRequestHandler { json.endArray(); } - private void writeIssues(Result result, Multimap commentsByIssues, Map usersByLogin, Map actionPlanByKeys, + private void writeIssues(SearchResult result, Multimap commentsByIssues, Map usersByLogin, + Map actionPlanByKeys, Map componentsByUuid, Map projectsByComponentUuid, @Nullable List extraFields, JsonWriter json) { json.name("issues").beginArray(); - for (Issue issue : result.getHits()) { + for (IssueDoc issue : result.getDocs()) { json.beginObject(); String actionPlanKey = issue.actionPlanKey(); @@ -520,7 +494,7 @@ public class SearchAction extends SearchRequestHandler { ComponentDto project = null, subProject = null; if (file != null) { project = projectsByComponentUuid.get(file.uuid()); - if (! file.projectUuid().equals(file.moduleUuid())) { + if (!file.projectUuid().equals(file.moduleUuid())) { subProject = componentsByUuid.get(file.moduleUuid()); } } @@ -565,7 +539,7 @@ public class SearchAction extends SearchRequestHandler { Collection tags = issue.tags(); if (tags != null && !tags.isEmpty()) { json.name("tags").beginArray(); - for (String tag: tags) { + for (String tag : tags) { json.value(tag); } json.endArray(); @@ -806,4 +780,28 @@ public class SearchAction extends SearchRequestHandler { return null; } + protected void addMandatoryFacetValues(SearchResult results, String facetName, @Nullable List mandatoryValues) { + Map buckets = results.getFacets().get(facetName); + if (buckets != null && mandatoryValues != null) { + for (String mandatoryValue : mandatoryValues) { + if (!buckets.containsKey(mandatoryValue)) { + buckets.put(mandatoryValue, 0L); + } + } + } + } + + private void addZeroFacetsForSelectedItems(Request request, String facetName, Set itemsFromFacets, JsonWriter json) { + List requestParams = request.paramAsStrings(facetName); + if (requestParams != null) { + for (String param : requestParams) { + if (!itemsFromFacets.contains(param)) { + json.beginObject(); + json.prop("val", param); + json.prop("count", 0); + json.endObject(); + } + } + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java index 1119fc96a6f..f6ccf7dc444 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java @@ -19,11 +19,8 @@ */ package org.sonar.server.issue.ws; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableSet; +import com.google.common.base.Objects; import org.sonar.api.server.ws.Request; -import org.sonar.api.server.ws.RequestHandler; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.NewAction; @@ -31,14 +28,13 @@ import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.issue.IssueService; import java.util.Collection; +import java.util.Collections; +import java.util.List; /** - * Set tags on an issue. - * @since 5.1 + * Set tags on an issue */ -public class SetTagsAction implements RequestHandler { - - private static final Splitter WS_TAGS_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults(); +public class SetTagsAction implements BaseIssuesWsAction { private final IssueService service; @@ -46,7 +42,8 @@ public class SetTagsAction implements RequestHandler { this.service = service; } - void define(WebService.NewController controller) { + @Override + public void define(WebService.NewController controller) { NewAction action = controller.createAction("set_tags") .setHandler(this) .setPost(true) @@ -64,8 +61,8 @@ public class SetTagsAction implements RequestHandler { @Override public void handle(Request request, Response response) throws Exception { String key = request.mandatoryParam("key"); - String tags = Strings.nullToEmpty(request.param("tags")); - Collection resultTags = service.setTags(key, ImmutableSet.copyOf(WS_TAGS_SPLITTER.split(tags))); + List tags = Objects.firstNonNull(request.paramAsStrings("tags"), Collections.emptyList()); + Collection resultTags = service.setTags(key, tags); JsonWriter json = response.newJsonWriter().beginObject().name("tags").beginArray(); for (String tag : resultTags) { json.value(tag); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java index ab47393cc39..c3da60898a4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java @@ -20,7 +20,9 @@ package org.sonar.server.issue.ws; import com.google.common.io.Resources; -import org.sonar.api.server.ws.*; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.NewAction; import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.issue.IssueService; @@ -29,7 +31,7 @@ import org.sonar.server.issue.IssueService; * List issue tags matching a given query. * @since 5.1 */ -public class TagsAction implements RequestHandler { +public class TagsAction implements BaseIssuesWsAction { private final IssueService service; @@ -37,7 +39,8 @@ public class TagsAction implements RequestHandler { this.service = service; } - void define(WebService.NewController controller) { + @Override + public void define(WebService.NewController controller) { NewAction action = controller.createAction("tags") .setHandler(this) .setSince("5.1") diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/StickyFacetBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/search/StickyFacetBuilder.java index ba932a23915..53b36c093ba 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/StickyFacetBuilder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/StickyFacetBuilder.java @@ -35,12 +35,10 @@ import java.util.Map; public class StickyFacetBuilder { private static final int FACET_DEFAULT_MIN_DOC_COUNT = 1; - private static final int FACET_DEFAULT_SIZE = 10; - private QueryBuilder query; - - private Map filters; + private final QueryBuilder query; + private final Map filters; public StickyFacetBuilder(QueryBuilder query, Map filters) { this.query = query; diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java b/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java index 0cbf88ebb1d..9d0393a340a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java @@ -25,20 +25,18 @@ import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.sort.SortOrder; -import org.sonar.api.ServerComponent; +import org.sonar.server.es.BaseIndex; import org.sonar.server.es.EsClient; import org.sonar.server.exceptions.NotFoundException; import java.util.List; -public class SourceLineIndex implements ServerComponent { +public class SourceLineIndex extends BaseIndex { private static final int MAX_RESULT = 500000; - private final EsClient esClient; - public SourceLineIndex(EsClient esClient) { - this.esClient = esClient; + super(esClient); } /** @@ -61,7 +59,7 @@ public class SourceLineIndex implements ServerComponent { } int toLimited = size + from - 1; - for (SearchHit hit : esClient.prepareSearch(SourceLineIndexDefinition.INDEX) + for (SearchHit hit : getClient().prepareSearch(SourceLineIndexDefinition.INDEX) .setTypes(SourceLineIndexDefinition.TYPE) .setSize(size) .setQuery(QueryBuilders.boolQuery() @@ -79,7 +77,7 @@ public class SourceLineIndex implements ServerComponent { public SourceLineDoc getLine(String fileUuid, int line) { Preconditions.checkArgument(line > 0, "Line should be greater than 0"); - SearchRequestBuilder request = esClient.prepareSearch(SourceLineIndexDefinition.INDEX) + SearchRequestBuilder request = getClient().prepareSearch(SourceLineIndexDefinition.INDEX) .setTypes(SourceLineIndexDefinition.TYPE) .setSize(1) .setQuery(QueryBuilders.boolQuery() diff --git a/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-search.json b/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-search.json index ff8974c9fc5..8494c14e03e 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-search.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-search.json @@ -1,12 +1,7 @@ { - "securityExclusions": false, - "maxResultsReached": false, - "paging": { - "pageIndex": 1, - "pageSize": 5, - "total": 206, - "pages": 42 - }, + "total": 206, + "p": 1, + "ps": 5, "issues": [ { "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", diff --git a/server/sonar-server/src/test/java/org/sonar/core/computation/dbcleaner/ProjectCleanerTest.java b/server/sonar-server/src/test/java/org/sonar/core/computation/dbcleaner/ProjectCleanerTest.java index 0cb24b9a9f7..f2a58bd6107 100644 --- a/server/sonar-server/src/test/java/org/sonar/core/computation/dbcleaner/ProjectCleanerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/core/computation/dbcleaner/ProjectCleanerTest.java @@ -25,12 +25,15 @@ import org.junit.Test; import org.slf4j.Logger; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; -import org.sonar.core.persistence.DbSession; -import org.sonar.core.purge.*; import org.sonar.core.computation.dbcleaner.period.DefaultPeriodCleaner; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.purge.IdUuidPair; +import org.sonar.core.purge.PurgeConfiguration; +import org.sonar.core.purge.PurgeDao; +import org.sonar.core.purge.PurgeListener; +import org.sonar.core.purge.PurgeProfiler; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.properties.ProjectSettingsFactory; -import org.sonar.server.search.IndexClient; import java.util.Date; @@ -41,35 +44,25 @@ import static org.mockito.Mockito.*; public class ProjectCleanerTest { private ProjectCleaner sut; - private PurgeDao dao; - private PurgeProfiler profiler; - private DefaultPeriodCleaner periodCleaner; - private PurgeListener purgeListener; + private PurgeDao dao= mock(PurgeDao.class); + private PurgeProfiler profiler= mock(PurgeProfiler.class); + private DefaultPeriodCleaner periodCleaner= mock(DefaultPeriodCleaner.class); + private PurgeListener purgeListener= mock(PurgeListener.class); private ProjectSettingsFactory projectSettingsFactory; - private IndexClient indexClient; - private IssueIndex issueIndex; - private Settings settings; + private IssueIndex issueIndex= mock(IssueIndex.class); + private Settings settings = new Settings(); @Before public void before() throws Exception { - this.dao = mock(PurgeDao.class); - this.profiler = mock(PurgeProfiler.class); - this.periodCleaner = mock(DefaultPeriodCleaner.class); - this.purgeListener = mock(PurgeListener.class); - this.settings = mock(Settings.class); this.projectSettingsFactory = mock(ProjectSettingsFactory.class); when(projectSettingsFactory.newProjectSettings(any(DbSession.class), any(Long.class))).thenReturn(settings); - this.issueIndex = mock(IssueIndex.class); - this.indexClient = mock(IndexClient.class); - when(indexClient.get(IssueIndex.class)).thenReturn(issueIndex); - - this.sut = new ProjectCleaner(dao, periodCleaner, profiler, purgeListener, projectSettingsFactory, indexClient); + this.sut = new ProjectCleaner(dao, periodCleaner, profiler, purgeListener, projectSettingsFactory, issueIndex); } @Test public void no_profiling_when_property_is_false() throws Exception { - when(settings.getBoolean(CoreProperties.PROFILING_LOG_PROPERTY)).thenReturn(false); + settings.setProperty(CoreProperties.PROFILING_LOG_PROPERTY, false); when(projectSettingsFactory.newProjectSettings(any(DbSession.class), any(Long.class))).thenReturn(settings); sut.purge(mock(DbSession.class), mock(IdUuidPair.class)); @@ -83,12 +76,12 @@ public class ProjectCleanerTest { sut.purge(mock(DbSession.class), mock(IdUuidPair.class)); - verify(indexClient, never()).get(IssueIndex.class); + verifyZeroInteractions(issueIndex); } @Test public void profiling_when_property_is_true() throws Exception { - when(settings.getBoolean(CoreProperties.PROFILING_LOG_PROPERTY)).thenReturn(true); + settings.setProperty(CoreProperties.PROFILING_LOG_PROPERTY, true); sut.purge(mock(DbSession.class), mock(IdUuidPair.class)); @@ -97,7 +90,7 @@ public class ProjectCleanerTest { @Test public void call_period_cleaner_index_client_and_purge_dao() throws Exception { - when(settings.getInt(DbCleanerConstants.DAYS_BEFORE_DELETING_CLOSED_ISSUES)).thenReturn(5); + settings.setProperty(DbCleanerConstants.DAYS_BEFORE_DELETING_CLOSED_ISSUES, 5); sut.purge(mock(DbSession.class), mock(IdUuidPair.class)); diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java new file mode 100644 index 00000000000..e5139c098b4 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java @@ -0,0 +1,69 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.es; + +import com.google.common.base.Function; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchHits; +import org.junit.Test; +import org.mockito.Mockito; +import org.sonar.server.issue.index.IssueDoc; +import org.sonar.server.search.BaseDoc; +import org.sonar.test.TestUtils; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EsUtilsTest { + + @Test + public void convertToDocs_empty() throws Exception { + SearchHits hits = mock(SearchHits.class, Mockito.RETURNS_MOCKS); + List docs = EsUtils.convertToDocs(hits, new Function, BaseDoc>() { + @Override + public BaseDoc apply(Map input) { + return new IssueDoc(input); + } + }); + assertThat(docs).isEmpty(); + } + + @Test + public void convertToDocs() throws Exception { + SearchHits hits = mock(SearchHits.class, Mockito.RETURNS_MOCKS); + when(hits.getHits()).thenReturn(new SearchHit[]{mock(SearchHit.class)}); + List docs = EsUtils.convertToDocs(hits, new Function, BaseDoc>() { + @Override + public BaseDoc apply(Map input) { + return new IssueDoc(input); + } + }); + assertThat(docs).hasSize(1); + } + + @Test + public void util_class() throws Exception { + assertThat(TestUtils.hasOnlyPrivateConstructors(EsUtils.class)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/SearchOptionsTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/SearchOptionsTest.java new file mode 100644 index 00000000000..e4cac26fca3 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/es/SearchOptionsTest.java @@ -0,0 +1,156 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.es; + +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.server.search.QueryContext; + +import java.io.StringWriter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class SearchOptionsTest { + + @Test + public void defaults() throws Exception { + SearchOptions options = new SearchOptions(); + + assertThat(options.getFacets()).isEmpty(); + assertThat(options.getFields()).isEmpty(); + assertThat(options.getOffset()).isEqualTo(0); + assertThat(options.getLimit()).isEqualTo(10); + assertThat(options.getPage()).isEqualTo(1); + } + + @Test + public void page_shortcut_for_limit_and_offset() throws Exception { + SearchOptions options = new SearchOptions().setPage(3, 10); + + assertThat(options.getLimit()).isEqualTo(10); + assertThat(options.getOffset()).isEqualTo(20); + } + + @Test + public void page_starts_at_one() throws Exception { + SearchOptions options = new SearchOptions().setPage(1, 10); + assertThat(options.getLimit()).isEqualTo(10); + assertThat(options.getOffset()).isEqualTo(0); + assertThat(options.getPage()).isEqualTo(1); + } + + @Test + public void with_zero_page_size() throws Exception { + SearchOptions options = new SearchOptions().setPage(1, 0); + assertThat(options.getLimit()).isEqualTo(0); + assertThat(options.getOffset()).isEqualTo(0); + assertThat(options.getPage()).isEqualTo(0); + } + + @Test + public void page_must_be_strictly_positive() throws Exception { + try { + new SearchOptions().setPage(0, 10); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Page must be greater or equal to 1 (got 0)"); + } + } + + @Test + public void use_max_limit_if_negative() throws Exception { + SearchOptions options = new SearchOptions().setPage(2, -1); + assertThat(options.getLimit()).isEqualTo(SearchOptions.MAX_LIMIT); + } + + @Test + public void max_limit() throws Exception { + SearchOptions options = new SearchOptions().setLimit(42); + assertThat(options.getLimit()).isEqualTo(42); + + options.setLimit(SearchOptions.MAX_LIMIT + 10); + assertThat(options.getLimit()).isEqualTo(QueryContext.MAX_LIMIT); + } + + @Test + public void disable_limit() throws Exception { + SearchOptions options = new SearchOptions().disableLimit(); + assertThat(options.getLimit()).isEqualTo(999999); + } + + @Test + public void max_page_size() throws Exception { + SearchOptions options = new SearchOptions().setPage(3, QueryContext.MAX_LIMIT + 10); + assertThat(options.getOffset()).isEqualTo(QueryContext.MAX_LIMIT * 2); + assertThat(options.getLimit()).isEqualTo(QueryContext.MAX_LIMIT); + } + + @Test + public void hasField() throws Exception { + // parameter is missing -> all the fields are returned by default + SearchOptions options = new SearchOptions(); + assertThat(options.hasField("repo")).isTrue(); + + // parameter is set to empty -> all the fields are returned by default + options = new SearchOptions().addFields(""); + assertThat(options.hasField("repo")).isTrue(); + + // parameter is set -> return only the selected fields + options = new SearchOptions().addFields("name", "repo"); + assertThat(options.hasField("name")).isTrue(); + assertThat(options.hasField("repo")).isTrue(); + assertThat(options.hasField("severity")).isFalse(); + } + + @Test + public void writeJson() throws Exception { + SearchOptions options = new SearchOptions().setPage(3, 10); + StringWriter json = new StringWriter(); + JsonWriter jsonWriter = JsonWriter.of(json).beginObject(); + options.writeJson(jsonWriter, 42L); + jsonWriter.endObject().close(); + + JSONAssert.assertEquals("{\"total\": 42, \"p\": 3, \"ps\": 10}", json.toString(), true); + } + + @Test + public void writeDeprecatedJson() throws Exception { + SearchOptions options = new SearchOptions().setPage(3, 10); + StringWriter json = new StringWriter(); + JsonWriter jsonWriter = JsonWriter.of(json).beginObject(); + options.writeDeprecatedJson(jsonWriter, 42L); + jsonWriter.endObject().close(); + + JSONAssert.assertEquals("{\"paging\": {\"pageIndex\": 3, \"pageSize\": 10, \"total\": 42, \"fTotal\": \"42\", \"pages\": 5}}", json.toString(), true); + } + + @Test + public void writeDeprecatedJson_exact_nb_of_pages() throws Exception { + SearchOptions options = new SearchOptions().setPage(3, 10); + StringWriter json = new StringWriter(); + JsonWriter jsonWriter = JsonWriter.of(json).beginObject(); + options.writeDeprecatedJson(jsonWriter, 30L); + jsonWriter.endObject().close(); + + JSONAssert.assertEquals("{\"paging\": {\"pageIndex\": 3, \"pageSize\": 10, \"total\": 30, \"fTotal\": \"30\", \"pages\": 3}}", json.toString(), true); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java index 939b355c973..553a424f293 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java @@ -24,12 +24,11 @@ import org.junit.Test; import org.sonar.api.issue.Issue; import org.sonar.server.user.UserSession; -import java.util.List; +import java.util.Collection; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; - public class ActionTest { @Test @@ -37,9 +36,10 @@ public class ActionTest { try { new Action("") { @Override - boolean verify(Map properties, List issues, UserSession userSession) { + boolean verify(Map properties, Collection issues, UserSession userSession) { return false; } + @Override boolean execute(Map properties, Context context) { return false; @@ -55,9 +55,10 @@ public class ActionTest { try { new Action(null) { @Override - boolean verify(Map properties, List issues, UserSession userSession) { + boolean verify(Map properties, Collection issues, UserSession userSession) { return false; } + @Override boolean execute(Map properties, Context context) { return false; diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java index 10a2efadbcc..b85e27a46e5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java @@ -21,7 +21,6 @@ package org.sonar.server.issue; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import org.junit.Before; import org.junit.Test; @@ -40,14 +39,16 @@ import org.sonar.core.issue.DefaultIssueFilter; import org.sonar.core.resource.ResourceDao; import org.sonar.core.resource.ResourceDto; import org.sonar.core.resource.ResourceQuery; +import org.sonar.server.es.SearchOptions; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.Message; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.issue.filter.IssueFilterService; -import org.sonar.server.search.QueryContext; import org.sonar.server.user.UserSession; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; import static com.google.common.collect.Lists.newArrayList; @@ -57,10 +58,7 @@ import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class InternalRubyIssueServiceTest { @@ -563,7 +561,7 @@ public class InternalRubyIssueServiceTest { @Test public void execute_issue_filter_from_issue_query() { service.execute(Maps.newHashMap()); - verify(issueFilterService).execute(any(IssueQuery.class), any(QueryContext.class)); + verify(issueFilterService).execute(any(IssueQuery.class), any(SearchOptions.class)); } @Test @@ -584,14 +582,14 @@ public class InternalRubyIssueServiceTest { service.execute(10L, overrideProps); ArgumentCaptor issueQueryArgumentCaptor = ArgumentCaptor.forClass(IssueQuery.class); - ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(QueryContext.class); + ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(SearchOptions.class); verify(issueFilterService).execute(issueQueryArgumentCaptor.capture(), contextArgumentCaptor.capture()); verify(issueFilterService).find(eq(10L), any(UserSession.class)); - QueryContext queryContext = contextArgumentCaptor.getValue(); - assertThat(queryContext.getLimit()).isEqualTo(20); - assertThat(queryContext.getPage()).isEqualTo(2); + SearchOptions searchOptions = contextArgumentCaptor.getValue(); + assertThat(searchOptions.getLimit()).isEqualTo(20); + assertThat(searchOptions.getPage()).isEqualTo(2); } @Test @@ -687,25 +685,25 @@ public class InternalRubyIssueServiceTest { Map map = newHashMap(); map.put("pageSize", 10l); map.put("pageIndex", 50); - QueryContext context = InternalRubyIssueService.toContext(map); - assertThat(context.getLimit()).isEqualTo(10); - assertThat(context.getPage()).isEqualTo(50); + SearchOptions searchOptions = InternalRubyIssueService.toSearchOptions(map); + assertThat(searchOptions.getLimit()).isEqualTo(10); + assertThat(searchOptions.getPage()).isEqualTo(50); map = newHashMap(); map.put("pageSize", -1); map.put("pageIndex", 50); - context = InternalRubyIssueService.toContext(map); - assertThat(context.getLimit()).isEqualTo(500); - assertThat(context.getPage()).isEqualTo(1); + searchOptions = InternalRubyIssueService.toSearchOptions(map); + assertThat(searchOptions.getLimit()).isEqualTo(500); + assertThat(searchOptions.getPage()).isEqualTo(1); - context = InternalRubyIssueService.toContext(Maps.newHashMap()); - assertThat(context.getLimit()).isEqualTo(100); - assertThat(context.getPage()).isEqualTo(1); + searchOptions = InternalRubyIssueService.toSearchOptions(Maps.newHashMap()); + assertThat(searchOptions.getLimit()).isEqualTo(100); + assertThat(searchOptions.getPage()).isEqualTo(1); } @Test public void list_tags() throws Exception { - ImmutableSet tags = ImmutableSet.of("tag1", "tag2", "tag3"); + List tags = Arrays.asList("tag1", "tag2", "tag3"); when(issueService.listTags(null, 0)).thenReturn(tags); assertThat(service.listTags()).isEqualTo(tags); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java index 39cb75ea3fd..a781c2deeec 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java @@ -20,367 +20,327 @@ package org.sonar.server.issue; -import com.google.common.collect.Lists; -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.condition.Condition; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.notifications.NotificationManager; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Scopes; -import org.sonar.api.rules.Rule; -import org.sonar.core.component.ComponentDto; -import org.sonar.core.issue.db.IssueDto; -import org.sonar.core.issue.db.IssueStorage; -import org.sonar.core.persistence.DbSession; -import org.sonar.server.component.db.ComponentDao; -import org.sonar.server.db.DbClient; -import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.exceptions.UnauthorizedException; -import org.sonar.server.issue.db.IssueDao; -import org.sonar.server.issue.notification.IssueChangeNotification; -import org.sonar.server.rule.DefaultRuleFinder; -import org.sonar.server.rule.RuleTesting; -import org.sonar.server.search.QueryContext; -import org.sonar.server.user.MockUserSession; -import org.sonar.server.user.UserSession; - -import java.util.List; -import java.util.Map; - -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Maps.newHashMap; -import static com.google.common.collect.Sets.newHashSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyListOf; -import static org.mockito.Matchers.anyMap; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.*; - public class IssueBulkChangeServiceTest { - DbClient dbClient = mock(DbClient.class); - DbSession dbSession = mock(DbSession.class); - - IssueDao issueDao = mock(IssueDao.class); - IssueService issueService = mock(IssueService.class); - IssueStorage issueStorage = mock(IssueStorage.class); - DefaultRuleFinder ruleFinder = mock(DefaultRuleFinder.class); - ComponentDao componentDao = mock(ComponentDao.class); - NotificationManager notificationService = mock(NotificationManager.class); - - IssueBulkChangeService service; - - UserSession userSession = MockUserSession.create().setLogin("john").setUserId(10); - - DefaultIssue issue; - Rule rule; - ComponentDto project; - ComponentDto file; - - List actions; - - @Before - public void before() { - when(dbClient.openSession(false)).thenReturn(dbSession); - when(dbClient.componentDao()).thenReturn(componentDao); - when(dbClient.issueDao()).thenReturn(issueDao); - - rule = Rule.create("repo", "key").setName("the rule name"); - when(ruleFinder.findByKeys(newHashSet(rule.ruleKey()))).thenReturn(newArrayList(rule)); - - project = new ComponentDto() - .setId(1L) - .setKey("MyProject") - .setLongName("My Project") - .setQualifier(Qualifiers.PROJECT) - .setScope(Scopes.PROJECT); - when(componentDao.getByKeys(dbSession, newHashSet(project.key()))).thenReturn(newArrayList(project)); - - file = new ComponentDto() - .setId(2L) - .setParentProjectId(project.getId()) - .setKey("MyComponent") - .setLongName("My Component"); - when(componentDao.getByKeys(dbSession, newHashSet(file.key()))).thenReturn(newArrayList(file)); - - IssueDto issueDto = IssueTesting.newDto(RuleTesting.newDto(rule.ruleKey()).setId(50), file, project).setKee("ABCD"); - issue = issueDto.toDefaultIssue(); - - org.sonar.server.search.Result result = mock(org.sonar.server.search.Result.class); - when(result.getHits()).thenReturn(newArrayList((Issue) issue)); - when(issueService.search(any(IssueQuery.class), any(QueryContext.class))).thenReturn(result); - when(issueDao.selectByKeys(dbSession, newArrayList(issue.key()))).thenReturn(newArrayList(issueDto)); - - actions = newArrayList(); - service = new IssueBulkChangeService(dbClient, issueService, issueStorage, ruleFinder, notificationService, actions); - } - - @Test - public void should_execute_bulk_change() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - actions.add(new MockAction("assign")); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).hasSize(1); - assertThat(result.issuesNotChanged()).isEmpty(); - - verify(issueStorage).save(eq(issue)); - verifyNoMoreInteractions(issueStorage); - verify(notificationService).scheduleForSending(any(IssueChangeNotification.class)); - verifyNoMoreInteractions(notificationService); - } - - @Test - public void should_skip_send_notifications() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - actions.add(new MockAction("assign")); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, false); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).hasSize(1); - assertThat(result.issuesNotChanged()).isEmpty(); - - verify(issueStorage).save(eq(issue)); - verifyNoMoreInteractions(issueStorage); - verifyZeroInteractions(notificationService); - } - - @Test - public void should_execute_bulk_change_with_comment() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - - Action commentAction = mock(Action.class); - when(commentAction.key()).thenReturn("comment"); - when(commentAction.supports(any(Issue.class))).thenReturn(true); - when(commentAction.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); - when(commentAction.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true); - actions.add(commentAction); - actions.add(new MockAction("assign")); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, "my comment", true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).hasSize(1); - assertThat(result.issuesNotChanged()).isEmpty(); - - verify(commentAction).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class)); - verify(issueStorage).save(eq(issue)); - } - - @Test - public void should_execute_bulk_change_with_comment_only_on_changed_issues() { - IssueDto issueDto1 = IssueTesting.newDto(RuleTesting.newDto(rule.ruleKey()).setId(50), file, project).setKee("ABCD"); - IssueDto issueDto2 = IssueTesting.newDto(RuleTesting.newDto(rule.ruleKey()).setId(50), file, project).setKee("EFGH"); - - org.sonar.server.search.Result resultIssues = mock(org.sonar.server.search.Result.class); - when(resultIssues.getHits()).thenReturn(Lists.newArrayList(issueDto1.toDefaultIssue(), issueDto2.toDefaultIssue())); - when(issueService.search(any(IssueQuery.class), any(QueryContext.class))).thenReturn(resultIssues); - when(issueDao.selectByKeys(dbSession, newArrayList("ABCD", "EFGH"))).thenReturn(newArrayList(issueDto1, issueDto2)); - - Map properties = newHashMap(); - properties.put("issues", "ABCD,EFGH"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - - Action commentAction = mock(Action.class); - when(commentAction.key()).thenReturn("comment"); - when(commentAction.supports(any(Issue.class))).thenReturn(true); - when(commentAction.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); - when(commentAction.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true); - actions.add(commentAction); - - // This action will only be executed on the first issue, not the second - Action assignAction = mock(Action.class); - when(assignAction.key()).thenReturn("assign"); - when(assignAction.supports(any(Issue.class))).thenReturn(true).thenReturn(false); - when(assignAction.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); - when(assignAction.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true).thenReturn(false); - actions.add(assignAction); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, "my comment", true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).hasSize(1); - assertThat(result.issuesNotChanged()).hasSize(1); - - // Only one issue will receive the comment - verify(assignAction, times(1)).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class)); - verify(issueStorage).save(eq(issueDto1.toDefaultIssue())); - } - - @Test - public void should_save_once_per_issue() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign,set_severity"); - properties.put("assign.assignee", "fred"); - properties.put("set_severity.severity", "MINOR"); - - actions.add(new MockAction("set_severity")); - actions.add(new MockAction("assign")); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).hasSize(1); - assertThat(result.issuesNotChanged()).isEmpty(); - - verify(issueStorage, times(1)).save(eq(issue)); - verifyNoMoreInteractions(issueStorage); - verify(notificationService).scheduleForSending(any(IssueChangeNotification.class)); - verifyNoMoreInteractions(notificationService); - } - - @Test - public void should_not_execute_bulk_if_issue_does_not_support_action() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - actions.add(new MockAction("assign", true, true, false)); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).isEmpty(); - assertThat(result.issuesNotChanged()).hasSize(1); - - verifyZeroInteractions(issueStorage); - verifyZeroInteractions(notificationService); - } - - @Test - public void should_not_execute_bulk_if_action_is_not_verified() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - actions.add(new MockAction("assign", false, true, true)); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).isEmpty(); - assertThat(result.issuesNotChanged()).isEmpty(); - - verifyZeroInteractions(issueStorage); - verifyZeroInteractions(notificationService); - } - - @Test - public void should_not_execute_bulk_if_action_could_not_be_executed_on_issue() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - actions.add(new MockAction("assign", true, false, true)); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).isEmpty(); - assertThat(result.issuesNotChanged()).hasSize(1); - - verifyZeroInteractions(issueStorage); - verifyZeroInteractions(notificationService); - } - - @Test - public void should_not_execute_bulk_on_unexpected_error() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - - Action action = mock(Action.class); - when(action.key()).thenReturn("assign"); - when(action.supports(any(Issue.class))).thenReturn(true); - when(action.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); - doThrow(new RuntimeException("Error")).when(action).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class)); - actions.add(action); - - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); - assertThat(result.issuesChanged()).isEmpty(); - assertThat(result.issuesNotChanged()).hasSize(1); - - verifyZeroInteractions(issueStorage); - verifyZeroInteractions(notificationService); - } - - @Test - public void should_fail_if_user_not_logged() { - userSession = MockUserSession.create().setLogin(null); - - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "assign"); - properties.put("assign.assignee", "fred"); - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - try { - service.execute(issueBulkChangeQuery, userSession); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(UnauthorizedException.class); - } - verifyZeroInteractions(issueStorage); - verifyZeroInteractions(notificationService); - } - - @Test - public void should_fail_if_action_not_found() { - Map properties = newHashMap(); - properties.put("issues", "ABCD"); - properties.put("actions", "unknown"); - properties.put("unknown.unknown", "unknown"); - IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); - try { - service.execute(issueBulkChangeQuery, userSession); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("The action : 'unknown' is unknown"); - } - verifyZeroInteractions(issueStorage); - verifyZeroInteractions(notificationService); - } - - class MockAction extends Action { - - private boolean verify; - private boolean execute; - - public MockAction(String key, boolean verify, boolean execute, final boolean support) { - super(key); - this.verify = verify; - this.execute = execute; - setConditions(new Condition() { - @Override - public boolean matches(Issue issue) { - return support; - } - }); - } - - public MockAction(String key) { - this(key, true, true, true); - } - - @Override - boolean verify(Map properties, List issues, UserSession userSession) { - return verify; - } - - @Override - boolean execute(Map properties, Context context) { - return execute; - } - } +// DbClient dbClient = mock(DbClient.class); +// DbSession dbSession = mock(DbSession.class); +// +// IssueDao issueDao = mock(IssueDao.class); +// IssueService issueService = mock(IssueService.class); +// IssueStorage issueStorage = mock(IssueStorage.class); +// DefaultRuleFinder ruleFinder = mock(DefaultRuleFinder.class); +// ComponentDao componentDao = mock(ComponentDao.class); +// NotificationManager notificationService = mock(NotificationManager.class); +// +// IssueBulkChangeService service; +// +// UserSession userSession = MockUserSession.create().setLogin("john").setUserId(10); +// +// IssueDoc issue; +// Rule rule; +// ComponentDto project; +// ComponentDto file; +// +// List actions; +// +// @Before +// public void before() { +// when(dbClient.openSession(false)).thenReturn(dbSession); +// when(dbClient.componentDao()).thenReturn(componentDao); +// when(dbClient.issueDao()).thenReturn(issueDao); +// +// rule = Rule.create("repo", "key").setName("the rule name"); +// when(ruleFinder.findByKeys(newHashSet(rule.ruleKey()))).thenReturn(newArrayList(rule)); +// +// project = new ComponentDto() +// .setId(1L) +// .setKey("MyProject") +// .setLongName("My Project") +// .setQualifier(Qualifiers.PROJECT) +// .setScope(Scopes.PROJECT); +// when(componentDao.getByKeys(dbSession, newHashSet(project.key()))).thenReturn(newArrayList(project)); +// +// file = new ComponentDto() +// .setId(2L) +// .setParentProjectId(project.getId()) +// .setKey("MyComponent") +// .setLongName("My Component"); +// when(componentDao.getByKeys(dbSession, newHashSet(file.key()))).thenReturn(newArrayList(file)); +// +// IssueDoc issueDto = IssueTesting.newDoc("ABCD", file).setRuleKey(rule.ruleKey().toString()); +// issue = issueDto.toDefaultIssue(); +// +// SearchResult result = mock(SearchResult.class); +// when(result.getDocs()).thenReturn(newArrayList((IssueDoc) issue)); +// when(issueService.search(any(IssueQuery.class), any(SearchOptions.class))).thenReturn(result); +// when(issueDao.selectByKeys(dbSession, newArrayList(issue.key()))).thenReturn(newArrayList(issueDto)); +// +// actions = newArrayList(); +// service = new IssueBulkChangeService(dbClient, issueService, issueStorage, ruleFinder, notificationService, actions); +// } +// +// @Test +// public void should_execute_bulk_change() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// actions.add(new MockAction("assign")); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).hasSize(1); +// assertThat(result.issuesNotChanged()).isEmpty(); +// +// verify(issueStorage).save(eq(issue)); +// verifyNoMoreInteractions(issueStorage); +// verify(notificationService).scheduleForSending(any(IssueChangeNotification.class)); +// verifyNoMoreInteractions(notificationService); +// } +// +// @Test +// public void should_skip_send_notifications() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// actions.add(new MockAction("assign")); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, false); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).hasSize(1); +// assertThat(result.issuesNotChanged()).isEmpty(); +// +// verify(issueStorage).save(eq(issue)); +// verifyNoMoreInteractions(issueStorage); +// verifyZeroInteractions(notificationService); +// } +// +// @Test +// public void should_execute_bulk_change_with_comment() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// +// Action commentAction = mock(Action.class); +// when(commentAction.key()).thenReturn("comment"); +// when(commentAction.supports(any(Issue.class))).thenReturn(true); +// when(commentAction.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); +// when(commentAction.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true); +// actions.add(commentAction); +// actions.add(new MockAction("assign")); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, "my comment", true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).hasSize(1); +// assertThat(result.issuesNotChanged()).isEmpty(); +// +// verify(commentAction).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class)); +// verify(issueStorage).save(eq(issue)); +// } +// +// @Test +// public void should_execute_bulk_change_with_comment_only_on_changed_issues() { +// IssueDto issueDto1 = IssueTesting.newDto(RuleTesting.newDto(rule.ruleKey()).setId(50), file, project).setKee("ABCD"); +// IssueDto issueDto2 = IssueTesting.newDto(RuleTesting.newDto(rule.ruleKey()).setId(50), file, project).setKee("EFGH"); +// +// org.sonar.server.search.Result resultIssues = mock(org.sonar.server.search.Result.class); +// when(resultIssues.getHits()).thenReturn(Lists.newArrayList(issueDto1.toDefaultIssue(), issueDto2.toDefaultIssue())); +// when(issueService.search(any(IssueQuery.class), any(QueryContext.class))).thenReturn(resultIssues); +// when(issueDao.selectByKeys(dbSession, newArrayList("ABCD", "EFGH"))).thenReturn(newArrayList(issueDto1, issueDto2)); +// +// Map properties = newHashMap(); +// properties.put("issues", "ABCD,EFGH"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// +// Action commentAction = mock(Action.class); +// when(commentAction.key()).thenReturn("comment"); +// when(commentAction.supports(any(Issue.class))).thenReturn(true); +// when(commentAction.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); +// when(commentAction.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true); +// actions.add(commentAction); +// +// // This action will only be executed on the first issue, not the second +// Action assignAction = mock(Action.class); +// when(assignAction.key()).thenReturn("assign"); +// when(assignAction.supports(any(Issue.class))).thenReturn(true).thenReturn(false); +// when(assignAction.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); +// when(assignAction.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true).thenReturn(false); +// actions.add(assignAction); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, "my comment", true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).hasSize(1); +// assertThat(result.issuesNotChanged()).hasSize(1); +// +// // Only one issue will receive the comment +// verify(assignAction, times(1)).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class)); +// verify(issueStorage).save(eq(issueDto1.toDefaultIssue())); +// } +// +// @Test +// public void should_save_once_per_issue() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign,set_severity"); +// properties.put("assign.assignee", "fred"); +// properties.put("set_severity.severity", "MINOR"); +// +// actions.add(new MockAction("set_severity")); +// actions.add(new MockAction("assign")); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).hasSize(1); +// assertThat(result.issuesNotChanged()).isEmpty(); +// +// verify(issueStorage, times(1)).save(eq(issue)); +// verifyNoMoreInteractions(issueStorage); +// verify(notificationService).scheduleForSending(any(IssueChangeNotification.class)); +// verifyNoMoreInteractions(notificationService); +// } +// +// @Test +// public void should_not_execute_bulk_if_issue_does_not_support_action() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// actions.add(new MockAction("assign", true, true, false)); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).isEmpty(); +// assertThat(result.issuesNotChanged()).hasSize(1); +// +// verifyZeroInteractions(issueStorage); +// verifyZeroInteractions(notificationService); +// } +// +// @Test +// public void should_not_execute_bulk_if_action_is_not_verified() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// actions.add(new MockAction("assign", false, true, true)); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).isEmpty(); +// assertThat(result.issuesNotChanged()).isEmpty(); +// +// verifyZeroInteractions(issueStorage); +// verifyZeroInteractions(notificationService); +// } +// +// @Test +// public void should_not_execute_bulk_if_action_could_not_be_executed_on_issue() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// actions.add(new MockAction("assign", true, false, true)); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).isEmpty(); +// assertThat(result.issuesNotChanged()).hasSize(1); +// +// verifyZeroInteractions(issueStorage); +// verifyZeroInteractions(notificationService); +// } +// +// @Test +// public void should_not_execute_bulk_on_unexpected_error() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// +// Action action = mock(Action.class); +// when(action.key()).thenReturn("assign"); +// when(action.supports(any(Issue.class))).thenReturn(true); +// when(action.verify(anyMap(), anyListOf(Issue.class), any(UserSession.class))).thenReturn(true); +// doThrow(new RuntimeException("Error")).when(action).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class)); +// actions.add(action); +// +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession); +// assertThat(result.issuesChanged()).isEmpty(); +// assertThat(result.issuesNotChanged()).hasSize(1); +// +// verifyZeroInteractions(issueStorage); +// verifyZeroInteractions(notificationService); +// } +// +// @Test +// public void should_fail_if_user_not_logged() { +// userSession = MockUserSession.create().setLogin(null); +// +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "assign"); +// properties.put("assign.assignee", "fred"); +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// try { +// service.execute(issueBulkChangeQuery, userSession); +// fail(); +// } catch (Exception e) { +// assertThat(e).isInstanceOf(UnauthorizedException.class); +// } +// verifyZeroInteractions(issueStorage); +// verifyZeroInteractions(notificationService); +// } +// +// @Test +// public void should_fail_if_action_not_found() { +// Map properties = newHashMap(); +// properties.put("issues", "ABCD"); +// properties.put("actions", "unknown"); +// properties.put("unknown.unknown", "unknown"); +// IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties, true); +// try { +// service.execute(issueBulkChangeQuery, userSession); +// fail(); +// } catch (Exception e) { +// assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("The action : 'unknown' is unknown"); +// } +// verifyZeroInteractions(issueStorage); +// verifyZeroInteractions(notificationService); +// } +// +// class MockAction extends Action { +// +// private boolean verify; +// private boolean execute; +// +// public MockAction(String key, boolean verify, boolean execute, final boolean support) { +// super(key); +// this.verify = verify; +// this.execute = execute; +// setConditions(new Condition() { +// @Override +// public boolean matches(Issue issue) { +// return support; +// } +// }); +// } +// +// public MockAction(String key) { +// this(key, true, true, true); +// } +// +// @Override +// boolean verify(Map properties, List issues, UserSession userSession) { +// return verify; +// } +// +// @Override +// boolean execute(Map properties, Context context) { +// return execute; +// } +// } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java index b405eef9efd..0ae5cf970a6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java @@ -19,7 +19,11 @@ */ package org.sonar.server.issue; -import com.google.common.collect.*; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; @@ -44,6 +48,8 @@ import org.sonar.core.user.UserDto; import org.sonar.server.component.ComponentTesting; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.db.DbClient; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.issue.db.IssueDao; @@ -55,8 +61,6 @@ import org.sonar.server.permission.PermissionChange; import org.sonar.server.rule.RuleTesting; import org.sonar.server.rule.db.RuleDao; import org.sonar.server.search.BaseNormalizer; -import org.sonar.server.search.IndexClient; -import org.sonar.server.search.QueryContext; import org.sonar.server.source.index.SourceLineDoc; import org.sonar.server.source.index.SourceLineIndexer; import org.sonar.server.source.index.SourceLineResultSetIterator; @@ -66,7 +70,6 @@ import org.sonar.server.user.NewUser; import org.sonar.server.user.UserService; import org.sonar.server.user.db.GroupDao; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; @@ -85,7 +88,7 @@ public class IssueServiceMediumTest { public static ServerTester tester = new ServerTester(); DbClient db; - IndexClient indexClient; + IssueIndex IssueIndex; DbSession session; IssueService service; @@ -93,7 +96,7 @@ public class IssueServiceMediumTest { public void setUp() throws Exception { tester.clearDbAndIndexes(); db = tester.get(DbClient.class); - indexClient = tester.get(IndexClient.class); + IssueIndex = tester.get(IssueIndex.class); session = db.openSession(false); service = tester.get(IssueService.class); } @@ -126,14 +129,14 @@ public class IssueServiceMediumTest { saveIssue(IssueTesting.newDto(rule, file, project).setActionPlanKey("P1")); saveIssue(IssueTesting.newDto(rule, file, project).setActionPlanKey("P2").setResolution("NONE")); - org.sonar.server.search.Result result = service.search(IssueQuery.builder().build(), new QueryContext()); - assertThat(result.getHits()).hasSize(2); - assertThat(result.getFacets()).isEmpty(); + SearchResult result = service.search(IssueQuery.builder().build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(2); + assertThat(result.getFacets().getNames()).isEmpty(); - result = service.search(IssueQuery.builder().build(), new QueryContext().addFacets(Arrays.asList("actionPlans", "assignees"))); - assertThat(result.getFacets().keySet()).hasSize(2); - assertThat(result.getFacetKeys("actionPlans")).hasSize(2); - assertThat(result.getFacetKeys("assignees")).hasSize(1); + result = service.search(IssueQuery.builder().build(), new SearchOptions().addFacets("actionPlans", "assignees")); + assertThat(result.getFacets().getNames()).hasSize(2); + assertThat(result.getFacets().get("actionPlans")).hasSize(2); + assertThat(result.getFacets().get("assignees")).hasSize(1); } @Test @@ -162,11 +165,11 @@ public class IssueServiceMediumTest { IssueDto issue = saveIssue(IssueTesting.newDto(rule, file, project).setStatus(Issue.STATUS_OPEN)); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).status()).isEqualTo(Issue.STATUS_OPEN); + assertThat(IssueIndex.getByKey(issue.getKey()).status()).isEqualTo(Issue.STATUS_OPEN); service.doTransition(issue.getKey(), DefaultTransitions.CONFIRM); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).status()).isEqualTo(Issue.STATUS_CONFIRMED); + assertThat(IssueIndex.getByKey(issue.getKey()).status()).isEqualTo(Issue.STATUS_CONFIRMED); } @Test @@ -183,11 +186,11 @@ public class IssueServiceMediumTest { session.commit(); index(); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).assignee()).isNull(); + assertThat(IssueIndex.getByKey(issue.getKey()).assignee()).isNull(); service.assign(issue.getKey(), user.getLogin()); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).assignee()).isEqualTo("perceval"); + assertThat(IssueIndex.getByKey(issue.getKey()).assignee()).isEqualTo("perceval"); } @Test @@ -204,11 +207,11 @@ public class IssueServiceMediumTest { session.commit(); index(); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).assignee()).isEqualTo("perceval"); + assertThat(IssueIndex.getByKey(issue.getKey()).assignee()).isEqualTo("perceval"); service.assign(issue.getKey(), ""); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).assignee()).isNull(); + assertThat(IssueIndex.getByKey(issue.getKey()).assignee()).isNull(); } @Test @@ -242,11 +245,11 @@ public class IssueServiceMediumTest { session.commit(); index(); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).actionPlanKey()).isNull(); + assertThat(IssueIndex.getByKey(issue.getKey()).actionPlanKey()).isNull(); service.plan(issue.getKey(), actionPlanKey); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).actionPlanKey()).isEqualTo(actionPlanKey); + assertThat(IssueIndex.getByKey(issue.getKey()).actionPlanKey()).isEqualTo(actionPlanKey); } @Test @@ -260,11 +263,11 @@ public class IssueServiceMediumTest { db.actionPlanDao().save(new ActionPlanDto().setKey(actionPlanKey).setProjectId(project.getId())); IssueDto issue = saveIssue(IssueTesting.newDto(rule, file, project).setActionPlanKey(actionPlanKey)); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).actionPlanKey()).isEqualTo(actionPlanKey); + assertThat(IssueIndex.getByKey(issue.getKey()).actionPlanKey()).isEqualTo(actionPlanKey); service.plan(issue.getKey(), null); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).actionPlanKey()).isNull(); + assertThat(IssueIndex.getByKey(issue.getKey()).actionPlanKey()).isNull(); } @Test @@ -292,11 +295,11 @@ public class IssueServiceMediumTest { IssueDto issue = saveIssue(IssueTesting.newDto(rule, file, project).setSeverity(Severity.BLOCKER)); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).severity()).isEqualTo(Severity.BLOCKER); + assertThat(IssueIndex.getByKey(issue.getKey()).severity()).isEqualTo(Severity.BLOCKER); service.setSeverity(issue.getKey(), Severity.MINOR); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).severity()).isEqualTo(Severity.MINOR); + assertThat(IssueIndex.getByKey(issue.getKey()).severity()).isEqualTo(Severity.MINOR); } @Test @@ -311,7 +314,7 @@ public class IssueServiceMediumTest { Issue result = service.createManualIssue(file.key(), manualRule.getKey(), null, "Fix it", Severity.MINOR, 2d); - IssueDoc manualIssue = (IssueDoc) indexClient.get(IssueIndex.class).getByKey(result.key()); + IssueDoc manualIssue = (IssueDoc) IssueIndex.getByKey(result.key()); assertThat(manualIssue.componentUuid()).isEqualTo(file.uuid()); assertThat(manualIssue.projectUuid()).isEqualTo(project.uuid()); assertThat(manualIssue.ruleKey()).isEqualTo(manualRule.getKey()); @@ -337,7 +340,7 @@ public class IssueServiceMediumTest { Issue result = service.createManualIssue(file.key(), manualRule.getKey(), 1, "Fix it", Severity.MINOR, 2d); - IssueDoc manualIssue = (IssueDoc) indexClient.get(IssueIndex.class).getByKey(result.key()); + IssueDoc manualIssue = (IssueDoc) IssueIndex.getByKey(result.key()); assertThat(manualIssue.componentUuid()).isEqualTo(file.uuid()); assertThat(manualIssue.projectUuid()).isEqualTo(project.uuid()); assertThat(manualIssue.ruleKey()).isEqualTo(manualRule.getKey()); @@ -361,7 +364,7 @@ public class IssueServiceMediumTest { Issue result = service.createManualIssue(file.key(), manualRule.getKey(), null, "Fix it", null, 2d); - Issue manualIssue = indexClient.get(IssueIndex.class).getByKey(result.key()); + Issue manualIssue = IssueIndex.getByKey(result.key()); assertThat(manualIssue.severity()).isEqualTo(Severity.MAJOR); } @@ -377,7 +380,7 @@ public class IssueServiceMediumTest { Issue result = service.createManualIssue(file.key(), manualRule.getKey(), null, null, null, 2d); - Issue manualIssue = indexClient.get(IssueIndex.class).getByKey(result.key()); + Issue manualIssue = IssueIndex.getByKey(result.key()); assertThat(manualIssue.message()).isEqualTo("Manual rule name"); } @@ -397,7 +400,7 @@ public class IssueServiceMediumTest { Issue result = service.createManualIssue(file.key(), manualRule.getKey(), 1, "Fix it", Severity.MINOR, 2d); - IssueDoc manualIssue = (IssueDoc) indexClient.get(IssueIndex.class).getByKey(result.key()); + IssueDoc manualIssue = (IssueDoc) IssueIndex.getByKey(result.key()); assertThat(manualIssue.assignee()).isNull(); } @@ -415,7 +418,7 @@ public class IssueServiceMediumTest { Issue result = service.createManualIssue(file.key(), manualRule.getKey(), 1, "Fix it", Severity.MINOR, 2d); - IssueDoc manualIssue = (IssueDoc) indexClient.get(IssueIndex.class).getByKey(result.key()); + IssueDoc manualIssue = (IssueDoc) IssueIndex.getByKey(result.key()); assertThat(manualIssue.assignee()).isNull(); } @@ -496,7 +499,7 @@ public class IssueServiceMediumTest { ComponentDto file = newFile(project); saveIssue(IssueTesting.newDto(rule, file, project)); - List result = service.search(IssueQuery.builder().build(), new QueryContext()).getHits(); + List result = service.search(IssueQuery.builder().build(), new SearchOptions()).getDocs(); assertThat(result).hasSize(1); } @@ -548,15 +551,15 @@ public class IssueServiceMediumTest { IssueDto issue = saveIssue(IssueTesting.newDto(rule, file, project)); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).isEmpty(); + assertThat(IssueIndex.getByKey(issue.getKey()).tags()).isEmpty(); // Tags are lowercased service.setTags(issue.getKey(), ImmutableSet.of("bug", "Convention")); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("bug", "convention"); + assertThat(IssueIndex.getByKey(issue.getKey()).tags()).containsOnly("bug", "convention"); // nulls and empty tags are ignored service.setTags(issue.getKey(), Sets.newHashSet("security", null, "", "convention")); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("security", "convention"); + assertThat(IssueIndex.getByKey(issue.getKey()).tags()).containsOnly("security", "convention"); // tag validation try { @@ -564,14 +567,14 @@ public class IssueServiceMediumTest { } catch (Exception exception) { assertThat(exception).isInstanceOf(IllegalArgumentException.class); } - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("security", "convention"); + assertThat(IssueIndex.getByKey(issue.getKey()).tags()).containsOnly("security", "convention"); // unchanged tags service.setTags(issue.getKey(), ImmutableSet.of("convention", "security")); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("security", "convention"); + assertThat(IssueIndex.getByKey(issue.getKey()).tags()).containsOnly("security", "convention"); service.setTags(issue.getKey(), ImmutableSet.of()); - assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).isEmpty(); + assertThat(IssueIndex.getByKey(issue.getKey()).tags()).isEmpty(); } @Test @@ -585,7 +588,7 @@ public class IssueServiceMediumTest { saveIssue(IssueTesting.newDto(rule, file, project).setTags(ImmutableSet.of("convention", "java8", "bug")).setResolution(Issue.RESOLUTION_FIXED)); saveIssue(IssueTesting.newDto(rule, file, project).setTags(ImmutableSet.of("convention"))); - assertThat(service.listTagsForComponent(project.uuid(), 5)).contains(entry("convention", 3L), entry("bug", 2L), entry("java8", 1L)); + assertThat(service.listTagsForComponent(project.uuid(), 5)).containsOnly(entry("convention", 3L), entry("bug", 2L), entry("java8", 1L)); assertThat(service.listTagsForComponent(project.uuid(), 2)).contains(entry("convention", 3L), entry("bug", 2L)).doesNotContainEntry("java8", 1L); assertThat(service.listTagsForComponent("other", 10)).isEmpty(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterServiceTest.java index bc1dd5ba7f9..363de1c16e5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterServiceTest.java @@ -26,8 +26,6 @@ import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.web.UserRole; import org.sonar.core.issue.DefaultIssueFilter; import org.sonar.core.issue.IssueFilterSerializer; @@ -37,14 +35,15 @@ import org.sonar.core.issue.db.IssueFilterFavouriteDao; import org.sonar.core.issue.db.IssueFilterFavouriteDto; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.user.AuthorizationDao; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.issue.IssueQuery; +import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.index.IssueIndex; -import org.sonar.server.search.QueryContext; -import org.sonar.server.search.Result; import org.sonar.server.user.MockUserSession; import org.sonar.server.user.UserSession; @@ -528,14 +527,14 @@ public class IssueFilterServiceTest { @Test public void should_execute_from_issue_query() { IssueQuery issueQuery = IssueQuery.builder().build(); - QueryContext queryContext = new QueryContext().setPage(2, 50); + SearchOptions searchOptions = new SearchOptions().setPage(2, 50); - Result result = mock(Result.class); - when(result.getHits()).thenReturn(newArrayList((Issue) new DefaultIssue())); + SearchResult result = mock(SearchResult.class); + when(result.getDocs()).thenReturn(newArrayList((IssueDoc) new IssueDoc())); when(result.getTotal()).thenReturn(100L); - when(issueIndex.search(issueQuery, queryContext)).thenReturn(result); + when(issueIndex.search(issueQuery, searchOptions)).thenReturn(result); - IssueFilterService.IssueFilterResult issueFilterResult = service.execute(issueQuery, queryContext); + IssueFilterService.IssueFilterResult issueFilterResult = service.execute(issueQuery, searchOptions); assertThat(issueFilterResult.issues()).hasSize(1); assertThat(issueFilterResult.paging().total()).isEqualTo(100); assertThat(issueFilterResult.paging().pageIndex()).isEqualTo(2); 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 fb39f468d5e..bcb26d6a3e2 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 @@ -32,12 +32,11 @@ import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.KeyValueFormat; import org.sonar.core.component.ComponentDto; import org.sonar.server.component.ComponentTesting; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.issue.IssueQuery; import org.sonar.server.issue.IssueTesting; -import org.sonar.server.search.FacetValue; -import org.sonar.server.search.QueryContext; -import org.sonar.server.search.Result; import org.sonar.server.tester.ServerTester; import org.sonar.server.user.MockUserSession; import org.sonar.server.view.index.ViewDoc; @@ -46,12 +45,15 @@ import org.sonar.server.view.index.ViewIndexer; import javax.annotation.Nullable; import java.util.Arrays; -import java.util.Collection; import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * As soon as IssueIndex take {@link org.sonar.server.es.EsClient} in its constructor, ServerTester should be replaced by EsTester, it will make this test going faster ! @@ -126,9 +128,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("1", ComponentTesting.newFileDto(project)), IssueTesting.newDoc("2", ComponentTesting.newFileDto(project))); - assertThat(index.search(IssueQuery.builder().issueKeys(newArrayList("1", "2")).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().issueKeys(newArrayList("1")).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().issueKeys(newArrayList("3", "4")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().issueKeys(newArrayList("1", "2")).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().issueKeys(newArrayList("1")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().issueKeys(newArrayList("3", "4")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -145,8 +147,8 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE5", subModule), IssueTesting.newDoc("ISSUE6", ComponentTesting.newFileDto(subModule))); - assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).build(), new QueryContext()).getHits()).hasSize(6); - assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).build(), new SearchOptions()).getDocs()).hasSize(6); + assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -159,9 +161,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", ComponentTesting.newFileDto(project)), IssueTesting.newDoc("ISSUE3", ComponentTesting.newFileDto(project2))); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("projectUuids"))); - assertThat(result.getFacets()).containsOnlyKeys("projectUuids"); - assertThat(result.getFacets().get("projectUuids")).containsOnly(new FacetValue("ABCD", 2), new FacetValue("EFGH", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("projectUuids"))); + assertThat(result.getFacets().getNames()).containsOnly("projectUuids"); + assertThat(result.getFacets().get("projectUuids")).containsOnly(entry("ABCD", 2L), entry("EFGH", 1L)); } @Test @@ -177,15 +179,15 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file)); assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())) - .moduleUuids(newArrayList(file.uuid())).build(), new QueryContext()).getHits()).isEmpty(); + .moduleUuids(newArrayList(file.uuid())).build(), new SearchOptions()).getDocs()).isEmpty(); assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())) - .moduleUuids(newArrayList(module.uuid())).build(), new QueryContext()).getHits()).hasSize(1); + .moduleUuids(newArrayList(module.uuid())).build(), new SearchOptions()).getDocs()).hasSize(1); assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())) - .moduleUuids(newArrayList(subModule.uuid())).build(), new QueryContext()).getHits()).hasSize(1); + .moduleUuids(newArrayList(subModule.uuid())).build(), new SearchOptions()).getDocs()).hasSize(1); assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())) - .moduleUuids(newArrayList(project.uuid())).build(), new QueryContext()).getHits()).hasSize(1); + .moduleUuids(newArrayList(project.uuid())).build(), new SearchOptions()).getDocs()).hasSize(1); assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())) - .moduleUuids(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + .moduleUuids(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -207,20 +209,20 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE5", subModule), IssueTesting.newDoc("ISSUE6", file3)); - assertThat(index.search(IssueQuery.builder().setContextualized(true).fileUuids(newArrayList(file1.uuid(), file2.uuid(), file3.uuid())).build(), new QueryContext()) - .getHits()).hasSize(3); - assertThat(index.search(IssueQuery.builder().setContextualized(true).fileUuids(newArrayList(file1.uuid())).build(), new QueryContext()) - .getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().setContextualized(true).moduleRootUuids(newArrayList(subModule.uuid())).build(), new QueryContext()) - .getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().setContextualized(true).moduleRootUuids(newArrayList(module.uuid())).build(), new QueryContext()) - .getHits()).hasSize(4); - assertThat(index.search(IssueQuery.builder().setContextualized(true).projectUuids(newArrayList(project.uuid())).build(), new QueryContext()) - .getHits()).hasSize(6); - assertThat(index.search(IssueQuery.builder().setContextualized(true).viewUuids(newArrayList(view)).build(), new QueryContext()) - .getHits()).hasSize(6); - assertThat(index.search(IssueQuery.builder().setContextualized(true).projectUuids(newArrayList("unknown")).build(), new QueryContext()) - .getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().setContextualized(true).fileUuids(newArrayList(file1.uuid(), file2.uuid(), file3.uuid())).build(), new SearchOptions()) + .getDocs()).hasSize(3); + assertThat(index.search(IssueQuery.builder().setContextualized(true).fileUuids(newArrayList(file1.uuid())).build(), new SearchOptions()) + .getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().setContextualized(true).moduleRootUuids(newArrayList(subModule.uuid())).build(), new SearchOptions()) + .getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().setContextualized(true).moduleRootUuids(newArrayList(module.uuid())).build(), new SearchOptions()) + .getDocs()).hasSize(4); + assertThat(index.search(IssueQuery.builder().setContextualized(true).projectUuids(newArrayList(project.uuid())).build(), new SearchOptions()) + .getDocs()).hasSize(6); + assertThat(index.search(IssueQuery.builder().setContextualized(true).viewUuids(newArrayList(view)).build(), new SearchOptions()) + .getDocs()).hasSize(6); + assertThat(index.search(IssueQuery.builder().setContextualized(true).projectUuids(newArrayList("unknown")).build(), new SearchOptions()) + .getDocs()).isEmpty(); } @Test @@ -242,15 +244,15 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE5", subModule), IssueTesting.newDoc("ISSUE6", file3)); - assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); - assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).build(), new QueryContext()).getHits()).hasSize(6); - assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view)).build(), new QueryContext()).getHits()).hasSize(6); - assertThat(index.search(IssueQuery.builder().moduleUuids(newArrayList(module.uuid())).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().moduleUuids(newArrayList(subModule.uuid())).build(), new QueryContext()).getHits()).hasSize(1); // XXX - // Misleading - // ! - assertThat(index.search(IssueQuery.builder().fileUuids(newArrayList(file1.uuid())).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().fileUuids(newArrayList(file1.uuid(), file2.uuid(), file3.uuid())).build(), new QueryContext()).getHits()).hasSize(3); + assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); + assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).build(), new SearchOptions()).getDocs()).hasSize(6); + assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view)).build(), new SearchOptions()).getDocs()).hasSize(6); + assertThat(index.search(IssueQuery.builder().moduleUuids(newArrayList(module.uuid())).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().moduleUuids(newArrayList(subModule.uuid())).build(), new SearchOptions()).getDocs()).hasSize(1); // XXX + // Misleading + // ! + assertThat(index.search(IssueQuery.builder().fileUuids(newArrayList(file1.uuid())).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().fileUuids(newArrayList(file1.uuid(), file2.uuid(), file3.uuid())).build(), new SearchOptions()).getDocs()).hasSize(3); } @Test @@ -267,9 +269,10 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE4", file2), IssueTesting.newDoc("ISSUE5", file3)); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("fileUuids"))); - assertThat(result.getFacets()).containsOnlyKeys("fileUuids"); - assertThat(result.getFacets().get("fileUuids")).containsOnly(new FacetValue("A", 1), new FacetValue("ABCD", 1), new FacetValue("BCDE", 2), new FacetValue("CDEF", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("fileUuids"))); + assertThat(result.getFacets().getNames()).containsOnly("fileUuids"); + assertThat(result.getFacets().get("fileUuids")) + .containsOnly(entry("A", 1L), entry("ABCD", 1L), entry("BCDE", 2L), entry("CDEF", 1L)); } @Test @@ -282,9 +285,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file1).setDirectoryPath("/src/main/xoo"), IssueTesting.newDoc("ISSUE2", file2).setDirectoryPath("/")); - assertThat(index.search(IssueQuery.builder().directories(newArrayList("/src/main/xoo")).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().directories(newArrayList("/")).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().directories(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().directories(newArrayList("/src/main/xoo")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().directories(newArrayList("/")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().directories(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -297,9 +300,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file1).setDirectoryPath("/src/main/xoo"), IssueTesting.newDoc("ISSUE2", file2).setDirectoryPath("/")); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("directories"))); - assertThat(result.getFacets()).containsOnlyKeys("directories"); - assertThat(result.getFacets().get("directories")).containsOnly(new FacetValue("/src/main/xoo", 1), new FacetValue("/", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("directories"))); + assertThat(result.getFacets().getNames()).containsOnly("directories"); + assertThat(result.getFacets().get("directories")).containsOnly(entry("/src/main/xoo", 1L), entry("/", 1L)); } @Test @@ -322,10 +325,10 @@ public class IssueIndexMediumTest { String view2 = "CDEF"; indexView(view2, newArrayList(project2.uuid())); - assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view1)).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view2)).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view1, view2)).build(), new QueryContext()).getHits()).hasSize(3); - assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view1)).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view2)).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList(view1, view2)).build(), new SearchOptions()).getDocs()).hasSize(3); + assertThat(index.search(IssueQuery.builder().viewUuids(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -337,9 +340,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setSeverity(Severity.INFO), IssueTesting.newDoc("ISSUE2", file).setSeverity(Severity.MAJOR)); - assertThat(index.search(IssueQuery.builder().severities(newArrayList(Severity.INFO, Severity.MAJOR)).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().severities(newArrayList(Severity.INFO)).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().severities(newArrayList(Severity.BLOCKER)).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().severities(newArrayList(Severity.INFO, Severity.MAJOR)).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().severities(newArrayList(Severity.INFO)).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().severities(newArrayList(Severity.BLOCKER)).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -352,9 +355,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setSeverity(Severity.INFO), IssueTesting.newDoc("ISSUE3", file).setSeverity(Severity.MAJOR)); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("severities"))); - assertThat(result.getFacets()).containsOnlyKeys("severities"); - assertThat(result.getFacets().get("severities")).containsOnly(new FacetValue("INFO", 2), new FacetValue("MAJOR", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("severities"))); + assertThat(result.getFacets().getNames()).containsOnly("severities"); + assertThat(result.getFacets().get("severities")).containsOnly(entry("INFO", 2L), entry("MAJOR", 1L)); } @Test @@ -366,9 +369,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setStatus(Issue.STATUS_CLOSED), IssueTesting.newDoc("ISSUE2", file).setStatus(Issue.STATUS_OPEN)); - assertThat(index.search(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_CLOSED, Issue.STATUS_OPEN)).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_CLOSED)).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_CONFIRMED)).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_CLOSED, Issue.STATUS_OPEN)).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_CLOSED)).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_CONFIRMED)).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -381,9 +384,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setStatus(Issue.STATUS_CLOSED), IssueTesting.newDoc("ISSUE3", file).setStatus(Issue.STATUS_OPEN)); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("statuses"))); - assertThat(result.getFacets()).containsOnlyKeys("statuses"); - assertThat(result.getFacets().get("statuses")).containsOnly(new FacetValue("CLOSED", 2), new FacetValue("OPEN", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("statuses"))); + assertThat(result.getFacets().getNames()).containsOnly("statuses"); + assertThat(result.getFacets().get("statuses")).containsOnly(entry("CLOSED", 2L), entry("OPEN", 1L)); } @Test @@ -395,10 +398,10 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE), IssueTesting.newDoc("ISSUE2", file).setResolution(Issue.RESOLUTION_FIXED)); - assertThat(index.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_FIXED)).build(), new QueryContext()).getHits()) + assertThat(index.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_FIXED)).build(), new SearchOptions()).getDocs()) .hasSize(2); - assertThat(index.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE)).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_REMOVED)).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE)).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_REMOVED)).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -411,9 +414,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE), IssueTesting.newDoc("ISSUE3", file).setResolution(Issue.RESOLUTION_FIXED)); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("resolutions"))); - assertThat(result.getFacets()).containsOnlyKeys("resolutions"); - assertThat(result.getFacets().get("resolutions")).containsOnly(new FacetValue("FALSE-POSITIVE", 2), new FacetValue("FIXED", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("resolutions"))); + assertThat(result.getFacets().getNames()).containsOnly("resolutions"); + assertThat(result.getFacets().get("resolutions")).containsOnly(entry("FALSE-POSITIVE", 2L), entry("FIXED", 1L)); } @Test @@ -426,9 +429,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setStatus(Issue.STATUS_OPEN).setResolution(null), IssueTesting.newDoc("ISSUE3", file).setStatus(Issue.STATUS_OPEN).setResolution(null)); - assertThat(index.search(IssueQuery.builder().resolved(true).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().resolved(false).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().resolved(null).build(), new QueryContext()).getHits()).hasSize(3); + assertThat(index.search(IssueQuery.builder().resolved(true).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().resolved(false).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().resolved(null).build(), new SearchOptions()).getDocs()).hasSize(3); } @Test @@ -440,10 +443,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setActionPlanKey("plan1"), IssueTesting.newDoc("ISSUE2", file).setActionPlanKey("plan2")); - assertThat(index.search(IssueQuery.builder().actionPlans(newArrayList("plan1")).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().actionPlans(newArrayList("plan1", "plan2")).build(), new - QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().actionPlans(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().actionPlans(newArrayList("plan1")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().actionPlans(newArrayList("plan1", "plan2")).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().actionPlans(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -455,9 +457,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setActionPlanKey("plan1"), IssueTesting.newDoc("ISSUE2", file).setActionPlanKey("plan2")); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("actionPlans"))); - assertThat(result.getFacets()).containsOnlyKeys("actionPlans"); - assertThat(result.getFacets().get("actionPlans")).containsOnly(new FacetValue("plan1", 1), new FacetValue("plan2", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("actionPlans"))); + assertThat(result.getFacets().getNames()).containsOnly("actionPlans"); + assertThat(result.getFacets().get("actionPlans")).containsOnly(entry("plan1", 1L), entry("plan2", 1L)); } @Test @@ -470,9 +472,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setActionPlanKey(null), IssueTesting.newDoc("ISSUE3", file).setActionPlanKey(null)); - assertThat(index.search(IssueQuery.builder().planned(true).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().planned(false).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().planned(null).build(), new QueryContext()).getHits()).hasSize(3); + assertThat(index.search(IssueQuery.builder().planned(true).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().planned(false).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().planned(null).build(), new SearchOptions()).getDocs()).hasSize(3); } @Test @@ -483,8 +485,8 @@ public class IssueIndexMediumTest { indexIssues(IssueTesting.newDoc("ISSUE1", file).setRuleKey(ruleKey.toString())); - assertThat(index.search(IssueQuery.builder().rules(newArrayList(ruleKey)).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().rules(newArrayList(RuleKey.of("rule", "without issue"))).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().rules(newArrayList(ruleKey)).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().rules(newArrayList(RuleKey.of("rule", "without issue"))).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -495,9 +497,9 @@ public class IssueIndexMediumTest { indexIssues(IssueTesting.newDoc("ISSUE1", file).setRuleKey(ruleKey.toString()).setLanguage("xoo")); - assertThat(index.search(IssueQuery.builder().languages(newArrayList("xoo")).build(), new - QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().languages(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().languages(newArrayList("xoo")).build(), + new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().languages(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -508,9 +510,9 @@ public class IssueIndexMediumTest { indexIssues(IssueTesting.newDoc("ISSUE1", file).setRuleKey(ruleKey.toString()).setLanguage("xoo")); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("languages"))); - assertThat(result.getFacets()).containsOnlyKeys("languages"); - assertThat(result.getFacets().get("languages")).containsOnly(new FacetValue("xoo", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("languages"))); + assertThat(result.getFacets().getNames()).containsOnly("languages"); + assertThat(result.getFacets().get("languages")).containsOnly(entry("xoo", 1L)); } @Test @@ -523,9 +525,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setAssignee("simon"), IssueTesting.newDoc("ISSUE3", file).setAssignee(null)); - assertThat(index.search(IssueQuery.builder().assignees(newArrayList("steph")).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().assignees(newArrayList("steph", "simon")).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().assignees(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().assignees(newArrayList("steph")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().assignees(newArrayList("steph", "simon")).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().assignees(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -539,9 +541,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE3", file).setAssignee("simon"), IssueTesting.newDoc("ISSUE4", file).setAssignee(null)); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("assignees"))); - assertThat(result.getFacets()).containsOnlyKeys("assignees"); - assertThat(result.getFacets().get("assignees")).containsOnly(new FacetValue("steph", 1), new FacetValue("simon", 2), new FacetValue("", 1)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("assignees"))); + assertThat(result.getFacets().getNames()).containsOnly("assignees"); + assertThat(result.getFacets().get("assignees")).containsOnly(entry("steph", 1L), entry("simon", 2L), entry("", 1L)); } @Test @@ -554,9 +556,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setAssignee(null), IssueTesting.newDoc("ISSUE3", file).setAssignee(null)); - assertThat(index.search(IssueQuery.builder().assigned(true).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().assigned(false).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().assigned(null).build(), new QueryContext()).getHits()).hasSize(3); + assertThat(index.search(IssueQuery.builder().assigned(true).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().assigned(false).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().assigned(null).build(), new SearchOptions()).getDocs()).hasSize(3); } @Test @@ -568,9 +570,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setReporter("fabrice"), IssueTesting.newDoc("ISSUE2", file).setReporter("stephane")); - assertThat(index.search(IssueQuery.builder().reporters(newArrayList("fabrice", "stephane")).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().reporters(newArrayList("fabrice")).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().reporters(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().reporters(newArrayList("fabrice", "stephane")).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().reporters(newArrayList("fabrice")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().reporters(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -583,9 +585,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setAuthorLogin("simon"), IssueTesting.newDoc("ISSUE3", file).setAssignee(null)); - assertThat(index.search(IssueQuery.builder().authors(newArrayList("steph")).build(), new QueryContext()).getHits()).hasSize(1); - assertThat(index.search(IssueQuery.builder().authors(newArrayList("steph", "simon")).build(), new QueryContext()).getHits()).hasSize(2); - assertThat(index.search(IssueQuery.builder().authors(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().authors(newArrayList("steph")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().authors(newArrayList("steph", "simon")).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().authors(newArrayList("unknown")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -599,9 +601,9 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE3", file).setAuthorLogin("simon"), IssueTesting.newDoc("ISSUE4", file).setAuthorLogin(null)); - Result result = index.search(IssueQuery.builder().build(), new QueryContext().addFacets(newArrayList("authors"))); - assertThat(result.getFacets()).containsOnlyKeys("authors"); - assertThat(result.getFacets().get("authors")).containsOnly(new FacetValue("steph", 1), new FacetValue("simon", 2)); + SearchResult result = index.search(IssueQuery.builder().build(), new SearchOptions().addFacets(newArrayList("authors"))); + assertThat(result.getFacets().getNames()).containsOnly("authors"); + assertThat(result.getFacets().get("authors")).containsOnly(entry("steph", 1L), entry("simon", 2L)); } @Test @@ -613,10 +615,10 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setFuncCreationDate(DateUtils.parseDate("2014-09-20")), IssueTesting.newDoc("ISSUE2", file).setFuncCreationDate(DateUtils.parseDate("2014-09-23"))); - 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(); + assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-19")).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-20")).build(), new SearchOptions()).getDocs()).hasSize(2); + assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-21")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-25")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -628,10 +630,10 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE1", file).setFuncCreationDate(DateUtils.parseDate("2014-09-20")), IssueTesting.newDoc("ISSUE2", file).setFuncCreationDate(DateUtils.parseDate("2014-09-23"))); - 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); + assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-19")).build(), new SearchOptions()).getDocs()).isEmpty(); + assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-20")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-21")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2014-09-25")).build(), new SearchOptions()).getDocs()).hasSize(2); } @Test @@ -641,121 +643,114 @@ public class IssueIndexMediumTest { indexIssues(IssueTesting.newDoc("ISSUE1", file).setFuncCreationDate(DateUtils.parseDate("2014-09-20"))); - 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(); + assertThat(index.search(IssueQuery.builder().createdAt(DateUtils.parseDate("2014-09-20")).build(), new SearchOptions()).getDocs()).hasSize(1); + assertThat(index.search(IssueQuery.builder().createdAt(DateUtils.parseDate("2014-09-21")).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test public void facet_on_created_at_with_less_than_20_days() throws Exception { - QueryContext queryContext = fixtureForCreatedAtFacet(); - - Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2014-09-08")).build(), - queryContext).getFacets().get("createdAt"); - assertThat(createdAt).hasSize(8) - .containsOnly( - new FacetValue("2014-08-31T01:00:00+0000", 0), - new FacetValue("2014-09-01T01:00:00+0000", 2), - new FacetValue("2014-09-02T01:00:00+0000", 1), - new FacetValue("2014-09-03T01:00:00+0000", 0), - new FacetValue("2014-09-04T01:00:00+0000", 0), - new FacetValue("2014-09-05T01:00:00+0000", 1), - new FacetValue("2014-09-06T01:00:00+0000", 0), - new FacetValue("2014-09-07T01:00:00+0000", 0)); + SearchOptions options = fixtureForCreatedAtFacet(); + + IssueQuery query = IssueQuery.builder() + .createdAfter(DateUtils.parseDate("2014-09-01")) + .createdBefore(DateUtils.parseDate("2014-09-08")) + .checkAuthorization(false) + .build(); + SearchResult result = index.search(query, options); + Map buckets = result.getFacets().get("createdAt"); + assertThat(buckets).containsOnly( + entry("2014-08-31T01:00:00+0000", 0L), + entry("2014-09-01T01:00:00+0000", 2L), + entry("2014-09-02T01:00:00+0000", 1L), + entry("2014-09-03T01:00:00+0000", 0L), + entry("2014-09-04T01:00:00+0000", 0L), + entry("2014-09-05T01:00:00+0000", 1L), + entry("2014-09-06T01:00:00+0000", 0L), + entry("2014-09-07T01:00:00+0000", 0L)); } @Test public void facet_on_created_at_with_less_than_20_weeks() throws Exception { - QueryContext queryContext = fixtureForCreatedAtFacet(); + SearchOptions SearchOptions = fixtureForCreatedAtFacet(); - Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2014-09-21")).build(), - queryContext).getFacets().get("createdAt"); - assertThat(createdAt).hasSize(4) - .containsOnly( - new FacetValue("2014-08-25T01:00:00+0000", 0), - new FacetValue("2014-09-01T01:00:00+0000", 4), - new FacetValue("2014-09-08T01:00:00+0000", 0), - new FacetValue("2014-09-15T01:00:00+0000", 1)); + Map createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2014-09-21")).build(), + SearchOptions).getFacets().get("createdAt"); + assertThat(createdAt).containsOnly( + entry("2014-08-25T01:00:00+0000", 0L), + entry("2014-09-01T01:00:00+0000", 4L), + entry("2014-09-08T01:00:00+0000", 0L), + entry("2014-09-15T01:00:00+0000", 1L)); } @Test public void facet_on_created_at_with_less_than_20_months() throws Exception { - QueryContext queryContext = fixtureForCreatedAtFacet(); - - Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2015-01-19")).build(), - queryContext).getFacets().get("createdAt"); - assertThat(createdAt).hasSize(6) - .containsOnly( - new FacetValue("2014-08-01T01:00:00+0000", 0), - new FacetValue("2014-09-01T01:00:00+0000", 5), - new FacetValue("2014-10-01T01:00:00+0000", 0), - new FacetValue("2014-11-01T01:00:00+0000", 0), - new FacetValue("2014-12-01T01:00:00+0000", 0), - new FacetValue("2015-01-01T01:00:00+0000", 1)); + SearchOptions SearchOptions = fixtureForCreatedAtFacet(); + + Map createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2015-01-19")).build(), + SearchOptions).getFacets().get("createdAt"); + assertThat(createdAt).containsOnly( + entry("2014-08-01T01:00:00+0000", 0L), + entry("2014-09-01T01:00:00+0000", 5L), + entry("2014-10-01T01:00:00+0000", 0L), + entry("2014-11-01T01:00:00+0000", 0L), + entry("2014-12-01T01:00:00+0000", 0L), + entry("2015-01-01T01:00:00+0000", 1L)); } @Test public void facet_on_created_at_with_more_than_20_months() throws Exception { - - QueryContext queryContext = fixtureForCreatedAtFacet(); - - Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2011-01-01")).createdBefore(DateUtils.parseDate("2016-01-01")).build(), - queryContext).getFacets().get("createdAt"); - assertThat(createdAt).hasSize(6) - .containsOnly( - new FacetValue("2011-01-01T01:00:00+0000", 1), - new FacetValue("2012-01-01T01:00:00+0000", 0), - new FacetValue("2013-01-01T01:00:00+0000", 0), - new FacetValue("2014-01-01T01:00:00+0000", 5), - new FacetValue("2015-01-01T01:00:00+0000", 1), - new FacetValue("2016-01-01T01:00:00+0000", 0)); + SearchOptions SearchOptions = fixtureForCreatedAtFacet(); + + Map createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2011-01-01")).createdBefore(DateUtils.parseDate("2016-01-01")).build(), + SearchOptions).getFacets().get("createdAt"); + assertThat(createdAt).containsOnly( + entry("2011-01-01T01:00:00+0000", 1L), + entry("2012-01-01T01:00:00+0000", 0L), + entry("2013-01-01T01:00:00+0000", 0L), + entry("2014-01-01T01:00:00+0000", 5L), + entry("2015-01-01T01:00:00+0000", 1L), + entry("2016-01-01T01:00:00+0000", 0L)); } @Test public void facet_on_created_at_with_bounds_outside_of_data() throws Exception { - - QueryContext queryContext = fixtureForCreatedAtFacet(); - - Collection createdAt = index.search(IssueQuery.builder() - .createdAfter(DateUtils.parseDate("2009-01-01")) - .createdBefore(DateUtils.parseDate("2016-01-01")) - .build(), - queryContext).getFacets().get("createdAt"); - assertThat(createdAt).hasSize(8) - .containsOnly( - new FacetValue("2009-01-01T01:00:00+0000", 0), - new FacetValue("2010-01-01T01:00:00+0000", 0), - new FacetValue("2011-01-01T01:00:00+0000", 1), - new FacetValue("2012-01-01T01:00:00+0000", 0), - new FacetValue("2013-01-01T01:00:00+0000", 0), - new FacetValue("2014-01-01T01:00:00+0000", 5), - new FacetValue("2015-01-01T01:00:00+0000", 1), - new FacetValue("2016-01-01T01:00:00+0000", 0)); - + SearchOptions options = fixtureForCreatedAtFacet(); + + Map createdAt = index.search(IssueQuery.builder() + .createdAfter(DateUtils.parseDate("2009-01-01")) + .createdBefore(DateUtils.parseDate("2016-01-01")) + .build(), options).getFacets().get("createdAt"); + assertThat(createdAt).containsOnly( + entry("2009-01-01T01:00:00+0000", 0L), + entry("2010-01-01T01:00:00+0000", 0L), + entry("2011-01-01T01:00:00+0000", 1L), + entry("2012-01-01T01:00:00+0000", 0L), + entry("2013-01-01T01:00:00+0000", 0L), + entry("2014-01-01T01:00:00+0000", 5L), + entry("2015-01-01T01:00:00+0000", 1L), + entry("2016-01-01T01:00:00+0000", 0L)); } @Test public void facet_on_created_at_without_start_bound() throws Exception { - - QueryContext queryContext = fixtureForCreatedAtFacet(); - - Collection createdAt = index.search(IssueQuery.builder() - .createdBefore(DateUtils.parseDate("2016-01-01")).build(), - queryContext).getFacets().get("createdAt"); - assertThat(createdAt).hasSize(6) - .containsOnly( - new FacetValue("2011-01-01T01:00:00+0000", 1), - new FacetValue("2012-01-01T01:00:00+0000", 0), - new FacetValue("2013-01-01T01:00:00+0000", 0), - new FacetValue("2014-01-01T01:00:00+0000", 5), - new FacetValue("2015-01-01T01:00:00+0000", 1), - new FacetValue("2016-01-01T01:00:00+0000", 0)); + SearchOptions SearchOptions = fixtureForCreatedAtFacet(); + + Map createdAt = index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2016-01-01")).build(), + SearchOptions).getFacets().get("createdAt"); + assertThat(createdAt).containsOnly( + entry("2011-01-01T01:00:00+0000", 1L), + entry("2012-01-01T01:00:00+0000", 0L), + entry("2013-01-01T01:00:00+0000", 0L), + entry("2014-01-01T01:00:00+0000", 5L), + entry("2015-01-01T01:00:00+0000", 1L), + entry("2016-01-01T01:00:00+0000", 0L)); } - protected QueryContext fixtureForCreatedAtFacet() { + protected SearchOptions fixtureForCreatedAtFacet() { ComponentDto project = ComponentTesting.newProjectDto(); ComponentDto file = ComponentTesting.newFileDto(project); @@ -769,8 +764,7 @@ public class IssueIndexMediumTest { indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6); - QueryContext queryContext = new QueryContext().addFacets(Arrays.asList("createdAt")); - return queryContext; + return new SearchOptions().addFacets("createdAt"); } @Test @@ -783,16 +777,16 @@ public class IssueIndexMediumTest { IssueQuery.Builder query = IssueQuery.builder(); // There are 12 issues in total, with 10 issues per page, the page 2 should only contain 2 elements - Result result = index.search(query.build(), new QueryContext().setPage(2, 10)); - assertThat(result.getHits()).hasSize(2); + SearchResult result = index.search(query.build(), new SearchOptions().setPage(2, 10)); + assertThat(result.getDocs()).hasSize(2); assertThat(result.getTotal()).isEqualTo(12); - result = index.search(IssueQuery.builder().build(), new QueryContext().setOffset(0).setLimit(5)); - assertThat(result.getHits()).hasSize(5); + result = index.search(IssueQuery.builder().build(), new SearchOptions().setOffset(0).setLimit(5)); + assertThat(result.getDocs()).hasSize(5); assertThat(result.getTotal()).isEqualTo(12); - result = index.search(IssueQuery.builder().build(), new QueryContext().setOffset(2).setLimit(0)); - assertThat(result.getHits()).hasSize(0); + result = index.search(IssueQuery.builder().build(), new SearchOptions().setOffset(2).setLimit(0)); + assertThat(result.getDocs()).hasSize(0); assertThat(result.getTotal()).isEqualTo(12); } @@ -808,8 +802,8 @@ public class IssueIndexMediumTest { indexIssues(issues.toArray(new IssueDoc[] {})); IssueQuery.Builder query = IssueQuery.builder(); - Result result = index.search(query.build(), new QueryContext().setMaxLimit()); - assertThat(result.getHits()).hasSize(500); + SearchResult result = index.search(query.build(), new SearchOptions().setLimit(Integer.MAX_VALUE)); + assertThat(result.getDocs()).hasSize(SearchOptions.MAX_LIMIT); } @Test @@ -823,16 +817,16 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE3", file).setStatus(Issue.STATUS_REOPENED)); IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_STATUS).asc(true); - Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().get(0).status()).isEqualTo(Issue.STATUS_CLOSED); - assertThat(result.getHits().get(1).status()).isEqualTo(Issue.STATUS_OPEN); - assertThat(result.getHits().get(2).status()).isEqualTo(Issue.STATUS_REOPENED); + SearchResult result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs().get(0).status()).isEqualTo(Issue.STATUS_CLOSED); + assertThat(result.getDocs().get(1).status()).isEqualTo(Issue.STATUS_OPEN); + assertThat(result.getDocs().get(2).status()).isEqualTo(Issue.STATUS_REOPENED); query = IssueQuery.builder().sort(IssueQuery.SORT_BY_STATUS).asc(false); - result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().get(0).status()).isEqualTo(Issue.STATUS_REOPENED); - assertThat(result.getHits().get(1).status()).isEqualTo(Issue.STATUS_OPEN); - assertThat(result.getHits().get(2).status()).isEqualTo(Issue.STATUS_CLOSED); + result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs().get(0).status()).isEqualTo(Issue.STATUS_REOPENED); + assertThat(result.getDocs().get(1).status()).isEqualTo(Issue.STATUS_OPEN); + assertThat(result.getDocs().get(2).status()).isEqualTo(Issue.STATUS_CLOSED); } @Test @@ -848,20 +842,20 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE5", file).setSeverity(Severity.MAJOR)); IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_SEVERITY).asc(true); - Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().get(0).severity()).isEqualTo(Severity.INFO); - assertThat(result.getHits().get(1).severity()).isEqualTo(Severity.MINOR); - assertThat(result.getHits().get(2).severity()).isEqualTo(Severity.MAJOR); - assertThat(result.getHits().get(3).severity()).isEqualTo(Severity.CRITICAL); - assertThat(result.getHits().get(4).severity()).isEqualTo(Severity.BLOCKER); + SearchResult result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs().get(0).severity()).isEqualTo(Severity.INFO); + assertThat(result.getDocs().get(1).severity()).isEqualTo(Severity.MINOR); + assertThat(result.getDocs().get(2).severity()).isEqualTo(Severity.MAJOR); + assertThat(result.getDocs().get(3).severity()).isEqualTo(Severity.CRITICAL); + assertThat(result.getDocs().get(4).severity()).isEqualTo(Severity.BLOCKER); query = IssueQuery.builder().sort(IssueQuery.SORT_BY_SEVERITY).asc(false); - result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().get(0).severity()).isEqualTo(Severity.BLOCKER); - assertThat(result.getHits().get(1).severity()).isEqualTo(Severity.CRITICAL); - assertThat(result.getHits().get(2).severity()).isEqualTo(Severity.MAJOR); - assertThat(result.getHits().get(3).severity()).isEqualTo(Severity.MINOR); - assertThat(result.getHits().get(4).severity()).isEqualTo(Severity.INFO); + result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs().get(0).severity()).isEqualTo(Severity.BLOCKER); + assertThat(result.getDocs().get(1).severity()).isEqualTo(Severity.CRITICAL); + assertThat(result.getDocs().get(2).severity()).isEqualTo(Severity.MAJOR); + assertThat(result.getDocs().get(3).severity()).isEqualTo(Severity.MINOR); + assertThat(result.getDocs().get(4).severity()).isEqualTo(Severity.INFO); } @Test @@ -874,16 +868,16 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setAssignee("simon")); IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_ASSIGNEE).asc(true); - Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(2); - assertThat(result.getHits().get(0).assignee()).isEqualTo("simon"); - assertThat(result.getHits().get(1).assignee()).isEqualTo("steph"); + SearchResult result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(2); + assertThat(result.getDocs().get(0).assignee()).isEqualTo("simon"); + assertThat(result.getDocs().get(1).assignee()).isEqualTo("steph"); query = IssueQuery.builder().sort(IssueQuery.SORT_BY_ASSIGNEE).asc(false); - result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(2); - assertThat(result.getHits().get(0).assignee()).isEqualTo("steph"); - assertThat(result.getHits().get(1).assignee()).isEqualTo("simon"); + result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(2); + assertThat(result.getDocs().get(0).assignee()).isEqualTo("steph"); + assertThat(result.getDocs().get(1).assignee()).isEqualTo("simon"); } @Test @@ -896,16 +890,16 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setFuncCreationDate(DateUtils.parseDate("2014-09-24"))); IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CREATION_DATE).asc(true); - Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(2); - assertThat(result.getHits().get(0).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); - assertThat(result.getHits().get(1).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); + SearchResult result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(2); + assertThat(result.getDocs().get(0).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); + assertThat(result.getDocs().get(1).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CREATION_DATE).asc(false); - result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(2); - assertThat(result.getHits().get(0).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); - assertThat(result.getHits().get(1).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); + result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(2); + assertThat(result.getDocs().get(0).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); + assertThat(result.getDocs().get(1).creationDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); } @Test @@ -918,16 +912,16 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE2", file).setFuncUpdateDate(DateUtils.parseDate("2014-09-24"))); IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_UPDATE_DATE).asc(true); - Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(2); - assertThat(result.getHits().get(0).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); - assertThat(result.getHits().get(1).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); + SearchResult result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(2); + assertThat(result.getDocs().get(0).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); + assertThat(result.getDocs().get(1).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); query = IssueQuery.builder().sort(IssueQuery.SORT_BY_UPDATE_DATE).asc(false); - result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(2); - assertThat(result.getHits().get(0).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); - assertThat(result.getHits().get(1).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); + result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(2); + assertThat(result.getDocs().get(0).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); + assertThat(result.getDocs().get(1).updateDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); } @Test @@ -941,18 +935,18 @@ public class IssueIndexMediumTest { IssueTesting.newDoc("ISSUE3", file).setFuncCloseDate(null)); IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CLOSE_DATE).asc(true); - Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(3); - assertThat(result.getHits().get(0).closeDate()).isNull(); - assertThat(result.getHits().get(1).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); - assertThat(result.getHits().get(2).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); + SearchResult result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(3); + assertThat(result.getDocs().get(0).closeDate()).isNull(); + assertThat(result.getDocs().get(1).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); + assertThat(result.getDocs().get(2).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CLOSE_DATE).asc(false); - result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(3); - assertThat(result.getHits().get(0).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); - assertThat(result.getHits().get(1).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); - assertThat(result.getHits().get(2).closeDate()).isNull(); + result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(3); + assertThat(result.getDocs().get(0).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-24")); + assertThat(result.getDocs().get(1).closeDate()).isEqualTo(DateUtils.parseDate("2014-09-23")); + assertThat(result.getDocs().get(2).closeDate()).isNull(); } @Test @@ -975,25 +969,25 @@ public class IssueIndexMediumTest { // ascending sort -> F1 then F2. Line "0" first. IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(true); - Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(6); - assertThat(result.getHits().get(0).key()).isEqualTo("F1_1"); - assertThat(result.getHits().get(1).key()).isEqualTo("F1_2"); - assertThat(result.getHits().get(2).key()).isEqualTo("F1_3"); - assertThat(result.getHits().get(3).key()).isEqualTo("F2_1"); - assertThat(result.getHits().get(4).key()).isEqualTo("F2_2"); - assertThat(result.getHits().get(5).key()).isEqualTo("F2_3"); + SearchResult result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(6); + assertThat(result.getDocs().get(0).key()).isEqualTo("F1_1"); + assertThat(result.getDocs().get(1).key()).isEqualTo("F1_2"); + assertThat(result.getDocs().get(2).key()).isEqualTo("F1_3"); + assertThat(result.getDocs().get(3).key()).isEqualTo("F2_1"); + assertThat(result.getDocs().get(4).key()).isEqualTo("F2_2"); + assertThat(result.getDocs().get(5).key()).isEqualTo("F2_3"); // descending sort -> F2 then F1 query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(false); - result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits()).hasSize(6); - assertThat(result.getHits().get(0).key()).isEqualTo("F2_3"); - assertThat(result.getHits().get(1).key()).isEqualTo("F2_2"); - assertThat(result.getHits().get(2).key()).isEqualTo("F2_1"); - assertThat(result.getHits().get(3).key()).isEqualTo("F1_3"); - assertThat(result.getHits().get(4).key()).isEqualTo("F1_2"); - assertThat(result.getHits().get(5).key()).isEqualTo("F1_1"); + result = index.search(query.build(), new SearchOptions()); + assertThat(result.getDocs()).hasSize(6); + assertThat(result.getDocs().get(0).key()).isEqualTo("F2_3"); + assertThat(result.getDocs().get(1).key()).isEqualTo("F2_2"); + assertThat(result.getDocs().get(2).key()).isEqualTo("F2_1"); + assertThat(result.getDocs().get(3).key()).isEqualTo("F1_3"); + assertThat(result.getDocs().get(4).key()).isEqualTo("F1_2"); + assertThat(result.getDocs().get(5).key()).isEqualTo("F1_1"); } @Test @@ -1013,22 +1007,20 @@ public class IssueIndexMediumTest { // project3 can be seen by nobody indexIssue(IssueTesting.newDoc("ISSUE3", file3), null, null); - IssueQuery.Builder query = IssueQuery.builder(); - MockUserSession.set().setUserGroups("sonar-users"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); + assertThat(index.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1); MockUserSession.set().setUserGroups("sonar-admins"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); + assertThat(index.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1); MockUserSession.set().setUserGroups("sonar-users", "sonar-admins"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(2); + assertThat(index.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(2); MockUserSession.set().setUserGroups("another group"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).isEmpty(); MockUserSession.set().setUserGroups("sonar-users", "sonar-admins"); - assertThat(index.search(query.projectUuids(newArrayList(project3.uuid())).build(), new QueryContext()).getHits()).isEmpty(); + assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project3.uuid())).build(), new SearchOptions()).getDocs()).isEmpty(); } @Test @@ -1046,19 +1038,18 @@ public class IssueIndexMediumTest { indexIssue(IssueTesting.newDoc("ISSUE2", file2), null, "max"); indexIssue(IssueTesting.newDoc("ISSUE3", file3), null, null); - IssueQuery.Builder query = IssueQuery.builder(); - MockUserSession.set().setLogin("john"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); + + assertThat(index.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1); MockUserSession.set().setLogin("max"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); + assertThat(index.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1); MockUserSession.set().setLogin("another guy"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(0); + assertThat(index.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(0); MockUserSession.set().setLogin("john"); - assertThat(index.search(query.projectUuids(newArrayList(project3.key())).build(), new QueryContext()).getHits()).hasSize(0); + assertThat(index.search(IssueQuery.builder().projectUuids(newArrayList(project3.key())).build(), new SearchOptions()).getDocs()).hasSize(0); } @Test @@ -1075,7 +1066,7 @@ public class IssueIndexMediumTest { IssueQuery.Builder query = IssueQuery.builder(); MockUserSession.set().setLogin("john").setUserGroups("sonar-users"); - assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); + assertThat(index.search(query.build(), new SearchOptions()).getDocs()).hasSize(1); } @Test @@ -1091,17 +1082,22 @@ public class IssueIndexMediumTest { // Issue assigned to julien should not be returned as the issue is closed IssueTesting.newDoc("ISSUE5", file).setAssignee("julien").setStatus(Issue.STATUS_CLOSED)); - List results = index.listAssignees(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_OPEN)).build()); - + LinkedHashMap results = index.searchForAssignees(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_OPEN)).build()); assertThat(results).hasSize(3); - assertThat(results.get(0).getKey()).isEqualTo("steph"); - assertThat(results.get(0).getValue()).isEqualTo(2); - assertThat(results.get(1).getKey()).isEqualTo("simon"); - assertThat(results.get(1).getValue()).isEqualTo(1); + Iterator> buckets = results.entrySet().iterator(); - assertThat(results.get(2).getKey()).isEqualTo("_notAssigned_"); - assertThat(results.get(2).getValue()).isEqualTo(1); + Map.Entry bucket = buckets.next(); + assertThat(bucket.getKey()).isEqualTo("steph"); + assertThat(bucket.getValue()).isEqualTo(2L); + + bucket = buckets.next(); + assertThat(bucket.getKey()).isEqualTo("simon"); + assertThat(bucket.getValue()).isEqualTo(1L); + + bucket = buckets.next(); + assertThat(bucket.getKey()).isEqualTo("_notAssigned_"); + assertThat(bucket.getValue()).isEqualTo(1L); } @Test @@ -1123,9 +1119,9 @@ public class IssueIndexMediumTest { index.deleteClosedIssuesOfProjectBefore(project.uuid(), yesterday); // ASSERT - List issues = index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).build(), new QueryContext()).getHits(); + List issues = index.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).build(), new SearchOptions()).getDocs(); List dates = newArrayList(); - for (Issue issue : issues) { + for (IssueDoc issue : issues) { dates.add(issue.closeDate()); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AuthorsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AuthorsActionTest.java index 33acb9a906c..c10978bb6ee 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AuthorsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AuthorsActionTest.java @@ -31,7 +31,6 @@ import org.sonar.server.ws.WsTester; import java.util.Arrays; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -47,9 +46,7 @@ public class AuthorsActionTest { @Before public void setUp() throws Exception { - tester = new WsTester(new IssuesWs(mock(IssueShowAction.class), new SearchAction(null, null, null, null, null, null, null, null, null, null, null), - mock(TagsAction.class), mock(SetTagsAction.class), mock(ComponentTagsAction.class), - new AuthorsAction(service))); + tester = new WsTester(new IssuesWs(new AuthorsAction(service))); controller = tester.controller("api/issues"); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java index 31bb581a730..5ea8d46672d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java @@ -49,10 +49,7 @@ public class ComponentTagsActionTest { @Before public void setUp() { componentTagsAction = new ComponentTagsAction(service); - tester = new WsTester( - new IssuesWs(new IssueShowAction(null, null, null, null, null, null, null, null, null, null, null), - new SearchAction(null, null, null, null, null, null, null, null, null, null,null), - new TagsAction(null), new SetTagsAction(null), componentTagsAction, new AuthorsAction(null))); + tester = new WsTester(new IssuesWs(componentTagsAction)); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowActionTest.java index 5dd1de61d0b..d730f222275 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowActionTest.java @@ -31,7 +31,6 @@ import org.sonar.api.issue.Issue; import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.issue.internal.DefaultIssueComment; import org.sonar.api.issue.internal.FieldDiffs; -import org.sonar.api.resources.Languages; import org.sonar.api.rule.RuleKey; import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic; import org.sonar.api.user.User; @@ -41,7 +40,6 @@ import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.core.component.ComponentDto; import org.sonar.core.issue.DefaultActionPlan; -import org.sonar.core.issue.db.IssueChangeDao; import org.sonar.core.issue.workflow.Transition; import org.sonar.core.persistence.DbSession; import org.sonar.core.user.DefaultUser; @@ -49,7 +47,11 @@ import org.sonar.server.component.ComponentTesting; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.db.DbClient; import org.sonar.server.debt.DebtModelService; -import org.sonar.server.issue.*; +import org.sonar.server.issue.ActionService; +import org.sonar.server.issue.IssueChangelog; +import org.sonar.server.issue.IssueChangelogService; +import org.sonar.server.issue.IssueCommentService; +import org.sonar.server.issue.IssueService; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.rule.Rule; import org.sonar.server.rule.RuleService; @@ -141,11 +143,7 @@ public class IssueShowActionTest { tester = new WsTester(new IssuesWs( new IssueShowAction(dbClient, issueService, issueChangelogService, commentService, - new IssueActionsWriter(issueService, actionService), actionPlanService, userFinder, debtModel, ruleService, i18n, durations), - new SearchAction(mock(DbClient.class), mock(IssueChangeDao.class), mock(IssueService.class), mock(IssueActionsWriter.class), mock(IssueQueryService.class), - mock(RuleService.class), - mock(ActionPlanService.class), mock(UserFinder.class), mock(I18n.class), mock(Durations.class), mock(Languages.class)), - new TagsAction(null), new SetTagsAction(null), new ComponentTagsAction(null), new AuthorsAction(null) + new IssueActionsWriter(issueService, actionService), actionPlanService, userFinder, debtModel, ruleService, i18n, durations) )); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueTagsActionTest.java index 45f97790bde..9519396292b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueTagsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueTagsActionTest.java @@ -47,10 +47,7 @@ public class IssueTagsActionTest { @Before public void setUp() { tagsAction = new TagsAction(service); - tester = new WsTester( - new IssuesWs(new IssueShowAction(null, null, null, null, null, null, null, null, null, null, null), - new SearchAction(null, null, null, null, null, null, null, null, null, null,null), - tagsAction, new SetTagsAction(null), new ComponentTagsAction(null), new AuthorsAction(null))); + tester = new WsTester(new IssuesWs(tagsAction)); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java index f3d0d3e73f9..e47f7fac894 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java @@ -19,242 +19,32 @@ */ package org.sonar.server.issue.ws; -import org.junit.Before; import org.junit.Test; -import org.sonar.api.i18n.I18n; -import org.sonar.api.resources.Languages; -import org.sonar.api.server.ws.RailsHandler; import org.sonar.api.server.ws.WebService; -import org.sonar.api.user.UserFinder; -import org.sonar.api.utils.Durations; -import org.sonar.core.issue.db.IssueChangeDao; -import org.sonar.server.db.DbClient; -import org.sonar.server.debt.DebtModelService; -import org.sonar.server.issue.IssueChangelogService; -import org.sonar.server.issue.IssueCommentService; -import org.sonar.server.issue.IssueQueryService; -import org.sonar.server.issue.IssueService; -import org.sonar.server.issue.actionplan.ActionPlanService; -import org.sonar.server.rule.RuleService; -import org.sonar.server.ws.WsTester; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class IssuesWsTest { - IssueShowAction showAction; - - WsTester tester; - - @Before - public void setUp() throws Exception { - IssueChangelogService issueChangelogService = mock(IssueChangelogService.class); - IssueActionsWriter actionsWriter = mock(IssueActionsWriter.class); - DebtModelService debtModelService = mock(DebtModelService.class); - I18n i18n = mock(I18n.class); - Durations durations = mock(Durations.class); - - showAction = new IssueShowAction(mock(DbClient.class), mock(IssueService.class), issueChangelogService, mock(IssueCommentService.class), actionsWriter, - mock(ActionPlanService.class), mock(UserFinder.class), - debtModelService, mock(RuleService.class), i18n, durations); - SearchAction searchAction = new SearchAction(mock(DbClient.class), mock(IssueChangeDao.class), mock(IssueService.class), mock(IssueActionsWriter.class), - mock(IssueQueryService.class), mock(RuleService.class), - mock(ActionPlanService.class), mock(UserFinder.class), mock(I18n.class), mock(Durations.class), mock(Languages.class)); - tester = new WsTester(new IssuesWs(showAction, searchAction, new TagsAction(null), new SetTagsAction(null), new ComponentTagsAction(null), new AuthorsAction(null))); - } - @Test - public void define_controller() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); + public void define_actions() throws Exception { + BaseIssuesWsAction action1 = mock(BaseIssuesWsAction.class); + BaseIssuesWsAction action2 = mock(BaseIssuesWsAction.class); + IssuesWs ws = new IssuesWs(action1, action2); + WebService.Context context = new WebService.Context(); + ws.define(context); + + WebService.Controller controller = context.controller("api/issues"); assertThat(controller).isNotNull(); assertThat(controller.description()).isNotEmpty(); assertThat(controller.since()).isEqualTo("3.6"); - assertThat(controller.actions()).hasSize(18); - } - - @Test - public void define_show_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("show"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("4.2"); - assertThat(action.isPost()).isFalse(); - assertThat(action.isInternal()).isTrue(); - assertThat(action.handler()).isSameAs(showAction); - assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).hasSize(1); - - WebService.Param key = action.param("key"); - assertThat(key).isNotNull(); - assertThat(key.description()).isNotNull(); - assertThat(key.isRequired()).isFalse(); - } - - @Test - public void define_changelog_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("changelog"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).hasSize(2); - } - - @Test - public void define_assign_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("assign"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(3); - } - - @Test - public void define_add_comment_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("add_comment"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(3); - } - - @Test - public void define_delete_comment_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("delete_comment"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(1); - } - - @Test - public void define_edit_comment_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("edit_comment"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(3); - } - - @Test - public void define_change_severity_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("set_severity"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(3); - } - - @Test - public void define_plan_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("plan"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(3); - } - - @Test - public void define_do_transition_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("do_transition"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(3); - } - - @Test - public void define_transitions_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("transitions"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isFalse(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).hasSize(1); - } - - @Test - public void define_create_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("create"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.params()).hasSize(6); - } - - @Test - public void define_do_action_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - - WebService.Action action = controller.action("do_action"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.6"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.params()).hasSize(3); - } - - @Test - public void define_bulk_change_action() throws Exception { - WebService.Controller controller = tester.controller("api/issues"); - WebService.Action action = controller.action("bulk_change"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isNotNull(); - assertThat(action.since()).isEqualTo("3.7"); - assertThat(action.isPost()).isTrue(); - assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.params()).hasSize(9); + assertThat(controller.actions()).isNotEmpty(); + verify(action1).define(any(WebService.NewController.class)); + verify(action2).define(any(WebService.NewController.class)); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java index 5a50b56856c..324210099d5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.sonar.api.resources.Qualifiers; import org.sonar.api.rule.RuleStatus; import org.sonar.api.security.DefaultGroups; +import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.DateUtils; import org.sonar.api.web.UserRole; import org.sonar.core.component.ComponentDto; @@ -158,13 +159,13 @@ public class SearchActionComponentsMediumTest { wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.PROJECT_UUIDS, project1.uuid()) - .setParam(SearchAction.PARAM_FACETS, "projectUuids") + .setParam(WebService.Param.FACETS, "projectUuids") .execute() .assertJson(this.getClass(), "display_sticky_project_facet.json", false); wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, project1.uuid()) - .setParam(SearchAction.PARAM_FACETS, "projectUuids") + .setParam(WebService.Param.FACETS, "projectUuids") .execute() .assertJson(this.getClass(), "display_non_sticky_project_facet.json", false); } @@ -242,7 +243,7 @@ public class SearchActionComponentsMediumTest { wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, project.uuid()) .setParam(IssueFilterParameters.FILE_UUIDS, file1.uuid() + "," + file3.uuid()) - .setParam(SearchAction.PARAM_FACETS, "fileUuids") + .setParam(WebService.Param.FACETS, "fileUuids") .execute() .assertJson(this.getClass(), "display_file_facet.json", false); } @@ -349,7 +350,7 @@ public class SearchActionComponentsMediumTest { wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, project.uuid()) .setParam(IssueFilterParameters.MODULE_UUIDS, subModule1.uuid() + "," + subModule3.uuid()) - .setParam(SearchAction.PARAM_FACETS, "moduleUuids") + .setParam(WebService.Param.FACETS, "moduleUuids") .execute() .assertJson(this.getClass(), "display_module_facet.json", false); } @@ -368,7 +369,7 @@ public class SearchActionComponentsMediumTest { MockUserSession.set().setLogin("john"); WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") - .setParam(SearchAction.PARAM_FACETS, "directories") + .setParam(WebService.Param.FACETS, "directories") .execute(); result.assertJson(this.getClass(), "display_directory_facet.json", false); } @@ -469,7 +470,7 @@ public class SearchActionComponentsMediumTest { wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.AUTHORS, "leia") - .setParam(SearchAction.PARAM_FACETS, "authors") + .setParam(WebService.Param.FACETS, "authors") .execute() .assertJson(this.getClass(), "search_by_authors.json", false); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java index 8111464b588..d1bf870bfdc 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java @@ -88,7 +88,7 @@ public class SearchActionMediumTest { assertThat(show.isPost()).isFalse(); assertThat(show.isInternal()).isFalse(); assertThat(show.responseExampleAsString()).isNotEmpty(); - assertThat(show.params()).hasSize(39); + assertThat(show.params()).hasSize(38); } @Test @@ -367,7 +367,7 @@ public class SearchActionMediumTest { MockUserSession.set().setLogin("john"); WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") - .setParam(SearchAction.PARAM_FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans") + .setParam(WebService.Param.FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans") .execute(); result.assertJson(this.getClass(), "display_facets.json", false); } @@ -393,7 +393,7 @@ public class SearchActionMediumTest { .setParam("resolved", "false") .setParam("severities", "MAJOR,MINOR") .setParam("languages", "xoo,polop,palap") - .setParam(SearchAction.PARAM_FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans") + .setParam(WebService.Param.FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans") .execute(); result.assertJson(this.getClass(), "display_zero_facets.json", false); } @@ -451,8 +451,8 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION); - request.setParam(SearchAction.PARAM_PAGE, "2"); - request.setParam(SearchAction.PARAM_PAGE_SIZE, "9"); + request.setParam(WebService.Param.PAGE, "2"); + request.setParam(WebService.Param.PAGE_SIZE, "9"); WsTester.Result result = request.execute(); result.assertJson(this.getClass(), "paging.json", false); @@ -472,8 +472,8 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION); - request.setParam(SearchAction.PARAM_PAGE, "1"); - request.setParam(SearchAction.PARAM_PAGE_SIZE, "-1"); + request.setParam(WebService.Param.PAGE, "1"); + request.setParam(WebService.Param.PAGE_SIZE, "-1"); WsTester.Result result = request.execute(); result.assertJson(this.getClass(), "paging_with_page_size_to_minus_one.json", false); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java index 2dd3a9f2bf3..7ccfb597930 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java @@ -19,6 +19,7 @@ */ package org.sonar.server.issue.ws; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Before; import org.junit.Test; @@ -38,19 +39,14 @@ import static org.mockito.Mockito.when; public class SetTagsActionTest { @Mock - private IssueService service; - - private SetTagsAction setTagsAction; - - private WsTester tester; + IssueService service; + SetTagsAction sut; + WsTester tester; @Before public void setUp() { - setTagsAction = new SetTagsAction(service); - tester = new WsTester( - new IssuesWs(new IssueShowAction(null, null, null, null, null, null, null, null, null, null, null), - new SearchAction(null, null, null, null, null, null, null, null, null, null,null), - new TagsAction(null), setTagsAction, new ComponentTagsAction(null), new AuthorsAction(null))); + sut = new SetTagsAction(service); + tester = new WsTester(new IssuesWs(sut)); } @Test @@ -60,7 +56,7 @@ public class SetTagsActionTest { assertThat(action.responseExampleAsString()).isNull(); assertThat(action.isPost()).isTrue(); assertThat(action.isInternal()).isFalse(); - assertThat(action.handler()).isEqualTo(setTagsAction); + assertThat(action.handler()).isEqualTo(sut); assertThat(action.params()).hasSize(2); Param query = action.param("key"); @@ -76,9 +72,9 @@ public class SetTagsActionTest { @Test public void should_set_tags() throws Exception { - when(service.setTags("polop", ImmutableSet.of("palap"))).thenReturn(ImmutableSet.of("palap")); + when(service.setTags("polop", ImmutableList.of("palap"))).thenReturn(ImmutableSet.of("palap")); tester.newPostRequest("api/issues", "set_tags").setParam("key", "polop").setParam("tags", "palap").execute() .assertJson("{\"tags\":[\"palap\"]}"); - verify(service).setTags("polop", ImmutableSet.of("palap")); + verify(service).setTags("polop", ImmutableList.of("palap")); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/view/index/ViewIndexerMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/view/index/ViewIndexerMediumTest.java index ff806db5e70..3f80f02f695 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/view/index/ViewIndexerMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/view/index/ViewIndexerMediumTest.java @@ -34,16 +34,18 @@ import org.sonar.core.rule.RuleDto; import org.sonar.server.component.ComponentTesting; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.db.DbClient; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.es.SearchResult; import org.sonar.server.issue.IssueQuery; import org.sonar.server.issue.IssueTesting; import org.sonar.server.issue.db.IssueDao; +import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.issue.index.IssueIndexer; import org.sonar.server.permission.InternalPermissionService; import org.sonar.server.permission.PermissionChange; import org.sonar.server.rule.RuleTesting; import org.sonar.server.rule.db.RuleDao; -import org.sonar.server.search.QueryContext; import org.sonar.server.tester.ServerTester; import org.sonar.server.user.MockUserSession; @@ -97,7 +99,9 @@ public class ViewIndexerMediumTest { indexer.index(viewUuid); // Execute issue query on view -> 1 issue on view - assertThat(tester.get(IssueIndex.class).search(IssueQuery.builder().viewUuids(newArrayList(viewUuid)).build(), new QueryContext()).getHits()).hasSize(1); + SearchResult docs = tester.get(IssueIndex.class).search(IssueQuery.builder().viewUuids(newArrayList(viewUuid)).build(), + new SearchOptions()); + assertThat(docs.getDocs()).hasSize(1); // Add a project to the view and index it again ComponentDto project2 = addProjectWithIssue(rule); @@ -107,7 +111,7 @@ public class ViewIndexerMediumTest { indexer.index(viewUuid); // Execute issue query on view -> issue of project2 are well taken into account : the cache has been cleared - assertThat(tester.get(IssueIndex.class).search(IssueQuery.builder().viewUuids(newArrayList(viewUuid)).build(), new QueryContext()).getHits()).hasSize(2); + assertThat(tester.get(IssueIndex.class).search(IssueQuery.builder().viewUuids(newArrayList(viewUuid)).build(), new SearchOptions()).getDocs()).hasSize(2); } private ComponentDto addProjectWithIssue(RuleDto rule) { diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/default_page_size_is_100.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/default_page_size_is_100.json index 0fc07a00b50..a4dc9a53ed5 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/default_page_size_is_100.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/default_page_size_is_100.json @@ -2,7 +2,6 @@ "total": 0, "p": 1, "ps": 100, - "maxResultsReached": false, "paging": { "pageIndex": 1, "pageSize": 100, diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/deprecated_paging.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/deprecated_paging.json index f67485819f6..1ecb54f2622 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/deprecated_paging.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/deprecated_paging.json @@ -1,5 +1,4 @@ { - "maxResultsReached": false, "paging": { "pageIndex": 2, "pageSize": 9, diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/empty_result.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/empty_result.json index 4d91f3e640f..787b0028a29 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/empty_result.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/empty_result.json @@ -2,7 +2,6 @@ "total": 0, "p": 1, "ps": 100, - "maxResultsReached": false, "paging": { "pageIndex": 1, "pageSize": 100, diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/issue.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/issue.json index ece88f7ed43..ee02f787d29 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/issue.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/issue.json @@ -1,5 +1,4 @@ { - "maxResultsReached": false, "total": 1, "p": 1, "ps": 100, diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java index b236a18b366..a766d024592 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java @@ -26,6 +26,7 @@ import org.sonar.api.BatchComponent; import org.sonar.api.ServerComponent; import org.sonar.api.issue.internal.DefaultIssueComment; import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.core.persistence.DaoComponent; import org.sonar.core.persistence.DbSession; import org.sonar.core.persistence.MyBatis; @@ -43,7 +44,7 @@ import static com.google.common.collect.Maps.newHashMap; /** * @since 3.6 */ -public class IssueChangeDao implements BatchComponent, ServerComponent { +public class IssueChangeDao implements DaoComponent, BatchComponent, ServerComponent { private final MyBatis mybatis; diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java index ac41ee8639e..3d4690c2f0e 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java @@ -23,7 +23,6 @@ import org.apache.ibatis.annotations.Param; import org.sonar.core.rule.RuleDto; import javax.annotation.Nullable; -import java.util.Collection; import java.util.Date; import java.util.List; @@ -31,7 +30,7 @@ public interface IssueMapper { IssueDto selectByKey(String key); - List selectByKeys(Collection keys); + List selectByKeys(List keys); List selectByActionPlan(String actionPlan); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java index f8bffedc019..faa0f05fb56 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java @@ -35,7 +35,11 @@ import javax.annotation.concurrent.Immutable; import java.io.IOException; import java.net.URL; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Defines a web service. Note that contrary to the deprecated {@link org.sonar.api.web.Webservice} @@ -117,8 +121,7 @@ public interface WebService extends ServerExtension { private void register(NewController newController) { if (controllers.containsKey(newController.path)) { throw new IllegalStateException( - String.format("The web service '%s' is defined multiple times", newController.path) - ); + String.format("The web service '%s' is defined multiple times", newController.path)); } controllers.put(newController.path, new Controller(newController)); } @@ -177,8 +180,7 @@ public interface WebService extends ServerExtension { public NewAction createAction(String actionKey) { if (actions.containsKey(actionKey)) { throw new IllegalStateException( - String.format("The action '%s' is defined multiple times in the web service '%s'", actionKey, path) - ); + String.format("The action '%s' is defined multiple times in the web service '%s'", actionKey, path)); } NewAction action = new NewAction(actionKey); actions.put(actionKey, action); @@ -194,8 +196,7 @@ public interface WebService extends ServerExtension { private Controller(NewController newController) { if (newController.actions.isEmpty()) { throw new IllegalStateException( - String.format("At least one action must be declared in the web service '%s'", newController.path) - ); + String.format("At least one action must be declared in the web service '%s'", newController.path)); } this.path = newController.path; this.description = newController.description; @@ -306,8 +307,7 @@ public interface WebService extends ServerExtension { public NewParam createParam(String paramKey) { if (newParams.containsKey(paramKey)) { throw new IllegalStateException( - String.format("The parameter '%s' is defined multiple times in the action '%s'", paramKey, key) - ); + String.format("The parameter '%s' is defined multiple times in the action '%s'", paramKey, key)); } NewParam newParam = new NewParam(paramKey); newParams.put(paramKey, newParam); @@ -322,6 +322,52 @@ public interface WebService extends ServerExtension { createParam(paramKey).setDescription(description); return this; } + + /** + * Add predefined parameters related to pagination of results. + */ + public NewAction addPagingParams(int defaultPageSize) { + createParam(Param.PAGE) + .setDescription("1-based page number") + .setExampleValue("42") + .setDeprecatedKey("pageIndex") + .setDefaultValue("1"); + + createParam(Param.PAGE_SIZE) + .setDescription("Page size. Must be greater than 0.") + .setExampleValue("20") + .setDeprecatedKey("pageSize") + .setDefaultValue(String.valueOf(defaultPageSize)); + return this; + } + + /** + * Creates the parameter {@link org.sonar.api.server.ws.WebService.Param#FIELDS}, which is + * used to restrict the number of fields returned in JSON response. + */ + public NewAction addFieldsParam(Collection possibleValues) { + createParam(Param.FIELDS) + .setDescription("Comma-separated list of the fields to be returned in response. All the fields are returned by default.") + .setPossibleValues(possibleValues); + return this; + } + + /** + * Add predefined parameters related to sorting of results. + */ + public NewAction addSortParams(Collection possibleValues, @Nullable V defaultValue, boolean defaultAscending) { + createParam(Param.SORT) + .setDescription("Sort field") + .setDeprecatedKey("sort") + .setDefaultValue(defaultValue) + .setPossibleValues(possibleValues); + + createParam(Param.ASCENDING) + .setDescription("Ascending sort") + .setBooleanPossibleValues() + .setDefaultValue(defaultAscending); + return this; + } } @Immutable @@ -531,6 +577,14 @@ public interface WebService extends ServerExtension { @Immutable class Param { + public static final String TEXT_QUERY = "q"; + public static final String PAGE = "p"; + public static final String PAGE_SIZE = "ps"; + public static final String FIELDS = "f"; + public static final String SORT = "s"; + public static final String ASCENDING = "asc"; + public static final String FACETS = "facets"; + private final String key, deprecatedKey, description, exampleValue, defaultValue; private final boolean required; private final Set possibleValues; diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/ws/WebServiceTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/ws/WebServiceTest.java index 550b959019f..9226ea1af60 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/server/ws/WebServiceTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/server/ws/WebServiceTest.java @@ -25,6 +25,7 @@ import org.sonar.api.rule.RuleStatus; import java.net.MalformedURLException; import java.net.URL; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -257,15 +258,18 @@ public class WebServiceTest { @Override public void define(Context context) { NewController newController = context.createController("api/rule"); - NewAction create = newController.createAction("create").setHandler(mock(RequestHandler.class)); - create.createParam("key").setDescription("Key of the new rule"); - create.createParam("severity").setDefaultValue("MAJOR").setPossibleValues("INFO", "MAJOR", "BLOCKER"); + NewAction newAction = newController.createAction("create").setHandler(mock(RequestHandler.class)); + newAction.createParam("key").setDescription("Key of the new rule"); + newAction.createParam("severity").setDefaultValue("MAJOR").setPossibleValues("INFO", "MAJOR", "BLOCKER"); + newAction.addPagingParams(20); + newAction.addFieldsParam(Arrays.asList("name", "severity")); + newAction.addSortParams(Arrays.asList("name", "updatedAt", "severity"), "updatedAt", false); newController.done(); } }.define(context); WebService.Action action = context.controller("api/rule").action("create"); - assertThat(action.params()).hasSize(2); + assertThat(action.params()).hasSize(7); assertThat(action.param("key").key()).isEqualTo("key"); assertThat(action.param("key").description()).isEqualTo("Key of the new rule"); @@ -275,6 +279,16 @@ public class WebServiceTest { assertThat(action.param("severity").description()).isNull(); assertThat(action.param("severity").defaultValue()).isEqualTo("MAJOR"); assertThat(action.param("severity").possibleValues()).containsOnly("INFO", "MAJOR", "BLOCKER"); + + // predefined fields + assertThat(action.param("p").defaultValue()).isEqualTo("1"); + assertThat(action.param("p").description()).isNotEmpty(); + assertThat(action.param("ps").defaultValue()).isEqualTo("20"); + assertThat(action.param("ps").description()).isNotEmpty(); + assertThat(action.param("f").possibleValues()).containsOnly("name", "severity"); + assertThat(action.param("s").possibleValues()).containsOnly("name", "severity", "updatedAt"); + assertThat(action.param("s").description()).isNotEmpty(); + assertThat(action.param("asc").defaultValue()).isEqualTo("false"); } @Test -- 2.39.5