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.
This commit is contained in:
parent
bd5a1567f8
commit
f81d0d5160
|
@ -4,7 +4,7 @@ class GraphsController < ApplicationController
|
||||||
|
|
||||||
before_filter :find_version, :only => [:target_version_graph]
|
before_filter :find_version, :only => [:target_version_graph]
|
||||||
before_filter :find_open_issues, :only => [:old_issues, :issue_age_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
|
helper IssuesHelper
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ class GraphsController < ApplicationController
|
||||||
|
|
||||||
# Initialize the graph
|
# Initialize the graph
|
||||||
graph = SVG::Graph::TimeSeries.new({
|
graph = SVG::Graph::TimeSeries.new({
|
||||||
|
:area_fill => true,
|
||||||
:height => 300,
|
:height => 300,
|
||||||
:min_y_value => 0,
|
:min_y_value => 0,
|
||||||
:no_css => true,
|
:no_css => true,
|
||||||
|
@ -26,26 +27,40 @@ class GraphsController < ApplicationController
|
||||||
:show_data_values => false,
|
:show_data_values => false,
|
||||||
:stagger_x_labels => true,
|
:stagger_x_labels => true,
|
||||||
:style_sheet => "/plugin_assets/redmine_graphs/stylesheets/issue_growth.css",
|
:style_sheet => "/plugin_assets/redmine_graphs/stylesheets/issue_growth.css",
|
||||||
:timescale_divisions => "1 weeks",
|
:timescale_divisions => "1 months",
|
||||||
:width => 800,
|
:width => 720,
|
||||||
:x_label_format => "%b %d"
|
:x_label_format => "%b %y"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Group issues
|
# Get the top visible projects by issue count
|
||||||
issues_by_project = @issues.group_by {|issue| issue.project }
|
sql = "SELECT project_id, COUNT(*) issue_count"
|
||||||
projects_by_size = issues_by_project.collect { |project, issues| [project, issues.size] }.sort { |a,b| b[1]<=>a[1] }[0..5]
|
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
|
# Get the issues created per project, per day
|
||||||
projects_by_size.each do |project, size|
|
sql = "SELECT project_id, date(#{Issue.table_name}.created_on) as date, COUNT(*) as issue_count"
|
||||||
issues_by_created_on = issues_by_project[project].group_by {|issue| issue.created_on.to_date }.sort
|
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_count = 0
|
||||||
created_on_line = Hash.new
|
created_on_line = Hash.new
|
||||||
created_on_line[(issues_by_created_on.first[0]-1).to_s] = 0
|
created_on_line[(Date.parse(counts.first["date"])-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 }
|
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
|
created_on_line[Date.today.to_s] = created_count
|
||||||
graph.add_data({
|
graph.add_data({
|
||||||
:data => created_on_line.sort.flatten,
|
:data => created_on_line.sort.flatten,
|
||||||
:title => project.name
|
:title => Project.find(project_id).to_s
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -71,12 +86,12 @@ class GraphsController < ApplicationController
|
||||||
:show_x_guidelines => true,
|
:show_x_guidelines => true,
|
||||||
:scale_x_integers => true,
|
:scale_x_integers => true,
|
||||||
:scale_y_integers => true,
|
:scale_y_integers => true,
|
||||||
:show_data_points => true,
|
:show_data_points => false,
|
||||||
:show_data_values => false,
|
:show_data_values => false,
|
||||||
:stagger_x_labels => true,
|
:stagger_x_labels => true,
|
||||||
:style_sheet => "/plugin_assets/redmine_graphs/stylesheets/issue_age.css",
|
:style_sheet => "/plugin_assets/redmine_graphs/stylesheets/issue_age.css",
|
||||||
:timescale_divisions => "1 weeks",
|
:timescale_divisions => "1 weeks",
|
||||||
:width => 800,
|
:width => 720,
|
||||||
:x_label_format => "%b %d"
|
:x_label_format => "%b %d"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -176,19 +191,16 @@ class GraphsController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_open_issues
|
def find_open_issues
|
||||||
@project = Project.find(params[:project_id]) unless params[:project_id].blank?
|
find_optional_project
|
||||||
deny_access unless User.current.allowed_to?(:view_issues, @project, :global => true)
|
|
||||||
@issues = Issue.visible.find(:all, :include => [:status], :conditions => ["#{IssueStatus.table_name}.is_closed=?", false]) if @project.nil?
|
@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?
|
@issues = @project.issues.collect { |issue| issue unless issue.closed? }.compact unless @project.nil?
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_all_issues
|
def find_optional_project
|
||||||
@project = Project.find(params[:project_id]) unless params[:project_id].blank?
|
@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?
|
deny_access unless User.current.allowed_to?(:view_issues, @project, :global => true)
|
||||||
@issues = Issue.visible.find(:all, :include => [:project])
|
|
||||||
@issues = @project.issues unless @project.nil?
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
.fill1 { fill: #666 !important; }
|
.line1 { stroke: #666666 !important; } .fill1 { fill: #666666 !important; } .key1 { fill: #666666 !important; }
|
||||||
.line1 { stroke: #666 !important; }
|
.line2 { stroke: #507AAA !important; } .fill2 { fill: #BACCE0 !important; } .key2 { fill: #507AAA !important; }
|
||||||
.dataPoint1, .key1 { fill: #666 !important; }
|
|
||||||
|
|
||||||
.fill2 { fill: #bacce0 !important; fill-opacity: 0.8 !important; }
|
.fill1, .fill2 { fill-opacity: 0.6 !important; }
|
||||||
.line2 { stroke: #507AAA !important; }
|
.line1, .line2 { stroke-width: 2px !important; }
|
||||||
.dataPoint2, .key2 { fill: #507AAA !important; }
|
|
|
@ -1,6 +1,9 @@
|
||||||
.line1 { stroke: #C00 !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key1 { fill: #C00 !important; }
|
.line1 { stroke: #C00 !important; } .fill1 { fill: #C00 !important; } .key1 { fill: #C00 !important; }
|
||||||
.line2 { stroke: #CC0 !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key2 { fill: #CC0 !important; }
|
.line2 { stroke: #CC0 !important; } .fill2 { fill: #CC0 !important; } .key2 { fill: #CC0 !important; }
|
||||||
.line3 { stroke: #0C0 !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key3 { fill: #0C0 !important; }
|
.line3 { stroke: #0C0 !important; } .fill3 { fill: #0C0 !important; } .key3 { fill: #0C0 !important; }
|
||||||
.line4 { stroke: #0CC !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key4 { fill: #0CC !important; }
|
.line4 { stroke: #0CC !important; } .fill4 { fill: #0CC !important; } .key4 { fill: #0CC !important; }
|
||||||
.line5 { stroke: #00C !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key5 { fill: #00C !important; }
|
.line5 { stroke: #00C !important; } .fill5 { fill: #00C !important; } .key5 { fill: #00C !important; }
|
||||||
.line6 { stroke: #C0C !important; stroke-width: 4px !important; stroke-opacity: 0.8 !important; } .key6 { fill: #C0C !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; }
|
|
@ -1,12 +1,11 @@
|
||||||
#target_version_graph { clear: both; }
|
#target_version_graph { clear: both; }
|
||||||
|
|
||||||
.fill1 { fill: #666 !important; }
|
.line1 { stroke: #666666 !important; } .fill1 { fill: #666666 !important; } .key1 { fill: #666666 !important; }
|
||||||
.line1 { stroke: #666 !important; }
|
.line2 { stroke: #009900 !important; } .fill2 { fill: #BAE0BA !important; } .key2 { fill: #009900 !important; }
|
||||||
.key1, .dataPoint1 { fill: #666 !important; }
|
|
||||||
|
|
||||||
.fill2 { fill: #BAE0BA !important; fill-opacity: 0.8 !important; }
|
.fill1, .fill2 { fill-opacity: 0.6 !important; }
|
||||||
.line2 { stroke: #090 !important; }
|
.line1, .line2 { stroke-width: 2px !important; }
|
||||||
.dataPoint2, .key2 { fill: #090 !important; }
|
|
||||||
|
|
||||||
.key3 { fill: #95964e !important; }
|
.dataPoint1, .dataPoint2 { display: none !important; }
|
||||||
.dataPoint3 { stroke: #95964e !important; stroke-width: 10px !important; }
|
.dataPoint3 { stroke: #AA5050 !important; stroke-width: 10px !important; }
|
||||||
|
.key3 { fill: #AA5050 !important; }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user