aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2015-10-07 14:12:10 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2015-10-09 16:48:24 +0200
commitf59052c8e1daed111d931893b2a08f9f5fa2a74a (patch)
tree247e34ce3bb90f2652a3fc0c4602990c66c76369
parent74af3d8161ca9a767d9b6a7a2bdc3f5d29bd5003 (diff)
downloadsonarqube-f59052c8e1daed111d931893b2a08f9f5fa2a74a.tar.gz
sonarqube-f59052c8e1daed111d931893b2a08f9f5fa2a74a.zip
SONAR-6717 Restore issues actions API
-rw-r--r--it/it-plugins/issue-action-plugin/pom.xml40
-rw-r--r--it/it-plugins/issue-action-plugin/src/main/java/ActionDefinition.java46
-rw-r--r--it/it-plugins/issue-action-plugin/src/main/java/IssueActionPlugin.java34
-rw-r--r--it/it-plugins/issue-action-plugin/src/main/resources/org/sonar/l10n/issueaction.properties21
-rw-r--r--it/it-plugins/pom.xml1
-rw-r--r--it/it-tests/src/test/java/issue/suite/IssueActionTest.java257
-rw-r--r--it/it-tests/src/test/java/issue/suite/IssueTestSuite.java26
-rw-r--r--it/it-tests/src/test/java/util/ItUtils.java20
-rw-r--r--it/it-tests/src/test/resources/issue/suite/IssueActionTest/xoo-one-issue-per-line-profile.xml12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java184
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java32
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java19
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettings.java72
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettingsFactory.java23
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java238
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java69
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java23
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsFactoryTest.java76
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryFactoryTest.java74
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryTest.java137
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_comments.json1
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_transitions.json1
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb25
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Action.java2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Actions.java8
30 files changed, 1034 insertions, 441 deletions
diff --git a/it/it-plugins/issue-action-plugin/pom.xml b/it/it-plugins/issue-action-plugin/pom.xml
new file mode 100644
index 00000000000..6b176b8e5bd
--- /dev/null
+++ b/it/it-plugins/issue-action-plugin/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.sonarsource.it</groupId>
+ <artifactId>it-plugins</artifactId>
+ <version>5.2-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>issue-action-plugin</artifactId>
+ <packaging>sonar-plugin</packaging>
+ <name>Plugins :: Issue Action</name>
+ <version>1.0-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ <version>${apiVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-packaging-maven-plugin</artifactId>
+ <version>1.1</version>
+ <extensions>true</extensions>
+ <configuration>
+ <pluginClass>IssueActionPlugin</pluginClass>
+ <pluginKey>issueaction</pluginKey>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/it/it-plugins/issue-action-plugin/src/main/java/ActionDefinition.java b/it/it-plugins/issue-action-plugin/src/main/java/ActionDefinition.java
new file mode 100644
index 00000000000..84f5e6c5a6a
--- /dev/null
+++ b/it/it-plugins/issue-action-plugin/src/main/java/ActionDefinition.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.
+ */
+
+import org.sonar.api.ServerExtension;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.action.Actions;
+import org.sonar.api.issue.action.Function;
+import org.sonar.api.issue.condition.HasResolution;
+
+public class ActionDefinition implements ServerExtension {
+
+ private final Actions actions;
+
+ public ActionDefinition(Actions actions) {
+ this.actions = actions;
+ }
+
+ public void start() {
+ actions.add("fake")
+ .setConditions(new HasResolution(Issue.RESOLUTION_FIXED))
+ .setFunctions(new Function() {
+ @Override
+ public void execute(Context context) {
+ context.setAttribute("fake", "fake action");
+ context.addComment("New Comment from fake action");
+ }
+ });
+ }
+}
diff --git a/it/it-plugins/issue-action-plugin/src/main/java/IssueActionPlugin.java b/it/it-plugins/issue-action-plugin/src/main/java/IssueActionPlugin.java
new file mode 100644
index 00000000000..d322d9f4d90
--- /dev/null
+++ b/it/it-plugins/issue-action-plugin/src/main/java/IssueActionPlugin.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.
+ */
+
+import org.sonar.api.SonarPlugin;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class IssueActionPlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ return Arrays.asList(
+ ActionDefinition.class
+ );
+ }
+
+}
diff --git a/it/it-plugins/issue-action-plugin/src/main/resources/org/sonar/l10n/issueaction.properties b/it/it-plugins/issue-action-plugin/src/main/resources/org/sonar/l10n/issueaction.properties
new file mode 100644
index 00000000000..f507ceed575
--- /dev/null
+++ b/it/it-plugins/issue-action-plugin/src/main/resources/org/sonar/l10n/issueaction.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+issue.action.fake.formlink=Fake
diff --git a/it/it-plugins/pom.xml b/it/it-plugins/pom.xml
index b0d7c374cef..984e022859d 100644
--- a/it/it-plugins/pom.xml
+++ b/it/it-plugins/pom.xml
@@ -35,6 +35,7 @@
<module>batch-plugin</module>
<module>extension-lifecycle-plugin</module>
<module>global-property-change-plugin</module>
+ <module>issue-action-plugin</module>
<module>l10n-fr-pack</module>
<module>license-plugin</module>
<module>project-builder-plugin</module>
diff --git a/it/it-tests/src/test/java/issue/suite/IssueActionTest.java b/it/it-tests/src/test/java/issue/suite/IssueActionTest.java
new file mode 100644
index 00000000000..ab23ce589fe
--- /dev/null
+++ b/it/it-tests/src/test/java/issue/suite/IssueActionTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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 issue.suite;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarRunner;
+import com.sonar.orchestrator.locator.FileLocation;
+import java.util.List;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.sonar.wsclient.base.HttpException;
+import org.sonar.wsclient.issue.ActionPlan;
+import org.sonar.wsclient.issue.ActionPlanClient;
+import org.sonar.wsclient.issue.Issue;
+import org.sonar.wsclient.issue.IssueComment;
+import org.sonar.wsclient.issue.IssueQuery;
+import org.sonar.wsclient.issue.Issues;
+import org.sonar.wsclient.issue.NewActionPlan;
+
+import static issue.suite.IssueTestSuite.ORCHESTRATOR;
+import static issue.suite.IssueTestSuite.adminIssueClient;
+import static issue.suite.IssueTestSuite.issueClient;
+import static issue.suite.IssueTestSuite.search;
+import static issue.suite.IssueTestSuite.searchIssueByKey;
+import static issue.suite.IssueTestSuite.searchIssues;
+import static issue.suite.IssueTestSuite.searchRandomIssue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static util.ItUtils.projectDir;
+import static util.ItUtils.toDate;
+import static util.ItUtils.verifyHttpException;
+
+public class IssueActionTest {
+
+ @ClassRule
+ public static Orchestrator orchestrator = ORCHESTRATOR;
+
+ Issue issue;
+ SonarRunner scan;
+
+ private static List<Issue> searchIssuesBySeverities(String componentKey, String... severities) {
+ return searchIssues(IssueQuery.create().componentRoots(componentKey).severities(severities));
+ }
+
+ private static ActionPlanClient adminActionPlanClient() {
+ return orchestrator.getServer().adminWsClient().actionPlanClient();
+ }
+
+ @Before
+ public void resetData() {
+ orchestrator.resetData();
+ orchestrator.getServer().restoreProfile(FileLocation.ofClasspath("/issue/suite/IssueActionTest/xoo-one-issue-per-line-profile.xml"));
+ orchestrator.getServer().provisionProject("sample", "Sample");
+ orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "xoo-one-issue-per-line-profile");
+
+ scan = SonarRunner.create(projectDir("shared/xoo-sample"));
+ orchestrator.executeBuild(scan);
+ issue = searchRandomIssue();
+ }
+
+ @Test
+ public void no_comments_by_default() throws Exception {
+ assertThat(issue.comments()).isEmpty();
+ }
+
+ @Test
+ public void add_comment() throws Exception {
+ IssueComment comment = adminIssueClient().addComment(issue.key(), "this is my *comment*");
+ assertThat(comment.key()).isNotNull();
+ assertThat(comment.htmlText()).isEqualTo("this is my <em>comment</em>");
+ assertThat(comment.login()).isEqualTo("admin");
+ assertThat(comment.createdAt()).isNotNull();
+
+ // reload issue
+ Issue reloaded = searchIssueByKey(issue.key());
+
+ assertThat(reloaded.comments()).hasSize(1);
+ assertThat(reloaded.comments().get(0).key()).isEqualTo(comment.key());
+ assertThat(reloaded.comments().get(0).htmlText()).isEqualTo("this is my <em>comment</em>");
+ assertThat(reloaded.updateDate().before(issue.creationDate())).isFalse();
+ }
+
+ /**
+ * SONAR-4450
+ */
+ @Test
+ public void should_reject_blank_comment() throws Exception {
+ try {
+ adminIssueClient().addComment(issue.key(), " ");
+ fail();
+ } catch (HttpException ex) {
+ assertThat(ex.status()).isEqualTo(400);
+ }
+
+ Issue reloaded = searchIssueByKey(issue.key());
+ assertThat(reloaded.comments()).hasSize(0);
+ }
+
+ /**
+ * SONAR-4352
+ */
+ @Test
+ public void change_severity() {
+ String componentKey = "sample";
+
+ // there are no blocker issues
+ assertThat(searchIssuesBySeverities(componentKey, "BLOCKER")).isEmpty();
+
+ // increase the severity of an issue
+ adminIssueClient().setSeverity(issue.key(), "BLOCKER");
+
+ assertThat(searchIssuesBySeverities(componentKey, "BLOCKER")).hasSize(1);
+
+ orchestrator.executeBuild(scan);
+ Issue reloaded = searchIssueByKey(issue.key());
+ assertThat(reloaded.severity()).isEqualTo("BLOCKER");
+ assertThat(reloaded.status()).isEqualTo("OPEN");
+ assertThat(reloaded.resolution()).isNull();
+ assertThat(reloaded.creationDate()).isEqualTo(issue.creationDate());
+ assertThat(reloaded.creationDate().before(reloaded.updateDate())).isTrue();
+ }
+
+ /**
+ * SONAR-4287
+ */
+ @Test
+ public void assign() {
+ assertThat(issue.assignee()).isNull();
+ Issues issues = search(IssueQuery.create().issues(issue.key()));
+ assertThat(issues.users()).isEmpty();
+
+ adminIssueClient().assign(issue.key(), "admin");
+ assertThat(search(IssueQuery.create().assignees("admin")).list()).hasSize(1);
+
+ orchestrator.executeBuild(scan);
+ Issue reloaded = searchIssueByKey(issue.key());
+ assertThat(reloaded.assignee()).isEqualTo("admin");
+ assertThat(reloaded.creationDate()).isEqualTo(issue.creationDate());
+
+ issues = search(IssueQuery.create().issues(issue.key()));
+ assertThat(issues.user("admin")).isNotNull();
+ assertThat(issues.user("admin").name()).isEqualTo("Administrator");
+
+ // unassign
+ adminIssueClient().assign(issue.key(), null);
+ reloaded = searchIssueByKey(issue.key());
+ assertThat(reloaded.assignee()).isNull();
+ assertThat(issueClient().find(IssueQuery.create().assignees("admin")).list()).isEmpty();
+ }
+
+ /**
+ * SONAR-4287
+ */
+ @Test
+ public void fail_assign_if_assignee_does_not_exist() {
+ assertThat(issue.assignee()).isNull();
+ try {
+ adminIssueClient().assign(issue.key(), "unknown");
+ fail();
+ } catch (Exception e) {
+ verifyHttpException(e, 400);
+ }
+ }
+
+ /**
+ * SONAR-4290
+ */
+ @Test
+ public void plan() {
+ assertThat(issue.actionPlan()).isNull();
+
+ // Set action plan to issue
+ ActionPlan newActionPlan = adminActionPlanClient().create(NewActionPlan.create().name("Short term").project("sample")
+ .description("Short term issues").deadLine(toDate("2113-01-31")));
+ assertThat(newActionPlan.key()).isNotNull();
+ adminIssueClient().plan(issue.key(), newActionPlan.key());
+ assertThat(search(IssueQuery.create().actionPlans(newActionPlan.key())).list()).hasSize(1);
+
+ orchestrator.executeBuild(scan);
+ Issue reloaded = searchIssueByKey(issue.key());
+ assertThat(reloaded.actionPlan()).isEqualTo(newActionPlan.key());
+ assertThat(reloaded.creationDate()).isEqualTo(issue.creationDate());
+ ActionPlan actionPlan = search(IssueQuery.create().actionPlans(newActionPlan.key())).actionPlans(reloaded);
+ assertThat(actionPlan.name()).isEqualTo(newActionPlan.name());
+ assertThat(actionPlan.deadLine()).isEqualTo(newActionPlan.deadLine());
+ }
+
+ @Test
+ public void fail_plan_if_action_plan_does_not_exist() {
+ assertThat(issue.actionPlan()).isNull();
+ try {
+ adminIssueClient().plan(issue.key(), "unknown");
+ fail();
+ } catch (Exception e) {
+ verifyHttpException(e, 400);
+ }
+ }
+
+ @Test
+ public void unplan() {
+ assertThat(issue.actionPlan()).isNull();
+
+ // Set action plan to issue
+ ActionPlan newActionPlan = adminActionPlanClient().create(NewActionPlan.create().name("Short term").project("sample")
+ .description("Short term issues").deadLine(toDate("2113-01-31")));
+ assertThat(newActionPlan.key()).isNotNull();
+ adminIssueClient().plan(issue.key(), newActionPlan.key());
+ assertThat(search(IssueQuery.create().actionPlans(newActionPlan.key())).list()).hasSize(1);
+
+ // Unplan
+ adminIssueClient().plan(issue.key(), null);
+ assertThat(search(IssueQuery.create().actionPlans(newActionPlan.key())).list()).hasSize(0);
+
+ orchestrator.executeBuild(scan);
+ Issue reloaded = searchIssueByKey(issue.key());
+ assertThat(reloaded.actionPlan()).isNull();
+ assertThat(reloaded.creationDate()).isEqualTo(issue.creationDate());
+ }
+
+ /**
+ * SONAR-4315
+ */
+ @Test
+ public void apply_action_from_plugin() {
+ // The condition on the action defined by the plugin is that the status must be resolved
+ adminIssueClient().doTransition(issue.key(), "resolve");
+ assertThat(adminIssueClient().actions(issue.key())).contains("fake");
+
+ adminIssueClient().doAction(issue.key(), "fake");
+
+ // reload issue
+ Issue reloaded = searchIssueByKey(issue.key());
+
+ assertThat(reloaded.comments()).hasSize(1);
+ assertThat(reloaded.comments().get(0).htmlText()).isEqualTo("New Comment from fake action");
+ }
+
+}
diff --git a/it/it-tests/src/test/java/issue/suite/IssueTestSuite.java b/it/it-tests/src/test/java/issue/suite/IssueTestSuite.java
index 6757552276e..3e6a1e4ce02 100644
--- a/it/it-tests/src/test/java/issue/suite/IssueTestSuite.java
+++ b/it/it-tests/src/test/java/issue/suite/IssueTestSuite.java
@@ -13,42 +13,52 @@ import org.junit.runners.Suite;
import org.sonar.wsclient.issue.Issue;
import org.sonar.wsclient.issue.IssueClient;
import org.sonar.wsclient.issue.IssueQuery;
-import util.ItUtils;
+import org.sonar.wsclient.issue.Issues;
import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.pluginArtifact;
+import static util.ItUtils.xooPlugin;
@RunWith(Suite.class)
@Suite.SuiteClasses({
- CommonRulesTest.class, IssueWorkflowTest.class, ManualRulesTest.class, CustomRulesTest.class
+ CommonRulesTest.class, IssueWorkflowTest.class, ManualRulesTest.class, CustomRulesTest.class, IssueActionTest.class
})
public class IssueTestSuite {
@ClassRule
public static final Orchestrator ORCHESTRATOR = Orchestrator.builderEnv()
.setSonarVersion("DEV")
- .addPlugin(ItUtils.xooPlugin())
+ .addPlugin(xooPlugin())
+ .addPlugin(pluginArtifact("issue-action-plugin"))
.build();
static IssueClient adminIssueClient() {
return ORCHESTRATOR.getServer().adminWsClient().issueClient();
}
+ static IssueClient issueClient() {
+ return ORCHESTRATOR.getServer().wsClient().issueClient();
+ }
+
static Issue searchRandomIssue() {
List<Issue> issues = searchIssues(IssueQuery.create());
assertThat(issues).isNotEmpty();
return issues.get(0);
}
- static List<Issue> searchIssues(IssueQuery issueQuery) {
+ static Issues search(IssueQuery issueQuery) {
issueQuery.urlParams().put("additionalFields", "_all");
- return adminIssueClient().find(issueQuery).list();
+ return issueClient().find(issueQuery);
+ }
+
+ static List<Issue> searchIssues(IssueQuery issueQuery) {
+ return search(issueQuery).list();
}
static Issue searchIssueByKey(String issueKey) {
- IssueQuery query = IssueQuery.create().issues(issueKey);
- query.urlParams().put("additionalFields", "_all");
- List<Issue> issues = searchIssues(query);
+ List<Issue> issues = searchIssues(IssueQuery.create().issues(issueKey));
assertThat(issues).hasSize(1);
return issues.get(0);
}
+
}
diff --git a/it/it-tests/src/test/java/util/ItUtils.java b/it/it-tests/src/test/java/util/ItUtils.java
index 1d8e3406ea8..4cb4df2c045 100644
--- a/it/it-tests/src/test/java/util/ItUtils.java
+++ b/it/it-tests/src/test/java/util/ItUtils.java
@@ -9,6 +9,9 @@ import com.google.common.collect.Iterables;
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.BuildResult;
import com.sonar.orchestrator.build.SonarRunner;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
@@ -27,6 +30,7 @@ import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThat;
import org.apache.commons.io.FileUtils;
+import org.sonar.wsclient.base.HttpException;
public class ItUtils {
@@ -157,4 +161,20 @@ public class ItUtils {
}
return from(Iterables.concat(asList(properties), asList(str))).toArray(String.class);
}
+
+ public static void verifyHttpException(Exception e, int expectedCode) {
+ assertThat(e).isInstanceOf(HttpException.class);
+ HttpException exception = (HttpException) e;
+ assertThat(exception.status()).isEqualTo(expectedCode);
+ }
+
+ public static Date toDate(String sDate) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ return sdf.parse(sDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
}
diff --git a/it/it-tests/src/test/resources/issue/suite/IssueActionTest/xoo-one-issue-per-line-profile.xml b/it/it-tests/src/test/resources/issue/suite/IssueActionTest/xoo-one-issue-per-line-profile.xml
new file mode 100644
index 00000000000..608f80cae96
--- /dev/null
+++ b/it/it-tests/src/test/resources/issue/suite/IssueActionTest/xoo-one-issue-per-line-profile.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?><!-- Generated by Sonar -->
+<profile>
+ <name>xoo-one-issue-per-line-profile</name>
+ <language>xoo</language>
+ <rules>
+ <rule>
+ <repositoryKey>xoo</repositoryKey>
+ <key>OneIssuePerLine</key>
+ <priority>CRITICAL</priority>
+ </rule>
+ </rules>
+</profile>
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java
index 42da9fd1b8e..c57be962187 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java
@@ -20,13 +20,32 @@
package org.sonar.server.issue;
+import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import java.util.Date;
import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.sonar.api.config.Settings;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.action.Action;
+import org.sonar.api.issue.action.Actions;
+import org.sonar.api.issue.action.Function;
import org.sonar.api.server.ServerSide;
-import org.sonar.api.web.UserRole;
-import org.sonar.db.issue.IssueDto;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.properties.ProjectSettingsFactory;
import org.sonar.server.user.UserSession;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.Iterables.find;
import static com.google.common.collect.Lists.newArrayList;
+import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
/**
* @since 3.6
@@ -34,30 +53,163 @@ import static com.google.common.collect.Lists.newArrayList;
@ServerSide
public class ActionService {
+ private final DbClient dbClient;
private final UserSession userSession;
+ private final Actions actions;
+ private final IssueService issueService;
+ private final IssueUpdater updater;
+ private final ProjectSettingsFactory projectSettingsFactory;
+ private final IssueStorage issueStorage;
- public ActionService(UserSession userSession) {
+ public ActionService(DbClient dbClient, UserSession userSession, ProjectSettingsFactory projectSettingsFactory, Actions actions, IssueService issueService, IssueUpdater updater,
+ IssueStorage issueStorage) {
+ this.dbClient = dbClient;
this.userSession = userSession;
+ this.actions = actions;
+ this.issueService = issueService;
+ this.updater = updater;
+ this.projectSettingsFactory = projectSettingsFactory;
+ this.issueStorage = issueStorage;
}
- public List<String> listAvailableActions(IssueDto issue) {
- List<String> actions = newArrayList();
+ public List<String> listAvailableActions(String issueKey) {
+ DbSession session = dbClient.openSession(false);
+ try {
+ return listAvailableActions(issueService.getByKeyForUpdate(session, issueKey).toDefaultIssue());
+ } finally {
+ dbClient.closeSession(session);
+ }
+ }
+
+ public List<String> listAvailableActions(Issue issue) {
+ List<String> availableActions = newArrayList();
String login = userSession.getLogin();
if (login != null) {
- actions.add("comment");
- if (issue.getResolution() == null) {
- actions.add("assign");
- actions.add("set_tags");
- if (!login.equals(issue.getAssignee())) {
- actions.add("assign_to_me");
+ availableActions.add("comment");
+ if (issue.resolution() == null) {
+ availableActions.add("assign");
+ availableActions.add("set_tags");
+ if (!login.equals(issue.assignee())) {
+ availableActions.add("assign_to_me");
}
- actions.add("plan");
- String projectUuid = issue.getProjectUuid();
- if (projectUuid != null && userSession.hasProjectPermissionByUuid(UserRole.ISSUE_ADMIN, projectUuid)) {
- actions.add("set_severity");
+ availableActions.add("plan");
+ String projectUuid = issue.projectUuid();
+ if (projectUuid != null && userSession.hasProjectPermissionByUuid(ISSUE_ADMIN, projectUuid)) {
+ availableActions.add("set_severity");
}
}
+ for (String action : loadPluginActions(issue)) {
+ availableActions.add(action);
+ }
+ }
+ return availableActions;
+ }
+
+ private List<String> loadPluginActions(final Issue issue) {
+ return from(actions.list())
+ .filter(new SupportIssue(issue))
+ .transform(ActionToKey.INSTANCE)
+ .toList();
+ }
+
+ public Issue execute(String issueKey, String actionKey) {
+ checkArgument(!Strings.isNullOrEmpty(actionKey), "Missing action");
+
+ DbSession session = dbClient.openSession(false);
+ try {
+ DefaultIssue issue = issueService.getByKeyForUpdate(session, issueKey).toDefaultIssue();
+ Action action = getAction(actionKey, issue);
+
+ IssueChangeContext changeContext = IssueChangeContext.createUser(new Date(), userSession.getLogin());
+ FunctionContext functionContext = new FunctionContext(issue, updater, changeContext, projectSettingsFactory.newProjectSettings(issue.projectKey()));
+ for (Function function : action.functions()) {
+ function.execute(functionContext);
+ }
+ issueStorage.save(issue);
+ return issue;
+ } finally {
+ dbClient.closeSession(session);
+ }
+ }
+
+ private Action getAction(String actionKey, Issue issue) {
+ Action action = find(actions.list(), new MatchActionKey(actionKey), null);
+ checkArgument(action != null, String.format("Action is not found : %s", actionKey));
+ checkState(action.supports(issue), "A condition is not respected");
+ return action;
+ }
+
+ static class FunctionContext implements Function.Context {
+
+ private final DefaultIssue issue;
+ private final IssueUpdater updater;
+ private final IssueChangeContext changeContext;
+ private final Settings projectSettings;
+
+ FunctionContext(DefaultIssue issue, IssueUpdater updater, IssueChangeContext changeContext, Settings projectSettings) {
+ this.updater = updater;
+ this.issue = issue;
+ this.changeContext = changeContext;
+ this.projectSettings = projectSettings;
+ }
+
+ @Override
+ public Issue issue() {
+ return issue;
+ }
+
+ @Override
+ public Settings projectSettings() {
+ return projectSettings;
+ }
+
+ @Override
+ public Function.Context setAttribute(String key, @Nullable String value) {
+ updater.setAttribute(issue, key, value, changeContext);
+ return this;
+ }
+
+ @Override
+ public Function.Context addComment(@Nullable String text) {
+ if (text != null) {
+ updater.addComment(issue, text, changeContext);
+ }
+ return this;
+ }
+ }
+
+ private static class SupportIssue implements Predicate<Action> {
+ private final Issue issue;
+
+ public SupportIssue(Issue issue) {
+ this.issue = issue;
+ }
+
+ @Override
+ public boolean apply(@Nonnull Action action) {
+ return action.supports(issue);
+ }
+ }
+
+ private enum ActionToKey implements com.google.common.base.Function<Action, String> {
+ INSTANCE;
+
+ @Override
+ public String apply(@Nonnull Action action) {
+ return action.key();
+ }
+ }
+
+ private static class MatchActionKey implements Predicate<Action> {
+ private final String actionKey;
+
+ private MatchActionKey(String actionKey) {
+ this.actionKey = actionKey;
+ }
+
+ @Override
+ public boolean apply(@Nonnull Action action) {
+ return action.key().equals(actionKey);
}
- return actions;
}
}
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 67c580aa7e0..d8a382025db 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
@@ -82,6 +82,7 @@ public class InternalRubyIssueService {
private final ResourceDao resourceDao;
private final IssueFilterService issueFilterService;
private final IssueBulkChangeService issueBulkChangeService;
+ private final ActionService actionService;
private final UserSession userSession;
public InternalRubyIssueService(
@@ -91,7 +92,7 @@ public class InternalRubyIssueService {
IssueChangelogService changelogService, ActionPlanService actionPlanService,
ResourceDao resourceDao,
IssueFilterService issueFilterService, IssueBulkChangeService issueBulkChangeService,
- UserSession userSession) {
+ ActionService actionService, UserSession userSession) {
this.issueService = issueService;
this.issueQueryService = issueQueryService;
this.commentService = commentService;
@@ -100,6 +101,7 @@ public class InternalRubyIssueService {
this.resourceDao = resourceDao;
this.issueFilterService = issueFilterService;
this.issueBulkChangeService = issueBulkChangeService;
+ this.actionService = actionService;
this.userSession = userSession;
}
@@ -314,6 +316,20 @@ public class InternalRubyIssueService {
return result;
}
+ public Result<Issue> executeAction(String issueKey, String actionKey) {
+ Result<Issue> result = Result.of();
+ try {
+ result.set(actionService.execute(issueKey, actionKey));
+ } catch (Exception e) {
+ result.addError(e.getMessage());
+ }
+ return result;
+ }
+
+ public List<String> listActions(String issueKey) {
+ return actionService.listAvailableActions(issueKey);
+ }
+
public IssueQuery emptyIssueQuery() {
return issueQueryService.createFromMap(Maps.<String, Object>newHashMap());
}
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 8660724f7f1..306b322f750 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
@@ -20,25 +20,24 @@
package org.sonar.server.issue.ws;
-import java.util.List;
import org.sonar.api.issue.Issue;
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;
-import static com.google.common.collect.Lists.newArrayList;
-
@ServerSide
public class IssueActionsWriter {
private final IssueService issueService;
+ private final ActionService actionService;
private final UserSession userSession;
- public IssueActionsWriter(IssueService issueService, UserSession userSession) {
+ public IssueActionsWriter(IssueService issueService, ActionService actionService, UserSession userSession) {
this.issueService = issueService;
+ this.actionService = actionService;
this.userSession = userSession;
}
@@ -54,31 +53,10 @@ public class IssueActionsWriter {
public void writeActions(Issue issue, JsonWriter json) {
json.name("actions").beginArray();
- for (String action : actions(issue)) {
+ for (String action : actionService.listAvailableActions(issue)) {
json.value(action);
}
json.endArray();
}
- private List<String> actions(Issue issue) {
- List<String> actions = newArrayList();
- String login = userSession.getLogin();
- if (login != null) {
- actions.add("comment");
- if (issue.resolution() == null) {
- actions.add("assign");
- actions.add("set_tags");
- if (!login.equals(issue.assignee())) {
- actions.add("assign_to_me");
- }
- actions.add("plan");
- String projectUuid = issue.projectUuid();
- if (projectUuid != null && userSession.hasProjectPermissionByUuid(UserRole.ISSUE_ADMIN, projectUuid)) {
- actions.add("set_severity");
- }
- }
- }
- return actions;
- }
-
}
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 8ac665b2b7b..d5911cc2011 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
@@ -35,6 +35,7 @@ public class IssuesWs implements WebService {
public static final String EDIT_COMMENT_ACTION = "edit_comment";
public static final String TRANSITIONS_ACTION = "transitions";
public static final String BULK_CHANGE_ACTION = "bulk_change";
+ public static final String DO_ACTION_ACTION = "do_action";
private final IssuesWsAction[] actions;
@@ -61,6 +62,7 @@ public class IssuesWs implements WebService {
defineEditCommentAction(controller);
defineTransitionsAction(controller);
defineBulkChangeAction(controller);
+ defineDoActionAction(controller);
}
private static void defineChangelogAction(NewController controller) {
@@ -176,4 +178,21 @@ public class IssuesWs implements WebService {
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);
+ }
+
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
index ec1b2257d61..ba7ba678e19 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
@@ -138,7 +138,6 @@ public class SearchResponseFormat {
if (fields.contains(SearchAdditionalField.COMMENTS)) {
formatIssueComments(data, issueBuilder, dto);
}
- // TODO attributes
result.add(issueBuilder.build());
}
return result;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
index 74d8a214659..ac510fe381b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
@@ -139,7 +139,7 @@ public class SearchResponseLoader {
for (IssueDto dto : result.getIssues()) {
// so that IssueDto can be used.
if (collector.contains(ACTIONS)) {
- result.addActions(dto.getKey(), actionService.listAvailableActions(dto));
+ result.addActions(dto.getKey(), actionService.listAvailableActions(dto.toDefaultIssue()));
}
if (collector.contains(TRANSITIONS)) {
// TODO workflow and action engines must not depend on org.sonar.api.issue.Issue but on a generic interface
diff --git a/server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettings.java b/server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettings.java
deleted file mode 100644
index 9eb94f93b4b..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettings.java
+++ /dev/null
@@ -1,72 +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.properties;
-
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.config.Settings;
-
-import java.util.Map;
-
-public class ProjectSettings extends Settings {
- private final Settings settings;
- private final Map<String, String> projectProperties;
-
- public ProjectSettings(Settings settings, Map<String, String> projectProperties) {
- this.settings = settings;
- this.projectProperties = projectProperties;
- }
-
- @Override
- public String getString(String key) {
- String value = get(key);
- if (value == null) {
- return settings.getString(key);
- }
-
- return value;
- }
-
- @Override
- public boolean getBoolean(String key) {
- String value = get(key);
- if (value == null) {
- return settings.getBoolean(key);
- }
-
- return StringUtils.isNotEmpty(value) && Boolean.parseBoolean(value);
- }
-
- @Override
- public int getInt(String key) {
- String value = get(key);
- if (value == null) {
- return settings.getInt(key);
- } else if (StringUtils.isNotEmpty(value)) {
- return Integer.parseInt(value);
- } else {
- return 0;
- }
- }
-
- private String get(String key) {
- return projectProperties.get(key);
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettingsFactory.java b/server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettingsFactory.java
index 43986c1ea35..994c3377aaf 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettingsFactory.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/properties/ProjectSettingsFactory.java
@@ -20,16 +20,12 @@
package org.sonar.server.properties;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Maps;
+import java.util.List;
import org.sonar.api.config.Settings;
import org.sonar.api.server.ServerSide;
import org.sonar.db.property.PropertiesDao;
import org.sonar.db.property.PropertyDto;
-import java.util.List;
-import java.util.Map;
-
@ServerSide
public class ProjectSettingsFactory {
@@ -43,19 +39,10 @@ public class ProjectSettingsFactory {
public Settings newProjectSettings(String projectKey) {
List<PropertyDto> propertyList = dao.selectProjectProperties(projectKey);
-
- return new ProjectSettings(settings, getPropertyMap(propertyList));
- }
-
- @VisibleForTesting
- Map<String, String> getPropertyMap(List<PropertyDto> propertyDtoList) {
- Map<String, String> propertyMap = Maps.newHashMap();
- for (PropertyDto property : propertyDtoList) {
- String key = property.getKey();
- String value = property.getValue();
- propertyMap.put(key, value);
+ Settings projectSettings = new Settings(settings);
+ for (PropertyDto property : propertyList) {
+ projectSettings.setProperty(property.getKey(), property.getValue());
}
-
- return propertyMap;
+ return projectSettings;
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java
new file mode 100644
index 00000000000..0d6570bcfc3
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Settings;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.action.Actions;
+import org.sonar.api.issue.action.Function;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.server.properties.ProjectSettingsFactory;
+import org.sonar.server.tester.UserSessionRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.rule.RuleTesting.newXooX1;
+
+public class ActionServiceTest {
+
+ static final String PROJECT_KEY = "PROJECT_KEY";
+ static final String PROJECT_UUID = "PROJECT_UUID";
+
+ static final String ISSUE_KEY = "ISSUE_KEY";
+ static final String PLUGIN_ACTION = "link-to-jira";
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone().login("arthur");
+
+ DbClient dbClient = mock(DbClient.class);
+ DbSession session = mock(DbSession.class);
+
+ IssueService issueService = mock(IssueService.class);
+ IssueStorage issueStorage = mock(IssueStorage.class);
+ IssueUpdater updater = mock(IssueUpdater.class);
+ ProjectSettingsFactory projectSettingsFactory = mock(ProjectSettingsFactory.class);
+ Settings settings = new Settings();
+ Actions actions = new Actions();
+ ActionService actionService;
+
+ ComponentDto project;
+ IssueDto issue;
+
+ @Before
+ public void before() {
+ when(dbClient.openSession(false)).thenReturn(session);
+ when(projectSettingsFactory.newProjectSettings(PROJECT_KEY)).thenReturn(settings);
+
+ project = newProjectDto(PROJECT_UUID).setKey(PROJECT_KEY);
+ issue = IssueTesting.newDto(newXooX1().setId(10), newFileDto(project), project).setKee(ISSUE_KEY);
+
+ actionService = new ActionService(dbClient, userSession, projectSettingsFactory, actions, issueService, updater, issueStorage);
+ }
+
+ @Test
+ public void execute_functions() {
+ Function function1 = mock(Function.class);
+ Function function2 = mock(Function.class);
+
+ when(issueService.getByKeyForUpdate(session, ISSUE_KEY)).thenReturn(issue);
+
+ actions.add(PLUGIN_ACTION).setConditions(new AlwaysMatch()).setFunctions(function1, function2);
+
+ assertThat(actionService.execute(ISSUE_KEY, PLUGIN_ACTION)).isNotNull();
+
+ verify(function1).execute(any(Function.Context.class));
+ verify(function2).execute(any(Function.Context.class));
+ verifyNoMoreInteractions(function1, function2);
+ }
+
+ @Test
+ public void modify_issue_when_executing_a_function() {
+ Function function = new TweetFunction();
+ when(issueService.getByKeyForUpdate(session, ISSUE_KEY)).thenReturn(issue);
+
+ actions.add(PLUGIN_ACTION).setConditions(new AlwaysMatch()).setFunctions(function);
+ assertThat(actionService.execute(ISSUE_KEY, PLUGIN_ACTION)).isNotNull();
+
+ verify(updater).addComment(any(DefaultIssue.class), eq("New tweet on issue ISSUE_KEY"), any(IssueChangeContext.class));
+ verify(updater).setAttribute(any(DefaultIssue.class), eq("tweet"), eq("tweet sent"), any(IssueChangeContext.class));
+ }
+
+ @Test
+ public void use_project_settings_when_executing_a_function() {
+ Function function = new SettingsFunction();
+ when(issueService.getByKeyForUpdate(session, ISSUE_KEY)).thenReturn(issue);
+ settings.setProperty("key", "value");
+
+ actions.add(PLUGIN_ACTION).setConditions(new AlwaysMatch()).setFunctions(function);
+ actionService.execute(ISSUE_KEY, PLUGIN_ACTION);
+
+ verify(updater).addComment(any(DefaultIssue.class), eq("Property 'key' is 'value'"), any(IssueChangeContext.class));
+ }
+
+ @Test
+ public void not_execute_function_if_action_not_found() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Action is not found : tweet");
+
+ Function function = mock(Function.class);
+ when(issueService.getByKeyForUpdate(session, ISSUE_KEY)).thenReturn(issue);
+ actions.add(PLUGIN_ACTION).setConditions(new AlwaysMatch()).setFunctions(function);
+ actionService.execute(ISSUE_KEY, "tweet");
+ }
+
+ @Test
+ public void not_execute_function_if_action_is_not_supported() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("A condition is not respected");
+
+ Function function = mock(Function.class);
+
+ when(issueService.getByKeyForUpdate(session, ISSUE_KEY)).thenReturn(issue);
+ actions.add(PLUGIN_ACTION).setConditions(new NeverMatch()).setFunctions(function);
+ actionService.execute(ISSUE_KEY, PLUGIN_ACTION);
+ }
+
+ @Test
+ public void return_plugin_actions() {
+ actions.add(PLUGIN_ACTION).setConditions(new AlwaysMatch());
+ actions.add("tweet").setConditions(new NeverMatch());
+ assertThat(actionService.listAvailableActions(issue.toDefaultIssue())).contains(PLUGIN_ACTION);
+ }
+
+ @Test
+ public void return_plugin_actions_on_resolved_issue() {
+ actions.add(PLUGIN_ACTION).setConditions(new AlwaysMatch());
+ actions.add("tweet").setConditions(new NeverMatch());
+ issue = IssueTesting.newDto(newXooX1().setId(10), newFileDto(project), project).setKee(ISSUE_KEY).setResolution(RESOLUTION_FIXED);
+
+ assertThat(actionService.listAvailableActions(issue.toDefaultIssue())).contains(PLUGIN_ACTION);
+ }
+
+ @Test
+ public void return_provided_actions_without_set_severity_when_not_issue_admin() {
+ assertThat(actionService.listAvailableActions(issue.toDefaultIssue())).containsOnly("comment", "assign", "set_tags", "assign_to_me", "plan");
+ }
+
+ @Test
+ public void return_provided_actions_with_set_severity_when_issue_admin() {
+ userSession.addProjectUuidPermissions(ISSUE_ADMIN, PROJECT_UUID);
+ assertThat(actionService.listAvailableActions(issue.toDefaultIssue())).containsOnly("comment", "assign", "set_tags", "assign_to_me", "plan", "set_severity");
+ }
+
+ @Test
+ public void return_no_actions_when_not_logged() {
+ userSession.anonymous();
+ assertThat(actionService.listAvailableActions(issue.toDefaultIssue())).isEmpty();
+ }
+
+ @Test
+ public void doest_not_return_assign_to_me_action_when_issue_already_assigned_to_user() {
+ userSession.login("julien");
+ IssueDto issue = IssueTesting.newDto(newXooX1().setId(10), newFileDto(project), project).setKee(ISSUE_KEY).setAssignee("julien");
+ assertThat(actionService.listAvailableActions(issue.toDefaultIssue())).doesNotContain("assign_to_me");
+ }
+
+ @Test
+ public void return_only_comment_action_when_issue_has_a_resolution() {
+ IssueDto issue = IssueTesting.newDto(newXooX1().setId(10), newFileDto(project), project).setKee(ISSUE_KEY).setResolution(RESOLUTION_FIXED);
+ assertThat(actionService.listAvailableActions(issue.toDefaultIssue())).containsOnly("comment");
+ }
+
+ @Test
+ public void return_actions_by_issue_key() {
+ when(issueService.getByKeyForUpdate(session, ISSUE_KEY)).thenReturn(issue);
+ assertThat(actionService.listAvailableActions(ISSUE_KEY)).isNotEmpty();
+ }
+
+ public class AlwaysMatch implements Condition {
+ @Override
+ public boolean matches(Issue issue) {
+ return true;
+ }
+ }
+
+ public class NeverMatch implements Condition {
+ @Override
+ public boolean matches(Issue issue) {
+ return false;
+ }
+ }
+
+ public class TweetFunction implements Function {
+ @Override
+ public void execute(Context context) {
+ context.addComment("New tweet on issue " + context.issue().key());
+ context.setAttribute("tweet", "tweet sent");
+ }
+ }
+
+ public class SettingsFunction implements Function {
+ @Override
+ public void execute(Context context) {
+ context.addComment(String.format("Property 'key' is '%s'", context.projectSettings().getString("key")));
+ }
+ }
+
+}
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 b184a828279..345cc039249 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
@@ -82,6 +82,8 @@ public class InternalRubyIssueServiceTest {
IssueBulkChangeService issueBulkChangeService;
+ ActionService actionService;
+
InternalRubyIssueService service;
@Before
@@ -94,12 +96,13 @@ public class InternalRubyIssueServiceTest {
resourceDao = mock(ResourceDao.class);
issueFilterService = mock(IssueFilterService.class);
issueBulkChangeService = mock(IssueBulkChangeService.class);
+ actionService = mock(ActionService.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,
- issueFilterService, issueBulkChangeService, userSessionRule);
+ issueFilterService, issueBulkChangeService, actionService, userSessionRule);
}
@Test
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 050ce927fe1..7232fb5b8a5 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
@@ -30,9 +30,9 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
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;
@@ -49,107 +49,74 @@ public class IssueActionsWriterTest {
@Mock
IssueService issueService;
+ @Mock
+ ActionService actionService;
+
IssueActionsWriter writer;
@Before
public void setUp() {
- writer = new IssueActionsWriter(issueService, userSessionRule);
- }
-
- @Test
- public void write_all_standard_actions() {
- Issue issue = new DefaultIssue()
- .setKey("ABCD")
- .setComponentUuid("BCDE")
- .setComponentKey("sample:src/main/xoo/sample/Sample.xoo")
- .setProjectUuid("ABCD")
- .setProjectKey("sample")
- .setRuleKey(RuleKey.of("squid", "AvoidCycle"));
-
- userSessionRule.login("john").addProjectUuidPermissions(UserRole.ISSUE_ADMIN, "ABCD");
-
- testActions(issue,
- "{\"actions\": " +
- "[" +
- "\"comment\", \"assign\", \"set_tags\", \"assign_to_me\", \"plan\", \"set_severity\"\n" +
- "]}");
+ writer = new IssueActionsWriter(issueService, actionService, userSessionRule);
}
@Test
- public void write_only_comment_action() {
+ public void write_actions() {
Issue issue = new DefaultIssue()
.setKey("ABCD")
.setComponentKey("sample:src/main/xoo/sample/Sample.xoo")
.setProjectKey("sample")
.setRuleKey(RuleKey.of("squid", "AvoidCycle"))
- .setResolution("CLOSED");
+ .setAssignee("john");
+ when(actionService.listAvailableActions(issue)).thenReturn(newArrayList("comment"));
userSessionRule.login("john");
testActions(issue,
"{\"actions\": " +
"[" +
- "\"comment\"" +
+ "\"comment\"\n" +
"]}");
}
@Test
- public void write_no_action_if_not_logged() {
+ public void write_transitions() {
Issue issue = new DefaultIssue()
.setKey("ABCD")
.setComponentKey("sample:src/main/xoo/sample/Sample.xoo")
.setProjectKey("sample")
.setRuleKey(RuleKey.of("squid", "AvoidCycle"));
- testActions(issue,
- "{\"actions\": []}");
- }
-
- @Test
- public void write_actions_without_assign_to_me() {
- Issue issue = new DefaultIssue()
- .setKey("ABCD")
- .setComponentKey("sample:src/main/xoo/sample/Sample.xoo")
- .setProjectKey("sample")
- .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
- .setAssignee("john");
-
+ when(issueService.listTransitions(eq(issue))).thenReturn(newArrayList(Transition.create("reopen", "RESOLVED", "REOPEN")));
userSessionRule.login("john");
- testActions(issue,
- "{\"actions\": " +
- "[" +
- "\"comment\", \"assign\", \"set_tags\", \"plan\"\n" +
- "]}");
+ testTransitions(issue,
+ "{\"transitions\": [\n" +
+ " \"reopen\"\n" +
+ " ]}");
}
@Test
- public void write_transitions() {
+ public void write_no_transitions() {
Issue issue = new DefaultIssue()
.setKey("ABCD")
.setComponentKey("sample:src/main/xoo/sample/Sample.xoo")
.setProjectKey("sample")
.setRuleKey(RuleKey.of("squid", "AvoidCycle"));
- when(issueService.listTransitions(eq(issue))).thenReturn(newArrayList(Transition.create("reopen", "RESOLVED", "REOPEN")));
userSessionRule.login("john");
testTransitions(issue,
- "{\"transitions\": [\n" +
- " \"reopen\"\n" +
- " ]}");
+ "{\"transitions\": []}");
}
@Test
- public void write_no_transitions() {
+ public void write_no_transitions_if_not_logged() {
Issue issue = new DefaultIssue()
.setKey("ABCD")
.setComponentKey("sample:src/main/xoo/sample/Sample.xoo")
.setProjectKey("sample")
.setRuleKey(RuleKey.of("squid", "AvoidCycle"));
- userSessionRule.login("john");
-
testTransitions(issue,
"{\"transitions\": []}");
}
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 df93f08dd4a..c592d1f993b 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
@@ -20,11 +20,9 @@
package org.sonar.server.issue.ws;
-import com.google.common.collect.ImmutableMap;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.Issue;
@@ -32,12 +30,12 @@ import org.sonar.api.rule.RuleStatus;
import org.sonar.api.security.DefaultGroups;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.KeyValueFormat;
import org.sonar.api.web.UserRole;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
import org.sonar.db.issue.ActionPlanDao;
import org.sonar.db.issue.ActionPlanDto;
import org.sonar.db.issue.IssueChangeDao;
@@ -47,7 +45,6 @@ import org.sonar.db.issue.IssueDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleTesting;
import org.sonar.db.user.UserDto;
-import org.sonar.db.component.ComponentTesting;
import org.sonar.server.issue.IssueQuery;
import org.sonar.server.issue.IssueTesting;
import org.sonar.server.issue.filter.IssueFilterParameters;
@@ -241,24 +238,6 @@ public class SearchActionMediumTest {
result.assertJson(this.getClass(), "issue_with_action_plan.json");
}
- @Ignore("temporarily disabled")
- @Test
- public void issue_with_attributes() throws Exception {
- ComponentDto project = insertComponent(ComponentTesting.newProjectDto("PROJECT_ID").setKey("PROJECT_KEY"));
- setDefaultProjectPermission(project);
- ComponentDto file = insertComponent(ComponentTesting.newFileDto(project, "FILE_ID").setKey("FILE_KEY"));
- IssueDto issue = IssueTesting.newDto(newRule(), file, project)
- .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")
- .setIssueAttributes(KeyValueFormat.format(ImmutableMap.of("jira-issue-key", "SONAR-1234")));
- db.issueDao().insert(session, issue);
- session.commit();
- tester.get(IssueIndexer.class).indexAll();
-
- WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION)
- .execute();
- result.assertJson(this.getClass(), "issue_with_attributes.json");
- }
-
@Test
public void load_additional_fields() throws Exception {
db.userDao().insert(session, new UserDto().setLogin("simon").setName("Simon").setEmail("simon@email.com"));
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 661ab46de43..5a8aefe66a9 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,6 +51,7 @@ import org.sonar.db.component.ComponentDao;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.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;
@@ -91,6 +92,9 @@ public class ShowActionTest {
IssueService issueService;
@Mock
+ ActionService actionService;
+
+ @Mock
IssueChangelogService issueChangelogService;
@Mock
@@ -142,7 +146,7 @@ public class ShowActionTest {
tester = new WsTester(new IssuesWs(
new ShowAction(
dbClient, issueService, issueChangelogService, commentService,
- new IssueActionsWriter(issueService, userSessionRule),
+ new IssueActionsWriter(issueService, actionService, userSessionRule),
actionPlanService, userFinder, debtModel, ruleService, i18n, durations, userSessionRule)));
}
@@ -455,6 +459,8 @@ public class ShowActionTest {
.setStatus("OPEN");
when(issueService.getByKey(issue.key())).thenReturn(issue);
+ when(actionService.listAvailableActions(issue)).thenReturn(newArrayList( "comment", "assign", "set_tags", "assign_to_me", "plan"));
+
userSessionRule.login("john");
WsTester.TestRequest request = tester.newGetRequest("api/issues", "show").setParam("key", issue.key());
request.execute().assertJson(getClass(), "show_issue_with_actions.json");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsFactoryTest.java
new file mode 100644
index 00000000000..8ceff5e1b68
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsFactoryTest.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.properties;
+
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.db.property.PropertiesDao;
+import org.sonar.db.property.PropertyDto;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ProjectSettingsFactoryTest {
+
+ static final String PROJECT_KEY = "PROJECT_KEY";
+
+ Settings settings = new Settings();
+ PropertiesDao dao = mock(PropertiesDao.class);
+
+ ProjectSettingsFactory underTest = new ProjectSettingsFactory(settings, dao);
+
+ @Test
+ public void return_global_settings() {
+ settings.setProperty("key", "value");
+ Settings projectSettings = underTest.newProjectSettings(PROJECT_KEY);
+
+ assertThat(projectSettings.getProperties()).hasSize(1);
+ assertThat(projectSettings.getString("key")).isEqualTo("value");
+ }
+
+ @Test
+ public void return_project_settings() {
+ when(dao.selectProjectProperties(PROJECT_KEY)).thenReturn(newArrayList(
+ new PropertyDto().setKey("1").setValue("val1"),
+ new PropertyDto().setKey("2").setValue("val2"),
+ new PropertyDto().setKey("3").setValue("val3"))
+ );
+
+ Settings projectSettings = underTest.newProjectSettings(PROJECT_KEY);
+
+ assertThat(projectSettings.getString("1")).isEqualTo("val1");
+ assertThat(projectSettings.getString("2")).isEqualTo("val2");
+ assertThat(projectSettings.getString("3")).isEqualTo("val3");
+ }
+
+ @Test
+ public void project_settings_override_global_settings() {
+ settings.setProperty("key", "value");
+ when(dao.selectProjectProperties(PROJECT_KEY)).thenReturn(newArrayList(
+ new PropertyDto().setKey("key").setValue("value2"))
+ );
+
+ Settings projectSettings = underTest.newProjectSettings(PROJECT_KEY);
+ assertThat(projectSettings.getString("key")).isEqualTo("value2");
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryFactoryTest.java
deleted file mode 100644
index 59ed79f206e..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryFactoryTest.java
+++ /dev/null
@@ -1,74 +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.properties;
-
-import com.google.common.collect.Lists;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.config.Settings;
-import org.sonar.db.property.PropertiesDao;
-import org.sonar.db.property.PropertyDto;
-
-import java.util.Map;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class ProjectSettingsRespositoryFactoryTest {
-
- private ProjectSettingsFactory underTest;
-
- @Before
- public void before() {
- Settings settings = mock(Settings.class);
- PropertiesDao dao = mock(PropertiesDao.class);
-
- this.underTest = new ProjectSettingsFactory(settings, dao);
- }
-
- @Test
- public void newProjectSettings_returns_a_ProjectSettings() {
- Settings projectSettings = underTest.newProjectSettings("PROJECT_KEY");
-
- assertThat(projectSettings).isInstanceOf(ProjectSettings.class);
- }
-
- @Test
- public void transform_empty_list_into_empty_map() {
- Map<String, String> propertyMap = underTest.getPropertyMap(Lists.<PropertyDto>newArrayList());
-
- assertThat(propertyMap).isEmpty();
- }
-
- @Test
- public void transform_list_of_properties_in_map_key_value() {
- PropertyDto property1 = new PropertyDto().setKey("1").setValue("val1");
- PropertyDto property2 = new PropertyDto().setKey("2").setValue("val2");
- PropertyDto property3 = new PropertyDto().setKey("3").setValue("val3");
-
- Map<String, String> propertyMap = underTest.getPropertyMap(newArrayList(property1, property2, property3));
-
- assertThat(propertyMap.get("1")).isEqualTo("val1");
- assertThat(propertyMap.get("2")).isEqualTo("val2");
- assertThat(propertyMap.get("3")).isEqualTo("val3");
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryTest.java
deleted file mode 100644
index 6ea1e586b69..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/properties/ProjectSettingsRespositoryTest.java
+++ /dev/null
@@ -1,137 +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.properties;
-
-import com.google.common.collect.Maps;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.config.Settings;
-
-import java.util.HashMap;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class ProjectSettingsRespositoryTest {
-
- private ProjectSettings underTest;
-
- private Settings settings;
-
- @Before
- public void before() {
- this.settings = mock(Settings.class);
- }
-
- @Test
- public void call_global_settings_method_when_no_project_specific_settings() {
- this.underTest = new ProjectSettings(settings, Maps.<String, String>newHashMap());
-
- underTest.getInt("anyKey");
- underTest.getBoolean("anyKey");
- underTest.getString("anyKey");
-
- verify(settings, times(1)).getBoolean(anyString());
- verify(settings, times(1)).getInt(anyString());
- verify(settings, times(1)).getString(anyString());
- }
-
- @Test(expected = NumberFormatException.class)
- public void getInt_property_throws_exception_when_value_is_not_formatted_correctly() {
- HashMap<String, String> properties = Maps.newHashMap();
- properties.put("intKey", "wrongIntValue");
- this.underTest = new ProjectSettings(settings, properties);
-
- underTest.getInt("intKey");
- }
-
- @Test
- public void getInt_property_return_0_when_empty_property() {
- HashMap<String, String> properties = Maps.newHashMap();
- properties.put("intKey", "");
- this.underTest = new ProjectSettings(settings, properties);
-
- int value = underTest.getInt("intKey");
-
- assertThat(value).isEqualTo(0);
- }
-
- @Test
- public void getInt_property_return_the_int_value() {
- HashMap<String, String> properties = Maps.newHashMap();
- properties.put("intKey", "123");
- this.underTest = new ProjectSettings(settings, properties);
-
- int value = underTest.getInt("intKey");
-
- assertThat(value).isEqualTo(123);
- }
-
- @Test
- public void getString_returns_String_property() {
- HashMap<String, String> properties = Maps.newHashMap();
- properties.put("stringKey", "stringValue");
- this.underTest = new ProjectSettings(settings, properties);
-
- String value = underTest.getString("stringKey");
-
- assertThat(value).isEqualTo("stringValue");
- }
-
- @Test
- public void getBoolean_returns_exception_when_value_is_not_formatted_correctly() {
- HashMap<String, String> properties = Maps.newHashMap();
- properties.put("boolKey", "wronglyFormattedBoolean");
- this.underTest = new ProjectSettings(settings, properties);
-
- boolean key = underTest.getBoolean("boolKey");
-
- assertThat(key).isFalse();
- }
-
- @Test
- public void getBoolean_returns_false_when_value_is_empty() {
- HashMap<String, String> properties = Maps.newHashMap();
- properties.put("boolKey", "");
- this.underTest = new ProjectSettings(settings, properties);
-
- boolean key = underTest.getBoolean("boolKey");
-
- assertThat(key).isFalse();
- }
-
- @Test
- public void getBoolean_returns_true_when_value_is_true_ignoring_case() {
- HashMap<String, String> properties = Maps.newHashMap();
- properties.put("boolKey1", "true");
- properties.put("boolKey2", "True");
- this.underTest = new ProjectSettings(settings, properties);
-
- boolean key1 = underTest.getBoolean("boolKey1");
- boolean key2 = underTest.getBoolean("boolKey2");
-
- assertThat(key1).isTrue();
- assertThat(key2).isTrue();
- }
-}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_comments.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_comments.json
index 0827d1f204e..8c143219aa2 100644
--- a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_comments.json
+++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_comments.json
@@ -12,7 +12,6 @@
"creationDate": "2014-01-22T19:10:03+0100",
"fCreationDate": "Jan 22, 2014 10:03 AM",
"transitions": [],
- "actions": ["comment", "assign", "set_tags", "assign_to_me", "plan"],
"comments": [
{
"key": "COMMENT-ABCD",
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_transitions.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_transitions.json
index 9d06cd9d942..fb2e1b2355f 100644
--- a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_transitions.json
+++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/ShowActionTest/show_issue_with_transitions.json
@@ -14,7 +14,6 @@
"creationDate": "2014-01-22T19:10:03+0100",
"fCreationDate": "Jan 22, 2014 10:03 AM",
"transitions": ["reopen"],
- "actions": ["comment"],
"comments": [],
"changelog": [
{
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 deb21bae193..e6488d26abc 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
@@ -140,15 +140,36 @@ class Api::IssuesController < Api::ApiController
def actions
require_parameters :issue
issue_key = params[:issue]
- actions = Internal.issues.listActions(issue_key)
render :json => jsonp(
{
- :actions => actions.map { |t| t.key() }
+ :actions => Internal.issues.listActions(issue_key)
}
)
end
#
+ # POST /api/issues/do_action?issue=<key>&actionKey=<action key>
+ #
+ # -- 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])
+
+ http_status = (result.ok ? 200 : 400)
+ hash = result_to_hash(result)
+
+ respond_to do |format|
+ # if the request header "Accept" is "*/*", then the default format is the first one (json)
+ format.json { render :json => jsonp(hash), :status => result.httpStatus }
+ format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'sonar', :status => http_status) }
+ end
+ end
+
+ #
# Execute a bulk change on a list of issues
#
# POST /api/issues/bulk_change?issue=<key>&text=<text>
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Action.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Action.java
index 4a7254cfabe..46f6dabbf4e 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Action.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Action.java
@@ -30,9 +30,7 @@ import static com.google.common.collect.Lists.newArrayList;
/**
* @since 3.6
- * @deprecated in 5.2. Webapp can not be customized anymore to define actions on issues.
*/
-@Deprecated
public class Action {
private final String key;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Actions.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Actions.java
index 3386cd33cf9..813a3e90990 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Actions.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/action/Actions.java
@@ -25,22 +25,20 @@ import org.sonar.api.server.ServerSide;
/**
* @since 3.6
- * @deprecated in 5.2. Webapp can not be customized anymore to define actions on issues.
*/
-@Deprecated
@ServerSide
public class Actions {
- private final List<Action> actions = new ArrayList<>();
+ private final List<Action> list = new ArrayList<>();
public Action add(String actionKey) {
Action action = new Action(actionKey);
- this.actions.add(action);
+ this.list.add(action);
return action;
}
public List<Action> list() {
- return actions;
+ return list;
}
}