--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.server.user.UserSession;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * @since 3.7
+ */
+public abstract class Action implements ServerComponent {
+
+ private final String key;
+ private final List<Condition> conditions;
+
+ public Action(String key) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Action key must be set");
+ this.key = key;
+ this.conditions = newArrayList();
+ }
+
+ public String key() {
+ return key;
+ }
+
+ public Action setConditions(Condition... conditions) {
+ this.conditions.addAll(ImmutableList.copyOf(conditions));
+ return this;
+ }
+
+ public List<Condition> conditions() {
+ return conditions;
+ }
+
+ public boolean supports(Issue issue) {
+ for (Condition condition : conditions) {
+ if (!condition.matches(issue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ abstract boolean verify(Map<String, Object> properties, UserSession userSession);
+
+ abstract boolean execute(Map<String, Object> properties, Context context);
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Action that = (Action) o;
+ if (!key.equals(that.key)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return key;
+ }
+
+ interface Context {
+ Issue issue();
+
+ IssueUpdater issueUpdater();
+
+ IssueChangeContext issueChangeContext();
+ }
+
+}
+
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.user.UserFinder;
+import org.sonar.server.user.UserSession;
+
+import java.util.Map;
+
+
+public class AssignAction extends Action implements ServerComponent {
+
+ public static final String ASSIGN_ACTION_KEY = "assign";
+
+ private final UserFinder userFinder;
+
+ public AssignAction(UserFinder userFinder) {
+ super(ASSIGN_ACTION_KEY);
+ this.userFinder = userFinder;
+ super.setConditions(new IsUnResolved());
+ }
+
+ @Override
+ public boolean verify(Map<String, Object> properties, UserSession userSession){
+ String assignee = assignee(properties);
+ if (assignee != null && userFinder.findByLogin(assignee) == null) {
+ throw new IllegalArgumentException("Unknown user: " + assignee);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean execute(Map<String, Object> properties, Context context) {
+ context.issueUpdater().assign((DefaultIssue) context.issue(), assignee(properties), context.issueChangeContext());
+ return true;
+ }
+
+ private String assignee(Map<String, Object> properties){
+ return (String) properties.get("assignee");
+ }
+}
\ No newline at end of file
public Result<List<Issue>> executeBulkChange(Map<String, Object> props) {
Result<List<Issue>> result = Result.of();
try {
- IssueBulkChangeQuery issueBulkChangeQuery = toIssueBulkChangeQuery(props);
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(props);
result.set(issueBulkChangeService.execute(issueBulkChangeQuery, UserSession.get()));
} catch (Exception e) {
result.addError(e.getMessage());
return result;
}
- private IssueBulkChangeQuery toIssueBulkChangeQuery(Map<String, Object> props) {
- return IssueBulkChangeQuery.builder()
- .issueKeys(RubyUtils.toStrings(props.get("issues")))
- .assignee((String) props.get("assignee"))
- .plan((String) props.get("plan"))
- .severity((String) props.get("severity"))
- .transition((String) props.get("transition"))
- .comment((String) props.get("comment"))
- .build();
- }
-
}
\ No newline at end of file
* 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;
-import com.google.common.base.Preconditions;
-import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+package org.sonar.server.issue;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.server.util.RubyUtils;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
-import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
/**
* @since 3.7
*/
public class IssueBulkChangeQuery {
- private static final String ASSIGNEE = "assign";
- private static final String PLAN = "plan";
- private static final String SEVERITY = "set_severity";
- private static final String TRANSITION = "do_transition";
- private static final String COMMENT = "comment";
-
- private final Collection<String> actions;
- private final Collection<String> issueKeys;
- private final String assignee;
- private final String plan;
- private final String severity;
- private final String transition;
- private final String comment;
-
- private IssueBulkChangeQuery(Builder builder) {
- this.actions = defaultCollection(builder.actions);
- this.issueKeys = defaultCollection(builder.issueKeys);
- this.assignee = builder.assignee;
- this.plan = builder.plan;
- this.severity = builder.severity;
- this.transition = builder.transition;
- this.comment = builder.comment;
- }
-
- public Collection<String> issueKeys() {
- return issueKeys;
- }
-
- @CheckForNull
- public String assignee() {
- return assignee;
- }
+ private List<String> issues;
+ private List<String> actions;
+ private String comment;
- @CheckForNull
- public String plan() {
- return plan;
- }
+ Map<String, Map<String, Object>> propertiesByActions = new HashMap<String, Map<String, Object>>();
- @CheckForNull
- public String severity() {
- return severity;
+ public IssueBulkChangeQuery(Map<String, Object> props) {
+ parse(props, null);
}
- @CheckForNull
- public String transition() {
- return transition;
+ public IssueBulkChangeQuery(Map<String, Object> props, String comment) {
+ parse(props, comment);
}
- @CheckForNull
- public String comment() {
- return comment;
- }
-
- public boolean isOnAssignee() {
- return actions.contains(ASSIGNEE);
- }
-
- public boolean isOnActionPlan() {
- return actions.contains(PLAN);
- }
-
- public boolean isOnSeverity() {
- return actions.contains(SEVERITY);
+ private void parse(Map<String, Object> props, String comment) {
+ this.comment = comment;
+ this.issues = RubyUtils.toStrings(props.get("issues"));
+ if (issues == null || issues.isEmpty()) {
+ throw new IllegalArgumentException("Issues must not be empty");
+ }
+ actions = RubyUtils.toStrings(props.get("actions"));
+ if (actions == null || actions.isEmpty()) {
+ throw new IllegalArgumentException("At least one action must be provided");
+ }
+ for (String action : actions) {
+ propertiesByActions.put(action, getActionProps(action, props));
+ }
}
- public boolean isOnTransition() {
- return actions.contains(TRANSITION);
+ public List<String> issues() {
+ return issues;
}
- public boolean isOnComment() {
- return actions.contains(COMMENT);
+ public List<String> actions() {
+ return actions;
}
- @Override
- public String toString() {
- return ReflectionToStringBuilder.toString(this);
+ public Map<String, Object> properties(String action) {
+ return propertiesByActions.get(action);
}
- public static Builder builder() {
- return new Builder();
+ public String getComment() {
+ return comment;
}
- public static class Builder {
-
- private Collection<String> actions = newArrayList();
- private Collection<String> issueKeys;
- private String assignee;
- private String plan;
- private String severity;
- private String transition;
- private String comment;
-
- private Builder() {
- }
-
- public Builder issueKeys(@Nullable Collection<String> l) {
- this.issueKeys = l;
- return this;
- }
-
- public Builder assignee(@Nullable String assignee) {
- actions.add(IssueBulkChangeQuery.ASSIGNEE);
- this.assignee = assignee;
- return this;
- }
-
- public Builder plan(@Nullable String plan) {
- actions.add(IssueBulkChangeQuery.PLAN);
- this.plan = plan;
- return this;
- }
-
- public Builder severity(@Nullable String severity) {
- actions.add(IssueBulkChangeQuery.SEVERITY);
- this.severity = severity;
- return this;
+ private static Map<String, Object> getActionProps(String action, Map<String, Object> props) {
+ Map<String, Object> actionProps = newHashMap();
+ for (Map.Entry<String, Object> propsEntry : props.entrySet()) {
+ String key = propsEntry.getKey();
+ String actionPrefix = action + ".";
+ String property = StringUtils.substringAfter(key, actionPrefix);
+ if (!property.isEmpty()) {
+ actionProps.put(property, propsEntry.getValue());
+ }
}
-
- public Builder transition(@Nullable String transition) {
- actions.add(IssueBulkChangeQuery.TRANSITION);
- this.transition = transition;
- return this;
- }
-
- public Builder comment(@Nullable String comment) {
- actions.add(IssueBulkChangeQuery.COMMENT);
- this.comment = comment;
- return this;
- }
-
- public IssueBulkChangeQuery build() {
- Preconditions.checkArgument(issueKeys != null && !issueKeys.isEmpty(), "Issues must not be empty");
- Preconditions.checkArgument(!actions.isEmpty(), "At least one action must be provided");
-
- return new IssueBulkChangeQuery(this);
- }
- }
-
- private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
- return c == null ? Collections.<T>emptyList() : Collections.unmodifiableCollection(c);
+ props.get(action);
+ return actionProps;
}
}
package org.sonar.server.issue;
-import com.google.common.base.Strings;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueQuery;
import org.sonar.api.issue.IssueQueryResult;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.user.UserFinder;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.IssueNotifications;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.server.user.UserSession;
+import javax.annotation.CheckForNull;
+
import java.util.Date;
import java.util.List;
public class IssueBulkChangeService {
+ private static final Logger LOG = LoggerFactory.getLogger(IssueBulkChangeService.class);
+
private final DefaultIssueFinder issueFinder;
- private final IssueWorkflow workflow;
private final IssueUpdater issueUpdater;
private final IssueStorage issueStorage;
private final IssueNotifications issueNotifications;
- private final ActionPlanService actionPlanService;
- private final UserFinder userFinder;
+ private final List<Action> actions;
- public IssueBulkChangeService(DefaultIssueFinder issueFinder, IssueWorkflow workflow, ActionPlanService actionPlanService, UserFinder userFinder,
- IssueUpdater issueUpdater, IssueStorage issueStorage, IssueNotifications issueNotifications) {
+ public IssueBulkChangeService(DefaultIssueFinder issueFinder, IssueUpdater issueUpdater, IssueStorage issueStorage, IssueNotifications issueNotifications, List<Action> actions) {
this.issueFinder = issueFinder;
- this.workflow = workflow;
this.issueUpdater = issueUpdater;
this.issueStorage = issueStorage;
this.issueNotifications = issueNotifications;
- this.actionPlanService = actionPlanService;
- this.userFinder = userFinder;
+ this.actions = actions;
}
public List<Issue> execute(IssueBulkChangeQuery issueBulkChangeQuery, UserSession userSession) {
List<Issue> issues = newArrayList();
verifyLoggedIn(userSession);
- IssueQueryResult issueQueryResult = issueFinder.find(IssueQuery.builder().issueKeys(issueBulkChangeQuery.issueKeys()).requiredRole(UserRole.USER).build());
-
- String assignee = issueBulkChangeQuery.assignee();
- if (assignee != null && userFinder.findByLogin(assignee) == null) {
- throw new IllegalArgumentException("Unknown user: " + assignee);
+ for (String actionName : issueBulkChangeQuery.actions()) {
+ Action action = getAction(actionName);
+ action.verify(issueBulkChangeQuery.properties(actionName), userSession);
}
- String actionPlanKey = issueBulkChangeQuery.plan();
- if (!Strings.isNullOrEmpty(actionPlanKey) && actionPlanService.findByKey(actionPlanKey, userSession) == null) {
- throw new IllegalArgumentException("Unknown action plan: " + actionPlanKey);
- }
- String severity = issueBulkChangeQuery.severity();
- String transition = issueBulkChangeQuery.transition();
- String comment = issueBulkChangeQuery.comment();
-
- IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login());
+ IssueQueryResult issueQueryResult = issueFinder.find(IssueQuery.builder().issueKeys(issueBulkChangeQuery.issues()).requiredRole(UserRole.USER).build());
+ IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(), userSession.login());
for (Issue issue : issueQueryResult.issues()) {
- DefaultIssue defaultIssue = (DefaultIssue) issue;
- try {
- if (issueBulkChangeQuery.isOnAssignee()) {
- issueUpdater.assign(defaultIssue, assignee, context);
- }
- if (issueBulkChangeQuery.isOnActionPlan()) {
- issueUpdater.plan(defaultIssue, actionPlanKey, context);
+ for (String actionName : issueBulkChangeQuery.actions()) {
+ try {
+ Action action = getAction(actionName);
+ ActionContext actionContext = new ActionContext(issue, issueUpdater, issueChangeContext);
+ if (action.supports(issue) && action.execute(issueBulkChangeQuery.properties(actionName), actionContext)) {
+ issueStorage.save((DefaultIssue) issue);
+ issueNotifications.sendChanges((DefaultIssue) issue, issueChangeContext, issueQueryResult);
+ issues.add(issue);
+ }
+ } catch (Exception e) {
+ // Do nothing, just go to the next issue
+ LOG.info("An error occur when trying to apply the action : "+ actionName + " on issue : "+ issue.key() + ". This issue has been ignored.", e);
}
- if (issueBulkChangeQuery.isOnSeverity()) {
- issueUpdater.setManualSeverity(defaultIssue, severity, context);
- }
- if (issueBulkChangeQuery.isOnTransition()) {
- workflow.doTransition(defaultIssue, transition, context);
- }
- if (issueBulkChangeQuery.isOnComment()) {
- issueUpdater.addComment(defaultIssue, comment, context);
- }
- issueStorage.save(defaultIssue);
- issueNotifications.sendChanges(defaultIssue, context, issueQueryResult);
- issues.add(defaultIssue);
- } catch (Exception e) {
- // Do nothing, just go to the next issue
}
}
return issues;
}
+ @CheckForNull
+ private Action getAction(final String actionKey) {
+ return Iterables.find(actions, new Predicate<Action>() {
+ @Override
+ public boolean apply(Action action) {
+ return action.key().equals(actionKey);
+ }
+ }, null);
+ }
+
private void verifyLoggedIn(UserSession userSession) {
if (!userSession.isLoggedIn()) {
// must be logged
throw new IllegalStateException("User is not logged in");
}
}
+
+ static class ActionContext implements Action.Context {
+ private final Issue issue;
+ private final IssueUpdater updater;
+ private final IssueChangeContext changeContext;
+
+ ActionContext(Issue issue, IssueUpdater updater, IssueChangeContext changeContext) {
+ this.updater = updater;
+ this.issue = issue;
+ this.changeContext = changeContext;
+ }
+
+ @Override
+ public Issue issue() {
+ return issue;
+ }
+
+ @Override
+ public IssueUpdater issueUpdater() {
+ return updater;
+ }
+
+ @Override
+ public IssueChangeContext issueChangeContext() {
+ return changeContext;
+ }
+ }
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+import com.google.common.base.Strings;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.server.user.UserSession;
+
+import java.util.Map;
+
+
+public class PlanAction extends Action implements ServerComponent {
+
+ public static final String PLAN_ACTION_KEY = "plan";
+
+ private final ActionPlanService actionPlanService;
+
+ public PlanAction(ActionPlanService actionPlanService) {
+ super(PLAN_ACTION_KEY);
+ this.actionPlanService = actionPlanService;
+ super.setConditions(new IsUnResolved());
+ }
+
+ @Override
+ public boolean verify(Map<String, Object> properties, UserSession userSession){
+ String actionPlanKey = planKey(properties);
+ if (!Strings.isNullOrEmpty(actionPlanKey) && actionPlanService.findByKey(actionPlanKey, userSession) == null) {
+ throw new IllegalArgumentException("Unknown action plan: " + actionPlanKey);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean execute(Map<String, Object> properties, Context context) {
+ context.issueUpdater().plan((DefaultIssue) context.issue(), planKey(properties), context.issueChangeContext());
+ return true;
+ }
+
+ private String planKey(Map<String, Object> properties){
+ return (String) properties.get("plan");
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.server.user.UserSession;
+
+import java.util.Map;
+
+
+public class SetSeverityAction extends Action implements ServerComponent {
+
+ public static final String SET_SEVERITY_ACTION_KEY = "set_severity";
+
+ public SetSeverityAction() {
+ super(SET_SEVERITY_ACTION_KEY);
+ super.setConditions(new IsUnResolved());
+ }
+
+ @Override
+ public boolean verify(Map<String, Object> properties, UserSession userSession) {
+ return true;
+ }
+
+ @Override
+ public boolean execute(Map<String, Object> properties, Context context) {
+ context.issueUpdater().setSeverity((DefaultIssue) context.issue(), severity(properties), context.issueChangeContext());
+ return true;
+ }
+
+ private String severity(Map<String, Object> properties) {
+ return (String) properties.get("severity");
+ }
+}
\ No newline at end of file
servicesContainer.addSingleton(IssueFilterSerializer.class);
servicesContainer.addSingleton(IssueFilterService.class);
servicesContainer.addSingleton(IssueBulkChangeService.class);
+ // issues actions
+ servicesContainer.addSingleton(AssignAction.class);
+ servicesContainer.addSingleton(PlanAction.class);
+ servicesContainer.addSingleton(SetSeverityAction.class);
// rules
servicesContainer.addSingleton(RubyRuleService.class);
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.user.UserFinder;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.user.DefaultUser;
+import org.sonar.server.user.UserSession;
+
+import java.util.Map;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class AssignActionTest {
+
+ private AssignAction action;
+
+ private final UserFinder userFinder = mock(UserFinder.class);
+
+ @Before
+ public void before(){
+ action = new AssignAction(userFinder);
+ }
+
+ @Test
+ public void should_execute(){
+ String assignee = "arthur";
+
+ Map<String, Object> properties = newHashMap();
+ properties.put("assignee", assignee);
+ DefaultIssue issue = mock(DefaultIssue.class);
+ IssueUpdater issueUpdater = mock(IssueUpdater.class);
+
+ Action.Context context = mock(Action.Context.class);
+ when(context.issueUpdater()).thenReturn(issueUpdater);
+ when(context.issue()).thenReturn(issue);
+
+ action.execute(properties, context);
+ verify(issueUpdater).assign(eq(issue), eq(assignee), any(IssueChangeContext.class));
+ }
+
+ @Test
+ public void should_verify_assignee_exists(){
+ String assignee = "arthur";
+ Map<String, Object> properties = newHashMap();
+ properties.put("assignee", assignee);
+
+ when(userFinder.findByLogin(assignee)).thenReturn(new DefaultUser());
+ assertThat(action.verify(properties, mock(UserSession.class))).isTrue();
+
+ when(userFinder.findByLogin(assignee)).thenReturn(null);
+ try {
+ action.verify(properties, mock(UserSession.class));
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown user: arthur");
+ }
+ }
+
+ @Test
+ public void should_support_only_unresolved_issues(){
+ assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isTrue();
+ assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_CLOSED))).isTrue();
+ }
+}
\ No newline at end of file
public void should_execute_bulk_change() {
Map<String, Object> params = newHashMap();
params.put("issues", newArrayList("ABCD", "EFGH"));
- params.put("assignee", "arthur");
+ params.put("actions", newArrayList("do_transition", "assign", "set_severity", "plan"));
+ params.put("do_transition.transition", "confirm");
+ params.put("assign.assignee", "arthur");
+ params.put("set_severity.severity", "MINOR");
+ params.put("plan.plan", "3.7");
service.executeBulkChange(params);
verify(issueBulkChangeService).execute(any(IssueBulkChangeQuery.class), any(UserSession.class));
}
Map<String, Object> params = newHashMap();
params.put("issues", newArrayList("ABCD", "EFGH"));
- params.put("assignee", "arthur");
+ params.put("actions", newArrayList("assign"));
+ params.put("assign.assignee", "arthur");
Result result = service.executeBulkChange(params);
assertThat(result.ok()).isFalse();
assertThat(((Result.Message) result.errors().get(0)).text()).contains("Error");
import org.junit.Test;
import java.util.Collections;
+import java.util.Map;
import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
import static org.fest.assertions.Assertions.assertThat;
public class IssueBulkChangeQueryTest {
@Test
- public void test_build(){
- IssueBulkChangeQuery issueBulkChangeQuery = IssueBulkChangeQuery.builder().issueKeys(newArrayList("ABCD", "EFGH")).assignee("perceval").build();
- assertThat(issueBulkChangeQuery.issueKeys()).isNotNull();
- assertThat(issueBulkChangeQuery.assignee()).isNotNull();
+ public void should_create_query(){
+ Map<String, Object> params = newHashMap();
+ params.put("issues", newArrayList("ABCD", "EFGH"));
+ params.put("actions", newArrayList("do_transition", "assign", "set_severity", "plan"));
+ params.put("do_transition.transition", "confirm");
+ params.put("assign.assignee", "arthur");
+ params.put("set_severity.severity", "MINOR");
+ params.put("plan.plan", "3.7");
+
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(params);
+ assertThat(issueBulkChangeQuery.actions()).containsOnly("do_transition", "assign", "set_severity", "plan");
+ assertThat(issueBulkChangeQuery.issues()).containsOnly("ABCD", "EFGH");
+ }
+
+ @Test
+ public void should_get_properties_action(){
+ Map<String, Object> params = newHashMap();
+ params.put("issues", newArrayList("ABCD", "EFGH"));
+ params.put("actions", newArrayList("assign"));
+ params.put("assign.assignee", "arthur");
+
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(params);
+ assertThat(issueBulkChangeQuery.properties("assign")).hasSize(1);
+ assertThat(issueBulkChangeQuery.properties("assign").get("assignee")).isEqualTo("arthur");
}
@Test(expected = IllegalArgumentException.class)
public void fail_to_build_if_no_issue(){
- IssueBulkChangeQuery.builder().assignee("perceval").build();
+ Map<String, Object> params = newHashMap();
+ params.put("actions", newArrayList("do_transition", "assign", "set_severity", "plan"));
+ new IssueBulkChangeQuery(params);
}
@Test(expected = IllegalArgumentException.class)
public void fail_to_build_if_issues_are_empty(){
- IssueBulkChangeQuery.builder().issueKeys(Collections.<String>emptyList()).assignee("perceval").build();
+ Map<String, Object> params = newHashMap();
+ params.put("issues", Collections.emptyList());
+ params.put("actions", newArrayList("do_transition", "assign", "set_severity", "plan"));
+ new IssueBulkChangeQuery(params);
}
@Test(expected = IllegalArgumentException.class)
public void fail_to_build_if_no_action(){
- IssueBulkChangeQuery.builder().issueKeys(newArrayList("ABCD", "EFGH")).build();
+ Map<String, Object> params = newHashMap();
+ params.put("issues", Collections.emptyList());
+ new IssueBulkChangeQuery(params);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void fail_to_build_if_actions_are_empty(){
+ Map<String, Object> params = newHashMap();
+ params.put("issues", newArrayList("ABCD", "EFGH"));
+ params.put("actions", Collections.emptyList());
+ new IssueBulkChangeQuery(params);
}
}
import org.sonar.api.issue.IssueQueryResult;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.user.UserFinder;
-import org.sonar.core.issue.DefaultActionPlan;
import org.sonar.core.issue.IssueNotifications;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.core.user.DefaultUser;
import org.sonar.server.user.UserSession;
import java.util.List;
+import java.util.Map;
import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
public class IssueBulkChangeServiceTest {
private DefaultIssueFinder finder = mock(DefaultIssueFinder.class);
- private IssueWorkflow workflow = mock(IssueWorkflow.class);
private IssueUpdater issueUpdater = mock(IssueUpdater.class);
private IssueStorage issueStorage = mock(IssueStorage.class);
private IssueNotifications issueNotifications = mock(IssueNotifications.class);
- private ActionPlanService actionPlanService = mock(ActionPlanService.class);
- private UserFinder userFinder = mock(UserFinder.class);
private IssueQueryResult issueQueryResult = mock(IssueQueryResult.class);
private UserSession userSession = mock(UserSession.class);
private IssueBulkChangeService service;
+ private Action action = mock(Action.class);
+ private List<Action> actions;
+
@Before
- public void before(){
+ public void before() {
when(userSession.isLoggedIn()).thenReturn(true);
when(userSession.userId()).thenReturn(10);
when(userSession.login()).thenReturn("fred");
when(finder.find(any(IssueQuery.class))).thenReturn(issueQueryResult);
when(issueQueryResult.issues()).thenReturn(newArrayList((Issue) issue));
- service = new IssueBulkChangeService(finder, workflow, actionPlanService, userFinder, issueUpdater, issueStorage, issueNotifications);
- }
-
- @Test
- public void should_do_bulk_assign(){
- String assignee = "perceval";
- when(userFinder.findByLogin(assignee)).thenReturn(new DefaultUser());
+ when(action.key()).thenReturn("assign");
- IssueBulkChangeQuery issueBulkChangeQuery = IssueBulkChangeQuery.builder().issueKeys(newArrayList(issue.key())).assignee(assignee).build();
- List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).hasSize(1);
+ actions = newArrayList();
+ actions.add(action);
- verify(issueUpdater).assign(eq(issue), eq(assignee), any(IssueChangeContext.class));
- verifyNoMoreInteractions(issueUpdater);
- verify(issueStorage).save(eq(issue));
- verifyNoMoreInteractions(issueStorage);
- verify(issueNotifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(issueQueryResult));
- verifyNoMoreInteractions(issueNotifications);
+ service = new IssueBulkChangeService(finder, issueUpdater, issueStorage, issueNotifications, actions);
}
@Test
- public void should_do_bulk_plan(){
- String actionPlanKey = "EFGH";
- when(actionPlanService.findByKey(actionPlanKey, userSession)).thenReturn(new DefaultActionPlan());
+ public void should_execute_bulk_change() {
+ Map<String, Object> properties = newHashMap();
+ properties.put("issues", "ABCD");
+ properties.put("actions", "assign");
- IssueBulkChangeQuery issueBulkChangeQuery = IssueBulkChangeQuery.builder().issueKeys(newArrayList(issue.key())).plan(actionPlanKey).build();
+ when(action.supports(any(Issue.class))).thenReturn(true);
+ when(action.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true);
+ when(action.execute(eq(properties), any(IssueBulkChangeService.ActionContext.class))).thenReturn(true);
+
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
assertThat(result).hasSize(1);
- verify(issueUpdater).plan(eq(issue), eq(actionPlanKey), any(IssueChangeContext.class));
verifyNoMoreInteractions(issueUpdater);
verify(issueStorage).save(eq(issue));
verifyNoMoreInteractions(issueStorage);
}
@Test
- public void should_do_bulk_change_severity(){
- String severity = "MINOR";
+ public void should_not_execute_bulk_if_issue_does_not_support_action() {
+ Map<String, Object> properties = newHashMap();
+ properties.put("issues", "ABCD");
+ properties.put("actions", "assign");
+
+ when(action.supports(any(Issue.class))).thenReturn(false);
- IssueBulkChangeQuery issueBulkChangeQuery = IssueBulkChangeQuery.builder().issueKeys(newArrayList(issue.key())).severity(severity).build();
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).hasSize(1);
+ assertThat(result).isEmpty();
- verify(issueUpdater).setManualSeverity(eq(issue), eq(severity), any(IssueChangeContext.class));
- verifyNoMoreInteractions(issueUpdater);
- verify(issueStorage).save(eq(issue));
- verifyNoMoreInteractions(issueStorage);
- verify(issueNotifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(issueQueryResult));
- verifyNoMoreInteractions(issueNotifications);
+ verifyZeroInteractions(issueUpdater);
+ verifyZeroInteractions(issueStorage);
+ verifyZeroInteractions(issueNotifications);
}
@Test
- public void should_do_bulk_transition(){
- String transition = "reopen";
+ public void should_not_execute_bulk_if_action_could_not_be_executed_on_issue() {
+ Map<String, Object> properties = newHashMap();
+ properties.put("issues", "ABCD");
+ properties.put("actions", "assign");
- when(workflow.doTransition(eq(issue), eq(transition), any(IssueChangeContext.class))).thenReturn(true);
+ when(action.supports(any(Issue.class))).thenReturn(true);
+ when(action.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(false);
- IssueBulkChangeQuery issueBulkChangeQuery = IssueBulkChangeQuery.builder().issueKeys(newArrayList(issue.key())).transition(transition).build();
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).hasSize(1);
+ assertThat(result).isEmpty();
- verify(workflow).doTransition(eq(issue), eq(transition), any(IssueChangeContext.class));
- verifyNoMoreInteractions(issueUpdater);
- verify(issueStorage).save(eq(issue));
- verifyNoMoreInteractions(issueStorage);
- verify(issueNotifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(issueQueryResult));
- verifyNoMoreInteractions(issueNotifications);
+ verifyZeroInteractions(issueUpdater);
+ verifyZeroInteractions(issueStorage);
+ verifyZeroInteractions(issueNotifications);
}
@Test
- public void should_do_bulk_comment(){
- String comment = "Bulk change comment";
+ public void should_not_execute_bulk_on_unexpected_error() {
+ Map<String, Object> properties = newHashMap();
+ properties.put("issues", "ABCD");
+ properties.put("actions", "assign");
+
+ when(action.supports(any(Issue.class))).thenReturn(true);
+ doThrow(new RuntimeException("Error")).when(action).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class));
- IssueBulkChangeQuery issueBulkChangeQuery = IssueBulkChangeQuery.builder().issueKeys(newArrayList(issue.key())).comment(comment).build();
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).hasSize(1);
+ assertThat(result).isEmpty();
- verify(issueUpdater).addComment(eq(issue), eq(comment), any(IssueChangeContext.class));
- verifyNoMoreInteractions(issueUpdater);
- verify(issueStorage).save(eq(issue));
- verifyNoMoreInteractions(issueStorage);
- verify(issueNotifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(issueQueryResult));
- verifyNoMoreInteractions(issueNotifications);
+ verifyZeroInteractions(issueUpdater);
+ verifyZeroInteractions(issueStorage);
+ verifyZeroInteractions(issueNotifications);
}
@Test
- public void should_ignore_an_issue_if_an_action_fail(){
- when(issueQueryResult.issues()).thenReturn(newArrayList((Issue) issue, new DefaultIssue().setKey("EFGH")));
-
- // Bulk change with 2 actions : severity and comment
- IssueBulkChangeQuery issueBulkChangeQuery = IssueBulkChangeQuery.builder().issueKeys(newArrayList("ABCD", "EFGH")).severity("MAJOR").comment("Bulk change comment").build();
-
- // The first call the change severity is ok, the second will fail
- when(issueUpdater.setManualSeverity(any(DefaultIssue.class), eq("MAJOR"),any(IssueChangeContext.class))).thenReturn(true).thenThrow(new RuntimeException("Cant change severity"));
-
- List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).hasSize(1);
- assertThat(result.get(0).key()).isEqualTo("ABCD");
-
- verify(issueStorage).save(eq(issue));
- verifyNoMoreInteractions(issueStorage);
- verify(issueNotifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(issueQueryResult));
- verifyNoMoreInteractions(issueNotifications);
+ public void should_fail_if_user_not_loggued() {
+ when(userSession.isLoggedIn()).thenReturn(false);
+
+ Map<String, Object> properties = newHashMap();
+ properties.put("issues", "ABCD");
+ properties.put("actions", "assign");
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
+ try {
+ service.execute(issueBulkChangeQuery, userSession);
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not logged in");
+ }
+ verifyZeroInteractions(issueUpdater);
+ verifyZeroInteractions(issueUpdater);
+ verifyZeroInteractions(issueStorage);
+ verifyZeroInteractions(issueNotifications);
}
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.core.issue.DefaultActionPlan;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.server.user.UserSession;
+
+import java.util.Map;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class PlanActionTest {
+
+ private PlanAction action;
+
+ private ActionPlanService actionPlanService = mock(ActionPlanService.class);
+
+ @Before
+ public void before(){
+ action = new PlanAction(actionPlanService);
+ }
+
+ @Test
+ public void should_execute(){
+ String planKey = "ABCD";
+ Map<String, Object> properties = newHashMap();
+ properties.put("plan", planKey);
+ DefaultIssue issue = mock(DefaultIssue.class);
+ IssueUpdater issueUpdater = mock(IssueUpdater.class);
+
+ Action.Context context = mock(Action.Context.class);
+ when(context.issueUpdater()).thenReturn(issueUpdater);
+ when(context.issue()).thenReturn(issue);
+
+ action.execute(properties, context);
+ verify(issueUpdater).plan(eq(issue), eq(planKey), any(IssueChangeContext.class));
+ }
+
+ @Test
+ public void should_verify_action_plan_exists(){
+ String planKey = "ABCD";
+ Map<String, Object> properties = newHashMap();
+ properties.put("plan", planKey);
+
+ when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(new DefaultActionPlan());
+ assertThat(action.verify(properties, mock(UserSession.class))).isTrue();
+
+ when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(null);
+ try {
+ action.verify(properties, mock(UserSession.class));
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown action plan: ABCD");
+ }
+ }
+
+ @Test
+ public void should_support_only_unresolved_issues(){
+ assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isTrue();
+ assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_CLOSED))).isTrue();
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+import java.util.Map;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class SetSeverityActionTest {
+
+ private SetSeverityAction action;
+
+ @Before
+ public void before(){
+ action = new SetSeverityAction();
+ }
+
+ @Test
+ public void should_execute(){
+ String severity = "MINOR";
+ Map<String, Object> properties = newHashMap();
+ properties.put("severity", severity);
+ DefaultIssue issue = mock(DefaultIssue.class);
+ IssueUpdater issueUpdater = mock(IssueUpdater.class);
+
+ Action.Context context = mock(Action.Context.class);
+ when(context.issueUpdater()).thenReturn(issueUpdater);
+ when(context.issue()).thenReturn(issue);
+
+ action.execute(properties, context);
+ verify(issueUpdater).setSeverity(eq(issue), eq(severity), any(IssueChangeContext.class));
+ }
+
+ @Test
+ public void should_support_only_unresolved_issues(){
+ assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isTrue();
+ assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_CLOSED))).isTrue();
+ }
+
+}