From: Julien Lancelot Date: Mon, 13 May 2013 15:04:58 +0000 (+0200) Subject: SONAR-3755 Add action plan WS X-Git-Tag: 3.6~429 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e2ae8af9e2a4817fcd7358ac22ec36ee607dc26a;p=sonarqube.git SONAR-3755 Add action plan WS --- diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties index fe8962cb50f..220bce0cfec 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -656,7 +656,8 @@ issues_action_plans.closed_action_plan=Closed action plans issues_action_plans.status.OPEN=Open issues_action_plans.status.CLOSED=Closed issues_action_plans.errors.date.cant_be_in_past=The dead-line can't be in the past - +issues_action_plans.errors.action_plan_does_not_exists=Action plan with key {0} does not exists +issues_action_plans.errors.project_does_not_exists=Project with key {0} does not exists #------------------------------------------------------------------------------ diff --git a/sonar-core/src/main/java/org/sonar/core/issue/ActionPlanStats.java b/sonar-core/src/main/java/org/sonar/core/issue/ActionPlanStats.java index ae6a533e8ca..328b6189191 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/ActionPlanStats.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/ActionPlanStats.java @@ -30,6 +30,7 @@ public class ActionPlanStats implements Serializable { private String key; private String name; + private String projectKey; private String description; private String userLogin; private String status; @@ -71,6 +72,15 @@ public class ActionPlanStats implements Serializable { return this; } + public String projectKey() { + return projectKey; + } + + public ActionPlanStats setProjectKey(String projectKey) { + this.projectKey = projectKey; + return this; + } + public String description() { return description; } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java index 8c48c5c5f2c..40f8797c816 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java @@ -29,6 +29,7 @@ public class DefaultActionPlan implements ActionPlan { private String key; private String name; + private String projectKey; private String description; private String userLogin; private String status; @@ -68,6 +69,15 @@ public class DefaultActionPlan implements ActionPlan { return this; } + public String projectKey() { + return projectKey; + } + + public DefaultActionPlan setProjectKey(String projectKey) { + this.projectKey = projectKey; + return this; + } + public String description() { return description; } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanDto.java index d78f7415e9d..63c4f8859be 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanDto.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanDto.java @@ -43,6 +43,9 @@ public class ActionPlanDto { private Date createdAt; private Date updatedAt; + // joins + private transient String projectKey; + public Long getId() { return id; } @@ -133,6 +136,18 @@ public class ActionPlanDto { return this; } + public String getProjectKey() { + return projectKey; + } + + /** + * Only for unit tests + */ + public ActionPlanDto setProjectKey_unit_test_only(String projectKey) { + this.projectKey = projectKey; + return this; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -159,6 +174,7 @@ public class ActionPlanDto { public DefaultActionPlan toActionPlan() { return DefaultActionPlan.create(name) .setKey(kee) + .setProjectKey(projectKey) .setDescription(description) .setStatus(status) .setDeadLine(deadLine) diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDto.java index e94d76c93cd..cb4d04ef00c 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDto.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDto.java @@ -41,10 +41,10 @@ public class ActionPlanStatsDto { private Date deadLine; private Date createdAt; private Date updatedAt; - private int totalIssues; private int openIssues; - + // joins + private transient String projectKey; public Integer getId() { return id; @@ -154,22 +154,35 @@ public class ActionPlanStatsDto { return this; } + public String getProjectKey() { + return projectKey; + } + + /** + * Only for unit tests + */ + public ActionPlanStatsDto setProjectKey_unit_test_only(String projectKey) { + this.projectKey = projectKey; + return this; + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } - public ActionPlanStats toActionPlanStat(){ + public ActionPlanStats toActionPlanStat() { return ActionPlanStats.create(name) - .setKey(kee) - .setDescription(description) - .setStatus(status) - .setDeadLine(deadLine) - .setUserLogin(userLogin) - .setCreatedAt(createdAt) - .setUpdatedAt(updatedAt) - .setTotalIssues(totalIssues) - .setOpenIssues(openIssues); + .setKey(kee) + .setProjectKey(projectKey) + .setDescription(description) + .setStatus(status) + .setDeadLine(deadLine) + .setUserLogin(userLogin) + .setCreatedAt(createdAt) + .setUpdatedAt(updatedAt) + .setTotalIssues(totalIssues) + .setOpenIssues(openIssues); } } diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanMapper.xml index df71967afb2..663284e21ac 100644 --- a/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanMapper.xml @@ -14,7 +14,8 @@ ap.status as status, ap.deadline as deadLine, ap.created_at as createdAt, - ap.updated_at as updatedAt + ap.updated_at as updatedAt, + p.kee as projectKey @@ -49,39 +50,43 @@ diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanStatsMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanStatsMapper.xml index 092e4747ddc..bbe19b21f49 100644 --- a/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanStatsMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanStatsMapper.xml @@ -14,18 +14,20 @@ ap.status as status, ap.deadline as deadLine, ap.created_at as createdAt, - ap.updated_at as updatedAt + ap.updated_at as updatedAt, + p.kee as projectKey diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanDaoTest.java index 52382cd2900..37fb16915ff 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanDaoTest.java @@ -70,16 +70,17 @@ public class ActionPlanDaoTest extends AbstractDaoTestCase { @Test public void should_find_by_key() { - setupData("should_find_by_key"); + setupData("shared", "should_find_by_key"); ActionPlanDto result = dao.findByKey("ABC"); assertThat(result).isNotNull(); assertThat(result.getKey()).isEqualTo("ABC"); + assertThat(result.getProjectKey()).isEqualTo("org.sonar.Sample"); } @Test public void should_find_by_keys() { - setupData("should_find_by_keys"); + setupData("shared", "should_find_by_keys"); Collection result = dao.findByKeys(newArrayList("ABC", "ABD", "ABE")); assertThat(result).hasSize(3); @@ -87,7 +88,7 @@ public class ActionPlanDaoTest extends AbstractDaoTestCase { @Test public void should_find_open_by_project_id() { - setupData("should_find_open_by_project_id"); + setupData("shared", "should_find_open_by_project_id"); Collection result = dao.findOpenByProjectId(1l); assertThat(result).hasSize(2); @@ -95,7 +96,7 @@ public class ActionPlanDaoTest extends AbstractDaoTestCase { @Test public void should_find_by_name_and_project_id() { - setupData("should_find_by_name_and_project_id"); + setupData("shared", "should_find_by_name_and_project_id"); Collection result = dao.findByNameAndProjectId("SHORT_TERM", 1l); assertThat(result).hasSize(2); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanStatsDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanStatsDaoTest.java index bb5aae2eb57..a346e4bfa44 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanStatsDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanStatsDaoTest.java @@ -39,12 +39,13 @@ public class ActionPlanStatsDaoTest extends AbstractDaoTestCase { @Test public void should_find_by_project() { - setupData("should_find_by_project"); + setupData("shared", "should_find_by_project"); Collection result = dao.findByProjectId(1l); assertThat(result).isNotEmpty(); ActionPlanStatsDto actionPlanStatsDto = result.iterator().next(); + assertThat(actionPlanStatsDto.getProjectKey()).isEqualTo("org.sonar.Sample"); assertThat(actionPlanStatsDto.getTotalIssues()).isEqualTo(3); assertThat(actionPlanStatsDto.getOpenIssues()).isEqualTo(1); } diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanDaoTest/shared.xml new file mode 100644 index 00000000000..396db5527f4 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanDaoTest/shared.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanStatsDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanStatsDaoTest/shared.xml new file mode 100644 index 00000000000..396db5527f4 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanStatsDaoTest/shared.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java index 464622807f9..b0b2ee5ceea 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java @@ -40,6 +40,8 @@ public interface ActionPlan extends Serializable { String name(); + String projectKey(); + @CheckForNull String description(); diff --git a/sonar-server/src/main/java/org/sonar/server/issue/ActionPlanService.java b/sonar-server/src/main/java/org/sonar/server/issue/ActionPlanService.java index 47631268433..cfe588fd800 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/ActionPlanService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/ActionPlanService.java @@ -57,13 +57,13 @@ public class ActionPlanService implements ServerComponent { this.resourceDao = resourceDao; } - public ActionPlan create(ActionPlan actionPlan, String projectKey){ - actionPlanDao.save(ActionPlanDto.toActionDto(actionPlan, findProject(projectKey).getId())); + public ActionPlan create(ActionPlan actionPlan){ + actionPlanDao.save(ActionPlanDto.toActionDto(actionPlan, findProject(actionPlan.projectKey()).getId())); return actionPlan; } - public ActionPlan update(ActionPlan actionPlan, String projectKey){ - actionPlanDao.update(ActionPlanDto.toActionDto(actionPlan, findProject(projectKey).getId())); + public ActionPlan update(ActionPlan actionPlan){ + actionPlanDao.update(ActionPlanDto.toActionDto(actionPlan, findProject(actionPlan.projectKey()).getId())); return actionPlan; } diff --git a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index 8018b648719..d7dcd93c7ab 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -31,6 +31,9 @@ import org.sonar.core.issue.DefaultActionPlan; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssueBuilder; import org.sonar.core.issue.workflow.Transition; +import org.sonar.core.resource.ResourceDao; +import org.sonar.core.resource.ResourceDto; +import org.sonar.core.resource.ResourceQuery; import org.sonar.server.platform.UserSession; import java.text.SimpleDateFormat; @@ -47,13 +50,16 @@ public class InternalRubyIssueService implements ServerComponent { private final IssueService issueService; private final IssueCommentService commentService; private final ActionPlanService actionPlanService; + private final ResourceDao resourceDao; public InternalRubyIssueService(IssueService issueService, IssueCommentService commentService, - ActionPlanService actionPlanService) { + ActionPlanService actionPlanService, + ResourceDao resourceDao) { this.issueService = issueService; this.commentService = commentService; this.actionPlanService = actionPlanService; + this.resourceDao = resourceDao; } public List listTransitions(String issueKey) { @@ -120,44 +126,59 @@ public class InternalRubyIssueService implements ServerComponent { public Result createActionPlan(Map parameters) { // TODO verify authorization - // TODO check existence of projectKey Result result = createActionPlanResult(parameters); if (result.ok()) { - result.setObject(actionPlanService.create(result.get(), parameters.get("projectKey"))); + result.setObject(actionPlanService.create(result.get())); } return result; } public Result updateActionPlan(String key, Map parameters) { // TODO verify authorization - // TODO check existence of projectKey DefaultActionPlan existingActionPlan = (DefaultActionPlan) actionPlanService.findByKey(key); - Result result = createActionPlanResult(parameters, existingActionPlan.name()); - if (result.ok()) { - String projectKey = parameters.get("projectKey"); - DefaultActionPlan actionPlan = (DefaultActionPlan) result.get(); - actionPlan.setKey(existingActionPlan.key()); - actionPlan.setUserLogin(existingActionPlan.userLogin()); - result.setObject(actionPlanService.update(actionPlan, projectKey)); + if (existingActionPlan == null) { + Result result = new Result(); + result.addError("issues_action_plans.errors.action_plan_does_not_exists", key); + return result; + } else { + Result result = createActionPlanResult(parameters, existingActionPlan.name()); + if (result.ok()) { + DefaultActionPlan actionPlan = (DefaultActionPlan) result.get(); + actionPlan.setKey(existingActionPlan.key()); + actionPlan.setUserLogin(existingActionPlan.userLogin()); + result.setObject(actionPlanService.update(actionPlan)); + } + return result; } - return result; } - public ActionPlan closeActionPlan(String actionPlanKey) { + public Result closeActionPlan(String actionPlanKey) { // TODO verify authorization - return actionPlanService.setStatus(actionPlanKey, ActionPlan.STATUS_CLOSED); + Result result = createResultForExistingActionPlan(actionPlanKey); + if (result.ok()) { + result.setObject(actionPlanService.setStatus(actionPlanKey, ActionPlan.STATUS_CLOSED)); + } + return result; } - public ActionPlan openActionPlan(String actionPlanKey) { + public Result openActionPlan(String actionPlanKey) { // TODO verify authorization - return actionPlanService.setStatus(actionPlanKey, ActionPlan.STATUS_OPEN); + Result result = createResultForExistingActionPlan(actionPlanKey); + if (result.ok()) { + result.setObject(actionPlanService.setStatus(actionPlanKey, ActionPlan.STATUS_OPEN)); + } + return result; } - public void deleteActionPlan(String actionPlanKey) { + public Result deleteActionPlan(String actionPlanKey) { // TODO verify authorization - actionPlanService.delete(actionPlanKey); + Result result = createResultForExistingActionPlan(actionPlanKey); + if (result.ok()) { + actionPlanService.delete(actionPlanKey); + } + return result; } @VisibleForTesting @@ -172,7 +193,7 @@ public class InternalRubyIssueService implements ServerComponent { String name = parameters.get("name"); String description = parameters.get("description"); String deadLineParam = parameters.get("deadLine"); - String projectParam = parameters.get("projectKey"); + String projectParam = parameters.get("project"); Date deadLine = null; if (Strings.isNullOrEmpty(name)) { @@ -187,8 +208,13 @@ public class InternalRubyIssueService implements ServerComponent { result.addError("errors.is_too_long", "description", 1000); } - if (Strings.isNullOrEmpty(projectParam)) { + if (Strings.isNullOrEmpty(projectParam) && oldName == null) { result.addError("errors.cant_be_empty", "project"); + } else { + ResourceDto project = resourceDao.getResource(ResourceQuery.create().setKey(projectParam)); + if (project == null) { + result.addError("issues_action_plans.errors.project_does_not_exists", projectParam); + } } if (!Strings.isNullOrEmpty(deadLineParam)) { @@ -208,12 +234,22 @@ public class InternalRubyIssueService implements ServerComponent { result.addError("issues_action_plans.same_name_in_same_project"); } - DefaultActionPlan actionPlan = DefaultActionPlan.create(name) - .setDescription(description) - .setUserLogin(UserSession.get().login()) - .setDeadLine(deadLine); - result.setObject(actionPlan); + if (result.ok()) { + DefaultActionPlan actionPlan = DefaultActionPlan.create(name) + .setProjectKey(projectParam) + .setDescription(description) + .setUserLogin(UserSession.get().login()) + .setDeadLine(deadLine); + result.setObject(actionPlan); + } return result; } -} + private Result createResultForExistingActionPlan(String actionPlanKey) { + Result result = new Result(); + if (findActionPlan(actionPlanKey) == null) { + result.addError("issues_action_plans.errors.action_plan_does_not_exists", actionPlanKey); + } + return result; + } +} \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/action_plans_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/action_plans_controller.rb index 752fccaf867..aff192b569f 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/action_plans_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/action_plans_controller.rb @@ -20,14 +20,189 @@ class Api::ActionPlansController < Api::ApiController + before_filter :admin_required, :only => [ :create, :delete, :update, :close, :open ] + # - # GET /api/issues/search? + # GET /api/action_plan/show?key= # # -- Example - # curl -v -u admin:admin 'http://localhost:9000/api/issues/search?statuses=OPEN,RESOLVED' + # curl -v -u 'http://localhost:9000/api/action_plans/show?key=9b6f89c0-3347-46f6-a6d1-dd6c761240e0' + # + def show + require_parameters :key + + action_plan = Internal.issues.findActionPlan(params[:key]) + hash = {} + hash[:actionPlans] = action_plan_to_hash(action_plan) if action_plan + + respond_to do |format| + format.json { render :json => jsonp(hash) } + format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'actionPlans') } + end + end + + + # + # GET /api/action_plans/search?project= + # + # -- Example + # curl -v -u 'http://localhost:9000/api/action_plans/search?project=org.sonar.Sample' # def search + require_parameters :project + + action_plans = Internal.issues.findActionPlanStats(params[:project]) + hash = {:actionPlans => action_plans.map { |plan| action_plan_to_hash(plan)}} + + respond_to do |format| + format.json { render :json => jsonp(hash) } + format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'actionPlans') } + end + end + + # + # POST /api/action_plans/create + # + # -- Mandatory parameters + # 'name' is the action plan name + # 'project' is the project key to link the action plan to + # + # -- Optional parameters + # 'description' is the plain-text description + # 'deadLine' is the due date of the action plan. Format is 'day/month/year', for instance, '31/12/2013'. + # + # -- Example + # curl -X POST -v -u admin:admin 'http://localhost:9000/api/action_plans/create?name=Current&project=org.sonar.Sample' + # + def create + verify_post_request + require_parameters :project, :name + + result = Internal.issues.createActionPlan(params) + if result.ok() + action_plan = result.get() + render :json => jsonp({:actionPlan => action_plan_to_hash(action_plan)}) + else + render_error(result) + end + end + + # + # POST /api/action_plans/delete + # + # -- Mandatory parameters + # 'key' is the action plan key + # + # -- Example + # curl -X POST -v -u admin:admin 'http://localhost:9000/api/action_plans/delete?key=9b6f89c0-3347-46f6-a6d1-dd6c761240e0' + # + def delete + verify_post_request + require_parameters :key + + result = Internal.issues.deleteActionPlan(params[:key]) + if result.ok() + render_success('Action plan deleted') + else + render_error(result) + end + end + + # + # POST /api/action_plans/update + # + # -- Optional parameters + # 'name' is the action plan name + # 'project' is the project key to link the action plan to + # 'description' is the plain-text description + # 'deadLine' is the due date of the action plan. Format is 'day/month/year', for instance, '31/12/2013'. + # + # -- Example + # curl -X POST -v -u admin:admin 'http://localhost:9000/api/action_plans/update?key=9b6f89c0-3347-46f6-a6d1-dd6c761240e0&name=Current' + # + def update + verify_post_request + require_parameters :key + + result = Internal.issues.updateActionPlan(params[:key], params) + if result.ok() + action_plan = result.get() + render :json => jsonp({:actionPlan => action_plan_to_hash(action_plan)}) + else + render_error(result) + end + end + + # + # POST /api/action_plans/close + # + # -- Mandatory parameters + # 'key' is the action plan key + # + # -- Example + # curl -X POST -v -u admin:admin 'http://localhost:9000/api/action_plans/close?key=9b6f89c0-3347-46f6-a6d1-dd6c761240e0' + # + def close + verify_post_request + require_parameters :key + + result = Internal.issues.closeActionPlan(params[:key]) + if result.ok() + action_plan = result.get() + render :json => jsonp({:actionPlan => action_plan_to_hash(action_plan)}) + else + render_error(result) + end + end + + # + # POST /api/action_plans/open + # + # -- Mandatory parameters + # 'key' is the action plan key + # + # -- Example + # curl -X POST -v -u admin:admin 'http://localhost:9000/api/action_plans/open?key=9b6f89c0-3347-46f6-a6d1-dd6c761240e0' + # + def open + verify_post_request + require_parameters :key + + result = Internal.issues.openActionPlan(params[:key]) + if result.ok() + action_plan = result.get() + render :json => jsonp({:actionPlan => action_plan_to_hash(action_plan)}) + else + render_error(result) + end + end + + + private + + def action_plan_to_hash(action_plan) + hash = {:key => action_plan.key(), :name => action_plan.name(), :status => action_plan.status()} + hash[:project] = action_plan.projectKey() if action_plan.projectKey() && !action_plan.projectKey().blank? + hash[:desc] = action_plan.description() if action_plan.description() && !action_plan.description().blank? + hash[:userLogin] = action_plan.userLogin() if action_plan.userLogin() + hash[:deadLine] = Api::Utils.format_datetime(action_plan.deadLine()) if action_plan.deadLine() + hash[:totalIssues] = action_plan.totalIssues() if action_plan.respond_to?('totalIssues') + hash[:openIssues] = action_plan.openIssues() if action_plan.respond_to?('openIssues') + hash[:createdAt] = Api::Utils.format_datetime(action_plan.createdAt()) if action_plan.createdAt() + hash[:updatedAt] = Api::Utils.format_datetime(action_plan.updatedAt()) if action_plan.updatedAt() + hash + end + + def error_to_hash(msg) + {:msg => message(msg.text(), {:params => msg.params()}).capitalize} + end + def render_error(result) + hash = {:errors => result.errors().map { |error| error_to_hash(error) }} + respond_to do |format| + format.json { render :json => jsonp(hash), :status => 400} + format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'errors', :status => 400)} + end end end \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_action_plans_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_action_plans_controller.rb index e336377b6a5..f4b79293ebf 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_action_plans_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_action_plans_controller.rb @@ -35,7 +35,7 @@ class IssuesActionPlansController < ApplicationController end def save - options = {'projectKey' => @resource.key, 'name' => params[:name], 'description' => params[:description], 'deadLine' => params[:deadline]} + options = {'project' => @resource.key, 'name' => params[:name], 'description' => params[:description], 'deadLine' => params[:deadline]} exiting_action_plan = find_by_key(params[:plan_key]) unless params[:plan_key].blank? if exiting_action_plan diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issues_action_plans/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issues_action_plans/index.html.erb index 534239ded10..62a38dc67b2 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/issues_action_plans/index.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issues_action_plans/index.html.erb @@ -81,7 +81,7 @@ <% @closed_action_plans.each do |plan| deadline = Api::Utils.java_to_ruby_datetime(plan.deadLine()) if plan.deadLine() - updated_at = Api::Utils.java_to_ruby_datetime(plan.updateDate()) + updated_at = Api::Utils.java_to_ruby_datetime(plan.updatedAt()) %> diff --git a/sonar-server/src/test/java/org/sonar/server/issue/ActionPlanServiceTest.java b/sonar-server/src/test/java/org/sonar/server/issue/ActionPlanServiceTest.java index c33adff4d17..203460b97d6 100644 --- a/sonar-server/src/test/java/org/sonar/server/issue/ActionPlanServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/issue/ActionPlanServiceTest.java @@ -57,7 +57,7 @@ public class ActionPlanServiceTest { when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(new ResourceDto().setKey("org.sonar.Sample").setId(1l)); ActionPlan actionPlan = DefaultActionPlan.create("Long term"); - actionPlanService.create(actionPlan, "org.sonar.Sample"); + actionPlanService.create(actionPlan); verify(actionPlanDao).save(any(ActionPlanDto.class)); } diff --git a/sonar-server/src/test/resources/org/sonar/server/issue/InternalRubyIssueServiceTest.java b/sonar-server/src/test/resources/org/sonar/server/issue/InternalRubyIssueServiceTest.java index 08fedad68e6..d13d50de187 100644 --- a/sonar-server/src/test/resources/org/sonar/server/issue/InternalRubyIssueServiceTest.java +++ b/sonar-server/src/test/resources/org/sonar/server/issue/InternalRubyIssueServiceTest.java @@ -24,6 +24,10 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.sonar.api.issue.ActionPlan; +import org.sonar.core.issue.DefaultActionPlan; +import org.sonar.core.resource.ResourceDao; +import org.sonar.core.resource.ResourceDto; +import org.sonar.core.resource.ResourceQuery; import java.util.Map; @@ -35,29 +39,31 @@ import static org.mockito.Mockito.*; public class InternalRubyIssueServiceTest { private InternalRubyIssueService internalRubyIssueService; - private IssueService issueService = mock(IssueService.class); private IssueCommentService commentService = mock(IssueCommentService.class); private ActionPlanService actionPlanService = mock(ActionPlanService.class); + private ResourceDao resourceDao = mock(ResourceDao.class); @Before - public void before(){ - internalRubyIssueService = new InternalRubyIssueService(issueService, commentService, actionPlanService); + public void before() { + ResourceDto project = new ResourceDto().setKey("org.sonar.Sample"); + when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(project); + internalRubyIssueService = new InternalRubyIssueService(issueService, commentService, actionPlanService, resourceDao); } @Test - public void should_create_action_plan(){ + public void should_create_action_plan() { Map parameters = newHashMap(); parameters.put("name", "Long term"); parameters.put("description", "Long term issues"); - parameters.put("projectKey", "org.sonar.Sample"); + parameters.put("project", "org.sonar.Sample"); parameters.put("deadLine", "13/05/2113"); ArgumentCaptor actionPlanCaptor = ArgumentCaptor.forClass(ActionPlan.class); Result result = internalRubyIssueService.createActionPlan(parameters); assertThat(result.ok()).isTrue(); - verify(actionPlanService).create(actionPlanCaptor.capture(), eq("org.sonar.Sample")); + verify(actionPlanService).create(actionPlanCaptor.capture()); ActionPlan actionPlan = actionPlanCaptor.getValue(); assertThat(actionPlan).isNotNull(); @@ -68,7 +74,101 @@ public class InternalRubyIssueServiceTest { } @Test - public void should_get_error_on_action_plan_result_when_no_project(){ + public void should_update_action_plan() { + when(actionPlanService.findByKey("ABCD")).thenReturn(DefaultActionPlan.create("Long term")); + + Map parameters = newHashMap(); + parameters.put("name", "New Long term"); + parameters.put("description", "New Long term issues"); + parameters.put("deadLine", "13/05/2113"); + + ArgumentCaptor actionPlanCaptor = ArgumentCaptor.forClass(ActionPlan.class); + Result result = internalRubyIssueService.updateActionPlan("ABCD", parameters); + assertThat(result.ok()).isTrue(); + + verify(actionPlanService).update(actionPlanCaptor.capture()); + ActionPlan actionPlan = actionPlanCaptor.getValue(); + + assertThat(actionPlan).isNotNull(); + assertThat(actionPlan.key()).isNotNull(); + assertThat(actionPlan.name()).isEqualTo("New Long term"); + assertThat(actionPlan.description()).isEqualTo("New Long term issues"); + assertThat(actionPlan.deadLine()).isNotNull(); + } + + @Test + public void should_update_action_plan_with_new_project() { + when(actionPlanService.findByKey("ABCD")).thenReturn(DefaultActionPlan.create("Long term")); + + Map parameters = newHashMap(); + parameters.put("name", "New Long term"); + parameters.put("description", "New Long term issues"); + parameters.put("deadLine", "13/05/2113"); + parameters.put("project", "org.sonar.MultiSample"); + + ArgumentCaptor actionPlanCaptor = ArgumentCaptor.forClass(ActionPlan.class); + Result result = internalRubyIssueService.updateActionPlan("ABCD", parameters); + assertThat(result.ok()).isTrue(); + + verify(actionPlanService).update(actionPlanCaptor.capture()); + ActionPlan actionPlan = actionPlanCaptor.getValue(); + + assertThat(actionPlan).isNotNull(); + assertThat(actionPlan.key()).isNotNull(); + assertThat(actionPlan.name()).isEqualTo("New Long term"); + assertThat(actionPlan.description()).isEqualTo("New Long term issues"); + assertThat(actionPlan.deadLine()).isNotNull(); + assertThat(actionPlan.projectKey()).isEqualTo("org.sonar.MultiSample"); + } + + @Test + public void should_not_update_action_plan_when_action_plan_is_not_found() { + when(actionPlanService.findByKey("ABCD")).thenReturn(null); + + Result result = internalRubyIssueService.updateActionPlan("ABCD", null); + assertThat(result.ok()).isFalse(); + assertThat(result.errors()).contains(new Result.Message("issues_action_plans.errors.action_plan_does_not_exists", "ABCD")); + } + + @Test + public void should_delete_action_plan() { + when(actionPlanService.findByKey("ABCD")).thenReturn(DefaultActionPlan.create("Long term")); + + Result result = internalRubyIssueService.deleteActionPlan("ABCD"); + verify(actionPlanService).delete("ABCD"); + assertThat(result.ok()).isTrue(); + } + + @Test + public void should_not_delete_action_plan_if_action_plan_not_found() { + when(actionPlanService.findByKey("ABCD")).thenReturn(null); + + Result result = internalRubyIssueService.deleteActionPlan("ABCD"); + verify(actionPlanService, never()).delete("ABCD"); + assertThat(result.ok()).isFalse(); + assertThat(result.errors()).contains(new Result.Message("issues_action_plans.errors.action_plan_does_not_exists", "ABCD")); + } + + @Test + public void should_close_action_plan() { + when(actionPlanService.findByKey("ABCD")).thenReturn(DefaultActionPlan.create("Long term")); + + Result result = internalRubyIssueService.closeActionPlan("ABCD"); + verify(actionPlanService).setStatus(eq("ABCD"), eq("CLOSED")); + assertThat(result.ok()).isTrue(); + } + + @Test + public void should_open_action_plan() { + when(actionPlanService.findByKey("ABCD")).thenReturn(DefaultActionPlan.create("Long term")); + + Result result = internalRubyIssueService.openActionPlan("ABCD"); + verify(actionPlanService).setStatus(eq("ABCD"), eq("OPEN")); + assertThat(result.ok()).isTrue(); + } + + @Test + public void should_get_error_on_action_plan_result_when_no_project() { Map parameters = newHashMap(); parameters.put("name", "Long term"); parameters.put("description", "Long term issues"); @@ -79,11 +179,11 @@ public class InternalRubyIssueServiceTest { } @Test - public void should_get_error_on_action_plan_result_when_no_name(){ + public void should_get_error_on_action_plan_result_when_no_name() { Map parameters = newHashMap(); parameters.put("name", null); parameters.put("description", "Long term issues"); - parameters.put("projectKey", "org.sonar.Sample"); + parameters.put("project", "org.sonar.Sample"); Result result = internalRubyIssueService.createActionPlanResult(parameters); assertThat(result.ok()).isFalse(); @@ -91,11 +191,11 @@ public class InternalRubyIssueServiceTest { } @Test - public void should_get_error_on_action_plan_result_when_name_is_too_long(){ + public void should_get_error_on_action_plan_result_when_name_is_too_long() { Map parameters = newHashMap(); parameters.put("name", createLongString(201)); parameters.put("description", "Long term issues"); - parameters.put("projectKey", "org.sonar.Sample"); + parameters.put("project", "org.sonar.Sample"); Result result = internalRubyIssueService.createActionPlanResult(parameters); assertThat(result.ok()).isFalse(); @@ -103,11 +203,11 @@ public class InternalRubyIssueServiceTest { } @Test - public void should_get_error_on_action_plan_result_when_description_is_too_long(){ + public void should_get_error_on_action_plan_result_when_description_is_too_long() { Map parameters = newHashMap(); parameters.put("name", "Long term"); parameters.put("description", createLongString(1001)); - parameters.put("projectKey", "org.sonar.Sample"); + parameters.put("project", "org.sonar.Sample"); Result result = internalRubyIssueService.createActionPlanResult(parameters); assertThat(result.ok()).isFalse(); @@ -115,11 +215,11 @@ public class InternalRubyIssueServiceTest { } @Test - public void should_get_error_on_action_plan_result_when_dead_line_use_wrong_format(){ + public void should_get_error_on_action_plan_result_when_dead_line_use_wrong_format() { Map parameters = newHashMap(); parameters.put("name", "Long term"); parameters.put("description", "Long term issues"); - parameters.put("projectKey", "org.sonar.Sample"); + parameters.put("project", "org.sonar.Sample"); parameters.put("deadLine", "2013-05-18"); Result result = internalRubyIssueService.createActionPlanResult(parameters); @@ -128,11 +228,11 @@ public class InternalRubyIssueServiceTest { } @Test - public void should_get_error_on_action_plan_result_when_dead_line_is_in_the_past(){ + public void should_get_error_on_action_plan_result_when_dead_line_is_in_the_past() { Map parameters = newHashMap(); parameters.put("name", "Long term"); parameters.put("description", "Long term issues"); - parameters.put("projectKey", "org.sonar.Sample"); + parameters.put("project", "org.sonar.Sample"); parameters.put("deadLine", "01/01/2000"); Result result = internalRubyIssueService.createActionPlanResult(parameters); @@ -141,11 +241,11 @@ public class InternalRubyIssueServiceTest { } @Test - public void should_get_error_on_action_plan_result_when_name_is_already_used_for_project(){ + public void should_get_error_on_action_plan_result_when_name_is_already_used_for_project() { Map parameters = newHashMap(); parameters.put("name", "Long term"); parameters.put("description", "Long term issues"); - parameters.put("projectKey", "org.sonar.Sample"); + parameters.put("project", "org.sonar.Sample"); when(actionPlanService.isNameAlreadyUsedForProject(anyString(), anyString())).thenReturn(true); @@ -154,9 +254,22 @@ public class InternalRubyIssueServiceTest { assertThat(result.errors()).contains(new Result.Message("issues_action_plans.same_name_in_same_project")); } - public String createLongString(int size){ + @Test + public void should_get_error_on_action_plan_result_when_project_not_found() { + Map parameters = newHashMap(); + parameters.put("name", "Long term"); + parameters.put("description", "Long term issues"); + parameters.put("project", "org.sonar.Sample"); + + when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(null); + + Result result = internalRubyIssueService.createActionPlanResult(parameters); + assertThat(result.ok()).isFalse(); + assertThat(result.errors()).contains(new Result.Message("issues_action_plans.errors.project_does_not_exists", "org.sonar.Sample")); + } + public String createLongString(int size) { String result = ""; - for (int i = 0; i