SearchRequestHandlertags/5.1-RC1
@@ -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<Issue> result = index.search(query, new QueryContext()); | |||
SearchResult<IssueDoc> result = index.search(query, new SearchOptions()); | |||
long end = System.currentTimeMillis(); | |||
LOGGER.info("Request (" + label + "): {} docs in {} ms", result.getTotal(), end - start); | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 <D extends BaseDoc> List<D> convertToDocs(SearchHits hits, Function<Map<String, Object>, D> converter) { | |||
List<D> docs = new ArrayList<>(); | |||
for (SearchHit hit : hits.getHits()) { | |||
docs.add(converter.apply(hit.getSource())); | |||
} | |||
return docs; | |||
} | |||
public static LinkedHashMap<String, Long> termsToMap(Terms terms) { | |||
LinkedHashMap<String, Long> map = new LinkedHashMap<>(); | |||
List<Terms.Bucket> buckets = terms.getBuckets(); | |||
for (Terms.Bucket bucket : buckets) { | |||
map.put(bucket.getKey(), bucket.getDocCount()); | |||
} | |||
return map; | |||
} | |||
public static List<String> termsKeys(Terms terms) { | |||
return Lists.transform(terms.getBuckets(), new Function<Terms.Bucket, String>() { | |||
@Override | |||
public String apply(Terms.Bucket bucket) { | |||
return bucket.getKey(); | |||
} | |||
}); | |||
} | |||
} |
@@ -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<String, LinkedHashMap<String, Long>> 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<String, Long> 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<String, Long> 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<String, Long> get(String facetName) { | |||
return facetsByName.get(facetName); | |||
} | |||
public Map<String, LinkedHashMap<String, Long>> 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<String, Long> facet = facetsByName.get(facetName); | |||
if (facet != null) { | |||
return facet.get(bucketKey); | |||
} | |||
return null; | |||
} | |||
public Set<String> getBucketKeys(String facetName) { | |||
LinkedHashMap<String, Long> facet = facetsByName.get(facetName); | |||
if (facet != null) { | |||
return facet.keySet(); | |||
} | |||
return Collections.emptySet(); | |||
} | |||
public Set<String> getNames() { | |||
return facetsByName.keySet(); | |||
} | |||
@Override | |||
public String toString() { | |||
return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE); | |||
} | |||
private LinkedHashMap<String, Long> getOrCreateFacet(String facetName) { | |||
LinkedHashMap<String, Long> facet = facetsByName.get(facetName); | |||
if (facet == null) { | |||
facet = new LinkedHashMap<>(); | |||
facetsByName.put(facetName, facet); | |||
} | |||
return facet; | |||
} | |||
} |
@@ -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<String> facets = new LinkedHashSet<>(); | |||
private final Set<String> 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<String> getFacets() { | |||
return facets; | |||
} | |||
/** | |||
* Selects facets to return for the domain. | |||
*/ | |||
public SearchOptions addFacets(@Nullable Collection<String> f) { | |||
if (f != null) { | |||
this.facets.addAll(f); | |||
} | |||
return this; | |||
} | |||
public SearchOptions addFacets(String... array) { | |||
Collections.addAll(facets, array); | |||
return this; | |||
} | |||
public Set<String> getFields() { | |||
return fieldsToReturn; | |||
} | |||
public boolean hasField(String key) { | |||
return fieldsToReturn.isEmpty() || fieldsToReturn.contains(key); | |||
} | |||
public SearchOptions addFields(@Nullable Collection<String> 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; | |||
} | |||
} |
@@ -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<DOC extends BaseDoc> { | |||
private final List<DOC> docs; | |||
private final Facets facets; | |||
private final long total; | |||
public SearchResult(SearchResponse response, Function<Map<String, Object>, DOC> converter) { | |||
this.facets = new Facets(response); | |||
this.total = response.getHits().totalHits(); | |||
this.docs = EsUtils.convertToDocs(response.getHits(), converter); | |||
} | |||
public List<DOC> getDocs() { | |||
return docs; | |||
} | |||
public long getTotal() { | |||
return total; | |||
} | |||
public Facets getFacets() { | |||
return this.facets; | |||
} | |||
@Override | |||
public String toString() { | |||
return ReflectionToStringBuilder.toString(this); | |||
} | |||
} |
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession){ | |||
public boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession) { | |||
parseTags(properties); | |||
return true; | |||
} | |||
@@ -67,7 +65,7 @@ public abstract class AbstractChangeTagsAction extends Action implements ServerC | |||
Set<String> 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); | |||
} |
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession); | |||
abstract boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession); | |||
abstract boolean execute(Map<String, Object> properties, Context context); | |||
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession){ | |||
public boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession){ | |||
String assignee = assigneeValue(properties); | |||
if(!Strings.isNullOrEmpty(assignee)) { | |||
User user = selectUser(assignee); |
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
public boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession) { | |||
comment(properties); | |||
return true; | |||
} | |||
@@ -58,7 +57,7 @@ public class CommentAction extends Action implements ServerComponent { | |||
private String comment(Map<String, Object> 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; | |||
} |
@@ -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<String, Object> 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<String, Object> props) { | |||
QueryContext context = new QueryContext(); | |||
static SearchOptions toSearchOptions(Map<String, Object> 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<String> listTags() { |
@@ -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<Issue> issues = getByKeysForUpdate(issueBulkChangeQuery.issues()); | |||
Collection<Issue> issues = getByKeysForUpdate(issueBulkChangeQuery.issues()); | |||
Repository repository = new Repository(issues); | |||
List<Action> bulkActions = getActionsToApply(issueBulkChangeQuery, issues, userSession); | |||
IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(), userSession.login()); | |||
Set<String> concernedProjects = new HashSet<String>(); | |||
Set<String> 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<Issue> getByKeysForUpdate(List<String> issueKeys) { | |||
private Collection<Issue> getByKeysForUpdate(List<String> issueKeys) { | |||
// Load from index to check permission | |||
List<Issue> authorizedIndexIssues = issueService.search(IssueQuery.builder().issueKeys(issueKeys).build(), new QueryContext().setMaxLimit()).getHits(); | |||
List<String> authorizedIssueKeys = newArrayList(Iterables.transform(authorizedIndexIssues, new Function<Issue, String>() { | |||
SearchOptions options = new SearchOptions().setLimit(SearchOptions.MAX_LIMIT); | |||
// TODO restrict fields to issue key, in order to not load all other fields; | |||
List<IssueDoc> authorizedIssues = issueService.search(IssueQuery.builder().issueKeys(issueKeys).build(), options).getDocs(); | |||
Collection<String> authorizedKeys = Collections2.transform(authorizedIssues, new Function<IssueDoc, String>() { | |||
@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<IssueDto> issueDtos = dbClient.issueDao().selectByKeys(session, authorizedIssueKeys); | |||
return newArrayList(Iterables.transform(issueDtos, new Function<IssueDto, Issue>() { | |||
@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<IssueDto> dtos = dbClient.issueDao().selectByKeys(session, Lists.newArrayList(authorizedKeys)); | |||
return Collections2.transform(dtos, new Function<IssueDto, Issue>() { | |||
@Override | |||
public Issue apply(@Nullable IssueDto input) { | |||
return input != null ? input.toDefaultIssue() : null; | |||
} | |||
}); | |||
} finally { | |||
MyBatis.closeQuietly(session); | |||
} | |||
} | |||
return Collections.emptyList(); | |||
} | |||
private List<Action> getActionsToApply(IssueBulkChangeQuery issueBulkChangeQuery, List<Issue> issues, UserSession userSession) { | |||
private List<Action> getActionsToApply(IssueBulkChangeQuery issueBulkChangeQuery, Collection<Issue> issues, UserSession userSession) { | |||
List<Action> bulkActions = newArrayList(); | |||
for (String actionKey : issueBulkChangeQuery.actions()) { | |||
Action action = getAction(actionKey); | |||
@@ -207,7 +218,7 @@ public class IssueBulkChangeService { | |||
private final Map<String, ComponentDto> components = newHashMap(); | |||
private final Map<String, ComponentDto> projects = newHashMap(); | |||
public Repository(List<Issue> issues) { | |||
public Repository(Collection<Issue> issues) { | |||
Set<RuleKey> ruleKeys = newHashSet(); | |||
Set<String> componentKeys = newHashSet(); | |||
Set<String> projectKeys = newHashSet(); |
@@ -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<String> 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<String> 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<String> 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<String> 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; |
@@ -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<String, Long> findIssueAssignees(IssueQuery query) { | |||
Map<String, Long> result = newLinkedHashMap(); | |||
List<FacetValue> facetValues = indexClient.get(IssueIndex.class).listAssignees(query); | |||
for (FacetValue facetValue : facetValues) { | |||
if ("_notAssigned_".equals(facetValue.getKey())) { | |||
result.put(null, facetValue.getValue()); | |||
Map<String,Long> buckets = issueIndex.searchForAssignees(query); | |||
for (Map.Entry<String, Long> 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<Issue> search(IssueQuery query, QueryContext options) { | |||
return indexClient.get(IssueIndex.class).search(query, options); | |||
public SearchResult<IssueDoc> search(IssueQuery query, SearchOptions options) { | |||
return issueIndex.search(query, options); | |||
} | |||
private void verifyLoggedIn() { | |||
UserSession.get().checkLoggedIn(); | |||
} | |||
public Collection<String> 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<String> listTags(@Nullable String textQuery, int pageSize) { | |||
IssueQuery query = IssueQuery.builder() | |||
.checkAuthorization(false) | |||
.build(); | |||
return issueIndex.listTags(query, textQuery, pageSize); | |||
} | |||
public Collection<String> listAuthors(@Nullable String query, int pageSize) { | |||
return indexClient.get(IssueIndex.class).listAuthorsMatching(query, pageSize); | |||
public List<String> listAuthors(@Nullable String textQuery, int pageSize) { | |||
IssueQuery query = IssueQuery.builder() | |||
.checkAuthorization(false) | |||
.build(); | |||
return issueIndex.listAuthors(query, textQuery, pageSize); | |||
} | |||
public Collection<String> setTags(String issueKey, Collection<String> tags) { | |||
@@ -379,8 +395,17 @@ public class IssueService implements ServerComponent { | |||
} | |||
} | |||
// TODO check compatibility with Views, projects, etc. | |||
public Map<String, Long> 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 |
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
public boolean verify(Map<String, Object> properties, Collection<Issue> 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<String, Object> 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<Issue> issues, ActionPlan actionPlan) { | |||
private void verifyIssuesAreAllRelatedOnActionPlanProject(Collection<Issue> issues, ActionPlan actionPlan) { | |||
String projectKey = actionPlan.projectKey(); | |||
for (Issue issue : issues) { | |||
DefaultIssue defaultIssue = (DefaultIssue) issue; |
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
public boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession) { | |||
severity(properties); | |||
return true; | |||
} |
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
public boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession) { | |||
transition(properties); | |||
return true; | |||
} |
@@ -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<IssueDto> selectByKeys(DbSession session, Collection<String> keys) { | |||
public List<IssueDto> selectByKeys(DbSession session, List<String> keys) { | |||
return mapper(session).selectByKeys(keys); | |||
} | |||
@@ -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<Issue> issues, QueryContext context) { | |||
return new IssueFilterResult(issues.getHits(), Paging.create(context.getLimit(), context.getPage(), ((Long) issues.getTotal()).intValue())); | |||
private IssueFilterResult createIssueFilterResult(SearchResult<IssueDoc> 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<Issue> issues; | |||
private final List<IssueDoc> issues; | |||
private final Paging paging; | |||
public IssueFilterResult(List<Issue> issues, Paging paging) { | |||
public IssueFilterResult(List<IssueDoc> issues, Paging paging) { | |||
this.issues = issues; | |||
this.paging = paging; | |||
} | |||
public List<Issue> issues(){ | |||
public List<IssueDoc> issues() { | |||
return issues; | |||
} | |||
public Paging paging(){ | |||
public Paging paging() { | |||
return paging; | |||
} | |||
} | |||
} | |||
} |
@@ -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<Issue, FakeIssueDto, String> { | |||
/** | |||
* 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<String> 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<Map<String, Object>, IssueDoc> DOC_CONVERTER = new Function<Map<String, Object>, IssueDoc>() { | |||
@Override | |||
public IssueDoc apply(Map<String, Object> 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<String, Object> fields) { | |||
Preconditions.checkNotNull(fields, "Cannot construct Issue with null response"); | |||
return new IssueDoc(fields); | |||
} | |||
@Override | |||
public Issue getNullableByKey(String key) { | |||
Result<Issue> 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<IssueDoc> 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<FacetValue> 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<FacetValue> 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<Issue> search(IssueQuery query, QueryContext options) { | |||
SearchRequestBuilder esSearch = getClient() | |||
public SearchResult<IssueDoc> 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<String, FilterBuilder> 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<String, FilterBuilder> filters) { | |||
BoolFilterBuilder esFilter = FilterBuilders.boolFilter(); | |||
Map<String, FilterBuilder> 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<String, FilterBuilder> getFilters(IssueQuery query, QueryContext options) { | |||
Map<String, FilterBuilder> filters = Maps.newHashMap(); | |||
filters.put("__authorization", getAuthorizationFilter(options)); | |||
private Map<String, FilterBuilder> createFilters(IssueQuery query) { | |||
Map<String, FilterBuilder> filters = new HashMap<>(); | |||
filters.put("__authorization", createAuthorizationFilter(query)); | |||
// Issue is assigned Filter | |||
String isAssigned = "__isAssigned"; | |||
@@ -255,20 +238,20 @@ public class IssueIndex extends BaseIndex<Issue, FakeIssueDto, String> { | |||
} | |||
// 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<Issue, FakeIssueDto, String> { | |||
} | |||
private void addComponentRelatedFilters(IssueQuery query, Map<String, FilterBuilder> 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<Issue, FakeIssueDto, String> { | |||
} | |||
@CheckForNull | |||
private FilterBuilder moduleRootFilter(Collection<String> componentUuids) { | |||
private FilterBuilder createModuleRootFilter(Collection<String> 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<Issue, FakeIssueDto, String> { | |||
} else if (modulePathFilter != null) { | |||
compositeFilter = modulePathFilter; | |||
} | |||
System.out.println("compositeFilter:" + compositeFilter); | |||
return compositeFilter; | |||
} | |||
@CheckForNull | |||
private FilterBuilder directoryRootFilter(Collection<String> moduleUuids, Collection<String> directoryPaths) { | |||
private FilterBuilder createDirectoryRootFilter(Collection<String> moduleUuids, Collection<String> 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<Issue, FakeIssueDto, String> { | |||
} | |||
@CheckForNull | |||
private FilterBuilder viewFilter(Collection<String> viewUuids) { | |||
private FilterBuilder createViewFilter(Collection<String> viewUuids) { | |||
if (viewUuids.isEmpty()) { | |||
return null; | |||
} | |||
@@ -381,26 +365,29 @@ public class IssueIndex extends BaseIndex<Issue, FakeIssueDto, String> { | |||
} | |||
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<String> 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<String, FilterBuilder> filters, IssueQuery query) { | |||
@@ -424,9 +411,9 @@ public class IssueIndex extends BaseIndex<Issue, FakeIssueDto, String> { | |||
} | |||
} | |||
private void setFacets(IssueQuery query, QueryContext options, Map<String, FilterBuilder> filters, QueryBuilder esQuery, SearchRequestBuilder esSearch) { | |||
if (options.isFacet()) { | |||
StickyFacetBuilder stickyFacetBuilder = stickyFacetBuilder(esQuery, filters); | |||
private void configureStickyFacets(IssueQuery query, SearchOptions options, Map<String, FilterBuilder> 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<Issue, FakeIssueDto, String> { | |||
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<String, FilterBuilder> 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<String, FilterBuilder> 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<String, FilterBuilder> filters, QueryBuilder esQuery) { | |||
private AggregationBuilder createAssigneesFacet(IssueQuery query, Map<String, FilterBuilder> filters, QueryBuilder queryBuilder) { | |||
String fieldName = IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE; | |||
String facetName = IssueFilterParameters.ASSIGNEES; | |||
@@ -484,9 +523,9 @@ public class IssueIndex extends BaseIndex<Issue, FakeIssueDto, String> { | |||
Map<String, FilterBuilder> 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<String> assignees = Lists.newArrayList(query.assignees()); | |||
UserSession session = UserSession.get(); | |||
@@ -507,7 +546,7 @@ public class IssueIndex extends BaseIndex<Issue, FakeIssueDto, String> { | |||
.subAggregation(facetTopAggregation); | |||
} | |||
private AggregationBuilder getResolutionFacet(Map<String, FilterBuilder> filters, QueryBuilder esQuery) { | |||
private AggregationBuilder createResolutionFacet(Map<String, FilterBuilder> filters, QueryBuilder esQuery) { | |||
String fieldName = IssueIndexDefinition.FIELD_ISSUE_RESOLUTION; | |||
String facetName = IssueFilterParameters.RESOLUTIONS; | |||
@@ -517,7 +556,7 @@ public class IssueIndex extends BaseIndex<Issue, FakeIssueDto, String> { | |||
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<Issue, FakeIssueDto, String> { | |||
.subAggregation(facetTopAggregation); | |||
} | |||
private AggregationBuilder getActionPlansFacet(IssueQuery query, Map<String, FilterBuilder> filters, QueryBuilder esQuery) { | |||
private AggregationBuilder createActionPlansFacet(IssueQuery query, Map<String, FilterBuilder> filters, QueryBuilder esQuery) { | |||
String fieldName = IssueIndexDefinition.FIELD_ISSUE_ACTION_PLAN; | |||
String facetName = IssueFilterParameters.ACTION_PLANS; | |||
@@ -542,7 +581,7 @@ public class IssueIndex extends BaseIndex<Issue, FakeIssueDto, String> { | |||
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<Issue, FakeIssueDto, String> { | |||
.subAggregation(facetTopAggregation); | |||
} | |||
private AggregationBuilder getCreatedAtFacet(IssueQuery query, Map<String, FilterBuilder> 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<String, FilterBuilder> 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<Issue, FakeIssueDto, String> { | |||
} | |||
} | |||
public Collection<String> listTagsMatching(@Nullable String query, int pageSize) { | |||
return listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_TAGS, query, pageSize); | |||
public List<String> 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<String> listAuthorsMatching(@Nullable String query, int pageSize) { | |||
return listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query, pageSize); | |||
public Map<String, Long> 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<String> listTermsMatching(String fieldName, @Nullable String query, int pageSize) { | |||
SearchRequestBuilder count = getClient().prepareSearch(IssueIndexDefinition.INDEX) | |||
.setTypes(IssueIndexDefinition.TYPE_ISSUE) | |||
.setQuery(QueryBuilders.matchAllQuery()); | |||
public List<String> 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<Bucket, String>() { | |||
@Override | |||
public String apply(Bucket bucket) { | |||
return bucket.getKey(); | |||
} | |||
}); | |||
SearchResponse searchResponse = requestBuilder.addAggregation(aggreg).get(); | |||
return searchResponse.getAggregations().get("_ref"); | |||
} | |||
public Map<String, Long> 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<String, Long> map = Maps.newHashMap(); | |||
for (Bucket bucket : result.getBuckets()) { | |||
map.put(bucket.getKey(), bucket.getDocCount()); | |||
public LinkedHashMap<String, Long> 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<String, Long> 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(); | |||
} | |||
} |
@@ -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"); |
@@ -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<String> { | |||
@Override | |||
public String getKey() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} | |||
@@ -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") |
@@ -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") |
@@ -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) { |
@@ -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<IssueQuery, Issue> { | |||
public class SearchAction implements BaseIssuesWsAction { | |||
public static final String SEARCH_ACTION = "search"; | |||
@@ -84,7 +88,6 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> { | |||
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<IssueQuery, Issue> { | |||
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<IssueQuery, Issue> { | |||
} | |||
@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<IssueQuery, Issue> { | |||
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<IssueQuery, Issue> { | |||
} | |||
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<IssueQuery, Issue> { | |||
} | |||
@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<IssueDoc> 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<Issue> doSearch(IssueQuery query, QueryContext context) { | |||
private SearchResult<IssueDoc> execute(IssueQuery query, SearchOptions options) { | |||
Collection<String> 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<String> possibleFields() { | |||
return Collections.emptyList(); | |||
} | |||
@Override | |||
@CheckForNull | |||
protected Collection<String> 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<Issue> result, JsonWriter json) { | |||
private void writeResponse(Request request, SearchResult<IssueDoc> result, JsonWriter json) { | |||
List<String> issueKeys = newArrayList(); | |||
Set<RuleKey> ruleKeys = newHashSet(); | |||
Set<String> projectUuids = newHashSet(); | |||
@@ -313,21 +300,19 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> { | |||
Map<String, ComponentDto> componentsByUuid = newHashMap(); | |||
Multimap<String, DefaultIssueComment> commentsByIssues = ArrayListMultimap.create(); | |||
Collection<ComponentDto> componentDtos = newHashSet(); | |||
List<ComponentDto> projectDtos = newArrayList(); | |||
Map<String, ComponentDto> 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<IssueQuery, Issue> { | |||
DbSession session = dbClient.openSession(false); | |||
try { | |||
List<DefaultIssueComment> comments = issueChangeDao.selectCommentsByIssues(session, issueKeys); | |||
List<DefaultIssueComment> 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<IssueQuery, Issue> { | |||
projectUuids.add(component.projectUuid()); | |||
} | |||
projectDtos = dbClient.componentDao().getByUuids(session, projectUuids); | |||
List<ComponentDto> 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<IssueQuery, Issue> { | |||
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<Issue> result, Set<RuleKey> ruleKeys) { | |||
Collection<FacetValue> facetRules = result.getFacetValues(IssueFilterParameters.RULES); | |||
private void collectRuleKeys(Request request, SearchResult<IssueDoc> result, Set<RuleKey> ruleKeys) { | |||
Set<String> 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<String> 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<IssueDoc> results, JsonWriter json) { | |||
addMandatoryFacetValues(results, IssueFilterParameters.SEVERITIES, Severity.ALL); | |||
addMandatoryFacetValues(results, IssueFilterParameters.STATUSES, Issue.STATUSES); | |||
List<String> resolutions = Lists.newArrayList(""); | |||
@@ -429,37 +410,51 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> { | |||
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<String> itemsFromFacets = Sets.newHashSet(); | |||
for (Map.Entry<String, Long> 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<Issue> result, Set<String> projectUuids, Set<String> componentUuids, List<String> userLogins, Set<String> actionPlanKeys) { | |||
collectFacetKeys(result, IssueFilterParameters.PROJECT_UUIDS, projectUuids); | |||
private void collectFacetsData(Request request, SearchResult<IssueDoc> result, Set<String> projectUuids, Set<String> componentUuids, List<String> userLogins, | |||
Set<String> 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<Issue> result, String facetName, Collection<String> facetKeys) { | |||
Collection<FacetValue> facetValues = result.getFacetValues(facetName); | |||
if (facetValues != null) { | |||
for (FacetValue project : facetValues) { | |||
facetKeys.add(project.getKey()); | |||
} | |||
} | |||
private void collectBucketKeys(SearchResult<IssueDoc> result, String facetName, Collection<String> bucketKeys) { | |||
bucketKeys.addAll(result.getFacets().getBucketKeys(facetName)); | |||
} | |||
private void collectParameterValues(Request request, String facetName, Collection<String> facetKeys) { | |||
@@ -469,28 +464,6 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> { | |||
} | |||
} | |||
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<Rule> rules) { | |||
json.name("rules").beginArray(); | |||
@@ -508,11 +481,12 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> { | |||
json.endArray(); | |||
} | |||
private void writeIssues(Result<Issue> result, Multimap<String, DefaultIssueComment> commentsByIssues, Map<String, User> usersByLogin, Map<String, ActionPlan> actionPlanByKeys, | |||
private void writeIssues(SearchResult<IssueDoc> result, Multimap<String, DefaultIssueComment> commentsByIssues, Map<String, User> usersByLogin, | |||
Map<String, ActionPlan> actionPlanByKeys, | |||
Map<String, ComponentDto> componentsByUuid, Map<String, ComponentDto> projectsByComponentUuid, @Nullable List<String> 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<IssueQuery, Issue> { | |||
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<IssueQuery, Issue> { | |||
Collection<String> 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<IssueQuery, Issue> { | |||
return null; | |||
} | |||
protected void addMandatoryFacetValues(SearchResult<IssueDoc> results, String facetName, @Nullable List<String> mandatoryValues) { | |||
Map<String, Long> 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<String> itemsFromFacets, JsonWriter json) { | |||
List<String> 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(); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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<String> resultTags = service.setTags(key, ImmutableSet.copyOf(WS_TAGS_SPLITTER.split(tags))); | |||
List<String> tags = Objects.firstNonNull(request.paramAsStrings("tags"), Collections.<String>emptyList()); | |||
Collection<String> resultTags = service.setTags(key, tags); | |||
JsonWriter json = response.newJsonWriter().beginObject().name("tags").beginArray(); | |||
for (String tag : resultTags) { | |||
json.value(tag); |
@@ -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") |
@@ -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<String, FilterBuilder> filters; | |||
private final QueryBuilder query; | |||
private final Map<String, FilterBuilder> filters; | |||
public StickyFacetBuilder(QueryBuilder query, Map<String, FilterBuilder> filters) { | |||
this.query = query; |
@@ -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() |
@@ -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", |
@@ -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)); | |||
@@ -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<BaseDoc> docs = EsUtils.convertToDocs(hits, new Function<Map<String, Object>, BaseDoc>() { | |||
@Override | |||
public BaseDoc apply(Map<String, Object> 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<BaseDoc> docs = EsUtils.convertToDocs(hits, new Function<Map<String, Object>, BaseDoc>() { | |||
@Override | |||
public BaseDoc apply(Map<String, Object> input) { | |||
return new IssueDoc(input); | |||
} | |||
}); | |||
assertThat(docs).hasSize(1); | |||
} | |||
@Test | |||
public void util_class() throws Exception { | |||
assertThat(TestUtils.hasOnlyPrivateConstructors(EsUtils.class)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession) { | |||
return false; | |||
} | |||
@Override | |||
boolean execute(Map<String, Object> properties, Context context) { | |||
return false; | |||
@@ -55,9 +55,10 @@ public class ActionTest { | |||
try { | |||
new Action(null) { | |||
@Override | |||
boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
boolean verify(Map<String, Object> properties, Collection<Issue> issues, UserSession userSession) { | |||
return false; | |||
} | |||
@Override | |||
boolean execute(Map<String, Object> properties, Context context) { | |||
return false; |
@@ -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.<String, Object>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<IssueQuery> issueQueryArgumentCaptor = ArgumentCaptor.forClass(IssueQuery.class); | |||
ArgumentCaptor<QueryContext> contextArgumentCaptor = ArgumentCaptor.forClass(QueryContext.class); | |||
ArgumentCaptor<SearchOptions> 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<String, Object> 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.<String, Object>newHashMap()); | |||
assertThat(context.getLimit()).isEqualTo(100); | |||
assertThat(context.getPage()).isEqualTo(1); | |||
searchOptions = InternalRubyIssueService.toSearchOptions(Maps.<String, Object>newHashMap()); | |||
assertThat(searchOptions.getLimit()).isEqualTo(100); | |||
assertThat(searchOptions.getPage()).isEqualTo(1); | |||
} | |||
@Test | |||
public void list_tags() throws Exception { | |||
ImmutableSet<String> tags = ImmutableSet.of("tag1", "tag2", "tag3"); | |||
List<String> tags = Arrays.asList("tag1", "tag2", "tag3"); | |||
when(issueService.listTags(null, 0)).thenReturn(tags); | |||
assertThat(service.listTags()).isEqualTo(tags); | |||
} |
@@ -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<Action> 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<Issue> 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<String, Object> 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<String, Object> 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<String, Object> 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<Issue> resultIssues = mock(org.sonar.server.search.Result.class); | |||
when(resultIssues.getHits()).thenReturn(Lists.<Issue>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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
return verify; | |||
} | |||
@Override | |||
boolean execute(Map<String, Object> 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<Action> 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<IssueDoc> 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<String, Object> 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<String, Object> 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<String, Object> 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<Issue> resultIssues = mock(org.sonar.server.search.Result.class); | |||
// when(resultIssues.getHits()).thenReturn(Lists.<Issue>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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> properties, List<Issue> issues, UserSession userSession) { | |||
// return verify; | |||
// } | |||
// | |||
// @Override | |||
// boolean execute(Map<String, Object> properties, Context context) { | |||
// return execute; | |||
// } | |||
// } | |||
} |
@@ -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<Issue> result = service.search(IssueQuery.builder().build(), new QueryContext()); | |||
assertThat(result.getHits()).hasSize(2); | |||
assertThat(result.getFacets()).isEmpty(); | |||
SearchResult<IssueDoc> 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<Issue> result = service.search(IssueQuery.builder().build(), new QueryContext()).getHits(); | |||
List<IssueDoc> 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.<String>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(); | |||
} |
@@ -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<Issue> result = mock(Result.class); | |||
when(result.getHits()).thenReturn(newArrayList((Issue) new DefaultIssue())); | |||
SearchResult<IssueDoc> 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); |
@@ -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"); | |||
} | |||
@@ -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 |
@@ -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) | |||
)); | |||
} | |||
@@ -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 |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
@@ -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); |
@@ -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")); | |||
} | |||
} |
@@ -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<IssueDoc> 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) { |
@@ -2,7 +2,6 @@ | |||
"total": 0, | |||
"p": 1, | |||
"ps": 100, | |||
"maxResultsReached": false, | |||
"paging": { | |||
"pageIndex": 1, | |||
"pageSize": 100, |
@@ -1,5 +1,4 @@ | |||
{ | |||
"maxResultsReached": false, | |||
"paging": { | |||
"pageIndex": 2, | |||
"pageSize": 9, |
@@ -2,7 +2,6 @@ | |||
"total": 0, | |||
"p": 1, | |||
"ps": 100, | |||
"maxResultsReached": false, | |||
"paging": { | |||
"pageIndex": 1, | |||
"pageSize": 100, |
@@ -1,5 +1,4 @@ | |||
{ | |||
"maxResultsReached": false, | |||
"total": 1, | |||
"p": 1, | |||
"ps": 100, |
@@ -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; | |||
@@ -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<IssueDto> selectByKeys(Collection<String> keys); | |||
List<IssueDto> selectByKeys(List<String> keys); | |||
List<IssueDto> selectByActionPlan(String actionPlan); | |||
@@ -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 <V> NewAction addSortParams(Collection<V> 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<String> possibleValues; |
@@ -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 |