aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2014-04-25 15:45:08 +0200
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2014-04-25 15:45:16 +0200
commit433fc34a5a8513fe80dc6cd8ad758afde5bfd0a7 (patch)
tree38970e694ee89e140a546283bd129aac262e18c6
parent88132bee081c22e3bad8265a3218bf853244aaa0 (diff)
downloadsonarqube-433fc34a5a8513fe80dc6cd8ad758afde5bfd0a7.tar.gz
sonarqube-433fc34a5a8513fe80dc6cd8ad758afde5bfd0a7.zip
SONAR-4927 Add Quality Gate widget
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java73
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/QualityGateWidget.java33
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/quality_gate.html.erb63
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties10
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb41
7 files changed, 140 insertions, 86 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 b8770197f47..6268c9b7109 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
@@ -20,11 +20,7 @@
package org.sonar.plugins.core;
import com.google.common.collect.ImmutableList;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.Properties;
-import org.sonar.api.Property;
-import org.sonar.api.PropertyType;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.*;
import org.sonar.api.checks.NoSonarFilter;
import org.sonar.core.timemachine.Periods;
import org.sonar.plugins.core.batch.IndexProjectPostJob;
@@ -32,71 +28,19 @@ import org.sonar.plugins.core.charts.DistributionAreaChart;
import org.sonar.plugins.core.charts.DistributionBarChart;
import org.sonar.plugins.core.charts.XradarChart;
import org.sonar.plugins.core.colorizers.JavaColorizerFormat;
-import org.sonar.plugins.core.dashboards.GlobalDefaultDashboard;
-import org.sonar.plugins.core.dashboards.ProjectDefaultDashboard;
-import org.sonar.plugins.core.dashboards.ProjectHotspotDashboard;
-import org.sonar.plugins.core.dashboards.ProjectIssuesDashboard;
-import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard;
-import org.sonar.plugins.core.issue.CountFalsePositivesDecorator;
-import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator;
-import org.sonar.plugins.core.issue.InitialOpenIssuesSensor;
-import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
-import org.sonar.plugins.core.issue.IssueHandlers;
-import org.sonar.plugins.core.issue.IssueTracking;
-import org.sonar.plugins.core.issue.IssueTrackingDecorator;
-import org.sonar.plugins.core.issue.IssuesDensityDecorator;
-import org.sonar.plugins.core.issue.WeightedIssuesDecorator;
-import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.NewIssuesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewIssuesNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.SendIssueNotificationsPostJob;
+import org.sonar.plugins.core.dashboards.*;
+import org.sonar.plugins.core.issue.*;
+import org.sonar.plugins.core.issue.notification.*;
import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
import org.sonar.plugins.core.measurefilters.ProjectFilter;
import org.sonar.plugins.core.notifications.alerts.NewAlerts;
import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
-import org.sonar.plugins.core.sensors.BranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.CommentDensityDecorator;
-import org.sonar.plugins.core.sensors.CoverageDecorator;
-import org.sonar.plugins.core.sensors.CoverageMeasurementFilter;
-import org.sonar.plugins.core.sensors.DirectoriesDecorator;
-import org.sonar.plugins.core.sensors.FileHashSensor;
-import org.sonar.plugins.core.sensors.FilesDecorator;
-import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.ItCoverageDecorator;
-import org.sonar.plugins.core.sensors.ItLineCoverageDecorator;
-import org.sonar.plugins.core.sensors.LineCoverageDecorator;
-import org.sonar.plugins.core.sensors.ManualMeasureDecorator;
-import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.OverallCoverageDecorator;
-import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator;
-import org.sonar.plugins.core.sensors.ProfileEventsSensor;
-import org.sonar.plugins.core.sensors.ProjectLinksSensor;
-import org.sonar.plugins.core.sensors.UnitTestDecorator;
-import org.sonar.plugins.core.sensors.VersionEventsSensor;
-import org.sonar.plugins.core.timemachine.NewCoverageAggregator;
-import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer;
-import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer;
-import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer;
-import org.sonar.plugins.core.timemachine.TendencyDecorator;
-import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister;
-import org.sonar.plugins.core.timemachine.VariationDecorator;
+import org.sonar.plugins.core.sensors.*;
+import org.sonar.plugins.core.timemachine.*;
import org.sonar.plugins.core.web.TestsViewer;
import org.sonar.plugins.core.widgets.*;
-import org.sonar.plugins.core.widgets.issues.ActionPlansWidget;
-import org.sonar.plugins.core.widgets.issues.FalsePositiveIssuesWidget;
-import org.sonar.plugins.core.widgets.issues.IssueFilterWidget;
-import org.sonar.plugins.core.widgets.issues.IssuesWidget;
-import org.sonar.plugins.core.widgets.issues.MyUnresolvedIssuesWidget;
-import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesPerAssigneeWidget;
-import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesStatusesWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterAsBubbleChartWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterAsCloudWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterAsHistogramWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterAsPieChartWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterListWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterTreemapWidget;
+import org.sonar.plugins.core.widgets.issues.*;
+import org.sonar.plugins.core.widgets.measures.*;
import java.util.List;
@@ -269,6 +213,7 @@ public final class CorePlugin extends SonarPlugin {
// widgets
AlertsWidget.class,
+ QualityGateWidget.class,
CoverageWidget.class,
ItCoverageWidget.class,
DescriptionWidget.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/QualityGateWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/QualityGateWidget.java
new file mode 100644
index 00000000000..dd7e1d0cf85
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/QualityGateWidget.java
@@ -0,0 +1,33 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.plugins.core.widgets;
+
+import org.sonar.api.web.*;
+
+@WidgetProperties({
+ @WidgetProperty(key = "show_ok", type = WidgetPropertyType.BOOLEAN, defaultValue = "false"),
+})
+@WidgetLayout(WidgetLayoutType.NONE)
+public class QualityGateWidget extends CoreWidget {
+
+ public QualityGateWidget() {
+ super("quality_gate", "Quality Gate Details", "/org/sonar/plugins/core/widgets/quality_gate.html.erb");
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/quality_gate.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/quality_gate.html.erb
new file mode 100644
index 00000000000..af8143e1a58
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/quality_gate.html.erb
@@ -0,0 +1,63 @@
+<% m=measure(Metric::QUALITY_GATE_DETAILS)
+ if m && !m.measure_data.blank?
+
+ details = JSON.parse m.measure_data.data
+ m.alert_status = details['level']
+
+ warn_message = message('measure_filter.criteria.alert.warn')
+ error_message = message('measure_filter.criteria.alert.error')
+
+ css_class = "color_#{m.alert_status}"
+ if m.alert_status==Metric::TYPE_LEVEL_OK
+ label = "<b>#{message('widget.alerts.no_alert')}.</b>"
+ elsif m.alert_status==Metric::TYPE_LEVEL_WARN
+ label = "<b>#{message('widget.alerts.warnings')}</b>"
+ else
+ label = "<b>#{message('widget.alerts.errors')}</b>"
+ end
+-%><div class="widget <%= css_class -%>" id="quality_gate_widget_<%= widget.id -%>">
+ <div><%= format_measure(m) -%> <%= label -%></div>
+ <table class="data" style="color: black; margin-top: 10px">
+ <thead>
+ <tr></tr>
+ </thead>
+ <tbody>
+ <% details['conditions'].sort_by {|condition| [ -condition['level'].length, metric(condition['metric']).short_name] }.each do |condition|
+
+ level = condition['level']
+ condition_metric = metric(condition['metric'])
+ operator = message("quality_gates.operator.#{condition['op']}.short")
+ period = condition['period']
+ warning_value = condition['warning']
+ error_value = condition['error']
+ actual_value = condition['actual']
+
+ detail_measure = ProjectMeasure.new :metric => m.metric, :alert_status => level
+
+ drilldown_url = period.blank? ? url_for_drilldown(condition_metric) : url_for_drilldown(condition_metric, :period => period)
+
+ actual_measure = ProjectMeasure.new :metric => condition_metric, :value => actual_value
+ warning_measure = ProjectMeasure.new :metric => condition_metric, :value => warning_value
+ error_measure = ProjectMeasure.new :metric => condition_metric, :value => error_value
+
+
+ unless level == 'OK' && !widget_properties['show_ok']
+ -%>
+ <tr>
+ <td><%= format_measure(detail_measure) -%></td>
+ <td><%= link_to "#{condition_metric.short_name} #{period_label(@snapshot, period) unless period.blank?}", drilldown_url, {:class => 'nolink'} -%></td>
+ <td align="right"><%= link_to format_measure(actual_measure), drilldown_url, {:class => 'nolink'} -%></td>
+ <td>
+ <% if level == 'WARN' -%><%= operator -%> <%= format_measure(warning_measure) -%><% end -%>
+ <% if level == 'ERROR' -%><%= operator -%> <%= format_measure(error_measure) -%><% end -%>
+ <% if level == 'OK' -%>
+ <% unless warning_value.blank? -%><%= warn_message -%> <%= operator -%> <%= format_measure(warning_measure) -%> <%= '|' unless error_value.blank? -%><% end %>
+ <% unless error_value.blank? -%><%= error_message -%> <%= operator -%> <%= format_measure(error_measure) -%><% end %>
+ <% end -%>
+ </tr>
+ <% end
+ end -%>
+ </tbody>
+ </table>
+</div>
+<% end %>
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 6353deb8990..ffbf75fbfe3 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -980,6 +980,10 @@ widget.alerts.no_alert=The project has passed the quality gate
widget.alerts.warnings=The project has warnings on the following quality gate conditions:\u00a0
widget.alerts.errors=The project failed the quality gate on the following conditions:\u00a0
+widget.quality_gate.name=Quality Gate Details
+widget.quality_gate.description=Displays a detailed view of the project's quality gate status.
+widget.quality_gate.property.show_ok.name=Show passed conditions
+
widget.code_coverage.name=Unit Tests Coverage
widget.code_coverage.description=Reports on units tests and code coverage by unit tests.
widget.code_coverage.line_coverage.suffix=\ line coverage
@@ -1651,6 +1655,8 @@ quality_gates.add_condition=Add Condition
quality_gates.no_conditions=No Conditions
quality_gates.introduction=Only project measures are checked against thresholds. Sub-projects, directories and files are ignored.
quality_gates.health_icons=Project health icons represent:
+quality_gates.metric=Metric
+quality_gates.threshold=Threshold
quality_gates.projects_for_default=Every project not specifically associated to a quality gate will be associated to this one by default.
quality_gates.projects_for_default.edit=You must not select specific projects for the default quality gate.
quality_gates.projects.with=With
@@ -1663,6 +1669,10 @@ quality_gates.operator.LT=is less than
quality_gates.operator.GT=is greater than
quality_gates.operator.EQ=equals
quality_gates.operator.NE=is not
+quality_gates.operator.LT.short=<
+quality_gates.operator.GT.short=>
+quality_gates.operator.EQ.short==
+quality_gates.operator.NE.short=\u2260
quality_gates.delete.confirm.message=Are you sure you want to delete the "{0}" quality gate?
quality_gates.delete.confirm.default=Are you sure you want to delete the "{0}" quality gate, which is the default quality gate?
quality_gates.delete_condition=Delete Condition
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
index 7ff4bb07bec..f1c7f33a454 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
@@ -2202,8 +2202,10 @@ public final class CoreMetrics {
.create();
public static final String QUALITY_GATE_DETAILS_KEY = "quality_gate_details";
- public static final Metric QUALITY_GATE_DETAILS = new Metric.Builder(QUALITY_GATE_DETAILS_KEY, "Quality Gate Details", Metric.ValueType.DATA)
+ public static final Metric QUALITY_GATE_DETAILS = new Metric.Builder(QUALITY_GATE_DETAILS_KEY, "Quality Gate Details", Metric.ValueType.LEVEL)
.setDescription("The project detailed status with regard to its quality gate.")
+ .setDirection(Metric.DIRECTION_BETTER)
+ .setQualitative(true)
.setDomain(DOMAIN_GENERAL)
.create();
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
index 1a9ffe1c9ae..08227825e4b 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
@@ -260,7 +260,7 @@ module ApplicationHelper
if options[:period]
html=m.format_numeric_value(m.variation(options[:period].to_i))
elsif m.metric.val_type==Metric::VALUE_TYPE_LEVEL
- html="<i class=\"icon-alert-#{m.data.downcase}\"></i>" unless m.data.blank?
+ html="<i class=\"icon-alert-#{m.alert_status.downcase}\"></i>" unless m.alert_status.blank?
else
html=m.formatted_value
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb
index 712cc1dc90a..a58c1498217 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb
@@ -75,12 +75,12 @@ class Metric < ActiveRecord::Base
# Localized domain name
def self.domain_for(domain_key)
return nil if domain_key.nil?
-
+
localeMap = Metric.i18n_domain_cache[domain_key]
locale = I18n.locale
-
+
return localeMap[locale] if localeMap && localeMap.has_key?(locale)
-
+
i18n_key = 'metric_domain.' + domain_key
result = Api::Utils.message(i18n_key, :default => domain_key)
localeMap[locale] = result if localeMap
@@ -91,7 +91,7 @@ class Metric < ActiveRecord::Base
m=by_key(metric_key)
m && m.short_name
end
-
+
def key
name
end
@@ -101,29 +101,29 @@ class Metric < ActiveRecord::Base
return default_string unless translate
Metric.domain_for(default_string)
end
-
+
def domain=(value)
write_attribute(:domain, value)
end
-
+
def short_name(translate=true)
default_string = read_attribute(:short_name)
return default_string unless translate
-
+
metric_key = read_attribute(:name)
return nil if metric_key.nil?
-
+
localeMap = Metric.i18n_short_name_cache[metric_key]
locale = I18n.locale
-
+
return localeMap[locale] if localeMap && localeMap.has_key?(locale)
-
+
i18n_key = 'metric.' + metric_key + '.name'
result = Api::Utils.message(i18n_key, :default => default_string)
localeMap[locale] = result if localeMap
result
end
-
+
def short_name=(value)
write_attribute(:short_name, value)
end
@@ -133,15 +133,15 @@ class Metric < ActiveRecord::Base
label = Api::Utils.message("metric.#{key}.name", :default => short_name) if label==''
label
end
-
+
def description(translate=true)
default_string = read_attribute(:description) || ''
return default_string unless translate
metric_name = read_attribute(:name)
-
+
return nil if metric_name.nil?
-
+
i18n_key = 'metric.' + metric_name + '.description'
result = Api::Utils.message(i18n_key, :default => default_string)
result
@@ -150,7 +150,7 @@ class Metric < ActiveRecord::Base
def description=(value)
write_attribute(:description, value)
end
-
+
def user_managed?
user_managed==true
end
@@ -170,7 +170,7 @@ class Metric < ActiveRecord::Base
def quantitative?
!qualitative?
end
-
+
def qualitative?
qualitative
end
@@ -260,7 +260,7 @@ class Metric < ActiveRecord::Base
ManualMeasure.delete_all(["metric_id = ?", id])
self.deactivate(id)
end
-
+
def self.deactivate(id)
metric = by_id(id)
metric.enabled = false
@@ -328,7 +328,7 @@ class Metric < ActiveRecord::Base
DIRECTORIES = 'directories'
ACCESSORS = 'accessors'
PUBLIC_API = 'public_api'
-
+
COMPLEXITY = 'complexity'
STATEMENTS = 'statements'
AVG_CMPX_BY_CLASS = 'class_complexity'
@@ -353,7 +353,7 @@ class Metric < ActiveRecord::Base
BRANCH_COVERAGE = 'branch_coverage'
UNCOVERED_LINES='uncovered_lines'
UNCOVERED_CONDITIONS='uncovered_conditions'
-
+
VIOLATIONS = 'violations'
VIOLATIONS_DENSITY = 'violations_density'
WEIGHTED_VIOLATIONS = 'weighted_violations'
@@ -376,6 +376,7 @@ class Metric < ActiveRecord::Base
COMMENTED_OUT_CODE_LINES = 'commented_out_code_lines'
ALERT_STATUS = 'alert_status'
+ QUALITY_GATE_DETAILS = 'quality_gate_details'
PROFILE='profile'
private
@@ -403,7 +404,7 @@ class Metric < ActiveRecord::Base
end
c
end
-
+
def self.i18n_short_name_cache
c = Caches.cache(I18N_SHORT_NAME_CACHE_KEY)
if c.size==0