aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephane Gamard <stephane.gamard@sonarsource.com>2014-09-04 15:55:51 +0200
committerStephane Gamard <stephane.gamard@sonarsource.com>2014-09-04 15:55:51 +0200
commit0b4912da7d3e242e0c79a0d1a711763b73dfb628 (patch)
treea521372dcafbbbaabbbf31f3f19102e48d336b75
parent7fab7d6899f65aa50b359ee912cc802d4a66e47c (diff)
downloadsonarqube-0b4912da7d3e242e0c79a0d1a711763b73dfb628.tar.gz
sonarqube-0b4912da7d3e242e0c79a0d1a711763b73dfb628.zip
SONAR-5531 - Wrapped IssueIndex with WS SearchAction
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java20
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java13
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueMapping.java11
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java351
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/search/Result.java2
6 files changed, 393 insertions, 8 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
index 258160aba25..6c8351a4319 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
@@ -24,6 +24,7 @@ import com.google.common.base.Strings;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.action.search.SearchResponse;
import org.sonar.api.ServerComponent;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
@@ -54,7 +55,10 @@ import org.sonar.core.rule.RuleDto;
import org.sonar.core.user.AuthorizationDao;
import org.sonar.server.db.DbClient;
import org.sonar.server.issue.actionplan.ActionPlanService;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueResult;
import org.sonar.server.search.IndexClient;
+import org.sonar.server.search.QueryOptions;
import org.sonar.server.user.UserSession;
import javax.annotation.Nullable;
@@ -324,4 +328,20 @@ public class IssueService implements ServerComponent {
return aggregation;
}
+ public Issue getByKey(String key) {
+ return indexClient.get(IssueIndex.class).getByKey(key);
+ }
+
+ public IssueResult search(IssueQuery query, QueryOptions options) {
+
+ IssueIndex issueIndex = indexClient.get(IssueIndex.class);
+
+ SearchResponse esResults = issueIndex.search(query, options);
+
+ // Extend the content of the resultSet to make an actual IssueResponse
+ IssueResult results = new IssueResult(issueIndex, esResults);
+
+ // TODO Implement the logic of search here!!!
+ return results;
+ }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
index 465013a2b91..50bf8fa0862 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
@@ -20,12 +20,16 @@
package org.sonar.server.issue.index;
import com.google.common.base.Preconditions;
+import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.sonar.api.issue.IssueQuery;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.server.search.BaseIndex;
import org.sonar.server.search.IndexDefinition;
import org.sonar.server.search.IndexField;
+import org.sonar.server.search.QueryOptions;
import org.sonar.server.search.SearchClient;
import java.io.IOException;
@@ -100,4 +104,13 @@ public class IssueIndex extends BaseIndex<IssueDoc, IssueDto, String> {
Preconditions.checkNotNull(fields, "Cannot construct Issue with null response");
return new IssueDoc(fields);
}
+
+ public SearchResponse search(IssueQuery query, QueryOptions options) {
+
+ // TODO implement filters and search
+
+ return getClient().execute(
+ getClient().prepareSearch(getIndexName())
+ .setQuery(QueryBuilders.matchAllQuery()));
+ }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueMapping.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueMapping.java
new file mode 100644
index 00000000000..fb0421a9cb7
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueMapping.java
@@ -0,0 +1,11 @@
+package org.sonar.server.issue.index;
+
+import org.sonar.server.search.ws.BaseMapping;
+
+public class IssueMapping extends BaseMapping<IssueDoc, IssueMappingContext> {
+
+}
+
+class IssueMappingContext {
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java
index 3f087f49756..c7ae8f393fb 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java
@@ -49,7 +49,7 @@ public class IssueResult extends Result<IssueDoc> implements IssueQueryResult {
Paging paging;
public IssueResult(SearchResponse response) {
- super(response);
+ this(null, response);
}
public IssueResult(@Nullable BaseIndex<IssueDoc, ?, ?> index, SearchResponse response) {
@@ -58,7 +58,7 @@ public class IssueResult extends Result<IssueDoc> implements IssueQueryResult {
@Override
public List<Issue> issues() {
- return ImmutableList.<Issue>copyOf(this.getHits());
+ return ImmutableList.<Issue>builder().addAll(this.getHits()).build();
}
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
index fa2e8e06c44..cd7f0f7c972 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
@@ -19,18 +19,51 @@
*/
package org.sonar.server.issue.ws;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.io.Resources;
+import org.sonar.api.component.Component;
import org.sonar.api.i18n.I18n;
+import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.IssueComment;
import org.sonar.api.issue.IssueQuery;
+import org.sonar.api.issue.IssueQueryResult;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.Rule;
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;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.markdown.Markdown;
+import org.sonar.server.issue.IssueService;
import org.sonar.server.issue.filter.IssueFilterParameters;
-import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueMapping;
+import org.sonar.server.issue.index.IssueResult;
+import org.sonar.server.search.QueryOptions;
+import org.sonar.server.search.ws.SearchOptions;
+import org.sonar.server.user.UserSession;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
public class SearchAction implements RequestHandler {
@@ -44,13 +77,17 @@ public class SearchAction implements RequestHandler {
private static final String EXTRA_FIELDS_PARAM = "extra_fields";
- private final IssueIndex issueIndex;
+ public static final String PARAM_FACETS = "facets";
+
+ private final IssueService service;
+ private final IssueMapping mapping;
private final IssueActionsWriter actionsWriter;
private final I18n i18n;
private final Durations durations;
- public SearchAction(IssueIndex index, IssueActionsWriter actionsWriter, I18n i18n, Durations durations) {
- this.issueIndex = index;
+ public SearchAction(IssueService service, IssueMapping mapping, IssueActionsWriter actionsWriter, I18n i18n, Durations durations) {
+ this.service = service;
+ this.mapping = mapping;
this.actionsWriter = actionsWriter;
this.i18n = i18n;
this.durations = durations;
@@ -64,6 +101,24 @@ public class SearchAction implements RequestHandler {
.setHandler(this)
.setResponseExample(Resources.getResource(this.getClass(), "example-search.json"));
+ // Add globalized search options. Will also support legacy params
+ // Generic search parameters
+ SearchOptions.defineFieldsParam(action,
+ ImmutableList.<String>builder().addAll(mapping.supportedFields()).build());
+ SearchOptions.definePageParams(action);
+
+ // Issue-specific search parameters
+ defineIssueSearchParameters(action);
+
+ // Other parameters
+ action.createParam(PARAM_FACETS)
+ .setDescription("Compute predefined facets")
+ .setBooleanPossibleValues()
+ .setDefaultValue("false");
+ }
+
+ public static void defineIssueSearchParameters(WebService.NewAction action) {
+
action.createParam(IssueFilterParameters.ISSUES)
.setDescription("Comma-separated list of issue keys")
.setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
@@ -147,7 +202,293 @@ public class SearchAction implements RequestHandler {
}
@Override
- public void handle(Request request, Response response) throws Exception {
+ public void handle(Request request, Response response) {
+ IssueQuery query = createQuery(request);
+ SearchOptions searchOptions = SearchOptions.create(request);
+ QueryOptions queryOptions = new QueryOptions();
+ mapping.newQueryOptions(searchOptions);
+ queryOptions.setFacet(request.mandatoryParamAsBoolean(PARAM_FACETS));
+
+ IssueResult results = service.search(query, queryOptions);
+
+ JsonWriter json = response.newJsonWriter();
+ json.beginObject();
+
+ writePaging(results, json);
+ writeIssues(results, request.paramAsStrings(EXTRA_FIELDS_PARAM), json);
+ writeComponents(results, json);
+ writeProjects(results, json);
+ writeRules(results, json);
+ writeUsers(results, json);
+ writeActionPlans(results, json);
+
+ json.endObject().close();
+ }
+
+ private void writePaging(IssueQueryResult result, JsonWriter json) {
+ json.prop("maxResultsReached", result.maxResultsReached());
+ json.name("paging").beginObject()
+ .prop("pageIndex", result.paging().pageIndex())
+ .prop("pageSize", result.paging().pageSize())
+ .prop("total", result.paging().total())
+ .prop("fTotal", i18n.formatInteger(UserSession.get().locale(), result.paging().total()))
+ .prop("pages", result.paging().pages())
+ .endObject();
+ }
+
+ private void writeIssues(IssueQueryResult result, @Nullable List<String> extraFields, JsonWriter json) {
+ json.name("issues").beginArray();
+
+ for (Issue i : result.issues()) {
+ json.beginObject();
+
+ DefaultIssue issue = (DefaultIssue) i;
+ String actionPlanKey = issue.actionPlanKey();
+ Duration debt = issue.debt();
+ Date updateDate = issue.updateDate();
+
+ json
+ .prop("key", issue.key())
+ .prop("component", issue.componentKey())
+ .prop("componentId", issue.componentId())
+ .prop("project", issue.projectKey())
+ .prop("rule", issue.ruleKey().toString())
+ .prop("status", issue.status())
+ .prop("resolution", issue.resolution())
+ .prop("severity", issue.severity())
+ .prop("message", issue.message())
+ .prop("line", issue.line())
+ .prop("debt", debt != null ? durations.encode(debt) : null)
+ .prop("reporter", issue.reporter())
+ .prop("assignee", issue.assignee())
+ .prop("author", issue.authorLogin())
+ .prop("actionPlan", actionPlanKey)
+ .prop("creationDate", isoDate(issue.creationDate()))
+ .prop("updateDate", isoDate(updateDate))
+ .prop("fUpdateAge", formatAgeDate(updateDate))
+ .prop("closeDate", isoDate(issue.closeDate()));
+
+ writeIssueComments(result, issue, json);
+ writeIssueAttributes(issue, json);
+ writeIssueExtraFields(result, issue, extraFields, json);
+ json.endObject();
+ }
+
+ json.endArray();
+ }
+
+ private void writeIssueComments(IssueQueryResult queryResult, Issue issue, JsonWriter json) {
+ if (!issue.comments().isEmpty()) {
+ json.name("comments").beginArray();
+ String login = UserSession.get().login();
+ for (IssueComment comment : issue.comments()) {
+ String userLogin = comment.userLogin();
+ User user = userLogin != null ? queryResult.user(userLogin) : null;
+ json.beginObject()
+ .prop("key", comment.key())
+ .prop("login", comment.userLogin())
+ .prop("userName", user != null ? user.name() : null)
+ .prop("htmlText", Markdown.convertToHtml(comment.markdownText()))
+ .prop("markdown", comment.markdownText())
+ .prop("updatable", login != null && login.equals(userLogin))
+ .prop("createdAt", DateUtils.formatDateTime(comment.createdAt()))
+ .endObject();
+ }
+ json.endArray();
+ }
+ }
+
+ private void writeIssueAttributes(Issue issue, JsonWriter json) {
+ if (!issue.attributes().isEmpty()) {
+ json.name("attr").beginObject();
+ for (Map.Entry<String, String> entry : issue.attributes().entrySet()) {
+ json.prop(entry.getKey(), entry.getValue());
+ }
+ json.endObject();
+ }
+ }
+
+ private void writeIssueExtraFields(IssueQueryResult result, Issue issue, @Nullable List<String> extraFields, JsonWriter json) {
+ if (extraFields != null && UserSession.get().isLoggedIn()) {
+ if (extraFields.contains(ACTIONS_EXTRA_FIELD)) {
+ actionsWriter.writeActions(issue, json);
+ }
+
+ if (extraFields.contains(TRANSITIONS_EXTRA_FIELD)) {
+ actionsWriter.writeTransitions(issue, json);
+ }
+
+ String assignee = issue.assignee();
+ if (extraFields.contains(ASSIGNEE_NAME_EXTRA_FIELD) && assignee != null) {
+ User user = result.user(assignee);
+ json.prop(ASSIGNEE_NAME_EXTRA_FIELD, user != null ? user.name() : null);
+ }
+
+ String reporter = issue.reporter();
+ if (extraFields.contains(REPORTER_NAME_EXTRA_FIELD) && reporter != null) {
+ User user = result.user(reporter);
+ json.prop(REPORTER_NAME_EXTRA_FIELD, user != null ? user.name() : null);
+ }
+
+ String actionPlanKey = issue.actionPlanKey();
+ if (extraFields.contains(ACTION_PLAN_NAME_EXTRA_FIELD) && actionPlanKey != null) {
+ ActionPlan actionPlan = result.actionPlan(issue);
+ json.prop(ACTION_PLAN_NAME_EXTRA_FIELD, actionPlan != null ? actionPlan.name() : null);
+ }
+ }
+ }
+
+ private void writeComponents(IssueQueryResult result, JsonWriter json) {
+ json.name("components").beginArray();
+ for (Component component : result.components()) {
+ ComponentDto componentDto = (ComponentDto) component;
+ json.beginObject()
+ .prop("key", component.key())
+ .prop("id", componentDto.getId())
+ .prop("qualifier", component.qualifier())
+ .prop("name", component.name())
+ .prop("longName", component.longName())
+ .prop("path", component.path())
+ // On a root project, subProjectId is null but projectId is equal to itself, which make no sense.
+ .prop("projectId", (componentDto.projectId() != null && componentDto.subProjectId() != null) ? componentDto.projectId() : null)
+ .prop("subProjectId", componentDto.subProjectId())
+ .endObject();
+ }
+ json.endArray();
+ }
+
+ private void writeProjects(IssueQueryResult result, JsonWriter json) {
+ json.name("projects").beginArray();
+ for (Component project : result.projects()) {
+ ComponentDto componentDto = (ComponentDto) project;
+ json.beginObject()
+ .prop("key", project.key())
+ .prop("id", componentDto.getId())
+ .prop("qualifier", project.qualifier())
+ .prop("name", project.name())
+ .prop("longName", project.longName())
+ .endObject();
+ }
+ json.endArray();
+ }
+
+ private void writeRules(IssueQueryResult result, JsonWriter json) {
+ json.name("rules").beginArray();
+ for (Rule rule : result.rules()) {
+ json.beginObject()
+ .prop("key", rule.ruleKey().toString())
+ .prop("name", rule.getName())
+ .prop("desc", rule.getDescription())
+ .prop("status", rule.getStatus())
+ .endObject();
+ }
+ json.endArray();
+ }
+
+ private void writeUsers(IssueQueryResult result, JsonWriter json) {
+ json.name("users").beginArray();
+ for (User user : result.users()) {
+ json.beginObject()
+ .prop("login", user.login())
+ .prop("name", user.name())
+ .prop("active", user.active())
+ .prop("email", user.email())
+ .endObject();
+ }
+ json.endArray();
+ }
+
+ private void writeActionPlans(IssueQueryResult result, JsonWriter json) {
+ if (!result.actionPlans().isEmpty()) {
+ json.name("actionPlans").beginArray();
+ for (ActionPlan actionPlan : result.actionPlans()) {
+ Date deadLine = actionPlan.deadLine();
+ Date updatedAt = actionPlan.updatedAt();
+
+ json.beginObject()
+ .prop("key", actionPlan.key())
+ .prop("name", actionPlan.name())
+ .prop("status", actionPlan.status())
+ .prop("project", actionPlan.projectKey())
+ .prop("userLogin", actionPlan.userLogin())
+ .prop("deadLine", isoDate(deadLine))
+ .prop("fDeadLine", formatDate(deadLine))
+ .prop("createdAt", isoDate(actionPlan.createdAt()))
+ .prop("fCreatedAt", formatDate(actionPlan.createdAt()))
+ .prop("updatedAt", isoDate(actionPlan.updatedAt()))
+ .prop("fUpdatedAt", formatDate(updatedAt))
+ .endObject();
+ }
+ json.endArray();
+ }
+ }
+
+ @CheckForNull
+ private String isoDate(@Nullable Date date) {
+ if (date != null) {
+ return DateUtils.formatDateTime(date);
+ }
+ return null;
+ }
+
+ @CheckForNull
+ private String formatDate(@Nullable Date date) {
+ if (date != null) {
+ return i18n.formatDateTime(UserSession.get().locale(), date);
+ }
+ return null;
+ }
+
+ @CheckForNull
+ private String formatAgeDate(@Nullable Date date) {
+ if (date != null) {
+ return i18n.ageFromNow(UserSession.get().locale(), date);
+ }
+ return null;
+ }
+
+ @CheckForNull
+ private static Collection<RuleKey> stringsToRules(@Nullable Collection<String> rules) {
+ if (rules != null) {
+ return newArrayList(Iterables.transform(rules, new Function<String, RuleKey>() {
+ @Override
+ public RuleKey apply(@Nullable String s) {
+ return s != null ? RuleKey.parse(s) : null;
+ }
+ }));
+ }
+ return null;
+ }
+ @VisibleForTesting
+ static IssueQuery createQuery(Request request) {
+ IssueQuery.Builder builder = IssueQuery.builder()
+ .requiredRole(UserRole.USER)
+ .issueKeys(request.paramAsStrings(IssueFilterParameters.ISSUES))
+ .severities(request.paramAsStrings(IssueFilterParameters.SEVERITIES))
+ .statuses(request.paramAsStrings(IssueFilterParameters.STATUSES))
+ .resolutions(request.paramAsStrings(IssueFilterParameters.RESOLUTIONS))
+ .resolved(request.paramAsBoolean(IssueFilterParameters.RESOLVED))
+ .components(request.paramAsStrings(IssueFilterParameters.COMPONENTS))
+ .componentRoots(request.paramAsStrings(IssueFilterParameters.COMPONENT_ROOTS))
+ .rules(stringsToRules(request.paramAsStrings(IssueFilterParameters.RULES)))
+ .actionPlans(request.paramAsStrings(IssueFilterParameters.ACTION_PLANS))
+ .reporters(request.paramAsStrings(IssueFilterParameters.REPORTERS))
+ .assignees(request.paramAsStrings(IssueFilterParameters.ASSIGNEES))
+ .languages(request.paramAsStrings(IssueFilterParameters.LANGUAGES))
+ .assigned(request.paramAsBoolean(IssueFilterParameters.ASSIGNED))
+ .planned(request.paramAsBoolean(IssueFilterParameters.PLANNED))
+ .hideRules(request.paramAsBoolean(IssueFilterParameters.HIDE_RULES))
+ .createdAt(request.paramAsDateTime(IssueFilterParameters.CREATED_AT))
+ .createdAfter(request.paramAsDateTime(IssueFilterParameters.CREATED_AFTER))
+ .createdBefore(request.paramAsDateTime(IssueFilterParameters.CREATED_BEFORE))
+ .pageSize(request.paramAsInt(IssueFilterParameters.PAGE_SIZE))
+ .pageIndex(request.paramAsInt(IssueFilterParameters.PAGE_INDEX));
+ String sort = request.param(IssueFilterParameters.SORT);
+ if (!Strings.isNullOrEmpty(sort)) {
+ builder.sort(sort);
+ builder.asc(request.paramAsBoolean(IssueFilterParameters.ASC));
+ }
+ return builder.build();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/Result.java b/server/sonar-server/src/main/java/org/sonar/server/search/Result.java
index ba45bd11052..85a32c26cf4 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/search/Result.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/search/Result.java
@@ -33,7 +33,7 @@ import javax.annotation.Nullable;
import java.util.*;
-public class Result<K extends BaseDoc> {
+public class Result<K> {
private final List<K> hits;
private final Multimap<String, FacetValue> facets;