From: Simon Brandhof Date: Tue, 4 Aug 2015 16:44:08 +0000 (+0200) Subject: Rewrite some Ruby WS api/issues/{operation} in Java X-Git-Tag: 5.2-RC1~840 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0ed8bdfe035f8de4b907d0f0ec6326cb5e5b3f02;p=sonarqube.git Rewrite some Ruby WS api/issues/{operation} in Java - api/issues/create - api/issues/set_severity - api/issues/plan - api/issues/do_transition - api/issues/plan --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentJsonWriter.java deleted file mode 100644 index 01bf01a4e0e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentJsonWriter.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.component.ws; - -import org.sonar.api.utils.text.JsonWriter; -import org.sonar.db.component.ComponentDto; - -public class ComponentJsonWriter { - - public void write(JsonWriter json, ComponentDto component, ComponentDto project) { - json.beginObject() - .prop("uuid", component.uuid()) - .prop("key", component.key()) - .prop("id", component.getId()) - .prop("qualifier", component.qualifier()) - .prop("name", component.name()) - .prop("longName", component.longName()) - .prop("enabled", component.isEnabled()) - .prop("path", component.path()) - // On a root project, parentProjectId is null but projectId is equal to itself, which make no sense. - .prop("projectId", (component.projectUuid() != null && component.parentProjectId() != null) ? project.getId() : null) - // TODO replace with parentProjectId when sonar-ws-client is not used anymore by tests... - .prop("subProjectId", component.parentProjectId()) - .endObject(); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index ecb4e0a02a3..67c580aa7e0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -20,68 +20,42 @@ package org.sonar.server.issue; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import java.io.StringWriter; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueComment; -import org.sonar.api.issue.action.Action; -import org.sonar.api.rule.RuleKey; import org.sonar.api.server.ServerSide; -import org.sonar.api.user.User; import org.sonar.api.utils.SonarException; -import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.web.UserRole; import org.sonar.core.issue.ActionPlanStats; import org.sonar.core.issue.DefaultActionPlan; -import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.issue.FieldDiffs; import org.sonar.core.issue.workflow.Transition; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.MyBatis; -import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ResourceDao; import org.sonar.db.component.ResourceDto; import org.sonar.db.component.ResourceQuery; import org.sonar.db.issue.IssueFilterDto; -import org.sonar.server.component.ws.ComponentJsonWriter; 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; import org.sonar.server.issue.filter.IssueFilterService; -import org.sonar.server.issue.ws.IssueComponentHelper; -import org.sonar.server.issue.ws.IssueJsonWriter; import org.sonar.server.search.QueryContext; import org.sonar.server.user.UserSession; -import org.sonar.server.user.index.UserIndex; -import org.sonar.server.user.ws.UserJsonWriter; import org.sonar.server.util.RubyUtils; import org.sonar.server.util.Validation; -import static com.google.common.collect.Maps.newHashMap; - /** * Used through ruby code
Internal.issues
*

@@ -100,50 +74,33 @@ public class InternalRubyIssueService { private static final String ACTION_PLANS_ERRORS_ACTION_PLAN_DOES_NOT_EXIST_MESSAGE = "action_plans.errors.action_plan_does_not_exist"; - private static final List ISSUE_FIELDS = ImmutableList.copyOf(IssueJsonWriter.SELECTABLE_FIELDS); - private final IssueService issueService; private final IssueQueryService issueQueryService; private final IssueCommentService commentService; private final IssueChangelogService changelogService; private final ActionPlanService actionPlanService; private final ResourceDao resourceDao; - private final ActionService actionService; private final IssueFilterService issueFilterService; private final IssueBulkChangeService issueBulkChangeService; - private final IssueJsonWriter issueWriter; - private final IssueComponentHelper issueComponentHelper; - private final ComponentJsonWriter componentWriter; - private final UserIndex userIndex; - private final DbClient dbClient; private final UserSession userSession; - private final UserJsonWriter userWriter; public InternalRubyIssueService( IssueService issueService, IssueQueryService issueQueryService, IssueCommentService commentService, IssueChangelogService changelogService, ActionPlanService actionPlanService, - ResourceDao resourceDao, ActionService actionService, + ResourceDao resourceDao, IssueFilterService issueFilterService, IssueBulkChangeService issueBulkChangeService, - IssueJsonWriter issueWriter, IssueComponentHelper issueComponentHelper, ComponentJsonWriter componentWriter, UserIndex userIndex, DbClient dbClient, - UserSession userSession, UserJsonWriter userWriter) { + UserSession userSession) { this.issueService = issueService; this.issueQueryService = issueQueryService; this.commentService = commentService; this.changelogService = changelogService; this.actionPlanService = actionPlanService; this.resourceDao = resourceDao; - this.actionService = actionService; this.issueFilterService = issueFilterService; this.issueBulkChangeService = issueBulkChangeService; - this.issueWriter = issueWriter; - this.issueComponentHelper = issueComponentHelper; - this.componentWriter = componentWriter; - this.userIndex = userIndex; - this.dbClient = dbClient; this.userSession = userSession; - this.userWriter = userWriter; } public List listTransitions(String issueKey) { @@ -182,36 +139,6 @@ public class InternalRubyIssueService { return commentService.findComments(issueKeys); } - public Result doTransition(String issueKey, String transitionKey) { - Result result = Result.of(); - try { - result.set(issueService.doTransition(issueKey, transitionKey)); - } catch (Exception e) { - result.addError(e.getMessage()); - } - return result; - } - - public Result setSeverity(String issueKey, String severity) { - Result result = Result.of(); - try { - result.set(issueService.setSeverity(issueKey, severity)); - } catch (Exception e) { - result.addError(e.getMessage()); - } - return result; - } - - public Result plan(String issueKey, @Nullable String actionPlanKey) { - Result result = Result.of(); - try { - result.set(issueService.plan(issueKey, actionPlanKey)); - } catch (Exception e) { - result.addError(e.getMessage()); - } - return result; - } - public Result addComment(String issueKey, String text) { Result result = Result.of(); try { @@ -240,37 +167,6 @@ public class InternalRubyIssueService { return commentService.findComment(commentKey); } - /** - * Create manual issue - */ - public Result create(Map params) { - Result result = Result.of(); - try { - // mandatory parameters - String componentKey = params.get("component"); - if (StringUtils.isBlank(componentKey)) { - result.addError("Component is not set"); - } - RuleKey ruleKey = null; - String rule = params.get("rule"); - if (StringUtils.isBlank(rule)) { - result.addError(Result.Message.ofL10n("issue.manual.missing_rule")); - } else { - ruleKey = RuleKey.parse(rule); - } - - if (result.ok()) { - DefaultIssue issue = issueService.createManualIssue(componentKey, ruleKey, RubyUtils.toInteger(params.get("line")), params.get("message"), params.get("severity"), - RubyUtils.toDouble(params.get("effortToFix"))); - result.set(issue); - } - - } catch (Exception e) { - result.addError(e.getMessage()); - } - return result; - } - public Collection findOpenActionPlans(String projectKey) { return actionPlanService.findOpenByProjectKey(projectKey, userSession); } @@ -656,113 +552,6 @@ public class InternalRubyIssueService { return userSession.hasProjectPermissionByUuid(UserRole.ISSUE_ADMIN, projectUuid); } - /** - * Used by issue modification actions currently implemented in Rails - * - * @return the JSON representation of the modified issue, as a ready to use string - */ - public String writeIssueJson(@Nullable Issue original) { - if (original == null) { - return "{}"; - } - - // Reloading from ES to avoid partial object, e.g for manual issues - Issue issue = issueService.getByKey(original.key()); - - StringWriter writer = new StringWriter(); - JsonWriter json = JsonWriter.of(writer); - DbSession dbSession = dbClient.openSession(false); - try { - Map usersByLogin = getIssueUsersByLogin(issue); - - Set componentUuids = ImmutableSet.of(issue.componentUuid()); - Set projectUuids = Sets.newHashSet(); - Set componentDtos = Sets.newHashSet(); - List projectDtos = Lists.newArrayList(); - - Map componentsByUuid = Maps.newHashMap(); - Map projectsByComponentUuid = Maps.newHashMap(); - - List fileDtos = dbClient.componentDao().selectByUuids(dbSession, componentUuids); - List subProjectDtos = dbClient.componentDao().selectSubProjectsByComponentUuids(dbSession, componentUuids); - componentDtos.addAll(fileDtos); - componentDtos.addAll(subProjectDtos); - for (ComponentDto component : componentDtos) { - projectUuids.add(component.projectUuid()); - } - projectDtos.addAll(dbClient.componentDao().selectByUuids(dbSession, projectUuids)); - componentDtos.addAll(projectDtos); - - for (ComponentDto componentDto : componentDtos) { - componentsByUuid.put(componentDto.uuid(), componentDto); - } - - projectsByComponentUuid = issueComponentHelper.prepareComponentsAndProjects(projectUuids, componentUuids, componentsByUuid, componentDtos, subProjectDtos, dbSession); - - Map actionPlans = newHashMap(); - String actionPlanKey = issue.actionPlanKey(); - if (actionPlanKey != null) { - actionPlans.put(actionPlanKey, actionPlanService.findByKey(actionPlanKey, userSession)); - } - - json.beginObject().name("issue"); - issueWriter.write(json, issue, - usersByLogin, - componentsByUuid, - projectsByComponentUuid, - ImmutableMultimap.of(), - actionPlans, - ISSUE_FIELDS); - - json.name("users").beginArray(); - String assignee = issue.assignee(); - if (assignee != null && usersByLogin.containsKey(assignee)) { - userWriter.write(json, usersByLogin.get(assignee)); - } - json.endArray(); - - json.name("projects").beginArray(); - for (ComponentDto project : projectDtos) { - componentWriter.write(json, project, project); - } - json.endArray(); - - json.name("components").beginArray(); - for (ComponentDto component : componentDtos) { - componentWriter.write(json, component, projectsByComponentUuid.get(component.uuid())); - } - json.endArray(); - - json.endObject().close(); - } finally { - MyBatis.closeQuietly(dbSession); - IOUtils.closeQuietly(writer); - } - return writer.toString(); - } - - private Map getIssueUsersByLogin(Issue issue) { - Map usersByLogin = Maps.newHashMap(); - String assignee = issue.assignee(); - if (assignee != null) { - usersByLogin.put(assignee, userIndex.getByLogin(assignee)); - } - String reporter = issue.reporter(); - if (reporter != null) { - usersByLogin.put(reporter, userIndex.getByLogin(reporter)); - } - return usersByLogin; - } - - private enum ActionToKey implements Function { - INSTANCE; - - @Override - public String apply(@Nonnull Action input) { - return input.key(); - } - } - private enum MatchIssueFilterParameters implements Predicate> { INSTANCE; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java index 9be3f1f3edf..01a3f0aa491 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java @@ -26,6 +26,8 @@ import org.sonar.api.server.ws.WebService; import org.sonar.server.issue.IssueService; import org.sonar.server.user.UserSession; +import static com.google.common.base.Strings.emptyToNull; + public class AssignAction implements IssuesWsAction { public static final String ASSIGN_ACTION = "assign"; @@ -64,7 +66,7 @@ public class AssignAction implements IssuesWsAction { @Override public void handle(Request request, Response response) throws Exception { - String assignee = request.param("assignee"); + String assignee = emptyToNull(request.param("assignee")); if ("_me".equals(assignee) || BooleanUtils.isTrue(request.paramAsBoolean("me"))) { // Permission is currently checked by IssueService. We still // check that user is authenticated in order to get his login. diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/CreateAction.java new file mode 100644 index 00000000000..442c6231e6a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/CreateAction.java @@ -0,0 +1,85 @@ +/* + * 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.issue.ws; + +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.core.issue.DefaultIssue; +import org.sonar.server.issue.IssueService; + +public class CreateAction implements IssuesWsAction { + + public static final String ACTION = "create"; + + private final IssueService issueService; + private final OperationResponseWriter responseWriter; + + public CreateAction(IssueService issueService, OperationResponseWriter responseWriter) { + this.issueService = issueService; + this.responseWriter = responseWriter; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction(ACTION) + .setDescription("Create a manual issue. Requires authentication and Browse permission on project") + .setSince("3.6") + .setHandler(this) + .setPost(true); + + action.createParam("component") + .setDescription("Key of the component on which to log the issue") + .setRequired(true) + .setExampleValue("org.apache.struts:struts:org.apache.struts.Action"); + action.createParam("rule") + .setDescription("Manual rule key") + .setRequired(true) + .setExampleValue("manual:performance"); + action.createParam("severity") + .setDescription("Severity of the issue") + .setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL) + .setPossibleValues(Severity.ALL); + action.createParam("line") + .setDescription("Line on which to log the issue. " + + "If no line is specified, the issue is attached to the component and not to a specific line") + .setExampleValue("15"); + action.createParam("message") + .setDescription("Description of the issue") + .setExampleValue("blabla..."); + } + + @Override + public void handle(Request request, Response response) throws Exception { + // required parameters + String componentKey = request.mandatoryParam("component"); + RuleKey ruleKey = RuleKey.parse(request.mandatoryParam("rule")); + + DefaultIssue issue = issueService.createManualIssue(componentKey, ruleKey, + request.paramAsInt("line"), + request.param("message"), + request.param("severity"), + null); + + responseWriter.write(issue.key(), request, response); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java new file mode 100644 index 00000000000..ec5a977ac26 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java @@ -0,0 +1,65 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.issue.ws; + +import org.sonar.api.issue.DefaultTransitions; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.server.issue.IssueService; + +public class DoTransitionAction implements IssuesWsAction { + + public static final String ACTION = "do_transition"; + + private final IssueService issueService; + private final OperationResponseWriter responseWriter; + + public DoTransitionAction(IssueService issueService, OperationResponseWriter responseWriter) { + this.issueService = issueService; + this.responseWriter = responseWriter; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction(ACTION) + .setDescription("Do workflow transition on an issue. Requires authentication and Browse permission on project") + .setSince("3.6") + .setHandler(this) + .setPost(true); + + action.createParam("issue") + .setDescription("Key of the issue") + .setRequired(true) + .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); + action.createParam("transition") + .setDescription("Transition") + .setRequired(true) + .setPossibleValues(DefaultTransitions.ALL); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String key = request.mandatoryParam("issue"); + issueService.doTransition(key, request.mandatoryParam("transition")); + + responseWriter.write(key, request, response); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java index d9023ea75ae..8660724f7f1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java @@ -21,13 +21,11 @@ package org.sonar.server.issue.ws; import java.util.List; -import org.sonar.api.server.ServerSide; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.action.Action; +import org.sonar.api.server.ServerSide; import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.web.UserRole; import org.sonar.core.issue.workflow.Transition; -import org.sonar.server.issue.ActionService; import org.sonar.server.issue.IssueService; import org.sonar.server.user.UserSession; @@ -37,12 +35,10 @@ import static com.google.common.collect.Lists.newArrayList; public class IssueActionsWriter { private final IssueService issueService; - private final ActionService actionService; private final UserSession userSession; - public IssueActionsWriter(IssueService issueService, ActionService actionService, UserSession userSession) { + public IssueActionsWriter(IssueService issueService, UserSession userSession) { this.issueService = issueService; - this.actionService = actionService; this.userSession = userSession; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueComponentHelper.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueComponentHelper.java deleted file mode 100644 index 7828aaf26ec..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueComponentHelper.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.issue.ws; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.component.ComponentDto; - -import static com.google.common.collect.Maps.newHashMap; - -/** - * This class computes some collections of {@link ComponentDto}s used to serialize issues. - */ -public class IssueComponentHelper { - - private final DbClient dbClient; - - public IssueComponentHelper(DbClient dbClient) { - this.dbClient = dbClient; - } - - public Map prepareComponentsAndProjects(Set projectUuids, Set componentUuids, Map componentsByUuid, - Collection componentDtos, List projectDtos, DbSession session) { - Map projectsByComponentUuid; - List fileDtos = dbClient.componentDao().selectByUuids(session, componentUuids); - List subProjectDtos = dbClient.componentDao().selectSubProjectsByComponentUuids(session, componentUuids); - componentDtos.addAll(fileDtos); - componentDtos.addAll(subProjectDtos); - for (ComponentDto component : componentDtos) { - projectUuids.add(component.projectUuid()); - } - projectDtos.addAll(dbClient.componentDao().selectByUuids(session, projectUuids)); - componentDtos.addAll(projectDtos); - - for (ComponentDto componentDto : componentDtos) { - componentsByUuid.put(componentDto.uuid(), componentDto); - } - - projectsByComponentUuid = getProjectsByComponentUuid(componentDtos, projectDtos); - return projectsByComponentUuid; - } - - private Map getProjectsByComponentUuid(Collection components, Collection projects) { - Map projectsByUuid = buildProjectsByUuid(projects); - return buildProjectsByComponentUuid(components, projectsByUuid); - } - - private static Map buildProjectsByUuid(Collection projects) { - Map projectsByUuid = newHashMap(); - for (ComponentDto project : projects) { - if (project == null) { - throw new IllegalStateException("Found a null project in issues"); - } - if (project.uuid() == null) { - throw new IllegalStateException("Project has no UUID: " + project.getKey()); - } - projectsByUuid.put(project.uuid(), project); - } - return projectsByUuid; - } - - private static Map buildProjectsByComponentUuid(Collection components, Map projectsByUuid) { - Map projectsByComponentUuid = newHashMap(); - for (ComponentDto component : components) { - if (component.uuid() == null) { - throw new IllegalStateException("Component has no UUID: " + component.getKey()); - } - if (!projectsByUuid.containsKey(component.projectUuid())) { - throw new IllegalStateException("Project cannot be found for component: " + component.getKey() + " / " + component.uuid()); - } - projectsByComponentUuid.put(component.uuid(), projectsByUuid.get(component.projectUuid())); - } - return projectsByComponentUuid; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java deleted file mode 100644 index 1bb99f9e91b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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.issue.ws; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.api.issue.ActionPlan; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.IssueComment; -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.db.component.ComponentDto; -import org.sonar.core.issue.DefaultIssueComment; -import org.sonar.markdown.Markdown; -import org.sonar.server.user.UserSession; -import org.sonar.server.ws.JsonWriterUtils; - -import static org.sonar.server.ws.JsonWriterUtils.writeIfNeeded; - -public class IssueJsonWriter { - - private static final String FIELD_KEY = "key"; - private static final String FIELD_COMPONENT = "component"; - private static final String FIELD_COMPONENT_ID = "componentId"; - private static final String FIELD_PROJECT = "project"; - private static final String FIELD_SUB_PROJECT = "subProject"; - private static final String FIELD_RULE = "rule"; - private static final String FIELD_STATUS = "status"; - private static final String FIELD_RESOLUTION = "resolution"; - private static final String FIELD_AUTHOR = "author"; - private static final String FIELD_REPORTER = "reporter"; - private static final String FIELD_ASSIGNEE = "assignee"; - private static final String FIELD_DEBT = "debt"; - private static final String FIELD_LINE = "line"; - private static final String FIELD_MESSAGE = "message"; - private static final String FIELD_SEVERITY = "severity"; - private static final String FIELD_ACTION_PLAN = "actionPlan"; - private static final String FIELD_CREATION_DATE = "creationDate"; - private static final String FIELD_UPDATE_DATE = "updateDate"; - private static final String FIELD_CLOSE_DATE = "closeDate"; - private static final String FIELD_TAGS = "tags"; - private static final String FIELD_COMMENTS = "comments"; - private static final String FIELD_ATTRIBUTES = "attr"; - public static final String FIELD_ACTIONS = "actions"; - public static final String FIELD_TRANSITIONS = "transitions"; - public static final String FIELD_ACTION_PLAN_NAME = "actionPlanName"; - - public static final Set EXTRA_FIELDS = ImmutableSet.of( - FIELD_ACTIONS, FIELD_TRANSITIONS, FIELD_ACTION_PLAN_NAME); - - public static final Set SELECTABLE_FIELDS = ImmutableSet.of(FIELD_COMPONENT, FIELD_PROJECT, FIELD_SUB_PROJECT, FIELD_RULE, FIELD_STATUS, FIELD_RESOLUTION, FIELD_AUTHOR, - FIELD_REPORTER, FIELD_ASSIGNEE, FIELD_DEBT, FIELD_LINE, FIELD_MESSAGE, FIELD_SEVERITY, FIELD_ACTION_PLAN, FIELD_CREATION_DATE, FIELD_UPDATE_DATE, FIELD_CLOSE_DATE, - FIELD_COMPONENT_ID, FIELD_TAGS, FIELD_COMMENTS, FIELD_ATTRIBUTES, FIELD_ACTIONS, FIELD_TRANSITIONS, FIELD_ACTION_PLAN_NAME); - - private static final List SELECTABLE_MINUS_EXTRAS = ImmutableList.copyOf(Sets.difference(SELECTABLE_FIELDS, EXTRA_FIELDS)); - - private final Durations durations; - private final UserSession userSession; - private final IssueActionsWriter actionsWriter; - - public IssueJsonWriter(Durations durations, UserSession userSession, IssueActionsWriter actionsWriter) { - this.durations = durations; - this.userSession = userSession; - this.actionsWriter = actionsWriter; - } - - public void write(JsonWriter json, Issue issue, Map usersByLogin, Map componentsByUuid, - Map projectsByComponentUuid, Multimap commentsByIssues, Map actionPlanByKeys, - @Nullable List selectedFields) { - - List fields = useDefaultFieldsIfNotSpecified(selectedFields); - - json.beginObject(); - - String actionPlanKey = issue.actionPlanKey(); - ComponentDto file = componentsByUuid.get(issue.componentUuid()); - ComponentDto project = null; - ComponentDto subProject = null; - if (file != null) { - project = projectsByComponentUuid.get(file.uuid()); - if (!file.projectUuid().equals(file.moduleUuid())) { - subProject = componentsByUuid.get(file.moduleUuid()); - } - } - Duration debt = issue.debt(); - Date updateDate = issue.updateDate(); - - json.prop(FIELD_KEY, issue.key()); - JsonWriterUtils.writeIfNeeded(json, file != null ? file.getKey() : null, FIELD_COMPONENT, fields); - // Only used for the compatibility with the Issues Java WS Client <= 4.4 used by Eclipse - writeIfNeeded(json, file != null ? file.getId() : null, FIELD_COMPONENT_ID, fields); - writeIfNeeded(json, project != null ? project.getKey() : null, FIELD_PROJECT, fields); - writeIfNeeded(json, subProject != null ? subProject.getKey() : null, FIELD_SUB_PROJECT, fields); - writeIfNeeded(json, issue.ruleKey().toString(), FIELD_RULE, fields); - writeIfNeeded(json, issue.status(), FIELD_STATUS, fields); - writeIfNeeded(json, issue.resolution(), FIELD_RESOLUTION, fields); - writeIfNeeded(json, issue.severity(), FIELD_SEVERITY, fields); - writeIfNeeded(json, issue.message(), FIELD_MESSAGE, fields); - writeIfNeeded(json, issue.line(), FIELD_LINE, fields); - writeIfNeeded(json, debt != null ? durations.encode(debt) : null, FIELD_DEBT, fields); - writeIfNeeded(json, issue.assignee(), FIELD_ASSIGNEE, fields); - writeIfNeeded(json, issue.reporter(), FIELD_REPORTER, fields); - writeIfNeeded(json, issue.authorLogin(), FIELD_AUTHOR, fields); - writeIfNeeded(json, actionPlanKey, FIELD_ACTION_PLAN, fields); - writeIfNeeded(json, isoDate(issue.creationDate()), FIELD_CREATION_DATE, fields); - writeIfNeeded(json, isoDate(updateDate), FIELD_UPDATE_DATE, fields); - writeIfNeeded(json, isoDate(issue.closeDate()), FIELD_CLOSE_DATE, fields); - - if (JsonWriterUtils.isFieldNeeded(FIELD_TAGS, fields)) { - writeTags(issue, json); - } - if (JsonWriterUtils.isFieldNeeded(FIELD_COMMENTS, fields)) { - writeIssueComments(commentsByIssues.get(issue.key()), usersByLogin, json); - } - if (JsonWriterUtils.isFieldNeeded(FIELD_ATTRIBUTES, fields)) { - writeIssueAttributes(issue, json); - } - writeIssueExtraFields(issue, actionPlanByKeys, fields, json); - json.endObject(); - } - - private List useDefaultFieldsIfNotSpecified(List selectedFields) { - List fields = Lists.newArrayList(); - if (selectedFields == null || selectedFields.isEmpty()) { - fields.addAll(SELECTABLE_MINUS_EXTRAS); - } else { - fields.addAll(selectedFields); - } - return fields; - } - - @CheckForNull - private static String isoDate(@Nullable Date date) { - if (date != null) { - return DateUtils.formatDateTime(date); - } - return null; - } - - private static void writeTags(Issue issue, JsonWriter json) { - Collection tags = issue.tags(); - if (tags != null && !tags.isEmpty()) { - json.name(FIELD_TAGS).beginArray(); - for (String tag : tags) { - json.value(tag); - } - json.endArray(); - } - } - - private void writeIssueComments(Collection issueComments, Map usersByLogin, JsonWriter json) { - if (!issueComments.isEmpty()) { - json.name(FIELD_COMMENTS).beginArray(); - String login = userSession.getLogin(); - for (IssueComment comment : issueComments) { - String userLogin = comment.userLogin(); - User user = userLogin != null ? usersByLogin.get(userLogin) : null; - json.beginObject() - .prop("key", comment.key()) - .prop("login", comment.userLogin()) - .prop("email", user != null ? user.email() : null) - .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 static void writeIssueAttributes(Issue issue, JsonWriter json) { - if (!issue.attributes().isEmpty()) { - json.name(FIELD_ATTRIBUTES).beginObject(); - for (Map.Entry entry : issue.attributes().entrySet()) { - json.prop(entry.getKey(), entry.getValue()); - } - json.endObject(); - } - } - - private void writeIssueExtraFields(Issue issue, Map actionPlanByKeys, - @Nullable List fields, JsonWriter json) { - if (JsonWriterUtils.isFieldNeeded(FIELD_ACTIONS, fields)) { - actionsWriter.writeActions(issue, json); - } - - if (JsonWriterUtils.isFieldNeeded(FIELD_TRANSITIONS, fields)) { - actionsWriter.writeTransitions(issue, json); - } - - if (JsonWriterUtils.isFieldNeeded(FIELD_ACTION_PLAN_NAME, fields)) { - writeActionPlanName(issue, actionPlanByKeys, json); - } - } - - private void writeActionPlanName(Issue issue, Map actionPlanByKeys, JsonWriter json) { - String actionPlanKey = issue.actionPlanKey(); - if (actionPlanKey != null) { - ActionPlan actionPlan = actionPlanByKeys.get(actionPlanKey); - json.prop(FIELD_ACTION_PLAN_NAME, actionPlan != null ? actionPlan.name() : null); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java new file mode 100644 index 00000000000..94fac589a8b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java @@ -0,0 +1,46 @@ +/* + * 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.issue.ws; + +import org.sonar.core.platform.Module; + +public class IssueWsModule extends Module { + @Override + protected void configureModule() { + add( + IssuesWs.class, + SearchResponseLoader.class, + SearchResponseFormat.class, + OperationResponseWriter.class, + AssignAction.class, + CreateAction.class, + DoTransitionAction.class, + PlanAction.class, + ShowAction.class, + SearchAction.class, + SetSeverityAction.class, + TagsAction.class, + SetTagsAction.class, + ComponentTagsAction.class, + IssueActionsWriter.class, + AuthorsAction.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java index 3232d0725da..8ac665b2b7b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java @@ -30,16 +30,10 @@ public class IssuesWs implements WebService { public static final String API_ENDPOINT = "api/issues"; public static final String CHANGELOG_ACTION = "changelog"; - public static final String ASSIGN_ACTION = "assign"; public static final String ADD_COMMENT_ACTION = "add_comment"; public static final String DELETE_COMMENT_ACTION = "delete_comment"; public static final String EDIT_COMMENT_ACTION = "edit_comment"; - public static final String SET_SEVERITY_ACTION = "set_severity"; - public static final String PLAN_ACTION = "plan"; - public static final String DO_TRANSITION_ACTION = "do_transition"; public static final String TRANSITIONS_ACTION = "transitions"; - public static final String CREATE_ACTION = "create"; - public static final String DO_ACTION_ACTION = "do_action"; public static final String BULK_CHANGE_ACTION = "bulk_change"; private final IssuesWsAction[] actions; @@ -65,12 +59,7 @@ public class IssuesWs implements WebService { defineAddCommentAction(controller); defineDeleteCommentAction(controller); defineEditCommentAction(controller); - defineSetSeverityAction(controller); - definePlanAction(controller); - defineDoTransitionAction(controller); defineTransitionsAction(controller); - defineCreateAction(controller); - defineDoActionAction(controller); defineBulkChangeAction(controller); } @@ -135,59 +124,6 @@ public class IssuesWs implements WebService { RailsHandler.addFormatParam(action); } - private static void defineSetSeverityAction(NewController controller) { - WebService.NewAction action = controller.createAction(SET_SEVERITY_ACTION) - .setDescription("Change severity. Requires authentication and Browse permission on project") - .setSince("3.6") - .setHandler(RailsHandler.INSTANCE) - .setPost(true); - - action.createParam("issue") - .setDescription("Key of the issue") - .setRequired(true) - .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); - action.createParam("severity") - .setDescription("New severity") - .setExampleValue(Severity.BLOCKER) - .setPossibleValues(Severity.ALL); - RailsHandler.addFormatParam(action); - } - - private static void definePlanAction(NewController controller) { - WebService.NewAction action = controller.createAction(PLAN_ACTION) - .setDescription("Plan/Unplan an issue. Requires authentication and Browse permission on project") - .setSince("3.6") - .setHandler(RailsHandler.INSTANCE) - .setPost(true); - - action.createParam("issue") - .setDescription("Key of the issue") - .setRequired(true) - .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); - action.createParam("plan") - .setDescription("Key of the action plan") - .setExampleValue("3f19de90-1521-4482-a737-a311758ff513"); - RailsHandler.addFormatParam(action); - } - - private static void defineDoTransitionAction(NewController controller) { - WebService.NewAction action = controller.createAction(DO_TRANSITION_ACTION) - .setDescription("Do workflow transition on an issue. Requires authentication and Browse permission on project") - .setSince("3.6") - .setHandler(RailsHandler.INSTANCE) - .setPost(true); - - action.createParam("issue") - .setDescription("Key of the issue") - .setRequired(true) - .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); - action.createParam("transition") - .setDescription("Transition") - .setExampleValue("reopen") - .setPossibleValues(DefaultTransitions.ALL); - RailsHandler.addFormatParam(action); - } - private static void defineTransitionsAction(NewController controller) { WebService.NewAction action = controller.createAction(TRANSITIONS_ACTION) .setDescription("Get Possible Workflow Transitions for an Issue. Requires Browse permission on project") @@ -201,52 +137,6 @@ public class IssuesWs implements WebService { .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); } - private static void defineCreateAction(NewController controller) { - WebService.NewAction action = controller.createAction(CREATE_ACTION) - .setDescription("Create a manual issue. Requires authentication and Browse permission on project") - .setSince("3.6") - .setHandler(RailsHandler.INSTANCE) - .setPost(true); - - action.createParam("component") - .setDescription("Key of the component on which to log the issue") - .setRequired(true) - .setExampleValue("org.apache.struts:struts:org.apache.struts.Action"); - action.createParam("rule") - .setDescription("Manual rule key") - .setRequired(true) - .setExampleValue("manual:performance"); - action.createParam("severity") - .setDescription("Severity of the issue") - .setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL) - .setPossibleValues(Severity.ALL); - action.createParam("line") - .setDescription("Line on which to log the issue. " + - "If no line is specified, the issue is attached to the component and not to a specific line") - .setExampleValue("15"); - action.createParam("message") - .setDescription("Description of the issue") - .setExampleValue("blabla..."); - RailsHandler.addFormatParam(action); - } - - private static void defineDoActionAction(NewController controller) { - WebService.NewAction action = controller.createAction(DO_ACTION_ACTION) - .setDescription("Do workflow transition on an issue. Requires authentication and Browse permission on project") - .setSince("3.6") - .setHandler(RailsHandler.INSTANCE) - .setPost(true); - - action.createParam("issue") - .setDescription("Key of the issue") - .setRequired(true) - .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); - action.createParam("actionKey") - .setDescription("Action to perform") - .setExampleValue("link-to-jira"); - RailsHandler.addFormatParam(action); - } - private static void defineBulkChangeAction(NewController controller) { WebService.NewAction action = controller.createAction(BULK_CHANGE_ACTION) .setDescription("Bulk change on issues. Requires authentication and User role on project(s)") diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/PlanAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/PlanAction.java new file mode 100644 index 00000000000..04d790efbc2 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/PlanAction.java @@ -0,0 +1,65 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.issue.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.server.issue.IssueService; + +import static com.google.common.base.Strings.emptyToNull; + +public class PlanAction implements IssuesWsAction { + + public static final String ACTION = "plan"; + + private final IssueService issueService; + private final OperationResponseWriter responseWriter; + + public PlanAction(IssueService issueService, OperationResponseWriter responseWriter) { + this.issueService = issueService; + this.responseWriter = responseWriter; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction(ACTION) + .setDescription("Plan/Unplan an issue. Requires authentication and Browse permission on project") + .setSince("3.6") + .setHandler(this) + .setPost(true); + + action.createParam("issue") + .setDescription("Key of the issue") + .setRequired(true) + .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); + action.createParam("plan") + .setDescription("Key of the action plan. Absent value removes the current plan.") + .setExampleValue("3f19de90-1521-4482-a737-a311758ff513"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String key = request.mandatoryParam("issue"); + issueService.plan(key, emptyToNull(request.param("plan"))); + + responseWriter.write(key, request, response); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/Search2Action.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/Search2Action.java deleted file mode 100644 index e0f5f66b573..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/Search2Action.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * 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.issue.ws; - -import com.google.common.base.Function; -import com.google.common.collect.Lists; -import com.google.common.io.Resources; -import java.util.Collection; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nullable; -import org.sonar.api.issue.Issue; -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.server.ws.WebService.Param; -import org.sonar.api.utils.Paging; -import org.sonar.core.rule.RuleKeyFunctions; -import org.sonar.server.es.Facets; -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.filter.IssueFilterParameters; -import org.sonar.server.issue.index.IssueDoc; -import org.sonar.server.issue.index.IssueIndex; -import org.sonar.server.user.UserSession; -import org.sonar.server.ws.WsUtils; -import org.sonarqube.ws.Issues; - -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Iterables.concat; -import static java.util.Collections.singletonList; - -public class Search2Action implements IssuesWsAction { - - private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. "; - public static final String ADDITIONAL_FIELDS = "additionalFields"; - public static final String SEARCH_ACTION = "search2"; - - private final UserSession userSession; - private final IssueService service; - private final IssueQueryService issueQueryService; - private final SearchResponseLoader searchResponseLoader; - private final SearchResponseFormat searchResponseFormat; - - public Search2Action(UserSession userSession, IssueService service, IssueQueryService issueQueryService, - SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat) { - this.userSession = userSession; - this.service = service; - this.issueQueryService = issueQueryService; - this.searchResponseLoader = searchResponseLoader; - this.searchResponseFormat = searchResponseFormat; - } - - @Override - public void define(WebService.NewController controller) { - WebService.NewAction action = controller - .createAction(SEARCH_ACTION) - .setHandler(this) - .setDescription( - "Search for issues. Requires Browse permission on project(s)") - .setSince("3.6") - .setResponseExample(Resources.getResource(this.getClass(), "example-search.json")); - - action.addPagingParams(100); - action.createParam(Param.FACETS) - .setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.") - .setPossibleValues(IssueIndex.SUPPORTED_FACETS); - action.createParam(IssueFilterParameters.FACET_MODE) - .setDefaultValue(IssueFilterParameters.FACET_MODE_COUNT) - .setDescription("Choose the returned value for facet items, either count of issues or sum of debt.") - .setPossibleValues(IssueFilterParameters.FACET_MODE_COUNT, IssueFilterParameters.FACET_MODE_DEBT); - action.addSortParams(IssueQuery.SORTS, null, true); - action.createParam(ADDITIONAL_FIELDS) - .setDescription("Comma-separated list of the optional fields to be returned in response.") - .setPossibleValues(SearchAdditionalField.possibleValues()); - addComponentRelatedParams(action); - action.createParam(IssueFilterParameters.ISSUES) - .setDescription("Comma-separated list of issue keys") - .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); - action.createParam(IssueFilterParameters.SEVERITIES) - .setDescription("Comma-separated list of severities") - .setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL) - .setPossibleValues(Severity.ALL); - action.createParam(IssueFilterParameters.STATUSES) - .setDescription("Comma-separated list of statuses") - .setExampleValue(Issue.STATUS_OPEN + "," + Issue.STATUS_REOPENED) - .setPossibleValues(Issue.STATUSES); - action.createParam(IssueFilterParameters.RESOLUTIONS) - .setDescription("Comma-separated list of resolutions") - .setExampleValue(Issue.RESOLUTION_FIXED + "," + Issue.RESOLUTION_REMOVED) - .setPossibleValues(Issue.RESOLUTIONS); - action.createParam(IssueFilterParameters.RESOLVED) - .setDescription("To match resolved or unresolved issues") - .setBooleanPossibleValues(); - action.createParam(IssueFilterParameters.RULES) - .setDescription("Comma-separated list of coding rule keys. Format is :") - .setExampleValue("squid:AvoidCycles"); - action.createParam(IssueFilterParameters.TAGS) - .setDescription("Comma-separated list of tags.") - .setExampleValue("security,convention"); - action.createParam(IssueFilterParameters.ACTION_PLANS) - .setDescription("Comma-separated list of action plan keys (not names)") - .setExampleValue("3f19de90-1521-4482-a737-a311758ff513"); - action.createParam(IssueFilterParameters.PLANNED) - .setDescription("To retrieve issues associated to an action plan or not") - .setBooleanPossibleValues(); - action.createParam(IssueFilterParameters.REPORTERS) - .setDescription("Comma-separated list of reporter logins") - .setExampleValue("admin"); - action.createParam(IssueFilterParameters.AUTHORS) - .setDescription("Comma-separated list of SCM accounts") - .setExampleValue("torvalds@linux-foundation.org"); - action.createParam(IssueFilterParameters.ASSIGNEES) - .setDescription("Comma-separated list of assignee logins. The value '__me__' can be used as a placeholder for user who performs the request") - .setExampleValue("admin,usera,__me__"); - action.createParam(IssueFilterParameters.ASSIGNED) - .setDescription("To retrieve assigned or unassigned issues") - .setBooleanPossibleValues(); - action.createParam(IssueFilterParameters.LANGUAGES) - .setDescription("Comma-separated list of languages. Available since 4.4") - .setExampleValue("java,js"); - action.createParam(IssueFilterParameters.CREATED_AT) - .setDescription("To retrieve issues created at a given date. Format: date or datetime ISO formats") - .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)"); - action.createParam(IssueFilterParameters.CREATED_AFTER) - .setDescription("To retrieve issues created after the given date (exclusive). Format: date or datetime ISO formats. If this parameter is set, createdSince must not be set") - .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)"); - 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(IssueFilterParameters.CREATED_IN_LAST) - .setDescription("To retrieve issues created during a time span before the current time (exclusive). " + - "Accepted units are 'y' for year, 'm' for month, 'w' for week and 'd' for day. " + - "If this parameter is set, createdAfter must not be set") - .setExampleValue("1m2w (1 month 2 weeks)"); - } - - private static 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. " + - "Using the deprecated componentRoots or componentRootUuids parameters will set this parameter to false. " + - "Using the deprecated components parameter will set this parameter to true.") - .setBooleanPossibleValues() - .setDefaultValue("false"); - - action.createParam(IssueFilterParameters.COMPONENT_KEYS) - .setDescription("To retrieve issues associated to a specific list of components sub-components (comma-separated list of component keys). " + - "A component can be a view, developer, project, module, directory or file. " + - "If this parameter is set, componentUuids must not be set.") - .setExampleValue("org.apache.struts:struts:org.apache.struts.Action"); - action.createParam(IssueFilterParameters.COMPONENTS) - .setDescription("Deprecated since 5.1. If used, will have the same meaning as componentKeys AND onComponentOnly=true."); - action.createParam(IssueFilterParameters.COMPONENT_UUIDS) - .setDescription("To retrieve issues associated to a specific list of components their sub-components (comma-separated list of component UUIDs). " + - INTERNAL_PARAMETER_DISCLAIMER + - "A component can be a project, module, directory or file. " + - "If this parameter is set, componentKeys must not be set.") - .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2"); - - action.createParam(IssueFilterParameters.COMPONENT_ROOTS) - .setDescription("Deprecated since 5.1. If used, will have the same meaning as componentKeys AND onComponentOnly=false."); - action.createParam(IssueFilterParameters.COMPONENT_ROOT_UUIDS) - .setDescription("Deprecated since 5.1. If used, will have the same meaning as componentUuids AND onComponentOnly=false."); - - action.createParam(IssueFilterParameters.PROJECTS) - .setDescription("Deprecated since 5.1. See projectKeys"); - action.createParam(IssueFilterParameters.PROJECT_KEYS) - .setDescription("To retrieve issues associated to a specific list of projects (comma-separated list of project keys). " + - INTERNAL_PARAMETER_DISCLAIMER + - "If this parameter is set, projectUuids must not be set.") - .setDeprecatedKey(IssueFilterParameters.PROJECTS) - .setExampleValue("org.apache.struts:struts:org.apache.struts.Action"); - action.createParam(IssueFilterParameters.PROJECT_UUIDS) - .setDescription("To retrieve issues associated to a specific list of projects (comma-separated list of project UUIDs). " + - INTERNAL_PARAMETER_DISCLAIMER + - "Views are not supported. If this parameter is set, projectKeys must not be set.") - .setExampleValue("7d8749e8-3070-4903-9188-bdd82933bb92"); - - action.createParam(IssueFilterParameters.MODULE_UUIDS) - .setDescription("To retrieve issues associated to a specific list of modules (comma-separated list of module UUIDs). " + - INTERNAL_PARAMETER_DISCLAIMER + - "Views are not supported. If this parameter is set, moduleKeys must not be set.") - .setExampleValue("7d8749e8-3070-4903-9188-bdd82933bb92"); - - action.createParam(IssueFilterParameters.DIRECTORIES) - .setDescription("Since 5.1. To retrieve issues associated to a specific list of directories (comma-separated list of directory paths). " + - "This parameter is only meaningful when a module is selected. " + - INTERNAL_PARAMETER_DISCLAIMER) - .setExampleValue("src/main/java/org/sonar/server/"); - - action.createParam(IssueFilterParameters.FILE_UUIDS) - .setDescription("To retrieve issues associated to a specific list of files (comma-separated list of file UUIDs). " + - INTERNAL_PARAMETER_DISCLAIMER) - .setExampleValue("bdd82933-3070-4903-9188-7d8749e8bb92"); - } - - @Override - public final void handle(Request request, Response response) throws Exception { - // prepare the Elasticsearch request - SearchOptions options = new SearchOptions(); - options.setPage(request.mandatoryParamAsInt(Param.PAGE), request.mandatoryParamAsInt(Param.PAGE_SIZE)); - options.addFacets(request.paramAsStrings(Param.FACETS)); - EnumSet additionalFields = SearchAdditionalField.getFromRequest(request); - IssueQuery query = issueQueryService.createFromRequest(request); - - // execute request - SearchResult result = service.search(query, options); - List issueKeys = from(result.getDocs()).transform(IssueDocToKey.INSTANCE).toList(); - - // load the additional information to be returned in response - SearchResponseLoader.Collector collector = new SearchResponseLoader.Collector(additionalFields, issueKeys); - collectLoggedInUser(collector); - collectRequestParams(collector, request); - Facets facets = null; - if (!options.getFacets().isEmpty()) { - facets = result.getFacets(); - // add missing values to facets. For example if assignee "john" and facet on "assignees" are requested, then - // "john" should always be listed in the facet. If it is not present, then it is added with value zero. - // This is a constraint from webapp UX. - completeFacets(facets, options, request); - collectFacets(collector, facets); - } - SearchResponseData data = searchResponseLoader.load(collector, facets); - - // format response - - // Filter and reorder facets according to the requested ordered names. - // Must be done after loading of data as the "hidden" facet "debt" - // can be used to get total debt. - facets = reorderFacets(facets, options.getFacets()); - - // FIXME allow long in Paging - Paging paging = Paging.create(options.getLimit(), options.getPage(), (int) result.getTotal()); - Issues.Search responseBody = searchResponseFormat.formatSearch(additionalFields, data, paging, facets); - WsUtils.writeProtobuf(responseBody, request, response); - } - - private Facets reorderFacets(@Nullable Facets facets, Collection orderedNames) { - if (facets == null) { - return null; - } - LinkedHashMap> orderedFacets = new LinkedHashMap<>(); - for (String facetName : orderedNames) { - LinkedHashMap facet = facets.get(facetName); - if (facet != null) { - orderedFacets.put(facetName, facet); - } - } - return new Facets(orderedFacets); - } - - private void completeFacets(Facets facets, SearchOptions options, Request request) { - addMandatoryValuesToFacet(facets, IssueFilterParameters.SEVERITIES, Severity.ALL); - addMandatoryValuesToFacet(facets, IssueFilterParameters.STATUSES, Issue.STATUSES); - addMandatoryValuesToFacet(facets, IssueFilterParameters.RESOLUTIONS, concat(singletonList(""), Issue.RESOLUTIONS)); - addMandatoryValuesToFacet(facets, IssueFilterParameters.PROJECT_UUIDS, request.paramAsStrings(IssueFilterParameters.PROJECT_UUIDS)); - - List assignees = Lists.newArrayList(""); - List assigneesFromRequest = request.paramAsStrings(IssueFilterParameters.ASSIGNEES); - if (assigneesFromRequest != null) { - assignees.addAll(assigneesFromRequest); - assignees.remove(IssueQueryService.LOGIN_MYSELF); - } - addMandatoryValuesToFacet(facets, IssueFilterParameters.ASSIGNEES, assignees); - addMandatoryValuesToFacet(facets, IssueFilterParameters.FACET_ASSIGNED_TO_ME, singletonList(userSession.getLogin())); - addMandatoryValuesToFacet(facets, IssueFilterParameters.REPORTERS, request.paramAsStrings(IssueFilterParameters.REPORTERS)); - addMandatoryValuesToFacet(facets, IssueFilterParameters.RULES, request.paramAsStrings(IssueFilterParameters.RULES)); - addMandatoryValuesToFacet(facets, IssueFilterParameters.LANGUAGES, request.paramAsStrings(IssueFilterParameters.LANGUAGES)); - addMandatoryValuesToFacet(facets, IssueFilterParameters.TAGS, request.paramAsStrings(IssueFilterParameters.TAGS)); - List actionPlans = Lists.newArrayList(""); - List actionPlansFromRequest = request.paramAsStrings(IssueFilterParameters.ACTION_PLANS); - if (actionPlansFromRequest != null) { - actionPlans.addAll(actionPlansFromRequest); - } - addMandatoryValuesToFacet(facets, IssueFilterParameters.ACTION_PLANS, actionPlans); - addMandatoryValuesToFacet(facets, IssueFilterParameters.COMPONENT_UUIDS, request.paramAsStrings(IssueFilterParameters.COMPONENT_UUIDS)); - - for (String facetName : options.getFacets()) { - LinkedHashMap buckets = facets.get(facetName); - if (!IssueFilterParameters.FACET_ASSIGNED_TO_ME.equals(facetName)) { - if (buckets != null) { - List requestParams = request.paramAsStrings(facetName); - if (requestParams != null) { - for (String param : requestParams) { - if (!buckets.containsKey(param) && !IssueQueryService.LOGIN_MYSELF.equals(param)) { - // Prevent appearance of a glitch value due to dedicated parameter for this facet - buckets.put(param, 0L); - } - } - } - } - } - } - } - - private void addMandatoryValuesToFacet(Facets facets, String facetName, @Nullable Iterable mandatoryValues) { - Map buckets = facets.get(facetName); - if (buckets != null && mandatoryValues != null) { - for (String mandatoryValue : mandatoryValues) { - if (!buckets.containsKey(mandatoryValue)) { - buckets.put(mandatoryValue, 0L); - } - } - } - } - - private void collectLoggedInUser(SearchResponseLoader.Collector collector) { - if (userSession.isLoggedIn()) { - collector.add(SearchAdditionalField.USERS, userSession.getLogin()); - } - } - - private void collectFacets(SearchResponseLoader.Collector collector, Facets facets) { - Set facetRules = facets.getBucketKeys(IssueFilterParameters.RULES); - if (facetRules != null) { - collector.addAll(SearchAdditionalField.RULES, from(facetRules).transform(RuleKeyFunctions.stringToRuleKey())); - } - collector.addProjectUuids(facets.getBucketKeys(IssueFilterParameters.PROJECT_UUIDS)); - collector.addComponentUuids(facets.getBucketKeys(IssueFilterParameters.COMPONENT_UUIDS)); - collector.addComponentUuids(facets.getBucketKeys(IssueFilterParameters.FILE_UUIDS)); - collector.addComponentUuids(facets.getBucketKeys(IssueFilterParameters.MODULE_UUIDS)); - collector.addAll(SearchAdditionalField.USERS, facets.getBucketKeys(IssueFilterParameters.ASSIGNEES)); - collector.addAll(SearchAdditionalField.USERS, facets.getBucketKeys(IssueFilterParameters.REPORTERS)); - collector.addAll(SearchAdditionalField.ACTION_PLANS, facets.getBucketKeys(IssueFilterParameters.ACTION_PLANS)); - } - - private void collectRequestParams(SearchResponseLoader.Collector collector, Request request) { - collector.addProjectUuids(request.paramAsStrings(IssueFilterParameters.PROJECT_UUIDS)); - collector.addComponentUuids(request.paramAsStrings(IssueFilterParameters.FILE_UUIDS)); - collector.addComponentUuids(request.paramAsStrings(IssueFilterParameters.MODULE_UUIDS)); - collector.addComponentUuids(request.paramAsStrings(IssueFilterParameters.COMPONENT_ROOT_UUIDS)); - collector.addAll(SearchAdditionalField.USERS, request.paramAsStrings(IssueFilterParameters.ASSIGNEES)); - collector.addAll(SearchAdditionalField.USERS, request.paramAsStrings(IssueFilterParameters.REPORTERS)); - collector.addAll(SearchAdditionalField.ACTION_PLANS, request.paramAsStrings(IssueFilterParameters.ACTION_PLANS)); - } - - private enum IssueDocToKey implements Function { - INSTANCE; - @Override - public String apply(IssueDoc input) { - return input.key(); - } - } -} 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 d9729d25d67..f0ce7bf9d1b 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,98 +19,60 @@ */ package org.sonar.server.issue.ws; -import com.google.common.collect.ArrayListMultimap; +import com.google.common.base.Function; 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 java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.Date; +import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.apache.commons.lang.BooleanUtils; -import org.sonar.api.i18n.I18n; -import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; -import org.sonar.api.resources.Language; -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.server.ws.WebService.Param; -import org.sonar.api.user.User; -import org.sonar.api.user.UserFinder; -import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.text.JsonWriter; -import org.sonar.db.component.ComponentDto; -import org.sonar.core.issue.DefaultIssueComment; -import org.sonar.db.DbSession; -import org.sonar.server.component.ws.ComponentJsonWriter; -import org.sonar.db.DbClient; +import org.sonar.api.utils.Paging; +import org.sonar.core.rule.RuleKeyFunctions; import org.sonar.server.es.Facets; 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.user.UserSession; -import org.sonar.server.user.ws.UserJsonWriter; +import org.sonar.server.ws.WsUtils; +import org.sonarqube.ws.Issues; -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 com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.Iterables.concat; +import static java.util.Collections.singletonList; public class SearchAction implements IssuesWsAction { - public static final String SEARCH_ACTION = "search"; - private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. "; + public static final String ADDITIONAL_FIELDS = "additionalFields"; + public static final String SEARCH_ACTION = "search"; + private final UserSession userSession; private final IssueService service; - private final IssueQueryService issueQueryService; - private final RuleService ruleService; - private final DbClient dbClient; - private final ActionPlanService actionPlanService; - private final UserFinder userFinder; - private final I18n i18n; - private final Languages languages; - private final UserSession userSession; - private final IssueJsonWriter issueWriter; - private final UserJsonWriter userWriter; - private final IssueComponentHelper issueComponentHelper; - private final ComponentJsonWriter componentWriter; + private final SearchResponseLoader searchResponseLoader; + private final SearchResponseFormat searchResponseFormat; - public SearchAction(DbClient dbClient, IssueService service, IssueQueryService issueQueryService, - RuleService ruleService, ActionPlanService actionPlanService, UserFinder userFinder, I18n i18n, Languages languages, - UserSession userSession, IssueJsonWriter issueWriter, IssueComponentHelper issueComponentHelper, ComponentJsonWriter componentWriter, UserJsonWriter userWriter) { - this.dbClient = dbClient; + public SearchAction(UserSession userSession, IssueService service, IssueQueryService issueQueryService, + SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat) { + this.userSession = userSession; this.service = service; this.issueQueryService = issueQueryService; - this.ruleService = ruleService; - this.actionPlanService = actionPlanService; - this.userFinder = userFinder; - this.i18n = i18n; - this.languages = languages; - this.userSession = userSession; - this.issueWriter = issueWriter; - this.userWriter = userWriter; - this.issueComponentHelper = issueComponentHelper; - this.componentWriter = componentWriter; + this.searchResponseLoader = searchResponseLoader; + this.searchResponseFormat = searchResponseFormat; } @Override @@ -119,12 +81,12 @@ public class SearchAction implements IssuesWsAction { .createAction(SEARCH_ACTION) .setHandler(this) .setDescription( - "Get a list of issues. Requires Browse permission on project(s)") + "Search for issues. 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) + action.createParam(Param.FACETS) .setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.") .setPossibleValues(IssueIndex.SUPPORTED_FACETS); action.createParam(IssueFilterParameters.FACET_MODE) @@ -132,8 +94,9 @@ public class SearchAction implements IssuesWsAction { .setDescription("Choose the returned value for facet items, either count of issues or sum of debt.") .setPossibleValues(IssueFilterParameters.FACET_MODE_COUNT, IssueFilterParameters.FACET_MODE_DEBT); action.addSortParams(IssueQuery.SORTS, null, true); - action.addFieldsParam(IssueJsonWriter.SELECTABLE_FIELDS); - + action.createParam(ADDITIONAL_FIELDS) + .setDescription("Comma-separated list of the optional fields to be returned in response.") + .setPossibleValues(SearchAdditionalField.possibleValues()); addComponentRelatedParams(action); action.createParam(IssueFilterParameters.ISSUES) .setDescription("Comma-separated list of issue keys") @@ -159,14 +122,6 @@ public class SearchAction implements IssuesWsAction { action.createParam(IssueFilterParameters.TAGS) .setDescription("Comma-separated list of tags.") .setExampleValue("security,convention"); - action.createParam(IssueFilterParameters.HIDE_RULES) - .setDescription("To not return rules") - .setDefaultValue(false) - .setBooleanPossibleValues(); - action.createParam(IssueFilterParameters.HIDE_COMMENTS) - .setDescription("To not return comments") - .setDefaultValue(false) - .setBooleanPossibleValues(); action.createParam(IssueFilterParameters.ACTION_PLANS) .setDescription("Comma-separated list of action plan keys (not names)") .setExampleValue("3f19de90-1521-4482-a737-a311758ff513"); @@ -202,8 +157,6 @@ public class SearchAction implements IssuesWsAction { "Accepted units are 'y' for year, 'm' for month, 'w' for week and 'd' for day. " + "If this parameter is set, createdAfter must not be set") .setExampleValue("1m2w (1 month 2 weeks)"); - action.createParam("format") - .setDescription("Only json format is available. This parameter is kept only for backward compatibility and shouldn't be used anymore"); } private static void addComponentRelatedParams(WebService.NewAction action) { @@ -268,116 +221,64 @@ public class SearchAction implements IssuesWsAction { @Override public final void handle(Request request, Response response) throws Exception { + // prepare the Elasticsearch request SearchOptions options = new SearchOptions(); - options.setPage(request.mandatoryParamAsInt(WebService.Param.PAGE), request.mandatoryParamAsInt(WebService.Param.PAGE_SIZE)); - options.addFacets(request.paramAsStrings(WebService.Param.FACETS)); - + options.setPage(request.mandatoryParamAsInt(Param.PAGE), request.mandatoryParamAsInt(Param.PAGE_SIZE)); + options.addFacets(request.paramAsStrings(Param.FACETS)); + EnumSet additionalFields = SearchAdditionalField.getFromRequest(request); IssueQuery query = issueQueryService.createFromRequest(request); - SearchResult result = service.search(query, options); - JsonWriter json = response.newJsonWriter().beginObject(); - options.writeJson(json, result.getTotal()); - options.writeDeprecatedJson(json, result.getTotal()); + // execute request + SearchResult result = service.search(query, options); + List issueKeys = from(result.getDocs()).transform(IssueDocToKey.INSTANCE).toList(); - writeResponse(request, result, json); + // load the additional information to be returned in response + SearchResponseLoader.Collector collector = new SearchResponseLoader.Collector(additionalFields, issueKeys); + collectLoggedInUser(collector); + collectRequestParams(collector, request); + Facets facets = null; if (!options.getFacets().isEmpty()) { - writeFacets(request, options, result, json); - } - json.endObject().close(); - } - - private void writeResponse(Request request, SearchResult result, JsonWriter json) { - Map debtFacet = result.getFacets().get(IssueIndex.DEBT_AGGREGATION_NAME); - if (debtFacet != null) { - json.prop("debtTotal", debtFacet.get(Facets.TOTAL)); - } - - List issueKeys = newArrayList(); - Set ruleKeys = newHashSet(); - Set projectUuids = newHashSet(); - Set componentUuids = newHashSet(); - Set actionPlanKeys = newHashSet(); - List userLogins = newArrayList(); - Map usersByLogin = newHashMap(); - Map componentsByUuid = newHashMap(); - Multimap commentsByIssues = ArrayListMultimap.create(); - Collection componentDtos = newHashSet(); - List projectDtos = Lists.newArrayList(); - Map projectsByComponentUuid = newHashMap(); - - for (IssueDoc issueDoc : result.getDocs()) { - issueKeys.add(issueDoc.key()); - ruleKeys.add(issueDoc.ruleKey()); - projectUuids.add(issueDoc.projectUuid()); - componentUuids.add(issueDoc.componentUuid()); - actionPlanKeys.add(issueDoc.actionPlanKey()); - if (issueDoc.reporter() != null) { - userLogins.add(issueDoc.reporter()); - } - if (issueDoc.assignee() != null) { - userLogins.add(issueDoc.assignee()); - } + facets = result.getFacets(); + // add missing values to facets. For example if assignee "john" and facet on "assignees" are requested, then + // "john" should always be listed in the facet. If it is not present, then it is added with value zero. + // This is a constraint from webapp UX. + completeFacets(facets, options, request); + collectFacets(collector, facets); } + SearchResponseData data = searchResponseLoader.load(collector, facets); - collectRuleKeys(request, result, ruleKeys); + // format response - collectFacetsData(request, result, projectUuids, componentUuids, userLogins, actionPlanKeys); + // Filter and reorder facets according to the requested ordered names. + // Must be done after loading of data as the "hidden" facet "debt" + // can be used to get total debt. + facets = reorderFacets(facets, options.getFacets()); - if (userSession.isLoggedIn()) { - userLogins.add(userSession.getLogin()); - } - - DbSession session = dbClient.openSession(false); - try { - if (!BooleanUtils.isTrue(request.paramAsBoolean(IssueFilterParameters.HIDE_COMMENTS))) { - List comments = dbClient.issueChangeDao().selectCommentsByIssues(session, issueKeys); - for (DefaultIssueComment issueComment : comments) { - userLogins.add(issueComment.userLogin()); - commentsByIssues.put(issueComment.issueKey(), issueComment); - } - } - usersByLogin = getUsersByLogin(userLogins); - - projectsByComponentUuid = issueComponentHelper.prepareComponentsAndProjects(projectUuids, componentUuids, componentsByUuid, componentDtos, projectDtos, session); - - writeProjects(json, projectDtos); - writeComponents(json, componentDtos, projectsByComponentUuid); - } finally { - session.close(); - } - - Map actionPlanByKeys = getActionPlanByKeys(actionPlanKeys); - - writeIssues(result, commentsByIssues, usersByLogin, actionPlanByKeys, componentsByUuid, projectsByComponentUuid, - request.paramAsStrings(Param.FIELDS), json); - writeRules(json, !request.mandatoryParamAsBoolean(IssueFilterParameters.HIDE_RULES) ? ruleService.getByKeys(ruleKeys) : Collections.emptyList()); - writeUsers(json, usersByLogin); - writeActionPlans(json, actionPlanByKeys.values()); - writeLanguages(json); + // FIXME allow long in Paging + Paging paging = Paging.create(options.getLimit(), options.getPage(), (int) result.getTotal()); + Issues.Search responseBody = searchResponseFormat.formatSearch(additionalFields, data, paging, facets); + WsUtils.writeProtobuf(responseBody, request, response); } - private static void collectRuleKeys(Request request, SearchResult result, Set ruleKeys) { - Set facetRules = result.getFacets().getBucketKeys(IssueFilterParameters.RULES); - if (facetRules != null) { - for (String rule : facetRules) { - ruleKeys.add(RuleKey.parse(rule)); - } + private Facets reorderFacets(@Nullable Facets facets, Collection orderedNames) { + if (facets == null) { + return null; } - List rulesFromRequest = request.paramAsStrings(IssueFilterParameters.RULES); - if (rulesFromRequest != null) { - for (String ruleKey : rulesFromRequest) { - ruleKeys.add(RuleKey.parse(ruleKey)); + LinkedHashMap> orderedFacets = new LinkedHashMap<>(); + for (String facetName : orderedNames) { + LinkedHashMap facet = facets.get(facetName); + if (facet != null) { + orderedFacets.put(facetName, facet); } } + return new Facets(orderedFacets); } - protected void writeFacets(Request request, SearchOptions options, SearchResult results, JsonWriter json) { - addMandatoryFacetValues(results, IssueFilterParameters.SEVERITIES, Severity.ALL); - addMandatoryFacetValues(results, IssueFilterParameters.STATUSES, Issue.STATUSES); - List resolutions = Lists.newArrayList(""); - resolutions.addAll(Issue.RESOLUTIONS); - addMandatoryFacetValues(results, IssueFilterParameters.RESOLUTIONS, resolutions); - addMandatoryFacetValues(results, IssueFilterParameters.PROJECT_UUIDS, request.paramAsStrings(IssueFilterParameters.PROJECT_UUIDS)); + private void completeFacets(Facets facets, SearchOptions options, Request request) { + addMandatoryValuesToFacet(facets, IssueFilterParameters.SEVERITIES, Severity.ALL); + addMandatoryValuesToFacet(facets, IssueFilterParameters.STATUSES, Issue.STATUSES); + addMandatoryValuesToFacet(facets, IssueFilterParameters.RESOLUTIONS, concat(singletonList(""), Issue.RESOLUTIONS)); + addMandatoryValuesToFacet(facets, IssueFilterParameters.PROJECT_UUIDS, request.paramAsStrings(IssueFilterParameters.PROJECT_UUIDS)); List assignees = Lists.newArrayList(""); List assigneesFromRequest = request.paramAsStrings(IssueFilterParameters.ASSIGNEES); @@ -385,229 +286,84 @@ public class SearchAction implements IssuesWsAction { assignees.addAll(assigneesFromRequest); assignees.remove(IssueQueryService.LOGIN_MYSELF); } - addMandatoryFacetValues(results, IssueFilterParameters.ASSIGNEES, assignees); - addMandatoryFacetValues(results, IssueFilterParameters.FACET_ASSIGNED_TO_ME, Arrays.asList(userSession.getLogin())); - addMandatoryFacetValues(results, IssueFilterParameters.REPORTERS, request.paramAsStrings(IssueFilterParameters.REPORTERS)); - addMandatoryFacetValues(results, IssueFilterParameters.RULES, request.paramAsStrings(IssueFilterParameters.RULES)); - addMandatoryFacetValues(results, IssueFilterParameters.LANGUAGES, request.paramAsStrings(IssueFilterParameters.LANGUAGES)); - addMandatoryFacetValues(results, IssueFilterParameters.TAGS, request.paramAsStrings(IssueFilterParameters.TAGS)); + addMandatoryValuesToFacet(facets, IssueFilterParameters.ASSIGNEES, assignees); + addMandatoryValuesToFacet(facets, IssueFilterParameters.FACET_ASSIGNED_TO_ME, singletonList(userSession.getLogin())); + addMandatoryValuesToFacet(facets, IssueFilterParameters.REPORTERS, request.paramAsStrings(IssueFilterParameters.REPORTERS)); + addMandatoryValuesToFacet(facets, IssueFilterParameters.RULES, request.paramAsStrings(IssueFilterParameters.RULES)); + addMandatoryValuesToFacet(facets, IssueFilterParameters.LANGUAGES, request.paramAsStrings(IssueFilterParameters.LANGUAGES)); + addMandatoryValuesToFacet(facets, IssueFilterParameters.TAGS, request.paramAsStrings(IssueFilterParameters.TAGS)); List actionPlans = Lists.newArrayList(""); List actionPlansFromRequest = request.paramAsStrings(IssueFilterParameters.ACTION_PLANS); if (actionPlansFromRequest != null) { actionPlans.addAll(actionPlansFromRequest); } - addMandatoryFacetValues(results, IssueFilterParameters.ACTION_PLANS, actionPlans); - addMandatoryFacetValues(results, IssueFilterParameters.COMPONENT_UUIDS, request.paramAsStrings(IssueFilterParameters.COMPONENT_UUIDS)); + addMandatoryValuesToFacet(facets, IssueFilterParameters.ACTION_PLANS, actionPlans); + addMandatoryValuesToFacet(facets, IssueFilterParameters.COMPONENT_UUIDS, request.paramAsStrings(IssueFilterParameters.COMPONENT_UUIDS)); - json.name("facets").beginArray(); for (String facetName : options.getFacets()) { - json.beginObject(); - json.prop("property", facetName); - json.name("values").beginArray(); - LinkedHashMap buckets = results.getFacets().get(facetName); - if (buckets != null) { - Set itemsFromFacets = Sets.newHashSet(); - for (Map.Entry bucket : buckets.entrySet()) { - itemsFromFacets.add(bucket.getKey()); - json.beginObject(); - json.prop("val", bucket.getKey()); - json.prop("count", bucket.getValue()); - json.endObject(); - } - // Prevent appearance of a glitch value due to dedicated parameter for this facet - if (!IssueFilterParameters.FACET_ASSIGNED_TO_ME.equals(facetName)) { - addZeroFacetsForSelectedItems(request, facetName, itemsFromFacets, json); + LinkedHashMap buckets = facets.get(facetName); + if (!IssueFilterParameters.FACET_ASSIGNED_TO_ME.equals(facetName)) { + if (buckets != null) { + List requestParams = request.paramAsStrings(facetName); + if (requestParams != null) { + for (String param : requestParams) { + if (!buckets.containsKey(param) && !IssueQueryService.LOGIN_MYSELF.equals(param)) { + // Prevent appearance of a glitch value due to dedicated parameter for this facet + buckets.put(param, 0L); + } + } + } } } - json.endArray().endObject(); - } - json.endArray(); - } - - private void collectFacetsData(Request request, SearchResult result, Set projectUuids, Set componentUuids, List userLogins, - Set actionPlanKeys) { - collectBucketKeys(result, IssueFilterParameters.PROJECT_UUIDS, projectUuids); - collectParameterValues(request, IssueFilterParameters.PROJECT_UUIDS, projectUuids); - - collectBucketKeys(result, IssueFilterParameters.COMPONENT_UUIDS, componentUuids); - collectParameterValues(request, IssueFilterParameters.COMPONENT_UUIDS, componentUuids); - collectBucketKeys(result, IssueFilterParameters.FILE_UUIDS, componentUuids); - collectParameterValues(request, IssueFilterParameters.FILE_UUIDS, componentUuids); - - collectBucketKeys(result, IssueFilterParameters.MODULE_UUIDS, componentUuids); - collectParameterValues(request, IssueFilterParameters.MODULE_UUIDS, componentUuids); - collectParameterValues(request, IssueFilterParameters.COMPONENT_ROOT_UUIDS, componentUuids); - - collectBucketKeys(result, IssueFilterParameters.ASSIGNEES, userLogins); - collectParameterValues(request, IssueFilterParameters.ASSIGNEES, userLogins); - collectBucketKeys(result, IssueFilterParameters.REPORTERS, userLogins); - collectParameterValues(request, IssueFilterParameters.REPORTERS, userLogins); - collectBucketKeys(result, IssueFilterParameters.ACTION_PLANS, actionPlanKeys); - collectParameterValues(request, IssueFilterParameters.ACTION_PLANS, actionPlanKeys); - } - - private static void collectBucketKeys(SearchResult result, String facetName, Collection bucketKeys) { - bucketKeys.addAll(result.getFacets().getBucketKeys(facetName)); - } - - private static void collectParameterValues(Request request, String facetName, Collection facetKeys) { - Collection paramValues = request.paramAsStrings(facetName); - if (paramValues != null) { - facetKeys.addAll(paramValues); - } - } - - // TODO change to use the RuleMapper - private void writeRules(JsonWriter json, Collection rules) { - json.name("rules").beginArray(); - for (Rule rule : rules) { - json.beginObject() - .prop("key", rule.key().toString()) - .prop("name", rule.name()) - .prop("lang", rule.language()) - .prop("desc", rule.htmlDescription()) - .prop("status", rule.status().toString()); - Language lang = languages.get(rule.language()); - json.prop("langName", lang == null ? null : lang.getName()); - json.endObject(); - } - json.endArray(); - } - - private void writeIssues(SearchResult result, Multimap commentsByIssues, Map usersByLogin, - Map actionPlanByKeys, - Map componentsByUuid, Map projectsByComponentUuid, @Nullable List fields, JsonWriter json) { - json.name("issues").beginArray(); - - for (IssueDoc issue : result.getDocs()) { - issueWriter.write(json, issue, usersByLogin, componentsByUuid, projectsByComponentUuid, commentsByIssues, actionPlanByKeys, fields); - } - - json.endArray(); - } - - private void writeComponents(JsonWriter json, Collection components, Map projectsByComponentUuid) { - json.name("components").beginArray(); - for (ComponentDto component : components) { - ComponentDto project = projectsByComponentUuid.get(component.uuid()); - componentWriter.write(json, component, project); - } - json.endArray(); - } - - private static void writeProjects(JsonWriter json, List projects) { - json.name("projects").beginArray(); - for (ComponentDto project : projects) { - json.beginObject() - .prop("uuid", project.uuid()) - .prop("key", project.key()) - .prop("id", project.getId()) - .prop("qualifier", project.qualifier()) - .prop("name", project.name()) - .prop("longName", project.longName()) - .endObject(); } - json.endArray(); } - private void writeUsers(JsonWriter json, Map usersByLogin) { - json.name("users").beginArray(); - for (User user : usersByLogin.values()) { - userWriter.write(json, user); - } - json.endArray(); - } - - private void writeLanguages(JsonWriter json) { - json.name("languages").beginArray(); - for (Language language : languages.all()) { - json.beginObject() - .prop("key", language.getKey()) - .prop("name", language.getName()) - .endObject(); - } - json.endArray(); - } - - private void writeActionPlans(JsonWriter json, Collection plans) { - if (!plans.isEmpty()) { - json.name("actionPlans").beginArray(); - for (ActionPlan actionPlan : plans) { - 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(); + private void addMandatoryValuesToFacet(Facets facets, String facetName, @Nullable Iterable mandatoryValues) { + Map buckets = facets.get(facetName); + if (buckets != null && mandatoryValues != null) { + for (String mandatoryValue : mandatoryValues) { + if (!buckets.containsKey(mandatoryValue)) { + buckets.put(mandatoryValue, 0L); + } } - json.endArray(); - } - } - - private Map getUsersByLogin(List userLogins) { - Map usersByLogin = newHashMap(); - for (User user : userFinder.findByLogins(userLogins)) { - usersByLogin.put(user.login(), user); - } - return usersByLogin; - } - - private Map getActionPlanByKeys(Collection actionPlanKeys) { - Map actionPlans = newHashMap(); - for (ActionPlan actionPlan : actionPlanService.findByKeys(actionPlanKeys)) { - actionPlans.put(actionPlan.key(), actionPlan); } - return actionPlans; } - @CheckForNull - private static String isoDate(@Nullable Date date) { - if (date != null) { - return DateUtils.formatDateTime(date); + private void collectLoggedInUser(SearchResponseLoader.Collector collector) { + if (userSession.isLoggedIn()) { + collector.add(SearchAdditionalField.USERS, userSession.getLogin()); } - return null; } - @CheckForNull - private String formatDate(@Nullable Date date) { - if (date != null) { - return i18n.formatDateTime(userSession.locale(), date); + private void collectFacets(SearchResponseLoader.Collector collector, Facets facets) { + Set facetRules = facets.getBucketKeys(IssueFilterParameters.RULES); + if (facetRules != null) { + collector.addAll(SearchAdditionalField.RULES, from(facetRules).transform(RuleKeyFunctions.stringToRuleKey())); } - return null; + collector.addProjectUuids(facets.getBucketKeys(IssueFilterParameters.PROJECT_UUIDS)); + collector.addComponentUuids(facets.getBucketKeys(IssueFilterParameters.COMPONENT_UUIDS)); + collector.addComponentUuids(facets.getBucketKeys(IssueFilterParameters.FILE_UUIDS)); + collector.addComponentUuids(facets.getBucketKeys(IssueFilterParameters.MODULE_UUIDS)); + collector.addAll(SearchAdditionalField.USERS, facets.getBucketKeys(IssueFilterParameters.ASSIGNEES)); + collector.addAll(SearchAdditionalField.USERS, facets.getBucketKeys(IssueFilterParameters.REPORTERS)); + collector.addAll(SearchAdditionalField.ACTION_PLANS, facets.getBucketKeys(IssueFilterParameters.ACTION_PLANS)); } - protected void addMandatoryFacetValues(SearchResult results, String facetName, @Nullable List mandatoryValues) { - Map buckets = results.getFacets().get(facetName); - if (buckets != null && mandatoryValues != null) { - for (String mandatoryValue : mandatoryValues) { - if (!buckets.containsKey(mandatoryValue)) { - buckets.put(mandatoryValue, 0L); - } - } - } + private void collectRequestParams(SearchResponseLoader.Collector collector, Request request) { + collector.addProjectUuids(request.paramAsStrings(IssueFilterParameters.PROJECT_UUIDS)); + collector.addComponentUuids(request.paramAsStrings(IssueFilterParameters.FILE_UUIDS)); + collector.addComponentUuids(request.paramAsStrings(IssueFilterParameters.MODULE_UUIDS)); + collector.addComponentUuids(request.paramAsStrings(IssueFilterParameters.COMPONENT_ROOT_UUIDS)); + collector.addAll(SearchAdditionalField.USERS, request.paramAsStrings(IssueFilterParameters.ASSIGNEES)); + collector.addAll(SearchAdditionalField.USERS, request.paramAsStrings(IssueFilterParameters.REPORTERS)); + collector.addAll(SearchAdditionalField.ACTION_PLANS, request.paramAsStrings(IssueFilterParameters.ACTION_PLANS)); } - private static void addZeroFacetsForSelectedItems(Request request, String facetName, Set itemsFromFacets, JsonWriter json) { - List requestParams = request.paramAsStrings(facetName); - if (requestParams != null) { - for (String param : requestParams) { - if (!itemsFromFacets.contains(param) && !IssueQueryService.LOGIN_MYSELF.equals(param)) { - json.beginObject(); - json.prop("val", param); - json.prop("count", 0); - json.endObject(); - } - } + private enum IssueDocToKey implements Function { + INSTANCE; + @Override + public String apply(IssueDoc input) { + return input.key(); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAdditionalField.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAdditionalField.java index 25a553b908f..dd91ba9f6ea 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAdditionalField.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAdditionalField.java @@ -67,7 +67,7 @@ public enum SearchAdditionalField { } public static EnumSet getFromRequest(Request request) { - List labels = request.paramAsStrings(Search2Action.ADDITIONAL_FIELDS); + List labels = request.paramAsStrings(SearchAction.ADDITIONAL_FIELDS); if (labels == null) { return EnumSet.noneOf(SearchAdditionalField.class); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java new file mode 100644 index 00000000000..4f652870afb --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java @@ -0,0 +1,65 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.issue.ws; + +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.server.issue.IssueService; + +public class SetSeverityAction implements IssuesWsAction { + + public static final String ACTION = "set_severity"; + + private final IssueService issueService; + private final OperationResponseWriter responseWriter; + + public SetSeverityAction(IssueService issueService, OperationResponseWriter responseWriter) { + this.issueService = issueService; + this.responseWriter = responseWriter; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction(ACTION) + .setDescription("Change severity. Requires authentication and Browse permission on project") + .setSince("3.6") + .setHandler(this) + .setPost(true); + + action.createParam("issue") + .setDescription("Key of the issue") + .setRequired(true) + .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); + action.createParam("severity") + .setDescription("New severity") + .setRequired(true) + .setPossibleValues(Severity.ALL); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String key = request.mandatoryParam("issue"); + issueService.setSeverity(key, request.mandatoryParam("severity")); + + responseWriter.write(key, request, response); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 2ac4baecc07..e86eb3208d5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -61,7 +61,6 @@ import org.sonar.server.component.ComponentFinder; import org.sonar.server.component.ComponentService; import org.sonar.server.component.DefaultComponentFinder; import org.sonar.server.component.DefaultRubyComponentService; -import org.sonar.server.component.ws.ComponentJsonWriter; import org.sonar.server.component.ws.ComponentsWs; import org.sonar.server.component.ws.EventsWs; import org.sonar.server.component.ws.ResourcesWs; @@ -153,15 +152,7 @@ import org.sonar.server.issue.notification.MyNewIssuesNotificationDispatcher; import org.sonar.server.issue.notification.NewIssuesEmailTemplate; import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher; import org.sonar.server.issue.notification.NewIssuesNotificationFactory; -import org.sonar.server.issue.ws.ComponentTagsAction; -import org.sonar.server.issue.ws.IssueActionsWriter; -import org.sonar.server.issue.ws.IssueComponentHelper; -import org.sonar.server.issue.ws.IssueJsonWriter; -import org.sonar.server.issue.ws.IssuesWs; -import org.sonar.server.issue.ws.OperationResponseWriter; -import org.sonar.server.issue.ws.SearchResponseFormat; -import org.sonar.server.issue.ws.SearchResponseLoader; -import org.sonar.server.issue.ws.SetTagsAction; +import org.sonar.server.issue.ws.IssueWsModule; import org.sonar.server.language.ws.LanguageWs; import org.sonar.server.measure.MeasureFilterEngine; import org.sonar.server.measure.MeasureFilterExecutor; @@ -178,9 +169,9 @@ import org.sonar.server.notification.NotificationCenter; import org.sonar.server.notification.NotificationService; import org.sonar.server.notification.email.AlertsEmailTemplate; import org.sonar.server.notification.email.EmailNotificationChannel; +import org.sonar.server.permission.PermissionFinder; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionFinder; import org.sonar.server.permission.ws.PermissionsWsModule; import org.sonar.server.platform.BackendCleanup; import org.sonar.server.platform.SettingsChangeNotifier; @@ -353,16 +344,16 @@ public class PlatformLevel4 extends PlatformLevel { IndexDefinitions.class, IndexCreator.class, - // Activity + // Activity ActivityService.class, ActivityIndexDefinition.class, ActivityIndexer.class, ActivityIndex.class, - // batch + // batch BatchWsModule.class, - // Dashboard + // Dashboard DashboardsWs.class, org.sonar.server.dashboard.ws.ShowAction.class, ProjectDefaultDashboard.class, @@ -400,12 +391,12 @@ public class PlatformLevel4 extends PlatformLevel { ProjectIssueFilterWidget.class, IssueTagCloudWidget.class, - // update center + // update center UpdateCenterClient.class, UpdateCenterMatrixFactory.class, UpdateCenterWs.class, - // quality profile + // quality profile XMLProfileParser.class, XMLProfileSerializer.class, AnnotationProfileParser.class, @@ -448,7 +439,7 @@ public class PlatformLevel4 extends PlatformLevel { QProfileReset.class, RubyQProfileActivityService.class, - // rule + // rule AnnotationRuleParser.class, XMLRuleParser.class, DefaultRuleFinder.class, @@ -476,17 +467,17 @@ public class PlatformLevel4 extends PlatformLevel { RepositoriesAction.class, org.sonar.server.rule.ws.AppAction.class, - // languages + // languages Languages.class, LanguageWs.class, org.sonar.server.language.ws.ListAction.class, - // activity + // activity ActivitiesWs.class, org.sonar.server.activity.ws.SearchAction.class, ActivityMapping.class, - // measure + // measure MeasureFilterFactory.class, MeasureFilterExecutor.class, MeasureFilterEngine.class, @@ -498,14 +489,14 @@ public class PlatformLevel4 extends PlatformLevel { DefaultMetricFinder.class, TimeMachineWs.class, - // quality gates + // quality gates QualityGateDao.class, QualityGateConditionDao.class, QualityGates.class, ProjectQgateAssociationDao.class, QgateProjectFinder.class, - org.sonar.server.qualitygate.ws.ListAction.class, + org.sonar.server.qualitygate.ws.ListAction.class, org.sonar.server.qualitygate.ws.SearchAction.class, org.sonar.server.qualitygate.ws.ShowAction.class, org.sonar.server.qualitygate.ws.CreateAction.class, @@ -522,17 +513,17 @@ public class PlatformLevel4 extends PlatformLevel { org.sonar.server.qualitygate.ws.AppAction.class, QGatesWs.class, - // web services + // web services WebServiceEngine.class, ListingWs.class, - // localization + // localization L10nWs.class, - // authentication + // authentication AuthenticationWs.class, - // users + // users SecurityRealmFactory.class, DeprecatedUserFinder.class, NewUserNotifier.class, @@ -547,7 +538,6 @@ public class PlatformLevel4 extends PlatformLevel { CurrentAction.class, org.sonar.server.user.ws.SearchAction.class, org.sonar.server.user.ws.GroupsAction.class, - org.sonar.server.issue.ws.AuthorsAction.class, FavoritesWs.class, UserPropertiesWs.class, UserIndexDefinition.class, @@ -555,19 +545,19 @@ public class PlatformLevel4 extends PlatformLevel { UserIndex.class, UserUpdater.class, - // groups + // groups GroupMembershipService.class, GroupMembershipFinder.class, UserGroupsModule.class, - // permissions + // permissions PermissionRepository.class, PermissionService.class, PermissionTemplateService.class, PermissionFinder.class, PermissionsWsModule.class, - // components + // components ProjectsWsModule.class, DefaultComponentFinder.class, DefaultRubyComponentService.class, @@ -581,14 +571,13 @@ public class PlatformLevel4 extends PlatformLevel { NewAlerts.class, NewAlerts.newMetadata(), ComponentCleanerService.class, - ComponentJsonWriter.class, - // views + // views ViewIndexDefinition.class, ViewIndexer.class, ViewIndex.class, - // issues + // issues IssueIndexDefinition.class, IssueIndexer.class, IssueAuthorizationIndexer.class, @@ -603,22 +592,9 @@ public class PlatformLevel4 extends PlatformLevel { Actions.class, IssueBulkChangeService.class, IssueChangelogFormatter.class, - IssuesWs.class, - IssueJsonWriter.class, - IssueComponentHelper.class, WsResponseCommonFormat.class, - SearchResponseLoader.class, - SearchResponseFormat.class, - OperationResponseWriter.class, - org.sonar.server.issue.ws.AssignAction.class, - org.sonar.server.issue.ws.ShowAction.class, - org.sonar.server.issue.ws.SearchAction.class, - org.sonar.server.issue.ws.Search2Action.class, - org.sonar.server.issue.ws.TagsAction.class, - SetTagsAction.class, - ComponentTagsAction.class, + IssueWsModule.class, IssueService.class, - IssueActionsWriter.class, IssueQueryService.class, NewIssuesEmailTemplate.class, MyNewIssuesEmailTemplate.class, @@ -635,13 +611,13 @@ public class PlatformLevel4 extends PlatformLevel { EmailNotificationChannel.class, AlertsEmailTemplate.class, - IssueFilterWsModule.class, + IssueFilterWsModule.class, - // action plan + // action plan ActionPlanWs.class, ActionPlanService.class, - // issues actions + // issues actions AssignAction.class, PlanAction.class, SetSeverityAction.class, @@ -650,7 +626,7 @@ public class PlatformLevel4 extends PlatformLevel { AddTagsAction.class, RemoveTagsAction.class, - // technical debt + // technical debt DebtModelService.class, DebtModelOperations.class, DebtModelLookup.class, @@ -660,7 +636,7 @@ public class PlatformLevel4 extends PlatformLevel { DebtRulesXMLImporter.class, DebtCharacteristicsXMLImporter.class, - // source + // source HtmlSourceDecorator.class, SourceService.class, SourcesWs.class, @@ -674,23 +650,23 @@ public class PlatformLevel4 extends PlatformLevel { SourceLineIndex.class, SourceLineIndexer.class, - // Duplications + // Duplications DuplicationsParser.class, DuplicationsWs.class, DuplicationsJsonWriter.class, org.sonar.server.duplication.ws.ShowAction.class, - // text + // text MacroInterpreter.class, RubyTextService.class, - // Notifications + // Notifications EmailSettings.class, NotificationService.class, NotificationCenter.class, DefaultNotificationManager.class, - // Tests + // Tests CoverageService.class, TestsWs.class, CoveredFilesAction.class, @@ -699,12 +675,12 @@ public class PlatformLevel4 extends PlatformLevel { TestIndex.class, TestIndexer.class, - // Properties + // Properties PropertiesWs.class, - TypeValidationModule.class, + TypeValidationModule.class, - // System + // System RestartAction.class, InfoAction.class, UpgradesAction.class, @@ -719,7 +695,7 @@ public class PlatformLevel4 extends PlatformLevel { MigrateDbAction.class, DbMigrationStatusAction.class, - // Plugins WS + // Plugins WS PluginWSCommons.class, PluginUpdateAggregator.class, InstalledAction.class, @@ -732,7 +708,7 @@ public class PlatformLevel4 extends PlatformLevel { CancelAllAction.class, PluginsWs.class, - // Compute engine + // Compute engine CEQueueStatusImpl.class, ComputeEngineQueueMonitor.class, ReportQueue.class, @@ -746,7 +722,7 @@ public class PlatformLevel4 extends PlatformLevel { ProjectSettingsFactory.class, IndexPurgeListener.class, - // UI + // UI GlobalNavigationAction.class, SettingsNavigationAction.class, ComponentNavigationAction.class, diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java index 51022792640..b184a828279 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java @@ -32,30 +32,22 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; -import org.sonar.api.issue.action.Action; import org.sonar.api.user.User; import org.sonar.api.web.UserRole; import org.sonar.core.issue.DefaultActionPlan; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; import org.sonar.db.component.ResourceDao; import org.sonar.db.component.ResourceDto; import org.sonar.db.component.ResourceQuery; import org.sonar.db.issue.IssueFilterDto; -import org.sonar.server.component.ws.ComponentJsonWriter; 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.issue.ws.IssueComponentHelper; -import org.sonar.server.issue.ws.IssueJsonWriter; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.user.ThreadLocalUserSession; -import org.sonar.server.user.index.UserIndex; -import org.sonar.server.user.ws.UserJsonWriter; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; @@ -86,28 +78,12 @@ public class InternalRubyIssueServiceTest { ResourceDao resourceDao; - ActionService actionService; - IssueFilterService issueFilterService; IssueBulkChangeService issueBulkChangeService; InternalRubyIssueService service; - IssueJsonWriter issueWriter; - - IssueComponentHelper issueComponentHelper; - - UserIndex userIndex; - - DbClient dbClient; - - DbSession dbSession; - - UserJsonWriter userWriter; - - ComponentJsonWriter componentWriter; - @Before public void setUp() { issueService = mock(IssueService.class); @@ -116,22 +92,14 @@ public class InternalRubyIssueServiceTest { changelogService = mock(IssueChangelogService.class); actionPlanService = mock(ActionPlanService.class); resourceDao = mock(ResourceDao.class); - actionService = mock(ActionService.class); issueFilterService = mock(IssueFilterService.class); issueBulkChangeService = mock(IssueBulkChangeService.class); - issueWriter = mock(IssueJsonWriter.class); - issueComponentHelper = mock(IssueComponentHelper.class); - userIndex = mock(UserIndex.class); - dbClient = mock(DbClient.class); - dbSession = mock(DbSession.class); - userWriter = mock(UserJsonWriter.class); - userWriter = mock(UserJsonWriter.class); ResourceDto project = new ResourceDto().setKey("org.sonar.Sample"); when(resourceDao.selectResource(any(ResourceQuery.class))).thenReturn(project); - service = new InternalRubyIssueService(issueService, issueQueryService, commentService, changelogService, actionPlanService, resourceDao, actionService, - issueFilterService, issueBulkChangeService, issueWriter, issueComponentHelper, componentWriter, userIndex, dbClient, userSessionRule, userWriter); + service = new InternalRubyIssueService(issueService, issueQueryService, commentService, changelogService, actionPlanService, resourceDao, + issueFilterService, issueBulkChangeService, userSessionRule); } @Test @@ -170,12 +138,6 @@ public class InternalRubyIssueServiceTest { verify(commentService).findComments(newArrayList("ABCD")); } - @Test - public void do_transition() { - service.doTransition("ABCD", Issue.STATUS_RESOLVED); - verify(issueService).doTransition(eq("ABCD"), eq(Issue.STATUS_RESOLVED)); - } - @Test public void create_action_plan() { Map parameters = newHashMap(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/CreateActionTest.java new file mode 100644 index 00000000000..c0ed4dc8da7 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/CreateActionTest.java @@ -0,0 +1,76 @@ +/* + * 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.issue.ws; + +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.issue.IssueService; +import org.sonar.server.ws.WsAction; +import org.sonar.server.ws.WsActionTester; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CreateActionTest { + + IssueService issueService = mock(IssueService.class); + OperationResponseWriter responseWriter = mock(OperationResponseWriter.class); + WsAction underTest = new CreateAction(issueService, responseWriter); + WsActionTester tester = new WsActionTester(underTest); + + @Test + public void create_manual_issue_with_default_values() throws Exception { + RuleKey ruleKey = RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "S1"); + when(issueService.createManualIssue("FILE_KEY", ruleKey, null, null, null, null)) + .thenReturn(new DefaultIssue().setKey("ISSUE_KEY")); + + tester.newRequest() + .setParam("component", "FILE_KEY") + .setParam("rule", ruleKey.toString()) + .execute(); + + verify(issueService).createManualIssue("FILE_KEY", ruleKey, null, null, null, null); + verify(responseWriter).write(eq("ISSUE_KEY"), any(Request.class), any(Response.class)); + } + + @Test + public void create_manual_issue() throws Exception { + RuleKey ruleKey = RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "S1"); + when(issueService.createManualIssue("FILE_KEY", ruleKey, 13, "the msg", "BLOCKER", null)) + .thenReturn(new DefaultIssue().setKey("ISSUE_KEY")); + + tester.newRequest() + .setParam("component", "FILE_KEY") + .setParam("rule", ruleKey.toString()) + .setParam("severity", "BLOCKER") + .setParam("line", "13") + .setParam("message", "the msg") + .execute(); + + verify(issueService).createManualIssue("FILE_KEY", ruleKey, 13, "the msg", "BLOCKER", null); + verify(responseWriter).write(eq("ISSUE_KEY"), any(Request.class), any(Response.class)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java new file mode 100644 index 00000000000..b504415503c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java @@ -0,0 +1,52 @@ +/* + * 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.issue.ws; + +import org.junit.Test; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.server.issue.IssueService; +import org.sonar.server.ws.WsAction; +import org.sonar.server.ws.WsActionTester; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DoTransitionActionTest { + + IssueService issueService = mock(IssueService.class); + OperationResponseWriter responseWriter = mock(OperationResponseWriter.class); + WsAction underTest = new DoTransitionAction(issueService, responseWriter); + WsActionTester tester = new WsActionTester(underTest); + + @Test + public void do_transition() throws Exception { + tester.newRequest() + .setParam("issue", "ABC") + .setParam("transition", "confirm") + .execute(); + + verify(issueService).doTransition("ABC", "confirm"); + verify(responseWriter).write(eq("ABC"), any(Request.class), any(Response.class)); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java index 7e9eb3d2d57..050ce927fe1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java @@ -33,7 +33,6 @@ import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.web.UserRole; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.workflow.Transition; -import org.sonar.server.issue.ActionService; import org.sonar.server.issue.IssueService; import org.sonar.server.tester.UserSessionRule; import org.sonar.test.JsonAssert; @@ -50,14 +49,11 @@ public class IssueActionsWriterTest { @Mock IssueService issueService; - @Mock - ActionService actionService; - IssueActionsWriter writer; @Before public void setUp() { - writer = new IssueActionsWriter(issueService, actionService, userSessionRule); + writer = new IssueActionsWriter(issueService, userSessionRule); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java new file mode 100644 index 00000000000..c3a20b139c0 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java @@ -0,0 +1,34 @@ +/* + * 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.issue.ws; + +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssueWsModuleTest { + @Test + public void verify_count_of_added_components() { + ComponentContainer container = new ComponentContainer(); + new IssueWsModule().configure(container); + assertThat(container.size()).isEqualTo(18); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/PlanActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/PlanActionTest.java new file mode 100644 index 00000000000..9ee14c908e6 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/PlanActionTest.java @@ -0,0 +1,61 @@ +/* + * 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.issue.ws; + +import org.junit.Test; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.server.issue.IssueService; +import org.sonar.server.ws.WsAction; +import org.sonar.server.ws.WsActionTester; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class PlanActionTest { + + IssueService issueService = mock(IssueService.class); + OperationResponseWriter responseWriter = mock(OperationResponseWriter.class); + WsAction underTest = new PlanAction(issueService, responseWriter); + WsActionTester tester = new WsActionTester(underTest); + + @Test + public void plan() throws Exception { + tester.newRequest() + .setParam("issue", "ABC") + .setParam("plan", "P1") + .execute(); + + verify(issueService).plan("ABC", "P1"); + verify(responseWriter).write(eq("ABC"), any(Request.class), any(Response.class)); + } + + @Test + public void unplan_if_value_is_absent() throws Exception { + tester.newRequest() + .setParam("issue", "ABC") + .execute(); + + verify(issueService).plan("ABC", null); + verify(responseWriter).write(eq("ABC"), any(Request.class), any(Response.class)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java index e1238e03686..90274dd1f73 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java @@ -107,7 +107,7 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION).execute(); + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION).execute(); result.assertJson(this.getClass(), "issues_on_different_projects.json"); } @@ -121,22 +121,22 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.PROJECT_UUIDS, project.uuid()) .execute() .assertJson(this.getClass(), "search_by_project_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.PROJECT_UUIDS, "unknown") .execute() .assertJson(this.getClass(), "no_issue.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, project.uuid()) .execute() .assertJson(this.getClass(), "search_by_project_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, "unknown") .execute() .assertJson(this.getClass(), "no_issue.json"); @@ -161,7 +161,7 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.PROJECT_UUIDS, project1.uuid()) .setParam(WebService.Param.FACETS, "projectUuids") .execute() @@ -178,22 +178,22 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.FILE_UUIDS, file.uuid()) .execute() .assertJson(this.getClass(), "search_by_file_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.FILE_UUIDS, "unknown") .execute() .assertJson(this.getClass(), "no_issue.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, file.uuid()) .execute() .assertJson(this.getClass(), "search_by_file_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, "unknown") .execute() .assertJson(this.getClass(), "no_issue.json"); @@ -212,12 +212,12 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENTS, file.key()) .execute() .assertJson(this.getClass(), "search_by_file_key.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENTS, unitTest.key()) .execute() .assertJson(this.getClass(), "search_by_test_key.json"); @@ -238,7 +238,7 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, project.uuid()) .setParam(IssueFilterParameters.FILE_UUIDS, file1.uuid() + "," + file3.uuid()) .setParam(WebService.Param.FACETS, "fileUuids") @@ -257,22 +257,22 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, directory.uuid()) .execute() .assertJson(this.getClass(), "search_by_file_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, "unknown") .execute() .assertJson(this.getClass(), "no_issue.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.DIRECTORIES, "src/main/java/dir") .execute() .assertJson(this.getClass(), "search_by_file_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.DIRECTORIES, "src/main/java") .execute() .assertJson(this.getClass(), "no_issue.json"); @@ -295,34 +295,34 @@ public class SearchActionComponentsMediumTest { tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, directory1.uuid()) .execute() .assertJson(this.getClass(), "search_by_directory_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, directory2.uuid()) .execute() .assertJson(this.getClass(), "no_issue.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.MODULE_UUIDS, module1.uuid()) .setParam(IssueFilterParameters.DIRECTORIES, "src/main/java/dir") .execute() .assertJson(this.getClass(), "search_by_directory_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.MODULE_UUIDS, module2.uuid()) .setParam(IssueFilterParameters.DIRECTORIES, "src/main/java/dir") .execute() .assertJson(this.getClass(), "no_issue.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.DIRECTORIES, "src/main/java/dir") .execute() .assertJson(this.getClass(), "search_by_directory_uuid.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.DIRECTORIES, "src/main/java") .execute() .assertJson(this.getClass(), "no_issue.json"); @@ -345,7 +345,7 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, module.uuid()) .setParam(IssueFilterParameters.MODULE_UUIDS, subModule1.uuid() + "," + subModule3.uuid()) .setParam(WebService.Param.FACETS, "moduleUuids") @@ -365,7 +365,7 @@ public class SearchActionComponentsMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") .setParam(WebService.Param.FACETS, "directories") .execute(); @@ -385,7 +385,7 @@ public class SearchActionComponentsMediumTest { setAnyoneProjectPermission(view, UserRole.USER); userSessionRule.login("john").addProjectUuidPermissions(UserRole.USER, view.uuid()); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, view.uuid()) .execute() .assertJson(this.getClass(), "search_by_view_uuid.json"); @@ -405,7 +405,7 @@ public class SearchActionComponentsMediumTest { // User has wrong permission on the view, no issue will be returned userSessionRule.login("john").addProjectUuidPermissions(UserRole.CODEVIEWER, view.uuid()); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, view.uuid()) .execute() .assertJson(this.getClass(), "no_issue.json"); @@ -426,7 +426,7 @@ public class SearchActionComponentsMediumTest { setAnyoneProjectPermission(view, UserRole.USER); userSessionRule.login("john").addComponentUuidPermission(UserRole.USER, view.uuid(), subView.uuid()); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, subView.uuid()) .execute() .assertJson(this.getClass(), "search_by_view_uuid.json"); @@ -448,7 +448,7 @@ public class SearchActionComponentsMediumTest { // User has wrong permission on the view, no issue will be returned userSessionRule.login("john").addComponentUuidPermission(UserRole.CODEVIEWER, view.uuid(), subView.uuid()); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, subView.uuid()) .execute() .assertJson(this.getClass(), "no_issue.json"); @@ -467,13 +467,13 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.AUTHORS, "leia") .setParam(WebService.Param.FACETS, "authors") .execute() .assertJson(this.getClass(), "search_by_authors.json"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.AUTHORS, "unknown") .execute() .assertJson(this.getClass(), "no_issue.json"); @@ -496,7 +496,7 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, developer.uuid()) .execute() .assertJson(this.getClass(), "search_by_developer.json"); @@ -528,7 +528,7 @@ public class SearchActionComponentsMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam(IssueFilterParameters.COMPONENT_UUIDS, technicalProject.uuid()) .execute(); result diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java index 168c21a6c0e..683b94a983c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java @@ -92,7 +92,7 @@ public class SearchActionMediumTest { public void define_action() { WebService.Controller controller = wsTester.controller("api/issues"); - WebService.Action show = controller.action("search2"); + WebService.Action show = controller.action("search"); assertThat(show).isNotNull(); assertThat(show.handler()).isNotNull(); assertThat(show.since()).isEqualTo("3.6"); @@ -104,7 +104,7 @@ public class SearchActionMediumTest { @Test public void empty_search() throws Exception { - WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION); + WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION); WsTester.Result result = request.execute(); assertThat(result).isNotNull(); @@ -137,7 +137,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION).execute(); + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION).execute(); result.assertJson(this.getClass(), "response_contains_all_fields_except_additional_fields.json"); } @@ -171,7 +171,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("additionalFields", "comments,users") .execute(); result.assertJson(this.getClass(), "issue_with_comments.json"); @@ -207,7 +207,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION).setParam(IssueFilterParameters.HIDE_COMMENTS, "true").execute(); + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION).setParam(IssueFilterParameters.HIDE_COMMENTS, "true").execute(); result.assertJson(this.getClass(), "issue_with_comment_hidden.json"); assertThat(result.outputAsString()).doesNotContain("fabrice"); } @@ -235,7 +235,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("additionalFields", "actionPlans") .execute(); result.assertJson(this.getClass(), "issue_with_action_plan.json"); @@ -254,7 +254,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .execute(); result.assertJson(this.getClass(), "issue_with_attributes.json"); } @@ -285,7 +285,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("additionalFields", "_all").execute(); result.assertJson(this.getClass(), "load_additional_fields.json"); } @@ -310,7 +310,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .execute(); result.assertJson(this.getClass(), "issue_on_removed_file.json"); } @@ -325,7 +325,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION).execute(); + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION).execute(); assertThat(result.outputAsString()).contains("\"componentId\":" + file.getId() + ","); } @@ -342,7 +342,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION).setParam(IssueFilterParameters.COMPONENTS, file.getKey()).execute(); + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION).setParam(IssueFilterParameters.COMPONENTS, file.getKey()).execute(); result.assertJson(this.getClass(), "apply_paging_with_one_component.json"); } @@ -357,7 +357,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION).setParam(Search2Action.ADDITIONAL_FIELDS, "_all").execute(); + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION).setParam(SearchAction.ADDITIONAL_FIELDS, "_all").execute(); result.assertJson(this.getClass(), "components_contains_sub_projects.json"); } @@ -378,7 +378,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") .setParam(WebService.Param.FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans") .execute(); @@ -402,7 +402,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") .setParam(WebService.Param.FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans") .setParam("facetMode", "debt") @@ -427,7 +427,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") .setParam("severities", "MAJOR,MINOR") .setParam("languages", "xoo,polop,palap") @@ -472,7 +472,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") .setParam("assignees", "__me__") .setParam(WebService.Param.FACETS, "assignees,assigned_to_me") @@ -503,7 +503,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") .setParam("assignees", "__me__") .execute() @@ -546,7 +546,7 @@ public class SearchActionMediumTest { tester.get(IssueIndexer.class).indexAll(); userSessionRule.login("john-bob.polop"); - wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("resolved", "false") .setParam("assignees", "alice") .setParam(WebService.Param.FACETS, "assignees,assigned_to_me") @@ -572,7 +572,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION) + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) .setParam("sort", IssueQuery.SORT_BY_UPDATE_DATE) .setParam("asc", "false") .execute(); @@ -592,7 +592,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION); + WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION); request.setParam(WebService.Param.PAGE, "2"); request.setParam(WebService.Param.PAGE_SIZE, "9"); @@ -613,7 +613,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION); + WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION); request.setParam(WebService.Param.PAGE, "1"); request.setParam(WebService.Param.PAGE_SIZE, "-1"); @@ -634,7 +634,7 @@ public class SearchActionMediumTest { session.commit(); tester.get(IssueIndexer.class).indexAll(); - WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION); + WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION); request.setParam(IssueFilterParameters.PAGE_INDEX, "2"); request.setParam(IssueFilterParameters.PAGE_SIZE, "9"); @@ -644,7 +644,7 @@ public class SearchActionMediumTest { @Test public void default_page_size_is_100() throws Exception { - WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, Search2Action.SEARCH_ACTION); + WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION); WsTester.Result result = request.execute(); result.assertJson(this.getClass(), "default_page_size_is_100.json"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java new file mode 100644 index 00000000000..1869feef4a3 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java @@ -0,0 +1,66 @@ +/* + * 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.issue.ws; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.server.issue.IssueService; +import org.sonar.server.ws.WsAction; +import org.sonar.server.ws.WsActionTester; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SetSeverityActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + IssueService issueService = mock(IssueService.class); + OperationResponseWriter responseWriter = mock(OperationResponseWriter.class); + WsAction underTest = new SetSeverityAction(issueService, responseWriter); + WsActionTester tester = new WsActionTester(underTest); + + @Test + public void set_severity() throws Exception { + tester.newRequest() + .setParam("issue", "ABC") + .setParam("severity", "BLOCKER") + .execute(); + + verify(issueService).setSeverity("ABC", "BLOCKER"); + verify(responseWriter).write(eq("ABC"), any(Request.class), any(Response.class)); + } + + @Test + public void fail_if_bad_severity_value() { + expectedException.expect(IllegalArgumentException.class); + + tester.newRequest() + .setParam("issue", "ABC") + .setParam("severity", "WAT") + .execute(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java index 2e227df5e83..d88520ca286 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java @@ -51,7 +51,6 @@ import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentDto; import org.sonar.server.component.ComponentTesting; import org.sonar.server.debt.DebtModelService; -import org.sonar.server.issue.ActionService; import org.sonar.server.issue.IssueChangelog; import org.sonar.server.issue.IssueChangelogService; import org.sonar.server.issue.IssueCommentService; @@ -101,9 +100,6 @@ public class ShowActionTest { @Mock ActionPlanService actionPlanService; - @Mock - ActionService actionService; - @Mock UserFinder userFinder; @@ -150,9 +146,8 @@ public class ShowActionTest { tester = new WsTester(new IssuesWs( new ShowAction( dbClient, issueService, issueChangelogService, commentService, - new IssueActionsWriter(issueService, actionService, userSessionRule), - actionPlanService, userFinder, debtModel, ruleService, i18n, durations, userSessionRule) - )); + new IssueActionsWriter(issueService, userSessionRule), + actionPlanService, userFinder, debtModel, ruleService, i18n, durations, userSessionRule))); } @Test @@ -431,8 +426,7 @@ public class ShowActionTest { .setKey("COMMENT-ABCE") .setMarkdownText("Another comment") .setUserLogin("arthur") - .setCreatedAt(date2) - )); + .setCreatedAt(date2))); when(userFinder.findByLogin("john")).thenReturn(new DefaultUser().setLogin("john").setName("John")); when(userFinder.findByLogin("arthur")).thenReturn(new DefaultUser().setLogin("arthur").setName("Arthur")); diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb index 9d06746a051..deb21bae193 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb @@ -60,20 +60,6 @@ class Api::IssuesController < Api::ApiController ) end - # - # POST /api/issues/do_transition?issue=&transition=&comment= - # - # -- Example - # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/do_transition?issue=9b6f89c0-3347-46f6-a6d1-dd6c761240e0&transition=resolve' - # - def do_transition - verify_post_request - require_parameters :issue, :transition - - result = Internal.issues.doTransition(params[:issue], params[:transition]) - render_result_issue(result) - end - # # POST /api/issues/add_comment?issue=&text= # @@ -145,66 +131,6 @@ class Api::IssuesController < Api::ApiController end end - - # - # Change the severity of an existing issue - # - # POST /api/issues/set_severity?issue=&severity= - # - # -- Example - # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/set_severity?issue=4a2881e7-825e-4140-a154-01f420c43d11&severity=BLOCKER' - # - def set_severity - verify_post_request - require_parameters :issue, :severity - - result = Internal.issues.setSeverity(params[:issue], params[:severity]) - render_result_issue(result) - end - - # - # Link an existing issue to an action plan or unlink - # - # POST /api/issues/plan?issue=&plan= - # A nil or blank plan removes the action plan. - # - # -- Example - # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/plan?issue=4a2881e7-825e-4140-a154-01f420c43d11&plan=6b851f3c-e25c-432c-aee0-0e13a4184ca3' - # - def plan - verify_post_request - require_parameters :issue - - plan = nil - plan = params[:plan] if params[:plan] && !params[:plan].blank? - result = Internal.issues.plan(params[:issue], plan) - render_result_issue(result) - end - - # - # Create a manual issue. - # - # POST /api/issues/create - # - # -- Mandatory parameters - # 'component' is the component key - # 'rule' is the rule key prefixed with "manual:", for example 'manual:performance' - # - # -- Optional parameters - # 'severity' is in BLOCKER, CRITICAL, ... INFO. Default value is MAJOR. - # 'line' starts at 1 - # 'message' is the plain-text message - # - # -- Example - # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/create?component=commons-io:commons-io:org.apache.commons.io.filefilter.OrFileFilter&rule=manual:performance&line=2&severity=BLOCKER' - # - def create - verify_post_request - - issue_result = Internal.issues.create(params) - render_result_issue(issue_result) - end - # # GET /api/issues/actions?issue= # @@ -222,21 +148,6 @@ class Api::IssuesController < Api::ApiController ) end - - # - # POST /api/issues/do_action?issue=&actionKey= - # - # -- Example - # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/do_action?issue=9b6f89c0-3347-46f6-a6d1-dd6c761240e0&actionKey=link-to-jira' - # - def do_action - verify_post_request - require_parameters :issue, :actionKey - - result = Internal.issues.executeAction(params[:issue], params[:actionKey]) - render_result_issue(result) - end - # # Execute a bulk change on a list of issues # @@ -283,16 +194,6 @@ class Api::IssuesController < Api::ApiController protected - def render_result_issue(result) - hash = result_to_hash(result) - hash[:issue] = Issue.to_hash(result.get) if result.get - - respond_to do |format| - # if the request header "Accept" is "*/*", then the default format is the first one (json) - format.json { render :json => Internal.issues.writeIssueJson(result.get), :status => result.httpStatus } - format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'sonar', :status => (result.ok ? 200 : 400)) } - end - end def result_to_hash(result) hash = {}