import org.sonar.plugins.core.testdetailsviewer.TestsViewerDefinition;
import org.sonar.plugins.core.timemachine.*;
import org.sonar.plugins.core.widgets.*;
+import org.sonar.plugins.core.widgets.reviews.FalsePositiveReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.MyReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.ProjectReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.ReviewsPerDeveloperWidget;
import java.util.List;
extensions.add(HotspotMetricWidget.class);
extensions.add(HotspotMostViolatedResourcesWidget.class);
extensions.add(HotspotMostViolatedRulesWidget.class);
+ extensions.add(MyReviewsWidget.class);
+ extensions.add(ProjectReviewsWidget.class);
+ extensions.add(FalsePositiveReviewsWidget.class);
+ extensions.add(ReviewsPerDeveloperWidget.class);
// chart
extensions.add(XradarChart.class);
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 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;
+import org.sonar.api.web.WidgetProperties;
+import org.sonar.api.web.WidgetProperty;
+import org.sonar.api.web.WidgetPropertyType;
+
+@WidgetCategory({ "Reviews" })
+@WidgetProperties(
+ {
+ @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5")
+ }
+)
+public class FalsePositiveReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
+ public String getId() {
+ return "false_positive_reviews";
+ }
+
+ public String getTitle() {
+ return "False positive open reviews";
+ }
+
+ @Override
+ protected String getTemplatePath() {
+ return "/org/sonar/plugins/core/widgets/reviews/false_positive_reviews.html.erb";
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 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;
+import org.sonar.api.web.WidgetProperties;
+import org.sonar.api.web.WidgetProperty;
+import org.sonar.api.web.WidgetPropertyType;
+
+@WidgetCategory({ "Reviews" })
+@WidgetProperties(
+ {
+ @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5")
+ }
+)
+public class MyReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
+ public String getId() {
+ return "my_reviews";
+ }
+
+ public String getTitle() {
+ return "My open reviews";
+ }
+
+ @Override
+ protected String getTemplatePath() {
+ return "/org/sonar/plugins/core/widgets/reviews/my_reviews.html.erb";
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 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;
+import org.sonar.api.web.WidgetProperties;
+import org.sonar.api.web.WidgetProperty;
+import org.sonar.api.web.WidgetPropertyType;
+
+@WidgetCategory({ "Reviews" })
+@WidgetProperties(
+ {
+ @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5")
+ }
+)
+public class ProjectReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
+ public String getId() {
+ return "project_reviews";
+ }
+
+ public String getTitle() {
+ return "Project open reviews";
+ }
+
+ @Override
+ protected String getTemplatePath() {
+ return "/org/sonar/plugins/core/widgets/reviews/project_reviews.html.erb";
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 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;
+import org.sonar.api.web.WidgetProperties;
+import org.sonar.api.web.WidgetProperty;
+import org.sonar.api.web.WidgetPropertyType;
+
+@WidgetCategory({ "Reviews" })
+public class ReviewsPerDeveloperWidget extends AbstractRubyTemplate implements RubyRailsWidget {
+ public String getId() {
+ return "reviews_per_developer";
+ }
+
+ public String getTitle() {
+ return "Open reviews per developer";
+ }
+
+ @Override
+ protected String getTemplatePath() {
+ return "/org/sonar/plugins/core/widgets/reviews/reviews_per_developer.html.erb";
+ }
+}
\ No newline at end of file
%>
-<% unless snapshots %>
+<% unless snapshots && !snapshots.empty? %>
<h3><%= title -%></h3>
- <span style="color: #777777; font-size: 93%; font-style:italic"><%= message('widget.hotspot_metric.cannot_display_not_numeric_metric') -%></span>
+ <span class="empty_widget"><%= message('widget.hotspot_metric.cannot_display_not_numeric_metric') -%></span>
<% else %>
<div class="line-block">
%>
<div id="most-violated-rules-<%= widget.id -%>-<%= priority -%>" class="hotspot" style="padding-top:10px">
- <span style="color: #777777; font-size: 93%; font-style:italic"><%= message('widget.hotspot_most_violated_rules.no_violation_for_severity') -%></span>
+ <span class="empty_widget"><%= message('widget.hotspot_most_violated_rules.no_violation_for_severity') -%></span>
</div>
<%
--- /dev/null
+<%
+ if has_role?(:user, @project)
+ limit = widget_properties["numberOfLines"]
+ unless limit
+ limit = 5
+ end
+%>
+
+<h3><%= message('widget.false_positive_reviews.name') -%></h3>
+
+<div id="reviews-widget-<%= widget.id -%>">
+ <%= render :partial => 'project/widgets/reviews/reviews_list',
+ :locals => {:assignee_login => '',
+ :project_key => @project.key,
+ :statuses => 'RESOLVED',
+ :resolution => 'FALSE-POSITIVE',
+ :limit => limit,
+ :widget_id => widget.id.to_s} %>
+</div>
+
+<% end %>
--- /dev/null
+<%
+ if current_user && has_role?(:user, @project)
+ limit = widget_properties["numberOfLines"]
+ unless limit
+ limit = 5
+ end
+%>
+
+<h3><%= message('widget.my_reviews.name') -%></h3>
+
+<div id="reviews-widget-<%= widget.id -%>">
+ <%= render :partial => 'project/widgets/reviews/reviews_list',
+ :locals => {:assignee_login => current_user.login,
+ :project_key => @project.key,
+ :statuses => 'OPEN,REOPENED',
+ :resolution => '',
+ :limit => limit,
+ :widget_id => widget.id.to_s} %>
+</div>
+
+<% end %>
\ No newline at end of file
--- /dev/null
+<%
+ if has_role?(:user, @project)
+ limit = widget_properties["numberOfLines"]
+ unless limit
+ limit = 5
+ end
+%>
+
+<h3><%= message('widget.project_reviews.name') -%></h3>
+
+<div id="reviews-widget-<%= widget.id -%>">
+ <%= render :partial => 'project/widgets/reviews/reviews_list',
+ :locals => {:assignee_login => '',
+ :project_key => @project.key,
+ :statuses => 'OPEN,REOPENED',
+ :resolution => '',
+ :limit => limit,
+ :widget_id => widget.id.to_s} %>
+</div>
+
+<% end %>
--- /dev/null
+<%
+ options = {}
+ options['statuses'] = 'OPEN,REOPENED'
+ options['projects'] = @project.key
+ reviews = Review.search(options)
+
+ reviews_by_dev = {}
+ counter_no_assignee = 0
+ reviews.each do |review|
+ dev = review.assignee
+ if dev
+ counter = reviews_by_dev[dev]
+ if counter
+ reviews_by_dev[dev] = counter+1
+ else
+ reviews_by_dev[dev] = 1
+ end
+ else
+ counter_no_assignee += 1
+ end
+ end
+
+ div_id = "review-per-dev-widget-#{widget.id.to_s}"
+%>
+
+<h3><%= message('widget.reviews_per_developer.name') -%></h3>
+
+<div id="<%= div_id -%>">
+
+ <table class="data width100">
+ <thead>
+ <tr>
+ <th coslpan="5">
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <%
+ reviews_by_dev.sort{|h1,h2| h2[1] <=> h1[1]}.each do |dev, count|
+ %>
+ <tr class="<%= cycle 'even', 'odd', :name => (div_id) -%>">
+ <td>
+ <%= link_to dev.name,
+ :controller => "reviews", :action => "index", :statuses => ['OPEN','REOPENED'], :assignee_login => dev.login -%>
+ </td>
+ <td>
+ <%= count.to_s -%>
+ </td>
+ </tr>
+ <%
+ end
+ %>
+ <tr class="<%= cycle 'even', 'odd', :name => (div_id) -%>">
+ <td>
+ <%= link_to message('widget.reviews_per_developer.not_assigned'),
+ :controller => "reviews", :action => "index", :statuses => ['OPEN','REOPENED'], :assignee_login => '' -%>
+ </td>
+ <td>
+ <%= counter_no_assignee.to_s -%>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+</div>
<% if metric_data_map.values[0].size == 1 %>
- <span style="color: #777777; font-size: 93%; font-style:italic"><%= message('widget.timeline.timeline_not_displayed') -%></span>
+ <span class="empty_widget"><%= message('widget.timeline.timeline_not_displayed') -%></span>
<% else %>
no_results=No results
over_x_days=over {0} days
page_size=Page size
+paging_next=Next
+paging_previous=Previous
project_name=Project name
remove_column=Remove this column
results_not_display_due_to_security=Due to security settings, some results are not being displayed.
widget.hotspot_metric.name=Metric hotspot
widget.hotspot_metric.description=Shows the files that have the worst result for a specific metric.
-widget.hotspot_metric.cannot_display_not_numeric_metric=The hotspot widget cannot display non-numerical metrics.
+widget.hotspot_metric.cannot_display_not_numeric_metric=The hotspot widget cannot display this metric.
widget.hotspot_metric.more=More
widget.hotspot_metric.hotspots_by_x=Hotspots by {0}
widget.hotspot_most_violated_resources.name=Most violated resources
widget.hotspot_most_violated_resources.description=Shows the resources that have the most violations.
+widget.my_reviews.name=My open reviews
+widget.my_reviews.description=Shows open/reopened reviews assigned to the current user.
+widget.my_reviews.no_review=No review.
+
+widget.project_reviews.name=Project open reviews
+widget.project_reviews.description=Shows all the open/reopened reviews.
+
+widget.false_positive_reviews.name=False positive reviews
+widget.false_positive_reviews.description=Shows all the false positives found on the project.
+
+widget.reviews_per_developer.name=Open reviews per developer
+widget.reviews_per_developer.description=Shows the number of open/reopened reviews per developer.
+widget.reviews_per_developer.not_assigned=Not assigned
+
#------------------------------------------------------------------------------
#
end
+ #
+ #
+ # ACTIONS FROM THE REVIEW WIDGETS
+ #
+ #
+
+ # GET
+ def widget_reviews_list
+ project = Project.by_key params[:project_key]
+ unless project && has_role?(:user, project)
+ render :text => "<b>Cannot access the reviews of this project</b>: access denied."
+ return
+ end
+
+ render :partial => 'project/widgets/reviews/reviews_list'
+ end
+
+
## -------------- PRIVATE -------------- ##
private
# table pagination
@page_size = 20
@page_size = params[:page_size].to_i if is_number?(params[:page_size]) && params[:page_size].to_i > 5
+ @total_number = @reviews.size
if @reviews.size > @page_size
@page_id = (params[:page_id] ? params[:page_id].to_i : 1)
@page_count = @reviews.size / @page_size
<% if @filter_context.page_count>1 %>
|
- <%= link_to_if @filter_context.page_id>1, 'previous', {:overwrite_params => {:page_id => @filter_context.page_id-1}} %>
+ <%= link_to_if @filter_context.page_id>1, message('paging_previous'), {:overwrite_params => {:page_id => @filter_context.page_id-1}} %>
<% for index in 1..@filter_context.page_count %>
<%= link_to_unless index==@filter_context.page_id, index.to_s, {:overwrite_params => {:page_id => index}} %>
<% end %>
- <%= link_to_if @filter_context.page_id<@filter_context.page_count, 'next', {:overwrite_params => {:page_id => 1+@filter_context.page_id}} %>
+ <%= link_to_if @filter_context.page_id<@filter_context.page_count, message('paging_next'), {:overwrite_params => {:page_id => 1+@filter_context.page_id}} %>
<% end %>
<% if @filter.projects_homepage? %>
--- /dev/null
+<%
+ assignee_login = params[:assignee_login] unless assignee_login
+ project_key = params[:project_key] unless project_key
+ statuses = params[:statuses] unless statuses
+ resolution = params[:resolution] unless resolution
+ limit = params[:limit] unless limit
+ widget_id = params[:widget_id] unless widget_id
+
+ options = {}
+ options['statuses'] = statuses
+ options['assignees'] = assignee_login
+ options['projects'] = project_key
+ options['resolutions'] = resolution
+ options['sort'] = 'updated_at'
+ reviews = Review.search(options)
+
+ # table pagination
+ page_size = 20
+ page_size = limit.to_i
+ total_number = reviews.size
+ if reviews.size > page_size
+ page_id = (params[:page_id] ? params[:page_id].to_i : 1)
+ page_count = reviews.size / page_size
+ page_count += 1 if (reviews.size % page_size > 0)
+ from = (page_id-1) * page_size
+ to = (page_id*page_size)-1
+ to = reviews.size-1 if to >= reviews.size
+ reviews = reviews[from..to]
+ end
+%>
+
+<% if reviews.size ==0 %>
+
+ <span class="empty_widget"><%= message('widget.my_reviews.no_review') -%></span>
+
+<% else %>
+
+ <table id="reviews-widget-list-<%= widget_id -%>" class="data width100">
+ <thead>
+ <tr>
+ <th coslpan="5">
+ </th>
+ </tr>
+ </thead>
+ <tfoot>
+ <tr>
+ <td colspan="6">
+ <%= total_number -%> <%= message('results').downcase -%>
+ <%
+ if page_count
+ link_params = {}
+ link_params[:controller] = 'reviews'
+ link_params[:action] = 'widget_reviews_list'
+ link_params[:assignee_login] = assignee_login if assignee_login && !assignee_login.blank?
+ link_params[:project_key] = project_key
+ link_params[:statuses] = statuses
+ link_params[:resolution] = resolution if resolution && !resolution.blank?
+ link_params[:limit] = limit
+ link_params[:widget_id] = widget_id
+ %>
+ |
+ <%= link_to_remote (message('paging_previous'),
+ :update => "reviews-widget-#{widget_id}",
+ :url => {:params => link_params.merge({:page_id => page_id-1})}) if page_id>1 %>
+ <%= message('paging_previous') unless page_id>1 %>
+ <% for index in 1..page_count %>
+ <%= index.to_s if index==page_id %>
+ <%= link_to_remote (index.to_s,
+ :update => "reviews-widget-#{widget_id}",
+ :url => {:params => link_params.merge({:page_id => index})}) unless index==page_id %>
+ <% end %>
+ <%= link_to_remote (message('paging_next'),
+ :update => "reviews-widget-#{widget_id}",
+ :url => {:params => link_params.merge({:page_id => page_id+1})}) if page_id<page_count %>
+ <%= message('paging_next') unless page_id<page_count %>
+ <%
+ end
+ %>
+ </td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <%
+ reviews.each do |review|
+ comment = review.comments.last
+ %>
+ <tr class="<%= cycle 'even', 'odd', :name => ('reviews-widget-list-' + widget_id) -%>">
+ <td>
+ <%= link_to h(review.id), :controller => "reviews", :action => "view", :id => review.id -%>
+ </td>
+ <td><img src="<%= ApplicationController.root_context -%>/images/priority/<%= review.severity -%>.png" title="<%= message(review.severity.downcase).capitalize -%>"/></td>
+ <td>
+ <%= link_to h(review.title), :controller => "reviews", :action => "view", :id => review.id -%>
+ <div class="comment-excerpt">
+ <img src="<%= ApplicationController.root_context -%>/images/reviews/comment.png"/>
+ <b><%= comment.user.name -%> :</b>
+ <%= comment.excerpt -%>
+ </div>
+ </td>
+ <td class="nowrap"><%= distance_of_time_in_words_to_now(review.updated_at) -%></td>
+ </tr>
+ <%
+ end
+ %>
+ </tbody>
+ </table>
+
+<% end %>
\ No newline at end of file
<tfoot>
<tr>
<td colspan="6">
- <%= @reviews.size -%> <%= message('results').downcase -%>
+ <%= @total_number -%> <%= message('results').downcase -%>
<% if @page_count %>
|
- <%= link_to_if @page_id>1, 'previous', {:overwrite_params => {:page_id => @page_id-1}} %>
+ <%= link_to_if @page_id>1, message('paging_previous'), {:overwrite_params => {:page_id => @page_id-1}} %>
<% for index in 1..@page_count %>
<%= link_to_unless index==@page_id, index.to_s, {:overwrite_params => {:page_id => index}} %>
<% end %>
- <%= link_to_if @page_id<@page_count, 'next', {:overwrite_params => {:page_id => 1+@page_id}} %>
+ <%= link_to_if @page_id<@page_count, message('paging_next'), {:overwrite_params => {:page_id => 1+@page_id}} %>
<% end %>
</td>
</tr>
vertical-align: bottom;
}
+span.empty_widget {
+ color: #777777;
+ font-size: 93%;
+ font-style:italic;
+}
+
.dashbox {
float: left;
vertical-align: top;