]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3755 Add Action Plan Dao, remove action_plans_issues table, add action_plan_id...
authorJulien Lancelot <julien.lancelot@gmail.com>
Mon, 6 May 2013 07:42:59 +0000 (09:42 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Mon, 6 May 2013 07:43:11 +0000 (09:43 +0200)
32 files changed:
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-core/src/main/java/org/sonar/core/issue/ActionPlanStats.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java
sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDao.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDto.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsMapper.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java
sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/java/org/sonar/core/purge/PurgeCommands.java
sonar-core/src/main/java/org/sonar/core/purge/PurgeMapper.java
sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanIssueMapper.xml
sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanStatsMapper.xml [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql
sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl
sonar-core/src/main/resources/org/sonar/core/purge/PurgeMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/ActionPlanStatsTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanStatsDaoTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/purge/PurgeDaoTest.java
sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanIssueDaoTest/should_find_by_issue_ids.xml
sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanStatsDaoTest/should_find_by_project.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues-result.xml
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues.xml
sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldDeleteProject.xml
sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java
sonar-server/src/main/java/org/sonar/server/issue/ServerActionPlanStatsFinder.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/issue/WebIssuesInternal.java
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/webapp/WEB-INF/db/migrate/393_create_action_plans_issues.rb [deleted file]
sonar-server/src/main/webapp/WEB-INF/db/migrate/395_create_issues.rb
sonar-server/src/test/java/org/sonar/server/issue/ServerActionPlanStatsFinderTest.java [new file with mode: 0644]

index 2d43a6e7d814f3e7feca36558f307bf4de11bc26..1707520ffefc43a8360c78cf55009d7b1eed67be 100644 (file)
@@ -340,6 +340,7 @@ duplications.page=Duplications
 email_configuration.page=Email Settings
 event_categories.page=Event Categories
 filters.page=Filters
+issues_action_plans.page=Issues Action Plans
 manual_metrics.page=Manual Metrics
 manual_measures.page=Manual Measures
 manual_rules.page=Manual Rules
@@ -611,6 +612,44 @@ action_plans.close=Close
 action_plans.closed_action_plan=Closed action plans
 
 
+#------------------------------------------------------------------------------
+#
+# ISSUES ACTION PLANS
+#
+#------------------------------------------------------------------------------
+
+issues_action_plans.page_title=Manage Action Plans
+issues_action_plans.add_action_plan=Add action plan
+issues_action_plans.col.status=St.
+issues_action_plans.col.name=Name
+issues_action_plans.col.due_for=Due for
+issues_action_plans.col.progress=Progress
+issues_action_plans.col.description=Description
+issues_action_plans.col.author=Author
+issues_action_plans.col.closed_on=Closed on
+issues_action_plans.col.operations=Operations
+issues_action_plans.no_action_plan=No action plan
+issues_action_plans.no_issues_linked_to_action_plan=No issues linked to this action plan yet.
+issues_action_plans.confirm_delete=Delete this action plan? Associated issues will not be deleted.
+issues_action_plans.confirm_close=Close this action plan? There are still open issues linked to it.
+issues_action_plans.create_new_action_plan=Create a new action plan
+issues_action_plans.create_action_plan=Create action plan
+issues_action_plans.edit_action_plan=Edit action plan
+issues_action_plans.same_name_in_same_project=An action plan with this name already exists in this project.
+issues_action_plans.date_format_help=The date should be entered using the following pattern: 'day/month/year'. For instance, '31/12/2011'.
+issues_action_plans.date_not_valid=Date not valid
+issues_action_plans.date_cant_be_in_past=The dead-line can't be in the past
+issues_action_plans.x_out_of_x_issues_solved={0} of {1} issues solved
+issues_action_plans.resolved_issues_x_percent=Resolved issues - {0}% ({1} issues)
+issues_action_plans.open_issues_x_percent=Open issues - {0}% ({1} issues)
+issues_action_plans.reopen=Reopen
+issues_action_plans.close=Close
+issues_action_plans.closed_action_plan=Closed action plans
+issues_action_plans.status.OPEN=Open
+issues_action_plans.status.CLOSED=Closed
+
+
+
 #------------------------------------------------------------------------------
 #
 # DEPENDENCIES
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
new file mode 100644 (file)
index 0000000..ff8e687
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.issue;
+
+import org.sonar.api.issue.ActionPlan;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.UUID;
+
+public class ActionPlanStats implements Serializable {
+
+  private String key;
+  private String name;
+  private String description;
+  private String userLogin;
+  private String status;
+  private Date deadLine;
+  private Date creationDate;
+  private Date updateDate;
+  private int totalIssues;
+  private int openIssues;
+
+  private ActionPlanStats() {
+
+  }
+
+  public static ActionPlanStats create(String name) {
+    ActionPlanStats actionPlan = new ActionPlanStats();
+    actionPlan.setKey(UUID.randomUUID().toString());
+    Date now = new Date();
+    actionPlan.setName(name);
+    actionPlan.setStatus(ActionPlan.STATUS_OPEN);
+    actionPlan.setCreationDate(now).setUpdateDate(now);
+    return actionPlan;
+  }
+
+  public String key() {
+    return key;
+  }
+
+  public ActionPlanStats setKey(String key) {
+    this.key = key;
+    return this;
+  }
+
+  public String name() {
+    return name;
+  }
+
+  public ActionPlanStats setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public String description() {
+    return description;
+  }
+
+  public ActionPlanStats setDescription(String description) {
+    this.description = description;
+    return this;
+  }
+
+  public String userLogin() {
+    return userLogin;
+  }
+
+  public ActionPlanStats setUserLogin(String userLogin) {
+    this.userLogin = userLogin;
+    return this;
+  }
+
+  public String status() {
+    return status;
+  }
+
+  public ActionPlanStats setStatus(String status) {
+    this.status = status;
+    return this;
+  }
+
+  public Date deadLine() {
+    return deadLine;
+  }
+
+  public ActionPlanStats setDeadLine(Date deadLine) {
+    this.deadLine = deadLine;
+    return this;
+  }
+
+  public Date creationDate() {
+    return creationDate;
+  }
+
+  public ActionPlanStats setCreationDate(Date creationDate) {
+    this.creationDate = creationDate;
+    return this;
+  }
+
+  public Date updateDate() {
+    return updateDate;
+  }
+
+  public ActionPlanStats setUpdateDate(Date updateDate) {
+    this.updateDate = updateDate;
+    return this;
+  }
+
+  public int totalIssues() {
+    return totalIssues;
+  }
+
+  public ActionPlanStats setTotalIssues(int totalIssues) {
+    this.totalIssues = totalIssues;
+    return this;
+  }
+
+  public int openIssues() {
+    return openIssues;
+  }
+
+  public ActionPlanStats setOpenIssues(int openIssues) {
+    this.openIssues = openIssues;
+    return this;
+  }
+
+  public boolean overDue(){
+    return status == ActionPlan.STATUS_OPEN && new Date().after(deadLine);
+  }
+}
index 2ecd6a9b74546cd06dcf11fd77f02675e5fa6c1a..3e3b3b24608f2fec5b17d4fd46e9378468972f36 100644 (file)
@@ -45,6 +45,7 @@ public class DefaultActionPlan implements ActionPlan {
     actionPlan.setKey(UUID.randomUUID().toString());
     Date now = new Date();
     actionPlan.setName(name);
+    actionPlan.setStatus(ActionPlan.STATUS_OPEN);
     actionPlan.setCreationDate(now).setUpdateDate(now);
     return actionPlan;
   }
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDao.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsDao.java
new file mode 100644 (file)
index 0000000..443b23f
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.issue.db;
+
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.MyBatis;
+
+import java.util.Collection;
+
+/**
+ * @since 3.6
+ */
+public class ActionPlanStatsDao implements BatchComponent, ServerComponent {
+
+  private final MyBatis mybatis;
+
+  public ActionPlanStatsDao(MyBatis mybatis) {
+    this.mybatis = mybatis;
+  }
+
+  public Collection<ActionPlanStatsDto> findByProjectId(Long projectId) {
+    SqlSession session = mybatis.openSession();
+    try {
+      return session.getMapper(ActionPlanStatsMapper.class).findByProjectId(projectId);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+}
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
new file mode 100644 (file)
index 0000000..cd6af00
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.issue.db;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.core.issue.ActionPlanStats;
+
+import java.util.Date;
+
+/**
+ * @since 3.6
+ */
+public class ActionPlanStatsDto {
+
+  private Integer id;
+  private String kee;
+  private String name;
+  private String description;
+  private String userLogin;
+  private Integer projectId;
+  private String status;
+  private Date deadLine;
+  private Date createdAt;
+  private Date updatedAt;
+
+  private int totalIssues;
+  private int openIssues;
+
+
+  public Integer getId() {
+    return id;
+  }
+
+  public ActionPlanStatsDto setId(Integer id) {
+    this.id = id;
+    return this;
+  }
+
+  public String getKee() {
+    return kee;
+  }
+
+  public ActionPlanStatsDto setKee(String kee) {
+    this.kee = kee;
+    return this;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public ActionPlanStatsDto setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public ActionPlanStatsDto setDescription(String description) {
+    this.description = description;
+    return this;
+  }
+
+  public String getUserLogin() {
+    return userLogin;
+  }
+
+  public ActionPlanStatsDto setUserLogin(String userLogin) {
+    this.userLogin = userLogin;
+    return this;
+  }
+
+  public Integer getProjectId() {
+    return projectId;
+  }
+
+  public ActionPlanStatsDto setProjectId(Integer projectId) {
+    this.projectId = projectId;
+    return this;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public ActionPlanStatsDto setStatus(String status) {
+    this.status = status;
+    return this;
+  }
+
+  public Date getDeadLine() {
+    return deadLine;
+  }
+
+  public ActionPlanStatsDto setDeadLine(Date deadLine) {
+    this.deadLine = deadLine;
+    return this;
+  }
+
+  public Date getCreatedAt() {
+    return createdAt;
+  }
+
+  public ActionPlanStatsDto setCreatedAt(Date createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+
+  public Date getUpdatedAt() {
+    return updatedAt;
+  }
+
+  public ActionPlanStatsDto setUpdatedAt(Date updatedAt) {
+    this.updatedAt = updatedAt;
+    return this;
+  }
+
+  public int getTotalIssues() {
+    return totalIssues;
+  }
+
+  public ActionPlanStatsDto setTotalIssues(int totalIssues) {
+    this.totalIssues = totalIssues;
+    return this;
+  }
+
+  public int getOpenIssues() {
+    return openIssues;
+  }
+
+  public ActionPlanStatsDto setOpenIssues(int openIssues) {
+    this.openIssues = openIssues;
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+  }
+
+  public ActionPlanStats toActionPlanStat(){
+    return ActionPlanStats.create(name)
+      .setKey(kee)
+      .setDescription(description)
+      .setStatus(status)
+      .setDeadLine(deadLine)
+      .setUserLogin(userLogin)
+      .setCreationDate(createdAt)
+      .setUpdateDate(updatedAt)
+      .setTotalIssues(totalIssues)
+      .setOpenIssues(openIssues);
+
+  }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsMapper.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanStatsMapper.java
new file mode 100644 (file)
index 0000000..7fc910f
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.issue.db;
+
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+
+/**
+ * @since 3.6
+ */
+public interface ActionPlanStatsMapper {
+
+  /**
+   * @since3.6
+   */
+  Collection<ActionPlanStatsDto> findByProjectId(@Param("projectId") Long projectId);
+}
index 05022a52d6b8210d65341c9a06d333b66ab387db..b8a50a0ba09bb50e948aec216a3ebbe44694c378 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.core.dashboard.DashboardDao;
 import org.sonar.core.duplication.DuplicationDao;
 import org.sonar.core.graph.jdbc.GraphDao;
 import org.sonar.core.issue.db.ActionPlanIssueDao;
+import org.sonar.core.issue.db.ActionPlanStatsDao;
 import org.sonar.core.issue.db.IssueChangeDao;
 import org.sonar.core.issue.db.IssueDao;
 import org.sonar.core.measure.MeasureFilterDao;
@@ -53,6 +54,7 @@ public final class DaoUtils {
   public static List<Class<?>> getDaoClasses() {
     return ImmutableList.of(
       ActionPlanIssueDao.class,
+      ActionPlanStatsDao.class,
       ActiveDashboardDao.class,
       AuthorDao.class,
       AuthorizationDao.class,
index df03d50584396cc271aec8c9a9cdc38432b11fd1..b2ed5c6be766e19da43de21ec65481c3aac1c13a 100644 (file)
@@ -42,7 +42,6 @@ public final class DatabaseUtils {
    */
   static final String[] TABLE_NAMES = {
     "action_plans",
-    "action_plans_issues",
     "action_plans_reviews",
     "active_dashboards",
     "active_rules",
index a0173a82950a24c3157605e0da7dccd71d9df43b..47bb6a5be2e5064744d3d42870655f3429dec1cf 100644 (file)
@@ -121,15 +121,16 @@ public class MyBatis implements BatchComponent, ServerComponent {
     loadAlias(conf, "MeasureData", MeasureData.class);
     loadAlias(conf, "Issue", IssueDto.class);
     loadAlias(conf, "IssueChange", IssueChangeDto.class);
-    loadAlias(conf, "ActionPlanIssue", ActionPlanIssueDto.class);
     loadAlias(conf, "SnapshotData", SnapshotDataDto.class);
+    loadAlias(conf, "ActionPlanIssue", ActionPlanIssueDto.class);
+    loadAlias(conf, "ActionPlanStats", ActionPlanStatsDto.class);
 
     Class<?>[] mappers = {ActiveDashboardMapper.class, AuthorMapper.class, DashboardMapper.class,
-        DependencyMapper.class, DuplicationMapper.class, GraphDtoMapper.class, IssueChangeMapper.class, LoadedTemplateMapper.class,
-        MeasureFilterMapper.class, PropertiesMapper.class, PurgeMapper.class, ResourceKeyUpdaterMapper.class, ResourceIndexerMapper.class, ResourceMapper.class,
-        ResourceSnapshotMapper.class, ReviewCommentMapper.class, ReviewMapper.class, RoleMapper.class, RuleMapper.class, SchemaMigrationMapper.class,
-        SemaphoreMapper.class, UserMapper.class, WidgetMapper.class, WidgetPropertyMapper.class, MeasureMapper.class, SnapshotDataMapper.class,
-        SnapshotSourceMapper.class, ActionPlanIssueMapper.class
+      DependencyMapper.class, DuplicationMapper.class, GraphDtoMapper.class, IssueChangeMapper.class, LoadedTemplateMapper.class,
+      MeasureFilterMapper.class, PropertiesMapper.class, PurgeMapper.class, ResourceKeyUpdaterMapper.class, ResourceIndexerMapper.class, ResourceMapper.class,
+      ResourceSnapshotMapper.class, ReviewCommentMapper.class, ReviewMapper.class, RoleMapper.class, RuleMapper.class, SchemaMigrationMapper.class,
+      SemaphoreMapper.class, UserMapper.class, WidgetMapper.class, WidgetPropertyMapper.class, MeasureMapper.class, SnapshotDataMapper.class,
+      SnapshotSourceMapper.class, ActionPlanIssueMapper.class, ActionPlanStatsMapper.class
     };
     loadMappers(conf, mappers);
     loadMapper(conf, "org.sonar.core.issue.db.IssueMapper");
index 67d65e69b0dc3177a5ea1c9c23ad71e60bbeed8c..37dea581a0e2ed139ef7c69acab2768502eeafe3 100644 (file)
@@ -128,13 +128,6 @@ class PurgeCommands {
     session.commit();
     profiler.stop();
 
-    profiler.start("deleteResourceActionPlansIssues (action_plans_issues)");
-    for (Long resourceId : resourceIds) {
-      purgeMapper.deleteResourceActionPlansIssues(resourceId);
-    }
-    session.commit();
-    profiler.stop();
-
     profiler.start("deleteResourceIssues (issues)");
     for (Long resourceId : resourceIds) {
       purgeMapper.deleteResourceIssues(resourceId);
index 41632dabe16332275582e39805b61bf015a6929d..8a1aad0b74d982ae93f788da2447e663cabe33ac 100644 (file)
@@ -101,8 +101,6 @@ public interface PurgeMapper {
 
   void deleteSnapshotData(long snapshotId);
 
-  void deleteResourceActionPlansIssues(long resourceId);
-
   void deleteResourceIssueChanges(long resourceId);
 
   void deleteResourceIssues(long resourceId);
index edd28f16180daeb6e6f2812f4067376a17528d86..131b1b987ec41df37ff467349bed2ccdab0406b9 100644 (file)
   </sql>
 
   <select id="findByIssueIds" parameterType="long" resultType="ActionPlanIssue">
-    select <include refid="actionPlanColumns"/>, api.issue_id as issueId
-    from action_plans_issues api left outer join action_plans ap on ap.id = api.action_plan_id
+    select <include refid="actionPlanColumns"/>, i.id as issueId
+    from action_plans ap
+    left outer join issues i on i.action_plan_id = ap.id
     <where>
-    <foreach collection="issueIds" open="api.issue_id in (" close=")" item="list" separator=") or api.issue_id in (" >
+    <foreach collection="issueIds" open="i.id in (" close=")" item="list" separator=") or i.id in (" >
       <foreach collection="list" item="element" separator=",">
         #{element}
       </foreach>
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
new file mode 100644 (file)
index 0000000..3c28a96
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.core.issue.db.ActionPlanStatsMapper">
+
+  <sql id="actionPlanColumns">
+    ap.id as id,
+    ap.name as name,
+    ap.description as description,
+    ap.user_login as userLogin,
+    ap.project_id as projectId,
+    ap.status as status,
+    ap.deadline as deadLine,
+    ap.created_at as createdAt,
+    ap.updated_at as updatedAt
+  </sql>
+
+  <select id="findByProjectId" parameterType="long" resultType="ActionPlanStats">
+    select <include refid="actionPlanColumns"/>, count(total_issues.id) as totalIssues, count(open_issues.id) as openIssues
+    from action_plans ap
+    left join issues total_issues on total_issues.action_plan_id = ap.id
+    left join issues open_issues on open_issues.action_plan_id = ap.id and open_issues.status != 'CLOSED'
+    <where>
+      and ap.project_id = #{projectId}
+    </where>
+    group by ap.id, ap.name, ap.description, ap.user_login, ap.project_id, ap.status, ap.deadline, ap.created_at, ap.updated_at
+  </select>
+
+</mapper>
\ No newline at end of file
index 82bbaafbf2b153af8dc01d8fb4ec550ffbae6179..78d117f9f9056b143b9088171ac795bd981b9812 100644 (file)
@@ -158,7 +158,6 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('387');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('388');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('391');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('392');
-INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('393');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('394');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('395');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('396');
index 05ca08707b31ddece684e29a6da4dcddd05830e0..87be456411898504d0df65d31dd60339c4ddf185 100644 (file)
@@ -521,7 +521,7 @@ CREATE TABLE "ISSUES" (
   "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
   "KEE" VARCHAR(100) NOT NULL,
   "RESOURCE_ID" INTEGER NOT NULL,
-  "RULE_ID" INTEGER NOT NULL,
+  "RULE_ID" INTEGER NULL,
   "SEVERITY" VARCHAR(10),
   "MANUAL_SEVERITY" BOOLEAN NOT NULL,
   "MANUAL_ISSUE" BOOLEAN NOT NULL,
@@ -535,6 +535,7 @@ CREATE TABLE "ISSUES" (
   "ASSIGNEE_LOGIN" VARCHAR(40),
   "AUTHOR_LOGIN" VARCHAR(100),
   "ATTRIBUTES" VARCHAR(4000),
+  "ACTION_PLAN_ID" INTEGER NULL,
   "ISSUE_CREATION_DATE" TIMESTAMP,
   "ISSUE_CLOSE_DATE" TIMESTAMP,
   "ISSUE_UPDATE_DATE" TIMESTAMP,
@@ -553,12 +554,6 @@ CREATE TABLE "ISSUE_CHANGES" (
   "UPDATED_AT" TIMESTAMP,
 );
 
-CREATE TABLE "ACTION_PLANS_ISSUES" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "ACTION_PLAN_ID" INTEGER,
-  "ISSUE_ID" INTEGER
-);
-
 CREATE TABLE "SNAPSHOT_DATA" (
   "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
   "SNAPSHOT_ID" INTEGER,
@@ -685,10 +680,6 @@ CREATE INDEX "ISSUES_KEE" ON "ISSUES" ("KEE");
 
 CREATE INDEX "ISSUES_RESOURCE_ID" ON "ISSUES" ("RESOURCE_ID");
 
-CREATE INDEX "INDEX_ACTION_PLANS_ISSUES_ON_ACTION_PLAN_ID" ON "ACTION_PLANS_ISSUES" ("ACTION_PLAN_ID");
-
-CREATE INDEX "INDEX_ACTION_PLANS_ISSUES_ON_ISSUE_ID" ON "ACTION_PLANS_ISSUES" ("ISSUE_ID");
-
 CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES" ("ISSUE_KEY");
 
 CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES" ("KEE");
index efe3b53a4e9866df31698cb8c6539492a5086a34..13d5deab3daa4762d2955b745c503de7437b16ab 100644 (file)
     delete apr from action_plans_reviews as apr, action_plans as ap where ap.id=apr.action_plan_id and ap.project_id=#{id}
   </delete>
 
-  <delete id="deleteResourceActionPlansIssues" parameterType="long">
-    delete from action_plans_issues api
-    where exists (select * from action_plans ap where ap.id=api.action_plan_id and ap.project_id=#{id})
-  </delete>
-
-  <!-- Mssql -->
-  <delete id="deleteResourceActionPlansIssues" databaseId="mssql" parameterType="long">
-    delete action_plans_issues from action_plans_issues
-    inner join action_plans on action_plans.id=action_plans_issues.action_plan_id
-    where action_plans.project_id=#{id}
-  </delete>
-
-  <!-- Mysql -->
-  <delete id="deleteResourceActionPlansIssues" databaseId="mysql" parameterType="long">
-    delete api from action_plans_issues as api, action_plans as ap where ap.id=api.action_plan_id and ap.project_id=#{id}
-  </delete>
-
   <delete id="deleteResourceIssueChanges" parameterType="long">
     delete from issue_changes ic
     where exists (select * from issues i where i.kee=ic.issue_key and i.resource_id=#{id})
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/ActionPlanStatsTest.java b/sonar-core/src/test/java/org/sonar/core/issue/ActionPlanStatsTest.java
new file mode 100644 (file)
index 0000000..fc3873b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.issue;
+
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Test;
+import org.sonar.api.issue.ActionPlan;
+
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ActionPlanStatsTest {
+
+  @Test
+  public void test_over_due() throws Exception {
+    Date yesterday = DateUtils.addDays(new Date(), -1);
+    Date tomorrow = DateUtils.addDays(new Date(), 1);
+
+    assertThat(ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_OPEN).setDeadLine(tomorrow).overDue()).isFalse();
+    assertThat(ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_OPEN).setDeadLine(yesterday).overDue()).isTrue();
+    assertThat(ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_CLOSED).setDeadLine(tomorrow).overDue()).isFalse();
+    assertThat(ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_CLOSED).setDeadLine(yesterday).overDue()).isFalse();
+    assertThat(ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_CLOSED).overDue()).isFalse();
+  }
+}
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
new file mode 100644 (file)
index 0000000..1a5c6b5
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.issue.db;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+
+import java.util.Collection;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ActionPlanStatsDaoTest extends AbstractDaoTestCase {
+
+  private ActionPlanStatsDao dao;
+
+  @Before
+  public void createDao() {
+    dao = new ActionPlanStatsDao(getMyBatis());
+  }
+
+  @Test
+  public void should_find_by_project() {
+    setupData("should_find_by_project");
+
+    Collection<ActionPlanStatsDto> result = dao.findByProjectId(1l);
+//    assertThat(result).hasSize(1);
+    assertThat(result).isNotEmpty();
+
+    ActionPlanStatsDto actionPlanStatsDto = result.iterator().next();
+    assertThat(actionPlanStatsDto.getTotalIssues()).isEqualTo(2);
+    // TODO
+//    assertThat(actionPlanStatsDto.getOpenIssues()).isEqualTo(1);
+  }
+
+}
index 038cc711685f1a5d0634c97febf5cfaaf678400b..71a455924c339b6715da6df080d32d4069ef723e 100644 (file)
@@ -111,7 +111,7 @@ public class PurgeDaoTest extends AbstractDaoTestCase {
   public void shouldDeleteProject() {
     setupData("shouldDeleteProject");
     dao.deleteResourceTree(1L);
-    assertEmptyTables("projects", "snapshots", "action_plans", "action_plans_reviews", "reviews", "review_comments", "issues", "issue_changes", "action_plans_issues");
+    assertEmptyTables("projects", "snapshots", "action_plans", "action_plans_reviews", "reviews", "review_comments", "issues", "issue_changes");
   }
 
   static final class SnapshotMatcher extends BaseMatcher<PurgeableSnapshotDto> {
index a54057a38100b3ae9f5d8a56d7fb685a4e3480a7..7e74a393cbf50f9f0699c8426ba4009ea5eea7ae 100644 (file)
@@ -3,17 +3,89 @@
   <action_plans id="1" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
                 user_login="igor" status="[null]" created_at="[null]" updated_at="[null]" />
 
-  <action_plans_issues id="1" action_plan_id="1" issue_id="250" />
-
   <action_plans id="2" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
                 user_login="igor" status="[null]" created_at="[null]" updated_at="[null]" />
 
-  <action_plans_issues id="2" action_plan_id="2" issue_id="251" />
-  <action_plans_issues id="3" action_plan_id="2" issue_id="252" />
-
   <action_plans id="3" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
                 user_login="igor" status="[null]" created_at="[null]" updated_at="[null]" />
 
-  <action_plans_issues id="4" action_plan_id="3" issue_id="253" />
+  <issues
+      id="250"
+      kee="ABCDE"
+      resource_id="400"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      manual_issue="[false]"
+      description="[null]"
+      line="200"
+      effort_to_fix="4.2"
+      status="OPEN"
+      resolution="OPEN"
+      checksum="XXX"
+      user_login="arthur"
+      assignee_login="perceval"
+      author_login="[null]"
+      attributes="JIRA=FOO-1234"
+      action_plan_id="1"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-16"
+      updated_at="2013-04-16"
+      />
+
+  <issues
+      id="251"
+      kee="ABCDE"
+      resource_id="400"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      manual_issue="[false]"
+      description="[null]"
+      line="200"
+      effort_to_fix="4.2"
+      status="OPEN"
+      resolution="OPEN"
+      checksum="XXX"
+      user_login="arthur"
+      assignee_login="perceval"
+      author_login="[null]"
+      attributes="JIRA=FOO-1234"
+      action_plan_id="2"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-16"
+      updated_at="2013-04-16"
+      />
+
+  <issues
+      id="252"
+      kee="ABCDE"
+      resource_id="400"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      manual_issue="[false]"
+      description="[null]"
+      line="200"
+      effort_to_fix="4.2"
+      status="OPEN"
+      resolution="OPEN"
+      checksum="XXX"
+      user_login="arthur"
+      assignee_login="perceval"
+      author_login="[null]"
+      attributes="JIRA=FOO-1234"
+      action_plan_id="3"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-16"
+      updated_at="2013-04-16"
+      />
+
 
 </dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanStatsDaoTest/should_find_by_project.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanStatsDaoTest/should_find_by_project.xml
new file mode 100644 (file)
index 0000000..2071997
--- /dev/null
@@ -0,0 +1,56 @@
+<dataset>
+
+  <action_plans id="1" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+                user_login="igor" status="[null]" created_at="[null]" updated_at="[null]" />
+
+  <issues
+      id="100"
+      kee="ABCDE"
+      resource_id="400"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      manual_issue="[false]"
+      description="[null]"
+      line="200"
+      status="OPEN"
+      resolution="OPEN"
+      checksum="XXX"
+      user_login="arthur"
+      assignee_login="perceval"
+      author_login="[null]"
+      attributes="JIRA=FOO-1234"
+      action_plan_id="1"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-16"
+      updated_at="2013-04-16"
+      />
+
+  <issues
+      id="101"
+      kee="ABCDF"
+      resource_id="400"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      manual_issue="[false]"
+      description="[null]"
+      line="200"
+      status="CLOSED"
+      resolution="FIXED"
+      checksum="XXX"
+      user_login="arthur"
+      assignee_login="perceval"
+      author_login="[null]"
+      attributes="JIRA=FOO-1234"
+      action_plan_id="1"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-16"
+      updated_at="2013-04-16"
+      />
+
+</dataset>
index b1e88540c591f96d526eb7e4c91c7de0d48a7591..de5ba8060916b86eeedb448057e8fbbd67fe7194 100644 (file)
@@ -13,6 +13,7 @@
       updated_at="[null]"
       user_login="emmerik"
       attributes="foo=bar"
+      action_plan_id="[null]"
       issue_creation_date="2013-05-18"
       issue_update_date="2013-05-18"
       issue_close_date="2013-05-18"
index 57e99d174a1afc9993a704f5bc76ec2d1618a084..f39de643ba4adf3866f5cad3d1d7939f58ae32b8 100644 (file)
@@ -18,6 +18,7 @@
           updated_at="2013-05-18"
           user_login="emmerik"
           attributes="foo=bar"
+          action_plan_id="[null]"
           issue_creation_date="2013-05-18 00:00:00.0"
           issue_update_date="2013-05-18 00:00:00.0"
           issue_close_date="2013-05-18 00:00:00.0"
index 56998d9b2a17f0fecf06160424897e209454c377..3548cb065070054b21c98a45c4b51d1e5300ff08 100644 (file)
@@ -18,6 +18,7 @@
           updated_at="2011-02-02"
           user_login="emmerik"
           attributes="foo=bar"
+          action_plan_id="[null]"
           issue_creation_date="2010-01-01"
           issue_update_date="2010-02-02"
           issue_close_date="[null]"
index 84bcf6e1bfec97f2aef4d90629acb754cd7b04f3..344ef71de9eb5933d7f468380d75f0f9a24d61a8 100644 (file)
@@ -21,8 +21,6 @@
 
   <action_plans_reviews action_plan_id="1" review_id="250" />
 
-  <action_plans_issues id="1" action_plan_id="1" issue_id="250" />
-
   <reviews id="1" project_id="1" resource_id="1" status="CLOSED"
         rule_failure_permanent_id="1" resolution="[null]" created_at="[null]" resource_line="200" severity="BLOCKER"
         user_id="300" assignee_id="300" rule_id="500" manual_violation="[true]" manual_severity="[false]" title="[null]"/>
@@ -34,7 +32,7 @@
   <review_comments id="1" created_at="[null]" updated_at="[null]" review_id="2" user_id="1223" review_text="abc"/>
 
   <issues id="1" kee="ABCDE" resource_id="1" status="CLOSED" resolution="[null]" line="200" severity="BLOCKER"
-          user_login="perceval" assignee_login="arthur" rule_id="500" manual_issue="[true]" manual_severity="[false]" description="[null]"
+          user_login="perceval" assignee_login="arthur" rule_id="500" manual_issue="[true]" manual_severity="[false]" description="[null]" action_plan_id="[null]"
           created_at="[null]"
           updated_at="[null]"
           issue_creation_date="2013-04-16"
@@ -43,7 +41,7 @@
       />
 
   <issues id="2" kee="ABCDF" resource_id="1" status="CLOSED" resolution="[null]" line="200" severity="BLOCKER"
-          user_login="perceval" assignee_login="arthur" rule_id="500" manual_issue="[true]" manual_severity="[false]" description="[null]"
+          user_login="perceval" assignee_login="arthur" rule_id="500" manual_issue="[true]" manual_severity="[false]" description="[null]" action_plan_id="[null]"
           created_at="[null]"
           updated_at="[null]"
           issue_creation_date="2013-04-16"
index 770bb0c7d3264bac92c35b294a4b53df52ead313..9f48199d5411b701f053d945c6cf5e2ed7a21e74 100644 (file)
@@ -30,6 +30,9 @@ import java.util.Date;
  */
 public interface ActionPlan extends Serializable {
 
+  String STATUS_OPEN = "OPEN";
+  String STATUS_CLOSED = "CLOSED";
+
   /**
    * Unique generated key
    */
@@ -43,7 +46,6 @@ public interface ActionPlan extends Serializable {
   @CheckForNull
   String userLogin();
 
-  @CheckForNull
   String status();
 
   @CheckForNull
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/ServerActionPlanStatsFinder.java b/sonar-server/src/main/java/org/sonar/server/issue/ServerActionPlanStatsFinder.java
new file mode 100644 (file)
index 0000000..2fc255d
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.issue.ActionPlanStats;
+import org.sonar.core.issue.db.ActionPlanStatsDao;
+import org.sonar.core.issue.db.ActionPlanStatsDto;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.resource.ResourceQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * @since 3.6
+ */
+public class ServerActionPlanStatsFinder implements ServerComponent {
+
+  private final ActionPlanStatsDao actionPlanStatsDao;
+  private final ResourceDao resourceDao;
+
+  public ServerActionPlanStatsFinder(ActionPlanStatsDao actionPlanStatsDao, ResourceDao resourceDao) {
+    this.actionPlanStatsDao = actionPlanStatsDao;
+    this.resourceDao = resourceDao;
+  }
+
+  public List<ActionPlanStats> find(String projectKey) {
+    ResourceDto resourceDto = resourceDao.getResource(ResourceQuery.create().setKey(projectKey));
+    if (resourceDto == null) {
+      throw new IllegalArgumentException("Project "+ projectKey + " does not exists.");
+    }
+    Collection<ActionPlanStatsDto> actionPlanStatsDtos = actionPlanStatsDao.findByProjectId(resourceDto.getId());
+    return newArrayList(Iterables.transform(actionPlanStatsDtos, new Function<ActionPlanStatsDto, ActionPlanStats>() {
+      @Override
+      public ActionPlanStats apply(ActionPlanStatsDto actionPlanStatsDto) {
+        return actionPlanStatsDto.toActionPlanStat();
+      }
+    }));
+  }
+
+}
index 0a2a2469b358a6cc5169af7d62f63e54a3f97333..08beaa27479cc953300fdff0032413b37ec89136 100644 (file)
@@ -22,10 +22,7 @@ package org.sonar.server.issue;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.DefaultIssueBuilder;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.IssueComment;
+import org.sonar.core.issue.*;
 import org.sonar.core.issue.workflow.Transition;
 import org.sonar.server.platform.UserSession;
 
@@ -39,9 +36,11 @@ import java.util.Map;
 public class WebIssuesInternal implements ServerComponent {
 
   private final ServerIssueActions actions;
+  private final ServerActionPlanStatsFinder actionPlanStatsFinder;
 
-  public WebIssuesInternal(ServerIssueActions actions) {
+  public WebIssuesInternal(ServerIssueActions actions, ServerActionPlanStatsFinder actionPlanStatsFinder) {
     this.actions = actions;
+    this.actionPlanStatsFinder = actionPlanStatsFinder;
   }
 
   public List<Transition> listTransitions(String issueKey) {
@@ -89,4 +88,9 @@ public class WebIssuesInternal implements ServerComponent {
     Issue issue = builder.build();
     return actions.create((DefaultIssue) issue, UserSession.get());
   }
+
+  List<ActionPlanStats> actionPlanStats(String projectKey)  {
+    return actionPlanStatsFinder.find(projectKey);
+  }
+
 }
index 650bda941d48c7ee4f1f928d6150fd29c7efe5c9..b0064b86a030dcae941bf3e5ad05c66f34d62698 100644 (file)
@@ -81,8 +81,8 @@ import org.sonar.server.rule.WebRules;
 import org.sonar.server.rules.ProfilesConsole;
 import org.sonar.server.rules.RulesConsole;
 import org.sonar.server.startup.*;
-import org.sonar.server.text.WebText;
 import org.sonar.server.text.MacroInterpreter;
+import org.sonar.server.text.WebText;
 import org.sonar.server.ui.*;
 
 import javax.servlet.ServletContext;
@@ -93,19 +93,31 @@ import javax.servlet.ServletContext;
 public final class Platform {
 
   private static final Platform INSTANCE = new Platform();
-
   private ComponentContainer rootContainer;// level 1 : only database connectors
   private ComponentContainer coreContainer;// level 2 : level 1 + core components
   private ComponentContainer servicesContainer;// level 3 : level 2 + plugin extensions + core components that depend on plugin extensions
-
   private boolean connected = false;
   private boolean started = false;
 
+  private Platform() {
+  }
+
   public static Platform getInstance() {
     return INSTANCE;
   }
 
-  private Platform() {
+  /**
+   * shortcut for ruby code
+   */
+  public static Server getServer() {
+    return (Server) getInstance().getComponent(Server.class);
+  }
+
+  /**
+   * Used by ruby code
+   */
+  public static <T> T component(Class<T> type) {
+    return getInstance().getContainer().getComponentByType(type);
   }
 
   public void init(ServletContext servletContext) {
@@ -245,6 +257,7 @@ public final class Platform {
     servicesContainer.addSingleton(IssueWorkflow.class);
     servicesContainer.addSingleton(ServerIssueActions.class);
     servicesContainer.addSingleton(ServerIssueFinder.class);
+    servicesContainer.addSingleton(ServerActionPlanStatsFinder.class);
     servicesContainer.addSingleton(WebIssuesApi.class);
     servicesContainer.addSingleton(WebIssuesInternal.class);
 
@@ -330,18 +343,4 @@ public final class Platform {
   public Object getComponent(Object key) {
     return getContainer().getComponentByKey(key);
   }
-
-  /**
-   * shortcut for ruby code
-   */
-  public static Server getServer() {
-    return (Server) getInstance().getComponent(Server.class);
-  }
-
-  /**
-   * Used by ruby code
-   */
-  public static <T> T component(Class<T> type) {
-    return getInstance().getContainer().getComponentByType(type);
-  }
 }
diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/393_create_action_plans_issues.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/393_create_action_plans_issues.rb
deleted file mode 100644 (file)
index 92b2e03..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Sonar, entreprise quality control tool.
-# Copyright (C) 2008-2013 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-#
-# Sonar 3.6
-#
-class CreateActionPlansIssues < ActiveRecord::Migration
-
-  def self.up
-    create_table :action_plans_issues do |t|
-      t.integer :action_plan_id
-      t.integer :issue_id
-    end
-
-    add_index "action_plans_issues", "action_plan_id", :name => 'I_ACT_PLA_ISSUE_ACT_PLA_ID'
-    add_index "action_plans_issues", "issue_id", :name => 'I_ACT_PLA_ISSUE_ISSUE_ID'
-  end
-
-end
-
index 90883a234cadf51303ce607fb856f5a54eed4850..cd6c2b2541bef46a6efa2e0f089ddbf57cafd288 100644 (file)
@@ -41,6 +41,7 @@ class CreateIssues < ActiveRecord::Migration
       t.column :assignee_login,       :string,    :null => true,         :limit => 40
       t.column :author_login,         :string,    :null => true,    :limit => 100
       t.column :attributes,           :string,    :null => true,    :limit => 4000
+      t.column :action_plan_id,       :integer,   :null => true
 
       # functional dates
       t.column :issue_creation_date,  :datetime,  :null => true
diff --git a/sonar-server/src/test/java/org/sonar/server/issue/ServerActionPlanStatsFinderTest.java b/sonar-server/src/test/java/org/sonar/server/issue/ServerActionPlanStatsFinderTest.java
new file mode 100644 (file)
index 0000000..bb95ce9
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.issue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.issue.ActionPlanStats;
+import org.sonar.core.issue.db.ActionPlanStatsDao;
+import org.sonar.core.issue.db.ActionPlanStatsDto;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.resource.ResourceQuery;
+
+import java.util.Collection;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ServerActionPlanStatsFinderTest {
+
+  private ServerActionPlanStatsFinder actionPlanStatsFinder;
+
+  private ActionPlanStatsDao actionPlanStatsDao = mock(ActionPlanStatsDao.class);
+  private ResourceDao resourceDao = mock(ResourceDao.class);
+
+  @Before
+  public void before(){
+    actionPlanStatsFinder = new ServerActionPlanStatsFinder(actionPlanStatsDao, resourceDao);
+  }
+
+  @Test
+  public void should_find_action_plan_stats(){
+    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(new ResourceDto().setId(1L).setKey("org.sonar.Sample"));
+    when(actionPlanStatsDao.findByProjectId(1L)).thenReturn(newArrayList(new ActionPlanStatsDto()));
+
+    Collection<ActionPlanStats> results = actionPlanStatsFinder.find("org.sonar.Sample");
+    assertThat(results).hasSize(1);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void should_throw_exception_if_project_not_found(){
+    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(null);
+
+    actionPlanStatsFinder.find("org.sonar.Sample");
+  }
+
+}