diff --git a/app/controllers/graphs_controller.rb b/app/controllers/graphs_controller.rb index 10f6fbd..ff78d95 100644 --- a/app/controllers/graphs_controller.rb +++ b/app/controllers/graphs_controller.rb @@ -7,23 +7,25 @@ class GraphsController < ApplicationController ############################################################################ # Initialization ############################################################################ - + menu_item :issues, :only => [:issue_growth, :old_issues] + before_filter :authorize_global, :except => [:recent_status_changes_graph, :recent_assigned_to_changes_graph] before_filter :find_version, :only => [:target_version_graph] before_filter :confirm_issues_exist, :only => [:issue_growth] before_filter :find_optional_project, :only => [:issue_growth_graph] before_filter :find_open_issues, :only => [:old_issues, :issue_age_graph] - + helper IssuesHelper - + ############################################################################ # My Page block graphs ############################################################################ - + # Displays a ring of issue assignement changes around the current user def recent_assigned_to_changes_graph + if User.current.allowed_to?(:view_graphs, nil, :global => true) # Get the top visible projects by issue count sql = " select u1.id as old_user, u2.id as new_user, count(*) as changes_count" sql << " from journals as j" @@ -40,10 +42,14 @@ def recent_assigned_to_changes_graph @users = User.find(:all, :conditions => "id IN ("+user_ids.join(',')+")").index_by { |user| user.id } unless user_ids.empty? headers["Content-Type"] = "image/svg+xml" render :layout => false + else + render :text => t("warning_not_allowed_block") + end end - + # Displays a ring of issue status changes def recent_status_changes_graph + if User.current.allowed_to?(:view_graphs, nil, :global => true) # Get the top visible projects by issue count sql = " select is1.id as old_status, is2.id as new_status, count(*) as changes_count" sql << " from journals as j" @@ -57,31 +63,34 @@ def recent_status_changes_graph @issue_statuses = IssueStatus.find(:all).sort { |a,b| a.position<=>b.position } headers["Content-Type"] = "image/svg+xml" render :layout => false + else + render :text => t("warning_not_allowed_block") + end end - - + + ############################################################################ # Graph pages ############################################################################ - + # Displays total number of issues over time def issue_growth end - - # Displays created vs update date on open issues over time + + # Displays created vs update date on open issues over time def old_issues - @issues_by_created_on = @issues.sort {|a,b| a.created_on<=>b.created_on} + @issues_by_created_on = @issues.sort {|a,b| a.created_on<=>b.created_on} @issues_by_updated_on = @issues.sort {|a,b| a.updated_on<=>b.updated_on} end - - + + ############################################################################ # Embedded graphs for graph pages ############################################################################ - + # Displays projects by total issues over time def issue_growth_graph - + # Initialize the graph graph = SVG::Graph::TimeSeries.new({ :area_fill => true, @@ -98,7 +107,7 @@ def issue_growth_graph :width => 720, :x_label_format => "%Y-%m-%d" }) - + # Get the top visible projects by issue count sql = "SELECT project_id, COUNT(*) as issue_count" sql << " FROM issues" @@ -108,12 +117,12 @@ def issue_growth_graph sql << " AND (project_id = #{@project.id}" sql << " OR project_id IN (%s)" % @project.descendants.active.visible.collect { |p| p.id }.join(',') unless @project.descendants.active.visible.empty? sql << " )" - end + end sql << " GROUP BY project_id" sql << " ORDER BY issue_count DESC" sql << " LIMIT 6" top_projects = ActiveRecord::Base.connection.select_all(sql).collect { |p| p["project_id"] } - + # Get the issues created per project, per day sql = "SELECT project_id, date(#{Issue.table_name}.created_on) as date, COUNT(*) as issue_count" sql << " FROM #{Issue.table_name}" @@ -123,18 +132,18 @@ def issue_growth_graph # Generate the created_on lines top_projects.each do |project_id, total_count| - counts = issue_counts[project_id].sort { |a,b| a["date"]<=>b["date"] } + counts = issue_counts[project_id].sort { |a,b| a["date"]<=>b["date"] } created_count = 0 created_on_line = Hash.new - created_on_line[(Date.parse(counts.first["date"])-1).to_s] = 0 - counts.each { |count| created_count += count["issue_count"].to_i; created_on_line[count["date"]] = created_count } + created_on_line[(Date.parse(counts.first["date"].to_s)-1).to_s] = 0 + counts.each { |count| created_count += count["issue_count"].to_i; created_on_line[count["date"].to_s] = created_count } created_on_line[Date.today.to_s] = created_count graph.add_data({ :data => created_on_line.sort.flatten, :title => Project.find(project_id).to_s }) end - + # Compile the graph headers["Content-Type"] = "image/svg+xml" send_data(graph.burn, :type => "image/svg+xml", :disposition => "inline") @@ -143,7 +152,7 @@ def issue_growth_graph # Displays issues by creation date, cumulatively def issue_age_graph - + # Initialize the graph graph = SVG::Graph::TimeSeries.new({ :area_fill => true, @@ -164,7 +173,7 @@ def issue_age_graph # Group issues issues_by_created_on = @issues.group_by {|issue| issue.created_on.to_date }.sort issues_by_updated_on = @issues.group_by {|issue| issue.updated_on.to_date }.sort - + # Generate the created_on line created_count = 0 created_on_line = Hash.new @@ -174,7 +183,7 @@ def issue_age_graph :data => created_on_line.sort.flatten, :title => l(:field_created_on) }) unless issues_by_created_on.empty? - + # Generate the closed_on line updated_count = 0 updated_on_line = Hash.new @@ -184,12 +193,12 @@ def issue_age_graph :data => updated_on_line.sort.flatten, :title => l(:field_updated_on) }) unless issues_by_updated_on.empty? - + # Compile the graph headers["Content-Type"] = "image/svg+xml" send_data(graph.burn, :type => "image/svg+xml", :disposition => "inline") end - + # Displays open and total issue counts over time def target_version_graph @@ -214,14 +223,14 @@ def target_version_graph issues_by_created_on = @version.fixed_issues.group_by {|issue| issue.created_on.to_date }.sort issues_by_updated_on = @version.fixed_issues.group_by {|issue| issue.updated_on.to_date }.sort issues_by_closed_on = @version.fixed_issues.collect {|issue| issue if issue.closed? }.compact.group_by {|issue| issue.updated_on.to_date }.sort - + # Set the scope of the graph scope_end_date = issues_by_updated_on.last.first scope_end_date = @version.effective_date if !@version.effective_date.nil? && @version.effective_date > scope_end_date scope_end_date = Date.today if !@version.completed? line_end_date = Date.today line_end_date = scope_end_date if scope_end_date < line_end_date - + # Generate the created_on line created_count = 0 created_on_line = Hash.new @@ -231,7 +240,7 @@ def target_version_graph :data => created_on_line.sort.flatten, :title => l(:label_total).capitalize }) - + # Generate the closed_on line closed_count = 0 closed_on_line = Hash.new @@ -241,25 +250,25 @@ def target_version_graph :data => closed_on_line.sort.flatten, :title => l(:label_closed_issues).capitalize }) - + # Add the version due date marker graph.add_data({ :data => [@version.effective_date.to_s, created_count], :title => l(:field_due_date).capitalize }) unless @version.effective_date.nil? - - + + # Compile the graph headers["Content-Type"] = "image/svg+xml" send_data(graph.burn, :type => "image/svg+xml", :disposition => "inline") end - - + + ############################################################################ # Private methods ############################################################################ private - + def confirm_issues_exist find_optional_project if !@project.nil? @@ -272,7 +281,7 @@ def confirm_issues_exist rescue ActiveRecord::RecordNotFound render_404 end - + def find_open_issues find_optional_project if !@project.nil? @@ -285,14 +294,14 @@ def find_open_issues rescue ActiveRecord::RecordNotFound render_404 end - + def find_optional_project @project = Project.find(params[:project_id]) unless params[:project_id].blank? deny_access unless User.current.allowed_to?(:view_issues, @project, :global => true) rescue ActiveRecord::RecordNotFound render_404 end - + def find_version @version = Version.find(params[:id]) deny_access unless User.current.allowed_to?(:view_issues, @version.project) diff --git a/config/locales/cs.yml b/config/locales/cs.yml new file mode 100644 index 0000000..d903a8c --- /dev/null +++ b/config/locales/cs.yml @@ -0,0 +1,9 @@ +cs: + label_graphs: Grafy + label_graphs_total_vs_closed_issues: Celkový počet úkolů a uzavřených úkolů + label_graphs_old_issues: Otevřít zastaralé úkoly + label_graphs_issue_growth: Všechny úkoly od počátku + label_graphs_issue_status_flow: Nedávno změněné stavy (během 24 hodin) + label_graphs_assigned_to_status_flow: Nedávno změněné přidělení (během 24 hodin) + warning_no_issues: Nejsou zde žádné úkoly. + \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 4eda405..e08d693 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -6,4 +6,7 @@ en: label_graphs_issue_status_flow: Recent status changes (within 24 hours) label_graphs_assigned_to_status_flow: Recent assignment changes (within 24 hours) warning_no_issues: There are no issues to graph. - \ No newline at end of file + warning_not_allowed_block: Sorry you can't use this block. + permission_view_graphs: View Graphs + + diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..45d7cf5 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,11 @@ +ActionController::Routing::Routes.draw do |map| + map.connect 'projects/:project_id/issues/old', :controller => 'graphs', :action => 'old_issues' + map.connect 'issues/old', :controller => 'graphs', :action => 'old_issues' + map.connect ':project_id/issue_age_graph', :controller => 'graphs', :action => 'issue_age_graph' + map.connect 'projects/:project_id/issues/growth', :controller => 'graphs', :action => 'issue_growth' + map.connect 'issues/growth', :controller => 'graphs', :action => 'issue_growth' + map.connect ':project_id/issue_growth_graph', :controller => 'graphs', :action => 'issue_growth_graph' + map.connect 'graphs/recent-status-changes', :controller=>"graphs", :action=>"recent_status_changes_graph" + map.connect 'graphs/recent-assigned-to-changes', :controller=>"graphs", :action=>"recent_assigned_to_changes_graph" + map.connect 'graphs/target-version/:id', :controller=>"graphs", :action=>"target_version_graph" +end diff --git a/init.rb b/init.rb index fb8f39b..6f8c94b 100644 --- a/init.rb +++ b/init.rb @@ -8,4 +8,8 @@ author 'Brad Beattie' description 'This plugin provides instances of Redmine with additional graphs.' version '0.1.0' + + project_module :graphs do + permission :view_graphs, :graphs => [:issue_growth, :old_issues] + end end diff --git a/lib/issues_sidebar_graph_hook.rb b/lib/issues_sidebar_graph_hook.rb index a4fd3de..a4f600e 100644 --- a/lib/issues_sidebar_graph_hook.rb +++ b/lib/issues_sidebar_graph_hook.rb @@ -1,20 +1,22 @@ # Provides a link to the issue age graph on the issue index page class IssuesSidebarGraphHook < Redmine::Hook::ViewListener def view_issues_sidebar_issues_bottom(context = { }) - if context[:project].nil? - output = "

