From f81d0d516021d39725e738d5520133825b14d795 Mon Sep 17 00:00:00 2001 From: Brad Beattie Date: Thu, 12 Mar 2009 10:45:55 -0700 Subject: [PATCH] Optimizing the issue growth graph. With thousands of issues, the old version ran quite slowly as it was grabbing information for each issue. This new implementation only selects what it needs to show the graph. Future versions might simplify it further by selecting issues created per week instead of per day, but I'll leave that for later. --- app/controllers/graphs_controller.rb | 60 ++++++++++++++++----------- assets/stylesheets/issue_age.css | 10 ++--- assets/stylesheets/issue_growth.css | 15 ++++--- assets/stylesheets/target_version.css | 15 ++++--- 4 files changed, 56 insertions(+), 44 deletions(-) diff --git a/app/controllers/graphs_controller.rb b/app/controllers/graphs_controller.rb index 359c453..792e7cc 100755 --- a/app/controllers/graphs_controller.rb +++ b/app/controllers/graphs_controller.rb @@ -4,7 +4,7 @@ class GraphsController < ApplicationController before_filter :find_version, :only => [:target_version_graph] before_filter :find_open_issues, :only => [:old_issues, :issue_age_graph] - before_filter :find_all_issues, :only => [:issue_growth_graph, :issue_growth] + before_filter :find_optional_project, :only => [:issue_growth_graph] helper IssuesHelper @@ -16,6 +16,7 @@ class GraphsController < ApplicationController # Initialize the graph graph = SVG::Graph::TimeSeries.new({ + :area_fill => true, :height => 300, :min_y_value => 0, :no_css => true, @@ -26,27 +27,41 @@ class GraphsController < ApplicationController :show_data_values => false, :stagger_x_labels => true, :style_sheet => "/plugin_assets/redmine_graphs/stylesheets/issue_growth.css", - :timescale_divisions => "1 weeks", - :width => 800, - :x_label_format => "%b %d" + :timescale_divisions => "1 months", + :width => 720, + :x_label_format => "%b %y" }) - - # Group issues - issues_by_project = @issues.group_by {|issue| issue.project } - projects_by_size = issues_by_project.collect { |project, issues| [project, issues.size] }.sort { |a,b| b[1]<=>a[1] }[0..5] + + # Get the top visible projects by issue count + sql = "SELECT project_id, COUNT(*) issue_count" + sql << " FROM issues" + sql << " LEFT JOIN #{Project.table_name} ON #{Issue.table_name}.project_id = #{Project.table_name}.id" + sql << " WHERE (%s)" % Project.allowed_to_condition(User.current, :view_issues) + 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"] } - # Generate the created_on line - projects_by_size.each do |project, size| - issues_by_created_on = issues_by_project[project].group_by {|issue| issue.created_on.to_date }.sort + # 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}" + sql << " WHERE project_id IN (%s)" % top_projects.compact.join(',') + sql << " AND (project_id = #{@project.id})" unless @project.nil? + sql << " GROUP BY project_id, date" + issue_counts = ActiveRecord::Base.connection.select_all(sql).group_by { |c| c["project_id"] } + + # 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"] } created_count = 0 created_on_line = Hash.new - created_on_line[(issues_by_created_on.first[0]-1).to_s] = 0 - issues_by_created_on.each { |created_on, issues| created_count += issues.size; created_on_line[created_on.to_s] = created_count } + 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.today.to_s] = created_count graph.add_data({ :data => created_on_line.sort.flatten, - :title => project.name - }) + :title => Project.find(project_id).to_s + }) end # Compile the graph @@ -71,12 +86,12 @@ class GraphsController < ApplicationController :show_x_guidelines => true, :scale_x_integers => true, :scale_y_integers => true, - :show_data_points => true, + :show_data_points => false, :show_data_values => false, :stagger_x_labels => true, :style_sheet => "/plugin_assets/redmine_graphs/stylesheets/issue_age.css", :timescale_divisions => "1 weeks", - :width => 800, + :width => 720, :x_label_format => "%b %d" }) @@ -176,19 +191,16 @@ class GraphsController < ApplicationController private def find_open_issues - @project = Project.find(params[:project_id]) unless params[:project_id].blank? - deny_access unless User.current.allowed_to?(:view_issues, @project, :global => true) + find_optional_project @issues = Issue.visible.find(:all, :include => [:status], :conditions => ["#{IssueStatus.table_name}.is_closed=?", false]) if @project.nil? @issues = @project.issues.collect { |issue| issue unless issue.closed? }.compact unless @project.nil? rescue ActiveRecord::RecordNotFound render_404 end - def find_all_issues + 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) if @project.nil? - @issues = Issue.visible.find(:all, :include => [:project]) - @issues = @project.issues unless @project.nil? + deny_access unless User.current.allowed_to?(:view_issues, @project, :global => true) rescue ActiveRecord::RecordNotFound render_404 end @@ -199,4 +211,4 @@ class GraphsController < ApplicationController rescue ActiveRecord::RecordNotFound render_404 end -end \ No newline at end of file +end diff --git a/assets/stylesheets/issue_age.css b/assets/stylesheets/issue_age.css index cae9bee..c59f893 100755 --- a/assets/stylesheets/issue_age.css +++ b/assets/stylesheets/issue_age.css @@ -1,7 +1,5 @@ -.fill1 { fill: #666 !important; } -.line1 { stroke: #666 !important; } -.dataPoint1, .key1 { fill: #666 !important; } +.line1 { stroke: #666666 !important; } .fill1 { fill: #666666 !important; } .key1 { fill: #666666 !important; } +.line2 { stroke: #507AAA !important; } .fill2 { fill: #BACCE0 !important; } .key2 { fill: #507AAA !important; } -.fill2 { fill: #bacce0 !important; fill-opacity: 0.8 !important; } -.line2 { stroke: #507AAA !important; } -.dataPoint2, .key2 { fill: #507AAA !important; } +.fill1, .fill2 { fill-opacity: 0.6 !important; } +.line1, .line2 { stroke-width: 2px !important; } \ No newline at end of file diff --git a/assets/stylesheets/issue_growth.css b/assets/stylesheets/issue_growth.css index 2029028..e96a0d8 100755 --- a/assets/stylesheets/issue_growth.css +++ b/assets/stylesheets/issue_growth.css @@ -1,6 +1,9 @@ -.line1 { stroke: #C00 !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key1 { fill: #C00 !important; } -.line2 { stroke: #CC0 !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key2 { fill: #CC0 !important; } -.line3 { stroke: #0C0 !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key3 { fill: #0C0 !important; } -.line4 { stroke: #0CC !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key4 { fill: #0CC !important; } -.line5 { stroke: #00C !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key5 { fill: #00C !important; } -.line6 { stroke: #C0C !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key6 { fill: #C0C !important; } \ No newline at end of file +.line1 { stroke: #C00 !important; } .fill1 { fill: #C00 !important; } .key1 { fill: #C00 !important; } +.line2 { stroke: #CC0 !important; } .fill2 { fill: #CC0 !important; } .key2 { fill: #CC0 !important; } +.line3 { stroke: #0C0 !important; } .fill3 { fill: #0C0 !important; } .key3 { fill: #0C0 !important; } +.line4 { stroke: #0CC !important; } .fill4 { fill: #0CC !important; } .key4 { fill: #0CC !important; } +.line5 { stroke: #00C !important; } .fill5 { fill: #00C !important; } .key5 { fill: #00C !important; } +.line6 { stroke: #C0C !important; } .fill6 { fill: #C0C !important; } .key6 { fill: #C0C !important; } + +.fill1, .fill2, .fill3, .fill4, .fill5, .fill6 { fill-opacity: 0.6 !important; } +.line1, .line2, .line3, .line4, .line5, .line6 { stroke-width: 2px !important; } \ No newline at end of file diff --git a/assets/stylesheets/target_version.css b/assets/stylesheets/target_version.css index e1421aa..9bc240c 100755 --- a/assets/stylesheets/target_version.css +++ b/assets/stylesheets/target_version.css @@ -1,12 +1,11 @@ #target_version_graph { clear: both; } -.fill1 { fill: #666 !important; } -.line1 { stroke: #666 !important; } -.key1, .dataPoint1 { fill: #666 !important; } +.line1 { stroke: #666666 !important; } .fill1 { fill: #666666 !important; } .key1 { fill: #666666 !important; } +.line2 { stroke: #009900 !important; } .fill2 { fill: #BAE0BA !important; } .key2 { fill: #009900 !important; } -.fill2 { fill: #BAE0BA !important; fill-opacity: 0.8 !important; } -.line2 { stroke: #090 !important; } -.dataPoint2, .key2 { fill: #090 !important; } +.fill1, .fill2 { fill-opacity: 0.6 !important; } +.line1, .line2 { stroke-width: 2px !important; } -.key3 { fill: #95964e !important; } -.dataPoint3 { stroke: #95964e !important; stroke-width: 10px !important; } \ No newline at end of file +.dataPoint1, .dataPoint2 { display: none !important; } +.dataPoint3 { stroke: #AA5050 !important; stroke-width: 10px !important; } +.key3 { fill: #AA5050 !important; }