aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorFabrice Bellingard <bellingard@gmail.com>2012-01-12 18:30:40 +0100
committerFabrice Bellingard <bellingard@gmail.com>2012-01-12 18:40:45 +0100
commitc82a64fb8ea2dc4b031dd1bb5e766c846fdbd443 (patch)
treee431daf56f84d610736a6500ed3c4e3bf2d5abc0 /plugins
parentd9336198cba4299a3fd2f0b911c9ce06a6514009 (diff)
downloadsonarqube-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 'plugins')
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java3
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ManualViolationInjector.java2
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecorator.java90
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ReviewsMetricsWidget.java40
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/reviews_metrics.html.erb50
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest.java162
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest/fixture.xml69
-rw-r--r--plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties29
8 files changed, 444 insertions, 1 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index 463ccb89502..24319ea90df 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -48,6 +48,7 @@ import org.sonar.plugins.core.widgets.reviews.FalsePositiveReviewsWidget;
import org.sonar.plugins.core.widgets.reviews.MyReviewsWidget;
import org.sonar.plugins.core.widgets.reviews.PlannedReviewsWidget;
import org.sonar.plugins.core.widgets.reviews.ProjectReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.ReviewsMetricsWidget;
import org.sonar.plugins.core.widgets.reviews.ReviewsPerDeveloperWidget;
import org.sonar.plugins.core.widgets.reviews.UnplannedReviewsWidget;
@@ -248,6 +249,7 @@ public class CorePlugin extends SonarPlugin {
extensions.add(PlannedReviewsWidget.class);
extensions.add(UnplannedReviewsWidget.class);
extensions.add(ActionPlansWidget.class);
+ extensions.add(ReviewsMetricsWidget.class);
// dashboards
extensions.add(DefaultDashboard.class);
@@ -291,6 +293,7 @@ public class CorePlugin extends SonarPlugin {
extensions.add(UpdateReviewsDecorator.class);
extensions.add(ViolationSeverityUpdater.class);
extensions.add(IndexProjectPostJob.class);
+ extensions.add(ReviewsMeasuresDecorator.class);
// time machine
extensions.add(TendencyDecorator.class);
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ManualViolationInjector.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ManualViolationInjector.java
index 78065d011db..5a5c0d64d02 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ManualViolationInjector.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ManualViolationInjector.java
@@ -51,7 +51,7 @@ public class ManualViolationInjector implements Decorator {
public void decorate(Resource resource, DecoratorContext context) {
if (resource.getId() != null) {
- ReviewQuery query = ReviewQuery.create().setManualViolation(true).setResourceId(resource.getId()).setStatus(ReviewDto.STATUS_OPEN);
+ ReviewQuery query = ReviewQuery.create().setManualViolation(true).setResourceId(resource.getId()).addStatus(ReviewDto.STATUS_OPEN);
List<ReviewDto> reviewDtos = reviewDao.selectByQuery(query);
for (ReviewDto reviewDto : reviewDtos) {
if (reviewDto.getRuleId() == null) {
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecorator.java
new file mode 100644
index 00000000000..706cc6fc047
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecorator.java
@@ -0,0 +1,90 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.sensors;
+
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.core.review.ReviewDao;
+import org.sonar.core.review.ReviewDto;
+import org.sonar.core.review.ReviewQuery;
+import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator;
+
+/**
+ * Decorator that creates measures related to reviews.
+ */
+@DependsUpon(CloseReviewsDecorator.REVIEW_LIFECYCLE_BARRIER)
+public class ReviewsMeasuresDecorator implements Decorator {
+
+ private ReviewDao reviewDao;
+
+ public ReviewsMeasuresDecorator(ReviewDao reviewDao) {
+ this.reviewDao = reviewDao;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return project.isLatestAnalysis();
+ }
+
+ @SuppressWarnings("rawtypes")
+ @DependsUpon
+ public Class dependsUponViolationTracking() {
+ // permanent ids of violations have been updated, so we can link them with reviews
+ return ViolationTrackingDecorator.class;
+ }
+
+ @SuppressWarnings({"rawtypes"})
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (!ResourceUtils.isFile(resource)) {
+ return;
+ }
+
+ // Open reviews
+ ReviewQuery openReviewQuery = ReviewQuery.create().setResourceId(resource.getId()).addStatus(ReviewDto.STATUS_OPEN)
+ .addStatus(ReviewDto.STATUS_REOPENED);
+ Integer openReviewsCount = reviewDao.countByQuery(openReviewQuery);
+ context.saveMeasure(CoreMetrics.ACTIVE_REVIEWS, openReviewsCount.doubleValue());
+
+ // Unassigned reviews
+ ReviewQuery unassignedReviewQuery = ReviewQuery.copy(openReviewQuery).setNoAssignee();
+ Integer unassignedReviewsCount = reviewDao.countByQuery(unassignedReviewQuery);
+ context.saveMeasure(CoreMetrics.UNASSIGNED_REVIEWS, unassignedReviewsCount.doubleValue());
+
+ // Unplanned reviews
+ ReviewQuery plannedReviewQuery = ReviewQuery.copy(openReviewQuery).setPlanned();
+ int plannedReviewsCount = reviewDao.countByQuery(plannedReviewQuery);
+ context.saveMeasure(CoreMetrics.UNPLANNED_REVIEWS, (double) (openReviewsCount - plannedReviewsCount));
+
+ // False positive reviews
+ ReviewQuery falsePositiveReviewQuery = ReviewQuery.create().setResourceId(resource.getId())
+ .addResolution(ReviewDto.RESOLUTION_FALSE_POSITIVE);
+ Integer falsePositiveReviewsCount = reviewDao.countByQuery(falsePositiveReviewQuery);
+ context.saveMeasure(CoreMetrics.FALSE_POSITIVE_REVIEWS, falsePositiveReviewsCount.doubleValue());
+
+ // Violations without a review
+ int violationsCount = context.getViolations().size();
+ context.saveMeasure(CoreMetrics.VIOLATIONS_WITHOUT_REVIEW, (double) (violationsCount - openReviewsCount - falsePositiveReviewsCount));
+ }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ReviewsMetricsWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ReviewsMetricsWidget.java
new file mode 100644
index 00000000000..7ad4e69cd18
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ReviewsMetricsWidget.java
@@ -0,0 +1,40 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.widgets.reviews;
+
+import org.sonar.api.web.AbstractRubyTemplate;
+import org.sonar.api.web.RubyRailsWidget;
+import org.sonar.api.web.WidgetCategory;
+
+@WidgetCategory({ "Reviews" })
+public class ReviewsMetricsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
+ public String getId() {
+ return "reviews_metrics";
+ }
+
+ public String getTitle() {
+ return "Reviews metrics";
+ }
+
+ @Override
+ protected String getTemplatePath() {
+ return "/org/sonar/plugins/core/widgets/reviews/reviews_metrics.html.erb";
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/reviews_metrics.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/reviews_metrics.html.erb
new file mode 100644
index 00000000000..3388d4fe12e
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/reviews_metrics.html.erb
@@ -0,0 +1,50 @@
+<%
+ active_reviews=measure('active_reviews')
+ unassigned_reviews=measure('unassigned_reviews')
+ unplanned_reviews=measure('unplanned_reviews')
+ false_positive_reviews=measure('false_positive_reviews')
+ violations_without_review=measure('violations_without_review')
+%>
+<table width="100%">
+ <tr>
+ <td valign="top" width="48%" nowrap>
+ <div class="dashbox">
+ <h3><%= message('widget.reviews_metrics.active_reviews') -%></h3>
+ <% if active_reviews %>
+ <p>
+ <span class="big"><%= format_measure(active_reviews, :suffix => '', :url => url_for_drilldown(active_reviews)) -%></span>
+ <%= dashboard_configuration.selected_period? ? format_variation(active_reviews) : trend_icon(active_reviews) -%>
+ </p>
+ <p>
+ <%= format_measure(unassigned_reviews, :suffix => message('widget.reviews_metrics.unassigned.suffix'), :url => url_for_drilldown(unassigned_reviews)) -%>
+ <%= dashboard_configuration.selected_period? ? format_variation(unassigned_reviews) : trend_icon(unassigned_reviews) -%>
+ </p>
+ <p>
+ <%= format_measure(unplanned_reviews, :suffix => message('widget.reviews_metrics.unplanned.suffix'), :url => url_for_drilldown(unplanned_reviews)) -%>
+ <%= dashboard_configuration.selected_period? ? format_variation(unplanned_reviews) : trend_icon(unplanned_reviews) -%>
+ </p>
+ <p>
+ <%= format_measure(false_positive_reviews, :suffix => message('widget.reviews_metrics.false_positives.suffix'), :url => url_for_drilldown(false_positive_reviews)) -%>
+ <%= dashboard_configuration.selected_period? ? format_variation(false_positive_reviews) : trend_icon(false_positive_reviews) -%>
+ </p>
+ <% else %>
+ <span class="empty_widget"><%= message('widget.reviews_metrics.no_data') -%></span>
+ <% end %>
+ </div>
+ </td>
+ <td width="10"> </td>
+ <td valign="top">
+ <div class="dashbox">
+ <h3><%= message('widget.reviews_metrics.unreviewed_violations') -%></h3>
+ <% if violations_without_review %>
+ <p>
+ <span class="big"><%= format_measure(violations_without_review, :suffix => '', :url => url_for_drilldown(violations_without_review)) -%></span>
+ <%= dashboard_configuration.selected_period? ? format_variation(violations_without_review) : trend_icon(violations_without_review) -%>
+ </p>
+ <% else %>
+ <span class="empty_widget"><%= message('widget.reviews_metrics.no_data') -%></span>
+ <% end %>
+ </div>
+ </td>
+ </tr>
+</table> \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest.java
new file mode 100644
index 00000000000..b72c4b989f8
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest.java
@@ -0,0 +1,162 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.sensors;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Violation;
+import org.sonar.core.review.ReviewDao;
+import org.sonar.core.review.ReviewDto;
+import org.sonar.core.review.ReviewQuery;
+import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator;
+
+import com.google.common.collect.Lists;
+
+public class ReviewsMeasuresDecoratorTest {
+
+ @Test
+ public void testDependsUponViolationTracking() throws Exception {
+ ReviewsMeasuresDecorator decorator = new ReviewsMeasuresDecorator(null);
+ assertEquals(decorator.dependsUponViolationTracking(), ViolationTrackingDecorator.class);
+ }
+
+ @Test
+ public void shouldExecuteOnProject() throws Exception {
+ ReviewsMeasuresDecorator decorator = new ReviewsMeasuresDecorator(null);
+ Project project = new Project("foo");
+ project.setLatestAnalysis(true);
+ assertThat(decorator.shouldExecuteOnProject(project), is(true));
+ }
+
+ @Test
+ public void shouldDecorateOnlyFiles() throws Exception {
+ ReviewsMeasuresDecorator decorator = new ReviewsMeasuresDecorator(null);
+ DecoratorContext context = mock(DecoratorContext.class);
+ Resource<?> resource = new Project("Foo");
+ decorator.decorate(resource, context);
+ verify(context, never()).saveMeasure(any(Metric.class), anyDouble());
+ }
+
+ @Test
+ public void shouldComputeReviewMetrics() throws Exception {
+ ReviewDao reviewDao = mock(ReviewDao.class);
+ when(reviewDao.countByQuery(argThat(openReviewQueryMatcher()))).thenReturn(10);
+ when(reviewDao.countByQuery(argThat(unassignedReviewQueryMatcher()))).thenReturn(2);
+ when(reviewDao.countByQuery(argThat(plannedReviewQueryMatcher()))).thenReturn(3);
+ when(reviewDao.countByQuery(argThat(falsePositiveReviewQueryMatcher()))).thenReturn(4);
+
+ ReviewsMeasuresDecorator decorator = new ReviewsMeasuresDecorator(reviewDao);
+ Resource<?> resource = new File("foo").setId(1);
+ DecoratorContext context = mock(DecoratorContext.class);
+ List<Violation> violations = mock(List.class);
+ when(violations.size()).thenReturn(35);
+ when(context.getViolations()).thenReturn(violations);
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.ACTIVE_REVIEWS, 10d);
+ verify(context).saveMeasure(CoreMetrics.UNASSIGNED_REVIEWS, 2d);
+ verify(context).saveMeasure(CoreMetrics.UNPLANNED_REVIEWS, 7d);
+ verify(context).saveMeasure(CoreMetrics.FALSE_POSITIVE_REVIEWS, 4d);
+ verify(context).saveMeasure(CoreMetrics.VIOLATIONS_WITHOUT_REVIEW, 21d);
+ }
+
+ private BaseMatcher<ReviewQuery> openReviewQueryMatcher() {
+ return new BaseMatcher<ReviewQuery>() {
+ public boolean matches(Object o) {
+ ReviewQuery query = (ReviewQuery) o;
+ if (query == null) {
+ return false;
+ }
+ return Lists.newArrayList(ReviewDto.STATUS_OPEN, ReviewDto.STATUS_REOPENED).equals(query.getStatuses())
+ && query.getNoAssignee() == null && query.getPlanned() == null;
+ }
+
+ public void describeTo(Description description) {
+ }
+ };
+ }
+
+ private BaseMatcher<ReviewQuery> unassignedReviewQueryMatcher() {
+ return new BaseMatcher<ReviewQuery>() {
+ public boolean matches(Object o) {
+ ReviewQuery query = (ReviewQuery) o;
+ if (query == null) {
+ return false;
+ }
+ return Lists.newArrayList(ReviewDto.STATUS_OPEN, ReviewDto.STATUS_REOPENED).equals(query.getStatuses())
+ && query.getNoAssignee() == Boolean.TRUE;
+ }
+
+ public void describeTo(Description description) {
+ }
+ };
+ }
+
+ private BaseMatcher<ReviewQuery> plannedReviewQueryMatcher() {
+ return new BaseMatcher<ReviewQuery>() {
+ public boolean matches(Object o) {
+ ReviewQuery query = (ReviewQuery) o;
+ if (query == null) {
+ return false;
+ }
+ return Lists.newArrayList(ReviewDto.STATUS_OPEN, ReviewDto.STATUS_REOPENED).equals(query.getStatuses())
+ && query.getPlanned() == Boolean.TRUE;
+ }
+
+ public void describeTo(Description description) {
+ }
+ };
+ }
+
+ private BaseMatcher<ReviewQuery> falsePositiveReviewQueryMatcher() {
+ return new BaseMatcher<ReviewQuery>() {
+ public boolean matches(Object o) {
+ ReviewQuery query = (ReviewQuery) o;
+ if (query == null) {
+ return false;
+ }
+ return Lists.newArrayList(ReviewDto.RESOLUTION_FALSE_POSITIVE).equals(query.getResolutions());
+ }
+
+ public void describeTo(Description description) {
+ }
+ };
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest/fixture.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest/fixture.xml
new file mode 100644
index 00000000000..7384c0e2e5f
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/ReviewsMeasuresDecoratorTest/fixture.xml
@@ -0,0 +1,69 @@
+<dataset>
+ <reviews
+ id="1"
+ status="OPEN"
+ rule_failure_permanent_id="1"
+ resource_id="1"
+ title="message OLD"
+ resource_line="0"
+ resolution="[null]"
+ created_at="[null]"
+ updated_at="[null]"
+ project_id="[null]"
+ severity="[null]"
+ user_id="[null]"
+ rule_id="[null]"
+ manual_violation="false"
+ manual_severity="false"/>
+ <reviews
+ id="2"
+ status="OPEN"
+ rule_failure_permanent_id="2"
+ resource_id="1"
+ title="message 2"
+ resource_line="2"
+ rule_id="[null]"
+ manual_violation="false"
+ manual_severity="false"/>
+ <reviews
+ id="3"
+ status="OPEN"
+ rule_failure_permanent_id="3"
+ resource_id="1"
+ title="message 3"
+ resource_line="0"
+ rule_id="[null]"
+ manual_violation="false"
+ manual_severity="false"/>
+ <reviews
+ id="4"
+ status="OPEN"
+ rule_failure_permanent_id="4"
+ resource_id="1"
+ title="message OLD"
+ resource_line="4"
+ rule_id="[null]"
+ manual_violation="false"
+ manual_severity="false"/>
+ <reviews
+ id="5"
+ status="OPEN"
+ rule_failure_permanent_id="5"
+ resource_id="1"
+ title="message 5"
+ resource_line="[null]"
+ rule_id="[null]"
+ manual_violation="false"
+ manual_severity="false"/>
+ <reviews
+ id="6"
+ status="OPEN"
+ rule_failure_permanent_id="6"
+ resource_id="1"
+ title="message OLD"
+ resource_line="[null]"
+ rule_id="[null]"
+ manual_violation="false"
+ manual_severity="false"/>
+
+</dataset> \ No newline at end of file
diff --git a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
index 5798be1507c..dbb2a5c49fc 100644
--- a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
+++ b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
@@ -719,6 +719,15 @@ widget.planned_reviews.no_action_plan=No action plan
widget.unplanned_reviews.name=Unplanned reviews
widget.unplanned_reviews.description=Shows all the reviews of the project that are not planned yet in an action plan
+widget.reviews_metrics.name=Reviews metrics
+widget.reviews_metrics.description=Reports metrics about reviews
+widget.reviews_metrics.no_data=No data
+widget.reviews_metrics.active_reviews=Active reviews
+widget.reviews_metrics.unassigned.suffix=\ unassigned
+widget.reviews_metrics.unplanned.suffix=\ unplanned
+widget.reviews_metrics.false_positives.suffix=\ false-positives
+widget.reviews_metrics.unreviewed_violations=Unreviewed violations
+
#------------------------------------------------------------------------------
@@ -1542,3 +1551,23 @@ metric.business_value.description=An indication on the value of the project for
metric.team_size.name=Team size
metric.team_size.description=Size of the project team
+
+#--------------------------------------------------------------------------------------------------------------------
+#
+# REVIEWS METRICS
+#
+#--------------------------------------------------------------------------------------------------------------------
+metric.violations_without_review.name=Unreviewed violations
+metric.violations_without_review.description=Violations that have not been reviewed yet
+
+metric.false_positive_reviews.name=False-positive reviews
+metric.false_positive_reviews.description=Active false-positive reviews
+
+metric.active_reviews.name=Active reviews
+metric.active_reviews.description=Active open and reopened reviews
+
+metric.unassigned_reviews.name=Unassigned reviews
+metric.unassigned_reviews.description=Active unassigned reviews
+
+metric.unplanned_reviews.name=Unplanned reviews
+metric.unplanned_reviews.description=Active unplanned reviews