diff options
author | Julien Lancelot <julien.lancelot@gmail.com> | 2013-05-03 09:37:55 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@gmail.com> | 2013-05-03 09:38:16 +0200 |
commit | 53c455995c5d49b2876f334a98ddc86bbdf1c896 (patch) | |
tree | 718d726bacb3e227a79ca2757edd8a6510976d0b | |
parent | 6194a1a3e19cd026da8806ccf84ebc8a22524b1d (diff) | |
download | sonarqube-53c455995c5d49b2876f334a98ddc86bbdf1c896.tar.gz sonarqube-53c455995c5d49b2876f334a98ddc86bbdf1c896.zip |
SONAR-3755 Return Action plans in Issue Finder
19 files changed, 716 insertions, 15 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java new file mode 100644 index 00000000000..2ecd6a9b745 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultActionPlan.java @@ -0,0 +1,123 @@ +/* + * 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.util.Date; +import java.util.UUID; + +public class DefaultActionPlan implements ActionPlan { + + 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 DefaultActionPlan(){ + + } + + public static DefaultActionPlan create(String name) { + DefaultActionPlan actionPlan = new DefaultActionPlan(); + actionPlan.setKey(UUID.randomUUID().toString()); + Date now = new Date(); + actionPlan.setName(name); + actionPlan.setCreationDate(now).setUpdateDate(now); + return actionPlan; + } + + public String key() { + return key; + } + + public DefaultActionPlan setKey(String key) { + this.key = key; + return this; + } + + public String name() { + return name; + } + + public DefaultActionPlan setName(String name) { + this.name = name; + return this; + } + + public String description() { + return description; + } + + public DefaultActionPlan setDescription(String description) { + this.description = description; + return this; + } + + public String userLogin() { + return userLogin; + } + + public DefaultActionPlan setUserLogin(String userLogin) { + this.userLogin = userLogin; + return this; + } + + public String status() { + return status; + } + + public DefaultActionPlan setStatus(String status) { + this.status = status; + return this; + } + + public Date deadLine() { + return deadLine; + } + + public DefaultActionPlan setDeadLine(Date deadLine) { + this.deadLine = deadLine; + return this; + } + + public Date creationDate() { + return creationDate; + } + + public DefaultActionPlan setCreationDate(Date creationDate) { + this.creationDate = creationDate; + return this; + } + + public Date updateDate() { + return updateDate; + } + + public DefaultActionPlan setUpdateDate(Date updateDate) { + this.updateDate = updateDate; + return this; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java index fdd12ebfa37..86eefaed14a 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java @@ -28,12 +28,14 @@ import com.google.common.collect.Maps; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; +import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import javax.annotation.CheckForNull; import javax.annotation.Nullable; + import java.io.Serializable; import java.util.Collections; import java.util.Date; @@ -61,6 +63,7 @@ public class DefaultIssue implements Issue { private Map<String, String> attributes = null; private String authorLogin = null; private FieldDiffs diffs = null; + private List<ActionPlan> actionPlans = null; private List<IssueComment> newComments = null; // functional dates @@ -315,6 +318,16 @@ public class DefaultIssue implements Issue { return this; } + @CheckForNull + public List<ActionPlan> actionPlans() { + return actionPlans; + } + + public DefaultIssue setActionPlans(List<ActionPlan> actionPlans) { + this.actionPlans = actionPlans; + return this; + } + public DefaultIssue setFieldDiff(IssueChangeContext context, String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { if (!Objects.equal(oldValue, newValue)) { if (diffs == null) { diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueDao.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueDao.java new file mode 100644 index 00000000000..9366553fbb9 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueDao.java @@ -0,0 +1,68 @@ +/* + * 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 com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +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; +import java.util.Collections; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; + +/** + * @since 3.6 + */ +public class ActionPlanIssueDao implements BatchComponent, ServerComponent { + + private final MyBatis mybatis; + + public ActionPlanIssueDao(MyBatis mybatis) { + this.mybatis = mybatis; + } + + public Collection<ActionPlanIssueDto> findByIssueIds(Collection<Long> issueIds, SqlSession session) { + if (issueIds.isEmpty()) { + return Collections.emptyList(); + } + try { + List<List<Long>> idsPartition = Lists.partition(newArrayList(issueIds), 1000); + return session.getMapper(ActionPlanIssueMapper.class).findByIssueIds(idsPartition); + } finally { + MyBatis.closeQuietly(session); + } + } + + @VisibleForTesting + Collection<ActionPlanIssueDto> findByIssueIds(Collection<Long> issueIds) { + SqlSession session = mybatis.openSession(); + try { + return findByIssueIds(issueIds, session); + } finally { + MyBatis.closeQuietly(session); + } + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueDto.java new file mode 100644 index 00000000000..988f4d39962 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueDto.java @@ -0,0 +1,178 @@ +/* + * 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.DefaultActionPlan; + +import java.util.Date; + +/** + * @since 3.6 + */ +public class ActionPlanIssueDto { + + 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 Long issueId; + + public Integer getId() { + return id; + } + + public ActionPlanIssueDto setId(Integer id) { + this.id = id; + return this; + } + + public String getKee() { + return kee; + } + + public ActionPlanIssueDto setKee(String kee) { + this.kee = kee; + return this; + } + + public String getName() { + return name; + } + + public ActionPlanIssueDto setName(String name) { + this.name = name; + return this; + } + + public String getDescription() { + return description; + } + + public ActionPlanIssueDto setDescription(String description) { + this.description = description; + return this; + } + + public String getUserLogin() { + return userLogin; + } + + public ActionPlanIssueDto setUserLogin(String userLogin) { + this.userLogin = userLogin; + return this; + } + + public Integer getProjectId() { + return projectId; + } + + public ActionPlanIssueDto setProjectId(Integer projectId) { + this.projectId = projectId; + return this; + } + + public String getStatus() { + return status; + } + + public ActionPlanIssueDto setStatus(String status) { + this.status = status; + return this; + } + + public Date getDeadLine() { + return deadLine; + } + + public ActionPlanIssueDto setDeadLine(Date deadLine) { + this.deadLine = deadLine; + return this; + } + + public Date getCreatedAt() { + return createdAt; + } + + public ActionPlanIssueDto setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + return this; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public ActionPlanIssueDto setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public Long getIssueId() { + return issueId; + } + + public ActionPlanIssueDto setIssueId(Long issueId) { + this.issueId = issueId; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ActionPlanIssueDto actionPlanIssueDto = (ActionPlanIssueDto) o; + return !(id != null ? !id.equals(actionPlanIssueDto.id) : actionPlanIssueDto.id != null); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + + public DefaultActionPlan toActionPlan() { + return DefaultActionPlan.create(name) + .setKey(kee) + .setDescription(description) + .setStatus(status) + .setDeadLine(deadLine) + .setUserLogin(userLogin) + .setCreationDate(createdAt) + .setUpdateDate(updatedAt); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueMapper.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueMapper.java new file mode 100644 index 00000000000..6814ea2c797 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/ActionPlanIssueMapper.java @@ -0,0 +1,37 @@ +/* + * 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; +import java.util.List; + +/** + * @since 3.6 + */ +public interface ActionPlanIssueMapper { + + /** + * @since3.6 + */ + Collection<ActionPlanIssueDto> findByIssueIds(@Param("issueIds") List <List<Long>> issueIds); +} diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java b/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java index dbb79e2a583..05022a52d6b 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java @@ -24,6 +24,7 @@ import org.sonar.core.dashboard.ActiveDashboardDao; 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.IssueChangeDao; import org.sonar.core.issue.db.IssueDao; import org.sonar.core.measure.MeasureFilterDao; @@ -51,6 +52,7 @@ public final class DaoUtils { @SuppressWarnings("unchecked") public static List<Class<?>> getDaoClasses() { return ImmutableList.of( + ActionPlanIssueDao.class, ActiveDashboardDao.class, AuthorDao.class, AuthorizationDao.class, diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java index 7b6db8321bf..389a2425c22 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java @@ -32,7 +32,7 @@ import java.util.List; */ public class DatabaseVersion implements BatchComponent, ServerComponent { - public static final int LAST_VERSION = 393; + public static final int LAST_VERSION = 394; public static enum Status { UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java index e89aa600365..a0173a82950 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java @@ -44,9 +44,7 @@ import org.sonar.core.duplication.DuplicationMapper; import org.sonar.core.duplication.DuplicationUnitDto; import org.sonar.core.graph.jdbc.GraphDto; import org.sonar.core.graph.jdbc.GraphDtoMapper; -import org.sonar.core.issue.db.IssueChangeDto; -import org.sonar.core.issue.db.IssueChangeMapper; -import org.sonar.core.issue.db.IssueDto; +import org.sonar.core.issue.db.*; import org.sonar.core.measure.MeasureFilterDto; import org.sonar.core.measure.MeasureFilterMapper; import org.sonar.core.properties.PropertiesMapper; @@ -123,6 +121,7 @@ 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); Class<?>[] mappers = {ActiveDashboardMapper.class, AuthorMapper.class, DashboardMapper.class, @@ -130,7 +129,7 @@ public class MyBatis implements BatchComponent, ServerComponent { 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 + SnapshotSourceMapper.class, ActionPlanIssueMapper.class }; loadMappers(conf, mappers); loadMapper(conf, "org.sonar.core.issue.db.IssueMapper"); diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanIssueMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanIssueMapper.xml new file mode 100644 index 00000000000..edd28f16180 --- /dev/null +++ b/sonar-core/src/main/resources/org/sonar/core/issue/db/ActionPlanIssueMapper.xml @@ -0,0 +1,31 @@ +<?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.ActionPlanIssueMapper"> + + <sql id="actionPlanColumns"> + ap.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="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 + <where> + <foreach collection="issueIds" open="api.issue_id in (" close=")" item="list" separator=") or api.issue_id in (" > + <foreach collection="list" item="element" separator=","> + #{element} + </foreach> + </foreach> + </where> + </select> + +</mapper>
\ No newline at end of file diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql index 1ea246041f5..65d8f228d19 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql @@ -161,6 +161,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('390'); 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 USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '2011-09-26 22:27:48.0', '2011-09-26 22:27:48.0', null, null); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl index 5067e167ea3..b02576d6933 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl @@ -435,6 +435,7 @@ CREATE TABLE "RESOURCE_INDEX" ( CREATE TABLE "ACTION_PLANS" ( "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), "USER_ID" INTEGER, + "KEE" VARCHAR(100), "NAME" VARCHAR(200), "DESCRIPTION" VARCHAR(1000), "DEADLINE" TIMESTAMP, diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanIssueDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanIssueDaoTest.java new file mode 100644 index 00000000000..8ac11e29428 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/ActionPlanIssueDaoTest.java @@ -0,0 +1,49 @@ +/* + * 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 com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.Assertions.assertThat; + +public class ActionPlanIssueDaoTest extends AbstractDaoTestCase { + + private ActionPlanIssueDao dao; + + @Before + public void createDao() { + dao = new ActionPlanIssueDao(getMyBatis()); + } + + @Test + public void should_find_by_issue_ids() { + setupData("should_find_by_issue_ids"); + + Collection<ActionPlanIssueDto> result = dao.findByIssueIds(newArrayList(250l, 251l, 252l)); + assertThat(result).hasSize(3); + assertThat(result.iterator().next().getIssueId()).isNotNull(); + } +} diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanIssueDaoTest/should_find_by_issue_ids.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanIssueDaoTest/should_find_by_issue_ids.xml new file mode 100644 index 00000000000..b68c783aa1a --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/ActionPlanIssueDaoTest/should_find_by_issue_ids.xml @@ -0,0 +1,19 @@ +<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]" /> + + <action_plans_issues 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 action_plan_id="2" issue_id="251" /> + <action_plans_issues 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 action_plan_id="3" issue_id="253" /> + +</dataset> diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java new file mode 100644 index 00000000000..770bb0c7d32 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/ActionPlan.java @@ -0,0 +1,56 @@ +/* + * 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.api.issue; + +import javax.annotation.CheckForNull; + +import java.io.Serializable; +import java.util.Date; + +/** + * @since 3.6 + */ +public interface ActionPlan extends Serializable { + + /** + * Unique generated key + */ + String key(); + + String name(); + + @CheckForNull + String description(); + + @CheckForNull + String userLogin(); + + @CheckForNull + String status(); + + @CheckForNull + Date deadLine() ; + + Date creationDate(); + + Date updateDate(); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java index 775e919c599..6f3de886588 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java @@ -22,8 +22,10 @@ package org.sonar.api.issue; import org.sonar.api.rule.RuleKey; import javax.annotation.CheckForNull; + import java.io.Serializable; import java.util.Date; +import java.util.List; import java.util.Map; /** @@ -87,4 +89,7 @@ public interface Issue extends Serializable { @CheckForNull String authorLogin(); + @CheckForNull + List<ActionPlan> actionPlans(); + } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueFinder.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueFinder.java index 70086f1b245..7ba13797017 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueFinder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueFinder.java @@ -49,6 +49,10 @@ public interface IssueFinder extends ServerComponent { Collection<Component> components(); + Collection<ActionPlan> actionPlans(Issue issue); + + Collection<ActionPlan> actionPlans(); + Paging paging(); boolean securityExclusions(); diff --git a/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueFinder.java b/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueFinder.java index 72423b91ef8..01402ad4217 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueFinder.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueFinder.java @@ -20,17 +20,19 @@ package org.sonar.server.issue; import com.google.common.base.Predicate; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; import org.apache.ibatis.session.SqlSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.component.Component; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.IssueFinder; -import org.sonar.api.issue.IssueQuery; -import org.sonar.api.issue.Paging; +import org.sonar.api.issue.*; import org.sonar.api.rules.Rule; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.db.ActionPlanIssueDao; +import org.sonar.core.issue.db.ActionPlanIssueDto; import org.sonar.core.issue.db.IssueDao; import org.sonar.core.issue.db.IssueDto; import org.sonar.core.persistence.MyBatis; @@ -59,13 +61,15 @@ public class ServerIssueFinder implements IssueFinder { private final AuthorizationDao authorizationDao; private final DefaultRuleFinder ruleFinder; private final ResourceDao resourceDao; + private final ActionPlanIssueDao actionPlanIssueDao; - public ServerIssueFinder(MyBatis myBatis, IssueDao issueDao, AuthorizationDao authorizationDao, DefaultRuleFinder ruleFinder, ResourceDao resourceDao) { + public ServerIssueFinder(MyBatis myBatis, IssueDao issueDao, AuthorizationDao authorizationDao, DefaultRuleFinder ruleFinder, ResourceDao resourceDao, ActionPlanIssueDao actionPlanIssueDao) { this.myBatis = myBatis; this.issueDao = issueDao; this.authorizationDao = authorizationDao; this.ruleFinder = ruleFinder; this.resourceDao = resourceDao; + this.actionPlanIssueDao = actionPlanIssueDao; } public Results find(IssueQuery query, @Nullable Integer currentUserId, String role) { @@ -82,14 +86,24 @@ public class ServerIssueFinder implements IssueFinder { Collection<IssueDto> dtos = issueDao.selectByIds(paginatedAuthorizedIssueIds, sqlSession); Set<Integer> ruleIds = Sets.newLinkedHashSet(); List<Issue> issues = newArrayList(); + List<Long> issueIds = newArrayList(); + Map<Long, Issue> issuesById = newHashMap(); for (IssueDto dto : dtos) { if (authorizedComponentIds.contains(dto.getResourceId())) { - issues.add(dto.toDefaultIssue()); + DefaultIssue defaultIssue = dto.toDefaultIssue(); + issuesById.put(dto.getId(), defaultIssue); + issueIds.add(dto.getId()); + issues.add(defaultIssue); ruleIds.add(dto.getRuleId()); } } - return new DefaultResults(issues, getRulesByIssue(issues, ruleIds), getComponentsByIssue(issues, componentIds), paging, authorizedIssues.size() != allIssuesDto.size()); + Collection<ActionPlanIssueDto> actionPlanIssueDtos = actionPlanIssueDao.findByIssueIds(issueIds, sqlSession); + ListMultimap<Issue, ActionPlan> actionPlansByIssueKey = createActionPlansByIssue(actionPlanIssueDtos, issuesById); + setActionPlans(issues, actionPlansByIssueKey); + + return new DefaultResults(issues, getRulesByIssue(issues, ruleIds), getComponentsByIssue(issues, componentIds), actionPlansByIssueKey, + paging, authorizedIssues.size() != allIssuesDto.size()); } finally { MyBatis.closeQuietly(sqlSession); } @@ -162,6 +176,23 @@ public class ServerIssueFinder implements IssueFinder { }, null); } + private ListMultimap createActionPlansByIssue(Collection<ActionPlanIssueDto> actionPlanIssueDtos, Map<Long, Issue> issuesById) { + ListMultimap<Issue, ActionPlan> actionPlansByIssue = ArrayListMultimap.create(); + for (ActionPlanIssueDto actionPlanIssueDto : actionPlanIssueDtos) { + Issue issue = issuesById.get(actionPlanIssueDto.getIssueId()); + actionPlansByIssue.put(issue, actionPlanIssueDto.toActionPlan()); + } + return actionPlansByIssue; + } + + private void setActionPlans(List<Issue> issues, ListMultimap<Issue, ActionPlan> actionPlansByIssueKey){ + for (Issue issue : issues) { + DefaultIssue defaultIssue = (DefaultIssue) issue; + List<ActionPlan> actionPlans = actionPlansByIssueKey.get(issue); + defaultIssue.setActionPlans(actionPlans); + } + } + public Issue findByKey(String key) { IssueDto dto = issueDao.selectByKey(key); return dto != null ? dto.toDefaultIssue() : null; @@ -172,12 +203,15 @@ public class ServerIssueFinder implements IssueFinder { private final Paging paging; private final Map<Issue, Rule> rulesByIssue; private final Map<Issue, Component> componentsByIssue; + private final ListMultimap<Issue, ActionPlan> actionPlansByIssue; private final boolean securityExclusions; - DefaultResults(List<Issue> issues, Map<Issue, Rule> rulesByIssue, Map<Issue, Component> componentsByIssue, Paging paging, boolean securityExclusions) { + DefaultResults(List<Issue> issues, Map<Issue, Rule> rulesByIssue, Map<Issue, Component> componentsByIssue, ListMultimap<Issue, ActionPlan> actionPlansByIssue, + Paging paging, boolean securityExclusions) { this.issues = issues; this.rulesByIssue = rulesByIssue; this.componentsByIssue = componentsByIssue; + this.actionPlansByIssue = actionPlansByIssue; this.paging = paging; this.securityExclusions = securityExclusions; } @@ -203,6 +237,14 @@ public class ServerIssueFinder implements IssueFinder { return componentsByIssue.values(); } + public Collection<ActionPlan> actionPlans(Issue issue) { + return actionPlansByIssue.get(issue); + } + + public Collection<ActionPlan> actionPlans() { + return actionPlansByIssue.values(); + } + public boolean securityExclusions() { return securityExclusions; } diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/394_add_key_to_action_plan.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/394_add_key_to_action_plan.rb new file mode 100644 index 00000000000..ded169f5996 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/394_add_key_to_action_plan.rb @@ -0,0 +1,38 @@ +# +# 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 AddKeyToActionPlan < ActiveRecord::Migration + + class ActionPlan < ActiveRecord::Base + end + + def self.up + add_column 'action_plans', 'kee', :string, :null => true, :limit => 100 + ActionPlan.reset_column_information + ActionPlan.all.each do |a| + a.update_attributes!(:kee => Java::JavaUtil::UUID.randomUUID().toString()) + end + end + +end + diff --git a/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueFinderTest.java b/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueFinderTest.java index 4a8b86b9091..d2090f4da40 100644 --- a/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueFinderTest.java +++ b/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueFinderTest.java @@ -31,6 +31,8 @@ import org.sonar.api.issue.IssueQuery; import org.sonar.api.rules.Rule; import org.sonar.api.web.UserRole; import org.sonar.core.component.ComponentDto; +import org.sonar.core.issue.db.ActionPlanIssueDao; +import org.sonar.core.issue.db.ActionPlanIssueDto; import org.sonar.core.issue.db.IssueDao; import org.sonar.core.issue.db.IssueDto; import org.sonar.core.persistence.MyBatis; @@ -61,6 +63,7 @@ public class ServerIssueFinderTest { AuthorizationDao authorizationDao; DefaultRuleFinder ruleFinder; ResourceDao resourceDao; + ActionPlanIssueDao actionPlanIssueDao; @Before public void before() { @@ -69,7 +72,8 @@ public class ServerIssueFinderTest { authorizationDao = mock(AuthorizationDao.class); ruleFinder = mock(DefaultRuleFinder.class); resourceDao = mock(ResourceDao.class); - finder = new ServerIssueFinder(mybatis, issueDao, authorizationDao, ruleFinder, resourceDao); + actionPlanIssueDao = mock(ActionPlanIssueDao.class); + finder = new ServerIssueFinder(mybatis, issueDao, authorizationDao, ruleFinder, resourceDao, actionPlanIssueDao); } @Test @@ -222,7 +226,37 @@ public class ServerIssueFinderTest { } @Test - public void should_get_empty_rule_and_component_from_result_when_no_issue() { + public void should_get_action_plans_from_result() { + ActionPlanIssueDto actionPlanIssueDto1 = new ActionPlanIssueDto().setIssueId(1L).setKee("A").setName("Short term"); + ActionPlanIssueDto actionPlanIssueDto2 = new ActionPlanIssueDto().setIssueId(2L).setKee("B").setName("Long term"); + + grantAccessRights(); + IssueQuery issueQuery = mock(IssueQuery.class); + + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setKey("ABC") + .setComponentKey_unit_test_only("Action.java") + .setRuleKey_unit_test_only("squid", "AvoidCycle") + .setStatus("OPEN").setResolution("OPEN"); + IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123).setKey("DEF") + .setComponentKey_unit_test_only("Action.java") + .setRuleKey_unit_test_only("squid", "AvoidCycle") + .setStatus("OPEN").setResolution("OPEN"); + List<IssueDto> dtoList = newArrayList(issue1, issue2); + when(issueDao.selectIssueIdsAndComponentsId(eq(issueQuery), any(SqlSession.class))).thenReturn(dtoList); + when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(dtoList); + + when(actionPlanIssueDao.findByIssueIds(anyCollection(), any(SqlSession.class))).thenReturn(newArrayList(actionPlanIssueDto1, actionPlanIssueDto2)); + + + IssueFinder.Results results = finder.find(issueQuery, null, UserRole.USER); + assertThat(results.issues()).hasSize(2); + Issue issue = results.issues().iterator().next(); + assertThat(results.issues()).hasSize(2); + assertThat(results.actionPlans(issue)).hasSize(1); + } + + @Test + public void should_get_empty_result_when_no_issue() { grantAccessRights(); IssueQuery issueQuery = mock(IssueQuery.class); when(issueDao.selectIssueIdsAndComponentsId(eq(issueQuery), any(SqlSession.class))).thenReturn(Collections.<IssueDto>emptyList()); @@ -233,6 +267,7 @@ public class ServerIssueFinderTest { assertThat(results.issues()).isEmpty(); assertThat(results.rules()).isEmpty(); assertThat(results.components()).isEmpty(); + assertThat(results.actionPlans()).isEmpty(); } private void grantAccessRights() { |