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
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
--- /dev/null
+/*
+ * 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);
+ }
+}
actionPlan.setKey(UUID.randomUUID().toString());
Date now = new Date();
actionPlan.setName(name);
+ actionPlan.setStatus(ActionPlan.STATUS_OPEN);
actionPlan.setCreationDate(now).setUpdateDate(now);
return actionPlan;
}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+}
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;
public static List<Class<?>> getDaoClasses() {
return ImmutableList.of(
ActionPlanIssueDao.class,
+ ActionPlanStatsDao.class,
ActiveDashboardDao.class,
AuthorDao.class,
AuthorizationDao.class,
*/
static final String[] TABLE_NAMES = {
"action_plans",
- "action_plans_issues",
"action_plans_reviews",
"active_dashboards",
"active_rules",
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");
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);
void deleteSnapshotData(long snapshotId);
- void deleteResourceActionPlansIssues(long resourceId);
-
void deleteResourceIssueChanges(long resourceId);
void deleteResourceIssues(long resourceId);
</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>
--- /dev/null
+<?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
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');
"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,
"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,
"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,
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");
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})
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
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> {
<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>
--- /dev/null
+<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>
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"
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"
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]"
<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]"/>
<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"
/>
<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"
*/
public interface ActionPlan extends Serializable {
+ String STATUS_OPEN = "OPEN";
+ String STATUS_CLOSED = "CLOSED";
+
/**
* Unique generated key
*/
@CheckForNull
String userLogin();
- @CheckForNull
String status();
@CheckForNull
--- /dev/null
+/*
+ * 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();
+ }
+ }));
+ }
+
+}
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;
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) {
Issue issue = builder.build();
return actions.create((DefaultIssue) issue, UserSession.get());
}
+
+ List<ActionPlanStats> actionPlanStats(String projectKey) {
+ return actionPlanStatsFinder.find(projectKey);
+ }
+
}
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;
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) {
servicesContainer.addSingleton(IssueWorkflow.class);
servicesContainer.addSingleton(ServerIssueActions.class);
servicesContainer.addSingleton(ServerIssueFinder.class);
+ servicesContainer.addSingleton(ServerActionPlanStatsFinder.class);
servicesContainer.addSingleton(WebIssuesApi.class);
servicesContainer.addSingleton(WebIssuesInternal.class);
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);
- }
}
+++ /dev/null
-#
-# 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
-
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
--- /dev/null
+/*
+ * 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");
+ }
+
+}