}
}
+ @CheckForNull
public Component findByKey(String key) {
ResourceDto resourceDto = getResource(ResourceQuery.create().setKey(key));
return resourceDto != null ? toComponent(resourceDto) : null;
return true;
}
- abstract boolean verify(Map<String, Object> properties, UserSession userSession);
+ abstract boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession);
abstract boolean execute(Map<String, Object> properties, Context context);
package org.sonar.server.issue;
import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
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.List;
import java.util.Map;
}
@Override
- public boolean verify(Map<String, Object> properties, UserSession userSession){
+ public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession){
String assignee = assignee(properties);
if (assignee != null && userFinder.findByLogin(assignee) == null) {
throw new IllegalArgumentException("Unknown user: " + assignee);
--- /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.Issue;
+import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.server.user.UserSession;
+
+import java.util.List;
+import java.util.Map;
+
+
+public class CommentAction extends Action implements ServerComponent {
+
+ public static final String COMMENT_ACTION_KEY = "comment";
+
+ public CommentAction() {
+ super(COMMENT_ACTION_KEY);
+ super.setConditions(new IsUnResolved());
+ }
+
+ @Override
+ public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
+ return true;
+ }
+
+ @Override
+ public boolean execute(Map<String, Object> properties, Context context) {
+ context.issueUpdater().addComment((DefaultIssue) context.issue(), comment(properties), context.issueChangeContext());
+ return true;
+ }
+
+ private String comment(Map<String, Object> properties) {
+ return (String) properties.get("comment");
+ }
+}
\ No newline at end of file
.setMaxResultsReached(authorizedIssues.size() == query.maxResults())
.addRules(findRules(ruleIds))
.addComponents(findComponents(componentIds))
- .addProjects(findProjects(projectIds))
+ .addProjects(findComponents(projectIds))
.addActionPlans(findActionPlans(actionPlanKeys))
.addUsers(findUsers(users))
.setPaging(paging);
return resourceDao.findByIds(componentIds);
}
- private Collection<Component> findProjects(Set<Long> projectIds) {
- return resourceDao.findByIds(projectIds);
- }
-
private Collection<ActionPlan> findActionPlans(Set<String> actionPlanKeys) {
return actionPlanService.findByKeys(actionPlanKeys);
}
/**
* Execute a bulk change
*/
- public Result<List<Issue>> executeBulkChange(Map<String, Object> props) {
- Result<List<Issue>> result = Result.of();
+ public Result<IssueBulkChangeResult> executeBulkChange(Map<String, Object> props) {
+ Result<IssueBulkChangeResult> result = Result.of();
try {
IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(props);
result.set(issueBulkChangeService.execute(issueBulkChangeQuery, UserSession.get()));
--- /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.issue.Issue;
+
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class IssueBulkChangeResult {
+
+ List<Issue> issuesChanged = newArrayList();
+ List<Issue> issuesNotChanged = newArrayList();
+
+ public void addIssueChanged(Issue issue){
+ this.issuesChanged.add(issue);
+ }
+
+ public void addIssueNotChanged(Issue issue){
+ this.issuesNotChanged.add(issue);
+ }
+
+ public List<Issue> issuesChanged() {
+ return issuesChanged;
+ }
+
+ public List<Issue> issuesInError() {
+ return issuesNotChanged;
+ }
+}
import java.util.Date;
import java.util.List;
-import static com.google.common.collect.Lists.newArrayList;
-
public class IssueBulkChangeService {
private static final Logger LOG = LoggerFactory.getLogger(IssueBulkChangeService.class);
this.actions = actions;
}
- public List<Issue> execute(IssueBulkChangeQuery issueBulkChangeQuery, UserSession userSession) {
- List<Issue> issues = newArrayList();
+ public IssueBulkChangeResult execute(IssueBulkChangeQuery issueBulkChangeQuery, UserSession userSession) {
verifyLoggedIn(userSession);
+ IssueBulkChangeResult result = new IssueBulkChangeResult();
+ IssueQueryResult issueQueryResult = issueFinder.find(IssueQuery.builder().issueKeys(issueBulkChangeQuery.issues()).requiredRole(UserRole.USER).build());
+ List<Issue> issues = issueQueryResult.issues();
for (String actionName : issueBulkChangeQuery.actions()) {
Action action = getAction(actionName);
- action.verify(issueBulkChangeQuery.properties(actionName), userSession);
+ if (action == null) {
+ throw new IllegalArgumentException("The action : '"+ actionName + "' is unknown");
+ }
+ action.verify(issueBulkChangeQuery.properties(actionName), issues, userSession);
}
- 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()) {
+ for (Issue issue : issues) {
for (String actionName : issueBulkChangeQuery.actions()) {
try {
Action action = getAction(actionName);
if (action.supports(issue) && action.execute(issueBulkChangeQuery.properties(actionName), actionContext)) {
issueStorage.save((DefaultIssue) issue);
issueNotifications.sendChanges((DefaultIssue) issue, issueChangeContext, issueQueryResult);
- issues.add(issue);
+ result.addIssueChanged(issue);
+ } else {
+ result.addIssueNotChanged(issue);
}
} catch (Exception e) {
- // Do nothing, just go to the next issue
+ result.addIssueNotChanged(issue);
LOG.info("An error occur when trying to apply the action : "+ actionName + " on issue : "+ issue.key() + ". This issue has been ignored.", e);
}
}
}
- return issues;
+ return result;
}
@CheckForNull
import com.google.common.base.Strings;
import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.ActionPlan;
+import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.IsUnResolved;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.server.user.UserSession;
+import java.util.List;
import java.util.Map;
}
@Override
- public boolean verify(Map<String, Object> properties, UserSession userSession){
+ public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
String actionPlanKey = planKey(properties);
- if (!Strings.isNullOrEmpty(actionPlanKey) && actionPlanService.findByKey(actionPlanKey, userSession) == null) {
+ if (!Strings.isNullOrEmpty(actionPlanKey)) {
+ ActionPlan actionPlan = actionPlanService.findByKey(actionPlanKey, userSession);
+ if (actionPlan == null) {
+ throw new IllegalArgumentException("Unknown action plan: " + actionPlanKey);
+ }
+ verifyIssuesAreAllRelatedOnActionPlanProject(issues, actionPlan);
+ } else {
throw new IllegalArgumentException("Unknown action plan: " + actionPlanKey);
}
return true;
return true;
}
- private String planKey(Map<String, Object> properties){
+ private String planKey(Map<String, Object> properties) {
return (String) properties.get("plan");
}
+ private void verifyIssuesAreAllRelatedOnActionPlanProject(List<Issue> issues, ActionPlan actionPlan) {
+ String projectKey = actionPlan.projectKey();
+ for (Issue issue : issues) {
+ DefaultIssue defaultIssue = (DefaultIssue) issue;
+ if (!defaultIssue.projectKey().equals(projectKey)) {
+ throw new IllegalArgumentException("Issues are not all related to the action plan project: " + projectKey);
+ }
+ }
+ }
+
}
\ No newline at end of file
package org.sonar.server.issue;
import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.IsUnResolved;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.server.user.UserSession;
+import java.util.List;
import java.util.Map;
}
@Override
- public boolean verify(Map<String, Object> properties, UserSession userSession) {
+ public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
return true;
}
--- /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.Issue;
+import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.server.user.UserSession;
+
+import java.util.List;
+import java.util.Map;
+
+
+public class TransitionAction extends Action implements ServerComponent {
+
+ public static final String TRANSITION_ACTION_KEY = "transition";
+
+ public TransitionAction() {
+ super(TRANSITION_ACTION_KEY);
+ super.setConditions(new IsUnResolved());
+ }
+
+ @Override
+ public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession) {
+ return true;
+ }
+
+ @Override
+ public boolean execute(Map<String, Object> properties, Context context) {
+ context.issueUpdater().addComment((DefaultIssue) context.issue(), transition(properties), context.issueChangeContext());
+ return true;
+ }
+
+ private String transition(Map<String, Object> properties) {
+ return (String) properties.get("transition");
+ }
+}
\ No newline at end of file
servicesContainer.addSingleton(AssignAction.class);
servicesContainer.addSingleton(PlanAction.class);
servicesContainer.addSingleton(SetSeverityAction.class);
+ servicesContainer.addSingleton(CommentAction.class);
// rules
servicesContainer.addSingleton(RubyRuleService.class);
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.mockito.Matchers.any;
Map<String, Object> properties = newHashMap();
properties.put("assignee", assignee);
+ List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC"));
when(userFinder.findByLogin(assignee)).thenReturn(new DefaultUser());
- assertThat(action.verify(properties, mock(UserSession.class))).isTrue();
+ assertThat(action.verify(properties, issues, mock(UserSession.class))).isTrue();
+ }
+
+ @Test
+ public void should_fail_if_assignee_does_not_exists(){
+ String assignee = "arthur";
+ Map<String, Object> properties = newHashMap();
+ properties.put("assignee", assignee);
+ List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC"));
when(userFinder.findByLogin(assignee)).thenReturn(null);
try {
- action.verify(properties, mock(UserSession.class));
+ action.verify(properties, issues, mock(UserSession.class));
} catch (Exception e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown user: arthur");
}
--- /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 CommentActionTest {
+
+ private CommentAction action;
+
+ @Before
+ public void before(){
+ action = new CommentAction();
+ }
+
+ @Test
+ public void should_execute(){
+ String comment = "My bulk change comment";
+ Map<String, Object> properties = newHashMap();
+ properties.put("comment", comment);
+ 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).addComment(eq(issue), eq(comment), 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();
+ }
+
+}
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);
+ IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+ assertThat(result.issuesChanged).hasSize(1);
+ assertThat(result.issuesNotChanged).isEmpty();
verifyNoMoreInteractions(issueUpdater);
verify(issueStorage).save(eq(issue));
when(action.supports(any(Issue.class))).thenReturn(false);
IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
- List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).isEmpty();
+ IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+ assertThat(result.issuesChanged).isEmpty();
+ assertThat(result.issuesNotChanged).hasSize(1);
verifyZeroInteractions(issueUpdater);
verifyZeroInteractions(issueStorage);
when(action.execute(anyMap(), any(IssueBulkChangeService.ActionContext.class))).thenReturn(false);
IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
- List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).isEmpty();
+ IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+ assertThat(result.issuesChanged).isEmpty();
+ assertThat(result.issuesNotChanged).hasSize(1);
verifyZeroInteractions(issueUpdater);
verifyZeroInteractions(issueStorage);
doThrow(new RuntimeException("Error")).when(action).execute(anyMap(), any(IssueBulkChangeService.ActionContext.class));
IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
- List<Issue> result = service.execute(issueBulkChangeQuery, userSession);
- assertThat(result).isEmpty();
+ IssueBulkChangeResult result = service.execute(issueBulkChangeQuery, userSession);
+ assertThat(result.issuesChanged).isEmpty();
+ assertThat(result.issuesNotChanged).hasSize(1);
verifyZeroInteractions(issueUpdater);
verifyZeroInteractions(issueStorage);
}
@Test
- public void should_fail_if_user_not_loggued() {
+ public void should_fail_if_user_not_logged() {
when(userSession.isLoggedIn()).thenReturn(false);
Map<String, Object> properties = newHashMap();
assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not logged in");
}
verifyZeroInteractions(issueUpdater);
+ verifyZeroInteractions(issueStorage);
+ verifyZeroInteractions(issueNotifications);
+ }
+
+ @Test
+ public void should_fail_if_action_not_found() {
+ Map<String, Object> properties = newHashMap();
+ properties.put("issues", "ABCD");
+ properties.put("actions", "unknown");
+ IssueBulkChangeQuery issueBulkChangeQuery = new IssueBulkChangeQuery(properties);
+ try {
+ service.execute(issueBulkChangeQuery, userSession);
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("The action : 'unknown' is unknown");
+ }
verifyZeroInteractions(issueUpdater);
verifyZeroInteractions(issueStorage);
verifyZeroInteractions(issueNotifications);
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;
import static com.google.common.collect.Maps.newHashMap;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.any;
}
@Test
- public void should_verify_action_plan_exists(){
+ public void should_verify(){
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();
+ List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC").setProjectKey("struts"));
+ when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(new DefaultActionPlan().setProjectKey("struts"));
+ assertThat(action.verify(properties, issues, mock(UserSession.class))).isTrue();
+ }
+
+ @Test
+ public void should_fail_if_action_plan_does_not_exists(){
+ String planKey = "ABCD";
+ Map<String, Object> properties = newHashMap();
+ properties.put("plan", planKey);
+ List<Issue> issues = newArrayList((Issue) new DefaultIssue().setKey("ABC").setProjectKey("struts"));
when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(null);
try {
- action.verify(properties, mock(UserSession.class));
+ action.verify(properties, issues, mock(UserSession.class));
} catch (Exception e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown action plan: ABCD");
}
}
+ @Test
+ public void should_verify_issues_are_on_the_same_project(){
+ String planKey = "ABCD";
+ Map<String, Object> properties = newHashMap();
+ properties.put("plan", planKey);
+
+ when(actionPlanService.findByKey(eq(planKey), any(UserSession.class))).thenReturn(new DefaultActionPlan().setProjectKey("struts"));
+ List<Issue> issues = newArrayList(new DefaultIssue().setKey("ABC").setProjectKey("struts"), (Issue) new DefaultIssue().setKey("ABE").setProjectKey("mybatis"));
+ try {
+ action.verify(properties, issues, mock(UserSession.class));
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Issues are not all related to the action plan project: struts");
+ }
+ }
+
@Test
public void should_support_only_unresolved_issues(){
assertThat(action.supports(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isTrue();