]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5901 Start add_tags bulk action on issues
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Mon, 15 Dec 2014 16:20:06 +0000 (17:20 +0100)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Tue, 16 Dec 2014 08:29:21 +0000 (09:29 +0100)
server/sonar-server/src/main/java/org/sonar/server/issue/AddTagsAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/_bulk_change_form.html.erb
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AddTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AddTagsAction.java
new file mode 100644 (file)
index 0000000..d52568f
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+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.server.rule.RuleTagFormat;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.server.user.UserSession;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+public class AddTagsAction extends Action implements ServerComponent {
+
+  public static final String KEY = "add_tags";
+
+  private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+
+  private final IssueUpdater issueUpdater;
+
+  public AddTagsAction(IssueUpdater issueUpdater) {
+    super(KEY);
+    this.issueUpdater = issueUpdater;
+    super.setConditions(new IsUnResolved());
+  }
+
+  @Override
+  public boolean verify(Map<String, Object> properties, List<Issue> issues, UserSession userSession){
+    parseTags(properties);
+    return true;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public boolean execute(Map<String, Object> properties, Context context) {
+    Collection<String> tags = CollectionUtils.union(context.issue().tags(), parseTags(properties));
+    return issueUpdater.setTags((DefaultIssue) context.issue(), tags, context.issueChangeContext());
+  }
+
+  private Set<String> parseTags(Map<String, Object> properties) {
+    Set<String> result = Sets.newHashSet();
+    String tagsString = (String) properties.get("add_tags.tags");
+    if (!Strings.isNullOrEmpty(tagsString)) {
+      for(String tag: TAGS_SPLITTER.split(tagsString)) {
+        RuleTagFormat.validate(tag);
+        result.add(tag);
+      }
+    }
+    return result;
+  }
+}
index 73eecdec7827a190fa35d3fb109e9107ac80c228..589a54a62322a1ecb229a86d756a3f32e42644ef 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.server.platform;
 
+import org.sonar.server.issue.AddTagsAction;
+
 import com.google.common.collect.Lists;
 import org.sonar.api.config.EmailSettings;
 import org.sonar.api.issue.action.Actions;
@@ -537,6 +539,7 @@ class ServerComponents {
     pico.addSingleton(SetSeverityAction.class);
     pico.addSingleton(CommentAction.class);
     pico.addSingleton(TransitionAction.class);
+    pico.addSingleton(AddTagsAction.class);
 
     // technical debt
     pico.addSingleton(DebtModelService.class);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java
new file mode 100644 (file)
index 0000000..ac07dc6
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import org.mockito.Matchers;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+import java.util.Collection;
+import java.util.Map;
+
+import static com.google.common.collect.Maps.newHashMap;
+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 AddTagsActionTest {
+
+  private AddTagsAction action;
+
+  private IssueUpdater issueUpdater = mock(IssueUpdater.class);
+
+  @Rule
+  public ExpectedException throwable = ExpectedException.none();
+
+  @Before
+  public void before() {
+    action = new AddTagsAction(issueUpdater);
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void should_execute() {
+    Map<String, Object> properties = newHashMap();
+    properties.put("add_tags.tags", "tag2,tag3");
+
+    DefaultIssue issue = mock(DefaultIssue.class);
+    when(issue.tags()).thenReturn(ImmutableSet.of("tag1", "tag3"));
+
+    Action.Context context = mock(Action.Context.class);
+    when(context.issue()).thenReturn(issue);
+
+    action.execute(properties, context);
+    verify(issueUpdater).setTags(eq(issue),
+      (Collection<String>) Matchers.argThat(org.hamcrest.Matchers.containsInAnyOrder("tag1", "tag2", "tag3")),
+      any(IssueChangeContext.class));
+  }
+
+  @Test
+  public void should_fail_if_tag_is_not_valid() throws Exception {
+    throwable.expect(IllegalArgumentException.class);
+    throwable.expectMessage("Tag 'th ag' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'");
+
+    Map<String, Object> properties = newHashMap();
+    properties.put("add_tags.tags", "th ag");
+
+    DefaultIssue issue = mock(DefaultIssue.class);
+    when(issue.tags()).thenReturn(ImmutableSet.of("tag1", "tag3"));
+
+    Action.Context context = mock(Action.Context.class);
+    when(context.issue()).thenReturn(issue);
+
+    action.execute(properties, context);
+  }
+}
index 3549729e8baa3bdd5c59d5fc6ee6b03bdb04efb5..eb6897386cca8ed6a298d20807c912626fc6c8e2 100644 (file)
@@ -262,18 +262,19 @@ class Api::IssuesController < Api::ApiController
   #
   # -- Mandatory parameters
   # 'issues' is the list of issue keys
-  # 'actions' the list of action to execute (expected at least one). Available actions are : assign,set_severity,plan,do_transition
+  # 'actions' the list of action to execute (expected at least one). Available actions are : assign,set_severity,plan,do_transition,add_tags
   #
   # -- Optional parameters
   # 'assign.assignee' to assign all issues to a user or un-assign.
   # 'set_severity.severity' to change the severity of all issues.
   # 'plan.plan' to plan all issues to an action plan or unlink.
   # 'do_transition.transition' to execute a transition on all issues.
+  # 'add_tags.tags' to add tags on all issues.
   # 'comment' to add a comment on all issues.
   # 'sendNotifications' to send notification for each modified issue (default is 'false')
   #
   # -- Example
-  # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/bulk_change?issues=4a2881e7-825e-4140-a154-01f420c43d11,4a2881e7-825e-4140-a154-01f420c43d30&actions=assign,plan&assign.assignee=simon&plan.plan=3.7'
+  # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/bulk_change?issues=4a2881e7-825e-4140-a154-01f420c43d11,4a2881e7-825e-4140-a154-01f420c43d30&actions=assign,plan,add_tags&assign.assignee=simon&plan.plan=3.7&add_tags.tags=design,convention'
   #
   def bulk_change
     verify_post_request
index 3802a05e99dd078f5e0678ac26672cb5c6d9078b..cf5e32c71b844fed4de68e3c3f11498317bc0f63 100644 (file)
@@ -80,6 +80,7 @@
         </div>
         <% end %>
       <% end %>
+
       <div class="modal-field">
         <label for="severity">
           <%= message('issue.set_severity') -%>
                          {:id => 'severity'}) -%>
         <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues_user_can_admin.to_s) -%>)</span>
       </div>
+
+      <div class="modal-field">
+        <label for="add_tags">
+          <%= message('issue.add_tags') -%>
+        </label>
+        <input id="add-tags-action" name="actions[]" type="checkbox" value="add_tags"/>
+
+        <%# TODO Here, select2 box with tags to add %>
+        <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues_user_can_admin.to_s) -%>)</span>
+      </div>
+
       <% end %>
 
       <% if transitions_by_issues.size > 0 %>
index 29fafb50953424e90e61791b1dc248962f280821..367ffb1c27f179fe2ceae6f3d569b69ee0d1f9fd 100644 (file)
@@ -637,6 +637,7 @@ measure_filter.error.VALUE_SHOULD_BE_A_NUMBER=Value used for metric should be a
 #
 #------------------------------------------------------------------------------
 
+issue.add_tags=Add Tags
 issue.assign.formlink=Assign
 issue.assign.submit=Assign
 issue.unassign.submit=Unassign