@@ -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> |
@@ -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"); | |||
} | |||
}); | |||
} | |||
} |
@@ -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 | |||
); | |||
} | |||
} |
@@ -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 |
@@ -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> |
@@ -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"); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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> |
@@ -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; | |||
} | |||
} |
@@ -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()); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -138,7 +138,6 @@ public class SearchResponseFormat { | |||
if (fields.contains(SearchAdditionalField.COMMENTS)) { | |||
formatIssueComments(data, issueBuilder, dto); | |||
} | |||
// TODO attributes | |||
result.add(issueBuilder.build()); | |||
} | |||
return result; |
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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"))); | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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\": []}"); | |||
} |
@@ -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")); |
@@ -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; | |||
@@ -90,6 +91,9 @@ public class ShowActionTest { | |||
@Mock | |||
IssueService issueService; | |||
@Mock | |||
ActionService actionService; | |||
@Mock | |||
IssueChangelogService issueChangelogService; | |||
@@ -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"); |
@@ -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"); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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", |
@@ -14,7 +14,6 @@ | |||
"creationDate": "2014-01-22T19:10:03+0100", | |||
"fCreationDate": "Jan 22, 2014 10:03 AM", | |||
"transitions": ["reopen"], | |||
"actions": ["comment"], | |||
"comments": [], | |||
"changelog": [ | |||
{ |
@@ -140,14 +140,35 @@ 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 | |||
# |
@@ -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; |
@@ -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; | |||
} | |||
} |