#{l(:label_graphs)}

" - output << link_to(l(:label_graphs_old_issues), {:controller => 'graphs', :action => 'old_issues', :only_path => true}) - output << "
" - output << link_to(l(:label_graphs_issue_growth), {:controller => 'graphs', :action => 'issue_growth', :only_path => true}) - output << "
" - return output - elsif !context[:project].nil? - output = "

#{l(:label_graphs)}

" - output << link_to(l(:label_graphs_old_issues), {:controller => 'graphs', :action => 'old_issues', :project_id => context[:project], :only_path => true}) - output << "
" - output << link_to(l(:label_graphs_issue_growth), {:controller => 'graphs', :action => 'issue_growth', :project_id => context[:project], :only_path => true}) - output << "
" - return output + if User.current.allowed_to?(:view_graphs, nil, :global => true) + if context[:project].nil? + output = "

#{l(:label_graphs)}

" + output << link_to(l(:label_graphs_old_issues), {:controller => 'graphs', :action => 'old_issues', :only_path => true}) + output << "
" + output << link_to(l(:label_graphs_issue_growth), {:controller => 'graphs', :action => 'issue_growth', :only_path => true}) + output << "
" + return output + elsif !context[:project].nil? + output = "

#{l(:label_graphs)}

" + output << link_to(l(:label_graphs_old_issues), {:controller => 'graphs', :action => 'old_issues', :project_id => context[:project], :only_path => true}) + output << "
" + output << link_to(l(:label_graphs_issue_growth), {:controller => 'graphs', :action => 'issue_growth', :project_id => context[:project], :only_path => true}) + output << "
" + return output + end end end end