diff options
author | Fabrice Bellingard <bellingard@gmail.com> | 2012-01-12 18:30:40 +0100 |
---|---|---|
committer | Fabrice Bellingard <bellingard@gmail.com> | 2012-01-12 18:40:45 +0100 |
commit | c82a64fb8ea2dc4b031dd1bb5e766c846fdbd443 (patch) | |
tree | e431daf56f84d610736a6500ed3c4e3bf2d5abc0 /sonar-core/src | |
parent | d9336198cba4299a3fd2f0b911c9ce06a6514009 (diff) | |
download | sonarqube-c82a64fb8ea2dc4b031dd1bb5e766c846fdbd443.tar.gz sonarqube-c82a64fb8ea2dc4b031dd1bb5e766c846fdbd443.zip |
SONAR-3012 New widget to monitor the review activity
- 5 new metrics added
- Decorator implemented to compute those metrics
- Widget implemented to report those metrics
Diffstat (limited to 'sonar-core/src')
7 files changed, 208 insertions, 60 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/review/ReviewDao.java b/sonar-core/src/main/java/org/sonar/core/review/ReviewDao.java index 27ea0b80764..438bb911021 100644 --- a/sonar-core/src/main/java/org/sonar/core/review/ReviewDao.java +++ b/sonar-core/src/main/java/org/sonar/core/review/ReviewDao.java @@ -19,13 +19,14 @@ */ package org.sonar.core.review; -import com.google.common.collect.Lists; +import java.util.List; + 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.List; +import com.google.common.collect.Lists; public class ReviewDao implements BatchComponent, ServerComponent { private final MyBatis mybatis; @@ -44,29 +45,36 @@ public class ReviewDao implements BatchComponent, ServerComponent { } } - public List<ReviewDto> selectByResource(int resourceId) { + public List<ReviewDto> selectByQuery(ReviewQuery query) { SqlSession sqlSession = mybatis.openSession(); try { ReviewMapper mapper = sqlSession.getMapper(ReviewMapper.class); - return mapper.selectByResource(resourceId); + List<ReviewDto> result; + if (query.needToPartitionQuery()) { + result = Lists.newArrayList(); + for (ReviewQuery partitionedQuery : query.partition()) { + result.addAll(mapper.selectByQuery(partitionedQuery)); + } + } else { + result = mapper.selectByQuery(query); + } + return result; } finally { sqlSession.close(); } } - public List<ReviewDto> selectByQuery(ReviewQuery query) { + public Integer countByQuery(ReviewQuery query) { SqlSession sqlSession = mybatis.openSession(); try { ReviewMapper mapper = sqlSession.getMapper(ReviewMapper.class); - List<ReviewDto> result; + Integer result = 0; if (query.needToPartitionQuery()) { - result = Lists.newArrayList(); for (ReviewQuery partitionedQuery : query.partition()) { - result.addAll(mapper.selectByQuery(partitionedQuery)); + result += mapper.countByQuery(partitionedQuery); } - } else { - result = mapper.selectByQuery(query); + result = mapper.countByQuery(query); } return result; } finally { diff --git a/sonar-core/src/main/java/org/sonar/core/review/ReviewDto.java b/sonar-core/src/main/java/org/sonar/core/review/ReviewDto.java index 7f349a6b627..9fe21da9b2e 100644 --- a/sonar-core/src/main/java/org/sonar/core/review/ReviewDto.java +++ b/sonar-core/src/main/java/org/sonar/core/review/ReviewDto.java @@ -34,6 +34,9 @@ public final class ReviewDto { public static final String STATUS_RESOLVED = "RESOLVED"; public static final String STATUS_CLOSED = "CLOSED"; + public static final String RESOLUTION_FALSE_POSITIVE = "FALSE-POSITIVE"; + public static final String RESOLUTION_FIXED = "FIXED"; + private Long id; private Integer userId; private Integer assigneeId; diff --git a/sonar-core/src/main/java/org/sonar/core/review/ReviewMapper.java b/sonar-core/src/main/java/org/sonar/core/review/ReviewMapper.java index 89c7a6a84c5..1048128212f 100644 --- a/sonar-core/src/main/java/org/sonar/core/review/ReviewMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/review/ReviewMapper.java @@ -19,8 +19,6 @@ */ package org.sonar.core.review; -import org.apache.ibatis.annotations.Param; - import java.util.List; /** @@ -29,7 +27,7 @@ import java.util.List; public interface ReviewMapper { ReviewDto selectById(long id); - List<ReviewDto> selectByResource(int resourceId); - List<ReviewDto> selectByQuery(ReviewQuery query); + + Integer countByQuery(ReviewQuery query); } diff --git a/sonar-core/src/main/java/org/sonar/core/review/ReviewQuery.java b/sonar-core/src/main/java/org/sonar/core/review/ReviewQuery.java index afaa5ec369d..9481adc008e 100644 --- a/sonar-core/src/main/java/org/sonar/core/review/ReviewQuery.java +++ b/sonar-core/src/main/java/org/sonar/core/review/ReviewQuery.java @@ -19,12 +19,13 @@ */ package org.sonar.core.review; -import com.google.common.collect.Lists; -import org.sonar.core.persistence.DatabaseUtils; - import java.util.Collection; import java.util.List; +import org.sonar.core.persistence.DatabaseUtils; + +import com.google.common.collect.Lists; + /** * @since 2.13 */ @@ -35,27 +36,35 @@ public final class ReviewQuery { private Integer userId; private List<Integer> violationPermanentIds; private Integer ruleId; - private String status; - private String resolution; + private List<String> statuses; + private List<String> resolutions; + private Boolean noAssignee; + private Boolean planned; private ReviewQuery() { } - private ReviewQuery(ReviewQuery other, List<Integer> permanentIds) { + private ReviewQuery(ReviewQuery other) { this.manualViolation = other.manualViolation; this.manualSeverity = other.manualSeverity; this.resourceId = other.resourceId; this.userId = other.userId; - this.violationPermanentIds = permanentIds; + this.violationPermanentIds = other.violationPermanentIds; this.ruleId = other.ruleId; - this.status = other.status; - this.resolution = other.resolution; + this.statuses = other.statuses; + this.resolutions = other.resolutions; + this.noAssignee = other.noAssignee; + this.planned = other.planned; } public static ReviewQuery create() { return new ReviewQuery(); } + public static ReviewQuery copy(ReviewQuery reviewQuery) { + return new ReviewQuery(reviewQuery); + } + public Boolean getManualViolation() { return manualViolation; } @@ -74,12 +83,15 @@ public final class ReviewQuery { return this; } - public String getStatus() { - return status; + public List<String> getStatuses() { + return statuses; } - public ReviewQuery setStatus(String status) { - this.status = status; + public ReviewQuery addStatus(String status) { + if (statuses == null) { + statuses = Lists.newArrayList(); + } + statuses.add(status); return this; } @@ -110,12 +122,15 @@ public final class ReviewQuery { return this; } - public String getResolution() { - return resolution; + public List<String> getResolutions() { + return resolutions; } - public ReviewQuery setResolution(String resolution) { - this.resolution = resolution; + public ReviewQuery addResolution(String resolution) { + if (resolutions == null) { + resolutions = Lists.newArrayList(); + } + resolutions.add(resolution); return this; } @@ -128,6 +143,24 @@ public final class ReviewQuery { return this; } + public Boolean getNoAssignee() { + return noAssignee; + } + + public ReviewQuery setNoAssignee() { + this.noAssignee = Boolean.TRUE; + return this; + } + + public Boolean getPlanned() { + return planned; + } + + public ReviewQuery setPlanned() { + this.planned = Boolean.TRUE; + return this; + } + boolean needToPartitionQuery() { return violationPermanentIds != null && violationPermanentIds.size() > DatabaseUtils.MAX_IN_ELEMENTS; } @@ -136,7 +169,7 @@ public final class ReviewQuery { List<List<Integer>> partitions = Lists.partition(violationPermanentIds, DatabaseUtils.MAX_IN_ELEMENTS); ReviewQuery[] result = new ReviewQuery[partitions.size()]; for (int index = 0; index < partitions.size(); index++) { - result[index] = new ReviewQuery(this, partitions.get(index)); + result[index] = ReviewQuery.copy(this).setViolationPermanentIds(partitions.get(index)); } return result; diff --git a/sonar-core/src/main/resources/org/sonar/core/review/ReviewMapper.xml b/sonar-core/src/main/resources/org/sonar/core/review/ReviewMapper.xml index 2f897ff6e1c..8af600daad9 100644 --- a/sonar-core/src/main/resources/org/sonar/core/review/ReviewMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/review/ReviewMapper.xml @@ -26,17 +26,10 @@ <include refid="reviewColumns"/> from reviews where id=#{id} </select> - - <select id="selectByResource" parameterType="int" resultMap="reviewResultMap"> - select - <include refid="reviewColumns"/> - from reviews where resource_id=#{id} - </select> - - <select id="selectByQuery" parameterType="org.sonar.core.review.ReviewQuery" resultMap="reviewResultMap"> - select - <include refid="reviewColumns"/> + + <sql id="selectOrCountFromWhere"> from reviews + <if test="planned != null">R, action_plans_reviews APR</if> <where> <if test="userId != null">user_id = #{userId}</if> <if test="violationPermanentIds != null">AND rule_failure_permanent_id in @@ -46,11 +39,33 @@ </if> <if test="ruleId != null">AND rule_id = #{ruleId}</if> <if test="resourceId != null">AND resource_id = #{resourceId}</if> - <if test="status != null">AND status = #{status}</if> + <if test="statuses != null">AND + <foreach item="status" index="index" collection="statuses" + open="(" separator=" OR " close=")">status = #{status} + </foreach> + </if> <if test="manualViolation != null">AND manual_violation = #{manualViolation}</if> <if test="manualSeverity != null">AND manual_severity = #{manualSeverity}</if> - <if test="resolution != null">AND resolution = #{resolution}</if> + <if test="resolutions != null">AND + <foreach item="resolution" index="index" collection="resolutions" + open="(" separator=" OR " close=")">resolution = #{resolution} + </foreach> + </if> + <if test="noAssignee != null">AND assignee_id IS NULL</if> + <if test="planned != null">AND R.id = APR.review_id</if> </where> + </sql> + + <select id="selectByQuery" parameterType="org.sonar.core.review.ReviewQuery" resultMap="reviewResultMap"> + select + <include refid="reviewColumns"/> + <include refid="selectOrCountFromWhere"/> + </select> + + + <select id="countByQuery" parameterType="org.sonar.core.review.ReviewQuery" resultType="Integer"> + select count(id) + <include refid="selectOrCountFromWhere"/> </select> </mapper> diff --git a/sonar-core/src/test/java/org/sonar/core/review/ReviewDaoTest.java b/sonar-core/src/test/java/org/sonar/core/review/ReviewDaoTest.java index de1806a978d..3a307c0f253 100644 --- a/sonar-core/src/test/java/org/sonar/core/review/ReviewDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/review/ReviewDaoTest.java @@ -19,18 +19,22 @@ */ package org.sonar.core.review; -import com.google.common.collect.Lists; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.List; + import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Before; import org.junit.Test; import org.sonar.core.persistence.DaoTestCase; -import java.util.List; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; +import com.google.common.collect.Lists; public class ReviewDaoTest extends DaoTestCase { @@ -48,7 +52,7 @@ public class ReviewDaoTest extends DaoTestCase { ReviewDto reviewDto = dao.selectById(100L); assertThat(reviewDto.getId(), is(100L)); assertThat(reviewDto.getStatus(), is("OPEN")); - assertThat(reviewDto.getResolution(), is("RESOLVE")); + assertThat(reviewDto.getResolution(), is(nullValue())); assertThat(reviewDto.getProjectId(), is(20)); assertThat(reviewDto.getViolationPermanentId(), is(1)); assertThat(reviewDto.getSeverity(), is("BLOCKER")); @@ -66,10 +70,10 @@ public class ReviewDaoTest extends DaoTestCase { } @Test - public void shouldSelectByResource() { + public void shouldSelectByQuery() { setupData("shared"); - List<ReviewDto> reviewDtos = dao.selectByResource(400); + List<ReviewDto> reviewDtos = dao.selectByQuery(ReviewQuery.create().setResourceId(400)); assertThat(reviewDtos.size(), is(2)); for (ReviewDto reviewDto : reviewDtos) { assertThat(reviewDto.getId(), anyOf(is(100L), is(101L))); @@ -78,19 +82,62 @@ public class ReviewDaoTest extends DaoTestCase { } @Test - public void shouldSelectByQuery() { + public void shouldSelectByQueryWithStatuses() { setupData("shared"); - List<ReviewDto> reviewDtos = dao.selectByQuery(ReviewQuery.create().setResourceId(400)); + List<ReviewDto> reviewDtos = dao.selectByQuery(ReviewQuery.create().addStatus(ReviewDto.STATUS_OPEN) + .addStatus(ReviewDto.STATUS_REOPENED)); + assertThat(reviewDtos.size(), is(3)); + for (ReviewDto reviewDto : reviewDtos) { + assertThat(reviewDto.getId(), anyOf(is(100L), is(102L), is(103L))); + } + } + + @Test + public void shouldSelectByQueryWithResolutions() { + setupData("shared"); + + List<ReviewDto> reviewDtos = dao.selectByQuery(ReviewQuery.create().addResolution(ReviewDto.RESOLUTION_FALSE_POSITIVE) + .addResolution(ReviewDto.RESOLUTION_FIXED)); + assertThat(reviewDtos.size(), is(2)); + for (ReviewDto reviewDto : reviewDtos) { + assertThat(reviewDto.getId(), anyOf(is(101L), is(104L))); + } + } + + @Test + public void shouldSelectByQueryWithNoAssignee() { + setupData("shared"); + + List<ReviewDto> reviewDtos = dao.selectByQuery(ReviewQuery.create().setNoAssignee()); + assertThat(reviewDtos.size(), is(2)); + for (ReviewDto reviewDto : reviewDtos) { + assertThat(reviewDto.getId(), anyOf(is(101L), is(103L))); + } + } + + @Test + public void shouldSelectByQueryWithPlanned() { + setupData("shared"); + + List<ReviewDto> reviewDtos = dao.selectByQuery(ReviewQuery.create().setPlanned()); assertThat(reviewDtos.size(), is(2)); for (ReviewDto reviewDto : reviewDtos) { assertThat(reviewDto.getId(), anyOf(is(100L), is(101L))); - assertThat(reviewDto.getResourceId(), is(400)); } } @Test - public void shouldSelectByQuery_booleanCriteria() { + public void shouldCountByQuery() { + setupData("shared"); + + Integer count = dao.countByQuery(ReviewQuery.create().addStatus(ReviewDto.STATUS_OPEN) + .addStatus(ReviewDto.STATUS_REOPENED)); + assertThat(count, is(3)); + } + + @Test + public void shouldSelectByQueryWithBooleanCriteria() { setupData("shared"); List<ReviewDto> reviewDtos = dao.selectByQuery(ReviewQuery.create().setResourceId(400).setManualViolation(true)); @@ -111,12 +158,16 @@ public class ReviewDaoTest extends DaoTestCase { } ReviewQuery query = ReviewQuery.create().setViolationPermanentIds(permanentIds); + // test select query List<ReviewDto> reviewDtos = dao.selectByQuery(query); assertThat(reviewDtos.size(), is(3)); assertThat(reviewDtos, hasItem(new ReviewMatcherByViolationPermanentId(100))); assertThat(reviewDtos, hasItem(new ReviewMatcherByViolationPermanentId(1300))); assertThat(reviewDtos, hasItem(new ReviewMatcherByViolationPermanentId(3200))); + + // and test count query + assertThat(dao.countByQuery(query), is(3)); } static class ReviewMatcherByViolationPermanentId extends BaseMatcher<ReviewDto> { diff --git a/sonar-core/src/test/resources/org/sonar/core/review/ReviewDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/review/ReviewDaoTest/shared.xml index 70c101759de..376ff96eb8c 100644 --- a/sonar-core/src/test/resources/org/sonar/core/review/ReviewDaoTest/shared.xml +++ b/sonar-core/src/test/resources/org/sonar/core/review/ReviewDaoTest/shared.xml @@ -1,17 +1,21 @@ <dataset> + <action_plans_reviews action_plan_id="1" review_id="100"/> + <action_plans_reviews action_plan_id="2" review_id="101"/> + <!-- First resource --> <reviews id="100" status="OPEN" rule_failure_permanent_id="1" - resolution="RESOLVE" + resolution="[null]" created_at="[null]" updated_at="[null]" project_id="20" resource_line="200" severity="BLOCKER" user_id="300" + assignee_id="300" resource_id="400" rule_id="500" manual_violation="[true]" @@ -21,13 +25,14 @@ id="101" status="CLOSED" rule_failure_permanent_id="1" - resolution="RESOLVE" + resolution="FIXED" created_at="[null]" updated_at="[null]" project_id="30" resource_line="120" severity="MAJOR" user_id="300" + assignee_id="[null]" resource_id="400" rule_id="505" manual_violation="[false]" @@ -39,16 +44,51 @@ id="102" status="OPEN" rule_failure_permanent_id="1" - resolution="RESOLVE" + resolution="[null]" created_at="[null]" updated_at="[null]" project_id="20" resource_line="200" severity="BLOCKER" user_id="300" + assignee_id="300" resource_id="401" rule_id="500" manual_violation="[true]" manual_severity="[false]"/> + + <reviews + id="103" + status="REOPENED" + rule_failure_permanent_id="1" + resolution="[null]" + created_at="[null]" + updated_at="[null]" + project_id="20" + resource_line="200" + severity="BLOCKER" + user_id="300" + assignee_id="[null]" + resource_id="401" + rule_id="500" + manual_violation="[false]" + manual_severity="[false]"/> + + <reviews + id="104" + status="RESOLVED" + rule_failure_permanent_id="1" + resolution="FALSE-POSITIVE" + created_at="[null]" + updated_at="[null]" + project_id="20" + resource_line="200" + severity="BLOCKER" + user_id="300" + assignee_id="300" + resource_id="401" + rule_id="500" + manual_violation="[false]" + manual_severity="[false]"/> </dataset> |