├── Gemfile
├── README.rdoc
├── app
├── controllers
│ └── charts_controller.rb
├── helpers
│ └── charts_helper.rb
├── models
│ └── chart.rb
└── views
│ └── charts
│ ├── _action_menu.html.erb
│ ├── _chart.html.erb
│ ├── _chart_menu.html.erb
│ ├── _chart_options.html.erb
│ ├── edit.html.erb
│ ├── index.html.erb
│ ├── new.html.erb
│ ├── show.html.erb
│ ├── update_edit_options.js.erb
│ └── update_options.js.erb
├── assets
└── javascripts
│ ├── issue_charts.js
│ └── issue_charts_edit.js
├── config
├── locales
│ ├── de.yml
│ ├── en.yml
│ ├── es.yml
│ ├── fr.yml
│ ├── it.yml
│ ├── sv.yml
│ └── zh.yml
└── routes.rb
├── db
└── migrate
│ ├── 001_create_charts.rb
│ ├── 002_add_user_id_to_charts.rb
│ ├── 003_add_public_to_charts.rb
│ ├── 004_add_columns_to_charts.rb
│ ├── 005_add_time_to_charts.rb
│ ├── 006_add_issue_status_to_charts.rb
│ └── 007_rename_columns.rb
├── init.rb
└── test
├── functional
└── charts_controller_test.rb
├── test_helper.rb
└── unit
└── chart_test.rb
/Gemfile:
--------------------------------------------------------------------------------
1 | gem 'chartkick', '>= 3.3.0'
2 | gem 'groupdate'
3 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = ISSUE CHARTS IS DEPRECATED
2 | issue_charts not compatible with chartkick v3 or greater. Issue_charts is deprecated until it is revised to work with chartkick v3.
3 |
4 | = Issue Charts plug-in
5 |
6 | This plugin provides the capability to create beautiful charts and graphs for your issues using Chartkick (http://chartkick.com). Create issue visualizations based on standard fields as well as custom fields!
7 |
8 | == Installation
9 |
10 | * Clone into your plugins folder: git clone https://github.com/masweetman/issue_charts.git
11 | * Run bundle install
12 | * Run rake redmine:plugins:migrate RAILS_ENV=production
13 | * Restart Redmine
14 | * Set Chart permissions in Redmine Administration
15 |
16 | == To use groupdate
17 |
18 | * In config/application.rb, set the time zone to utc: config.active_record.default_timezone = :utc
19 | * Install time zone support: https://github.com/ankane/groupdate#for-mysql
20 |
--------------------------------------------------------------------------------
/app/controllers/charts_controller.rb:
--------------------------------------------------------------------------------
1 | include ChartsHelper
2 |
3 | class ChartsController < ApplicationController
4 | unloadable
5 |
6 | def index
7 | @project = Project.find(params[:project_id])
8 | if !User.current.allowed_to?(:view_charts, @project)
9 | render_404
10 | else
11 | @my_charts = Chart.where('project_id = ? AND user_id = ? AND is_public = false', @project.id, User.current.id).order(:name)
12 | @public_charts = Chart.where('project_id = ? AND is_public = true', @project.id).order(:name)
13 | end
14 | end
15 |
16 | def show
17 | @chart = Chart.find(params[:id])
18 | @project = Project.find(@chart.project_id)
19 | if !User.current.allowed_to?(:view_charts, @project)
20 | render_404
21 | end
22 | end
23 |
24 | def set_options
25 | @chart_type_options = { l(:label_line_chart) => 'Line',
26 | l(:label_pie_chart) => 'Pie',
27 | l(:label_column_chart) => 'Column',
28 | l(:label_bar_chart) => 'Bar',
29 | l(:label_area_chart) => 'Area'}.merge(predefined_types)
30 |
31 | @tracker_options = { l(:label_tracker_all) => 0 }
32 | @project.trackers.order(:name).map{ |t| @tracker_options[t.name] = t.id.to_s }
33 |
34 | @range_type_options = { l(:label_day_plural) => 'days', l(:label_month_plural) => 'months', l(:label_year_plural) => 'years' }
35 |
36 | if !predefined_types.values.include?(params[:chart_type])
37 | @issue_status_options = { l(:label_open_issues_plural) => 'o', l(:label_closed_issues_plural) => 'c', l(:label_total) => '*' }
38 | @time_options = { l(:field_estimated_hours) => 'estimated_hours', l(:label_spent_time) => 'spent_hours' }
39 | end
40 |
41 | unless params[:chart_type].to_s.empty? || predefined_types.values.include?(params[:chart_type])
42 | options = standard_fields
43 | if params[:time] == 'spent_hours'
44 | options.delete(l(:field_created_on))
45 | end
46 | @group_by_field_options = { l(:field_tracker) => 'tracker' }.merge(options) if (params[:tracker_id] == '0' || !params[:time].to_s.empty?)
47 | @group_by_field_options = group_by_field_options(params[:tracker_id]) unless (params[:tracker_id] == '' || params[:tracker_id] == '0' || !params[:time].to_s.empty?)
48 | end
49 | end
50 |
51 | def update_edit_options
52 | @chart = Chart.find(params[:id])
53 | @project = Project.find(@chart.project_id)
54 | params[:name] ||= @chart.name
55 | params[:tracker_id] ||= @chart.tracker_id.to_s
56 | params[:chart_type] ||= @chart.chart_type
57 | params[:group_by_field] ||= @chart.group_by_field
58 | params[:is_public] ||= @chart.is_public.to_s
59 | params[:range_integer] ||= @chart.range_integer.to_s
60 | params[:range_type] ||= @chart.range_type
61 | params[:time] ||= @chart.time
62 | params[:issue_status] ||= @chart.issue_status
63 |
64 | set_options
65 |
66 | respond_to do |format|
67 | format.js
68 | end
69 | end
70 |
71 | def update_options
72 | @project = Project.find(params[:project_id])
73 | @chart = Chart.new
74 |
75 | set_options
76 |
77 | respond_to do |format|
78 | format.js
79 | end
80 | end
81 |
82 | def new
83 | @project = Project.find(params[:project_id])
84 | if !(User.current.allowed_to?(:create_charts, @project) || User.current.allowed_to?(:create_public_charts, @project))
85 | render_404
86 | else
87 | @chart = Chart.new
88 | end
89 |
90 | @chart_type_options = { l(:label_line_chart) => 'Line',
91 | l(:label_pie_chart) => 'Pie',
92 | l(:label_column_chart) => 'Column',
93 | l(:label_bar_chart) => 'Bar',
94 | l(:label_area_chart) => 'Area'}.merge(predefined_types)
95 |
96 | @tracker_options = { l(:label_tracker_all) => 0 }
97 | @project.trackers.order(:name).map{ |t| @tracker_options[t.name] = t.id.to_s }
98 |
99 | @range_type_options = { l(:label_day_plural) => 'days', l(:label_month_plural) => 'months', l(:label_year_plural) => 'years' }
100 | end
101 |
102 | def create
103 | @chart = Chart.new(chart_params)
104 | @project = Project.find(@chart.project_id)
105 |
106 | if @chart.save
107 | redirect_to @chart
108 | else
109 | params[:name] ||= @chart.name
110 | params[:tracker_id] ||= @chart.tracker_id.to_s
111 | params[:chart_type] ||= @chart.chart_type
112 | params[:group_by_field] ||= @chart.group_by_field
113 | params[:is_public] ||= @chart.is_public.to_s
114 | params[:range_integer] ||= @chart.range_integer.to_s
115 | params[:range_type] ||= @chart.range_type
116 | params[:time] ||= @chart.time
117 | params[:issue_status] ||= @chart.issue_status
118 |
119 | set_options
120 |
121 | respond_to do |format|
122 | format.html { redirect_to action: 'new', project_id: @project.identifier }
123 | format.api { render_validation_errors(@chart) }
124 | end
125 | end
126 | end
127 |
128 | def edit
129 | @chart = Chart.find(params[:id])
130 | @project = Project.find(@chart.project_id)
131 | params[:name] = @chart.name
132 | params[:tracker_id] = @chart.tracker_id.to_s
133 | params[:chart_type] = @chart.chart_type
134 | params[:group_by_field] = @chart.group_by_field
135 | params[:is_public] = @chart.is_public.to_s
136 | params[:range_integer] = @chart.range_integer.to_s
137 | params[:range_type] = @chart.range_type
138 | params[:time] = @chart.time
139 | params[:issue_status] = @chart.issue_status
140 |
141 | @chart_type_options = { l(:label_line_chart) => 'Line',
142 | l(:label_pie_chart) => 'Pie',
143 | l(:label_column_chart) => 'Column',
144 | l(:label_bar_chart) => 'Bar',
145 | l(:label_area_chart) => 'Area'}.merge(predefined_types)
146 |
147 | @tracker_options = { l(:label_tracker_all) => 0 }
148 | @project.trackers.order(:name).map{ |t| @tracker_options[t.name] = t.id.to_s }
149 |
150 | @range_type_options = { l(:label_day_plural) => 'days', l(:label_month_plural) => 'months', l(:label_year_plural) => 'years' }
151 |
152 | if !predefined_types.values.include?(params[:chart_type])
153 | @issue_status_options = { l(:label_open_issues_plural) => 'o', l(:label_closed_issues_plural) => 'c', l(:label_total) => '*' }
154 | @time_options = { l(:field_estimated_hours) => 'estimated_hours', l(:label_spent_time) => 'spent_hours' }
155 | end
156 |
157 | unless params[:chart_type].to_s.empty? || predefined_types.values.include?(params[:chart_type])
158 | options = standard_fields
159 | if params[:time] == 'spent_hours'
160 | options.delete(l(:field_created_on))
161 | end
162 | @group_by_field_options = { l(:field_tracker) => 'tracker' }.merge(options) if (params[:tracker_id] == '0' || !params[:time].to_s.empty?)
163 | @group_by_field_options = group_by_field_options(params[:tracker_id]) unless (params[:tracker_id] == '' || params[:tracker_id] == '0' || !params[:time].to_s.empty?)
164 | end
165 |
166 | if !(User.current.allowed_to?(:edit_charts, @project) || User.current.allowed_to?(:edit_public_charts, @project))
167 | render_404
168 | end
169 | end
170 |
171 | def update
172 | @chart = Chart.find(params[:id])
173 | @project = Project.find(@chart.project_id)
174 | if @chart.update_attributes(chart_params)
175 | redirect_to action: 'show', id: @chart.id
176 | flash[:notice] = l(:notice_successful_update)
177 | else
178 | params[:name] ||= @chart.name
179 | params[:tracker_id] ||= @chart.tracker_id.to_s
180 | params[:chart_type] ||= @chart.chart_type
181 | params[:group_by_field] ||= @chart.group_by_field
182 | params[:is_public] ||= @chart.is_public.to_s
183 | params[:range_integer] ||= @chart.range_integer.to_s
184 | params[:range_type] ||= @chart.range_type
185 | params[:time] ||= @chart.time
186 | params[:issue_status] ||= @chart.issue_status
187 |
188 | set_options
189 |
190 | respond_to do |format|
191 | format.html { redirect_to action: 'edit', id: @chart.id }
192 | format.api { render_validation_errors(@chart) }
193 | end
194 | end
195 | end
196 |
197 | def destroy
198 | @chart = Chart.find(params[:id])
199 | @project = Project.find(@chart.project_id)
200 | if !(User.current.allowed_to?(:edit_charts, @project) || User.current.allowed_to?(:edit_public_charts, @project))
201 | render_404
202 | else
203 | @chart.destroy
204 | redirect_to action: 'index', project_id: @project.identifier
205 | flash[:notice] = l(:notice_successful_delete)
206 | end
207 | end
208 |
209 | private
210 |
211 | def chart_params
212 | params.require(:chart).permit(:project_id, :name, :tracker_id, :chart_type, :group_by_field, :user_id, :is_public, :range_integer, :range_type, :time, :issue_status)
213 | end
214 |
215 | end
216 |
--------------------------------------------------------------------------------
/app/helpers/charts_helper.rb:
--------------------------------------------------------------------------------
1 | module ChartsHelper
2 |
3 | def predefined_types
4 | return { l(:label_created_vs_closed_issues) => 'Created vs Closed Issues' }
5 | end
6 |
7 | def standard_fields
8 | return { l(:field_category) => 'category', l(:field_status) => 'status', l(:field_assigned_to) => 'assigned_to', l(:field_author) => 'author', l(:field_created_on) => 'created_on' }
9 | end
10 |
11 | def group_by_field_options(tracker_id)
12 | options = standard_fields
13 | group_by_custom_field_options(tracker_id).map{ |cf| options[cf.name] = cf.id.to_s }
14 | return options
15 | end
16 |
17 | def group_by_custom_field_options(tracker_id)
18 | return Tracker.find(tracker_id).custom_fields.order(:name)
19 | end
20 |
21 | def chart_start_date(chart)
22 | unless chart.range_integer.nil? || chart.range_type.nil?
23 | start_date = Date.today
24 | if chart.range_type == "days"
25 | start_date = Date.today - chart.range_integer.days
26 | elsif chart.range_type == "months"
27 | start_date = Date.today - chart.range_integer.months
28 | elsif chart.range_type == "years"
29 | start_date = Date.today - chart.range_integer.years
30 | end
31 | start_date
32 | end
33 |
34 | end
35 |
36 | def all_project_children(project)
37 | project.children.map do |child|
38 | [child.id] + all_project_children(child)
39 | end.flatten.uniq
40 | end
41 |
42 | def issue_scope(chart)
43 | start_date = chart_start_date(chart)
44 | if ('0' + chart.group_by_field.to_s).to_i > 0
45 | scope = Issue.where('issues.project_id IN (?) AND issues.tracker_id = ? AND issues.created_on > ?', chart.projects, chart.tracker_id, start_date)
46 | scope = scope.joins('INNER JOIN custom_values ON (issues.id = custom_values.customized_id)').where('custom_values.custom_field_id = ?', chart.group_by_field)
47 | else
48 | if chart.tracker_id == 0
49 | scope = Issue.where('issues.project_id IN (?) AND issues.created_on > ?', chart.projects, start_date)
50 | else
51 | scope = Issue.where('issues.project_id IN (?) AND issues.tracker_id = ? AND issues.created_on > ?', chart.projects, chart.tracker_id, start_date)
52 | end
53 | end
54 | scope
55 | end
56 |
57 | def render_link_objects(chart)
58 | objects = []
59 | scope = issue_scope(chart)
60 | if ('0' + chart.group_by_field.to_s).to_i > 0
61 | objects = scope.map{ |i| i.custom_field_value(chart.group_by_field.to_i) }.flatten.uniq.compact.sort
62 | else
63 | if chart.group_by_field.to_s == 'tracker'
64 | objects = scope.map{ |i| i.tracker }.uniq.compact.sort
65 | elsif chart.group_by_field.to_s == 'category'
66 | objects = scope.map{ |i| i.category }.uniq.compact.sort
67 | elsif chart.group_by_field.to_s == 'status'
68 | objects = scope.map{ |i| i.status }.uniq.compact.sort
69 | elsif chart.group_by_field.to_s == 'assigned_to'
70 | objects = scope.map{ |i| i.assigned_to }.uniq.compact.sort
71 | elsif chart.group_by_field.to_s == 'author'
72 | objects = scope.map{ |i| i.author }.uniq.compact.sort
73 | end
74 | end
75 | return objects
76 | end
77 |
78 | def chart_issues_path(chart, object_id, status)
79 | begin
80 | if chart.group_by_field == 'status'
81 | status_op = '='
82 | else
83 | status_op = status
84 | end
85 | if ('0' + chart.group_by_field.to_s).to_i > 0
86 | if chart.tracker_id > 0
87 | project_issues_path(Project.find(chart.project_id), :set_filter => 1,
88 | :f=>[:status_id, :tracker_id, :created_on, 'cf_' + chart.group_by_field.to_s],
89 | :op=>{:status_id => status_op, :tracker_id => '=', :created_on => '>=', 'cf_' + chart.group_by_field.to_s => '='},
90 | :v=>{:tracker_id => [chart.tracker_id.to_s], :created_on => [chart_start_date(chart).to_s], 'cf_' + chart.group_by_field.to_s => [object_id.to_s]},
91 | :c=>[:tracker, :status, :priority, :subject, :assigned_to, 'cf_' + chart.group_by_field.to_s, :estimated_hours, :spent_hours]
92 | )
93 | elsif chart.tracker_id == 0
94 | project_issues_path(Project.find(chart.project_id), :set_filter => 1,
95 | :f=>[:status_id, :created_on, 'cf_' + chart.group_by_field.to_s],
96 | :op=>{:status_id => status_op, :created_on => '>=', 'cf_' + chart.group_by_field.to_s => '='},
97 | :v=>{:created_on => [chart_start_date(chart).to_s], 'cf_' + chart.group_by_field.to_s => [object_id.to_s]},
98 | :c=>[:tracker, :status, :priority, :subject, :assigned_to, 'cf_' + chart.group_by_field.to_s, :estimated_hours, :spent_hours]
99 | )
100 | end
101 | else
102 | if chart.tracker_id > 0
103 | project_issues_path(Project.find(chart.project_id), :set_filter => 1,
104 | :f=>[:status_id, :tracker_id, :created_on, chart.group_by_field.to_s + '_id'],
105 | :op=>{:status_id => status_op, :tracker_id => '=', :created_on => '>=', chart.group_by_field.to_s + '_id' => '='},
106 | :v=>{:tracker_id => [chart.tracker_id.to_s], :created_on => [chart_start_date(chart).to_s], chart.group_by_field.to_s + '_id' => [object_id.to_s]},
107 | :c=>[:tracker, :status, :priority, :subject, :assigned_to, :estimated_hours, :spent_hours]
108 | )
109 | elsif chart.tracker_id == 0
110 | project_issues_path(Project.find(chart.project_id), :set_filter => 1,
111 | :f=>[:status_id, :created_on, chart.group_by_field.to_s + '_id'],
112 | :op=>{:status_id => status_op, :created_on => '>=', chart.group_by_field.to_s + '_id' => '='},
113 | :v=>{:created_on => [chart_start_date(chart).to_s], chart.group_by_field.to_s + '_id' => [object_id.to_s]},
114 | :c=>[:tracker, :status, :priority, :subject, :assigned_to, :estimated_hours, :spent_hours]
115 | )
116 | end
117 | end
118 | rescue Exception => e
119 | flash[:error] = "Error loading links for chart '" + chart.name + "'. " + e.message
120 | end
121 | end
122 |
123 | def chart_issues_count(chart, object_id, status)
124 | scope = issue_scope(chart)
125 | if ('0' + chart.group_by_field.to_s).to_i > 0
126 | scope = scope.where('custom_values.value = ?', object_id)
127 | else
128 | query = 'issues.' + chart.group_by_field.to_s + '_id = ?'
129 | scope = scope.where(query, object_id)
130 | end
131 | if status == 'o'
132 | count = scope.open.count.to_s if chart.time.to_s.empty?
133 | count = scope.open.sum(:estimated_hours).to_s + ' h' if chart.time == 'estimated_hours'
134 | count = scope.open.joins(:time_entries).sum(:hours).to_s + ' h' if chart.time == 'spent_hours'
135 | elsif status == '*'
136 | count = scope.count.to_s if chart.time.to_s.empty?
137 | count = scope.sum(:estimated_hours).to_s + ' h' if chart.time == 'estimated_hours'
138 | count = scope.joins(:time_entries).sum(:hours).to_s + ' h' if chart.time == 'spent_hours'
139 | elsif status == 'c'
140 | count = (scope.count - scope.open.count).to_s if chart.time.to_s.empty?
141 | count = (scope.sum(:estimated_hours) - scope.open.sum(:estimated_hours)).to_s + ' h' if chart.time == 'estimated_hours'
142 | count = (scope.joins(:time_entries).sum(:hours) - scope.open.joins(:time_entries).sum(:hours)).to_s + ' h' if chart.time == 'spent_hours'
143 | end
144 | return count
145 | end
146 |
147 | def render_chart(chart)
148 | begin
149 | scope = issue_scope(chart)
150 |
151 | if ('0' + chart.group_by_field.to_s).to_i > 0
152 | group = 'custom_values.value'
153 | else
154 | group = chart.group_by_field
155 | end
156 |
157 | if !chart.predefined?
158 |
159 | if chart.issue_status == 'o'
160 | scope = scope.joins(:status).where('issue_statuses.is_closed = ?', false)
161 | elsif chart.issue_status == 'c'
162 | scope = scope.joins(:status).where('issue_statuses.is_closed = ?', true)
163 | end
164 |
165 | chart_code = ''
166 | if chart.chart_type == 'Line'
167 | if group == 'created_on'
168 | if chart.time.to_s.empty?
169 | line_chart scope.group_by_day(group).count
170 | else
171 | line_chart scope.group_by_day(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
172 | line_chart scope.joins(:time_entries).group_by_day(group).sum(:hours) if chart.time == 'spent_hours'
173 | end
174 | else
175 | if chart.time.to_s.empty?
176 | line_chart scope.group(group).count
177 | else
178 | line_chart scope.group(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
179 | line_chart scope.joins(:time_entries).group(group).sum(:hours) if chart.time == 'spent_hours'
180 | end
181 | end
182 | elsif chart.chart_type == 'Pie'
183 | if group == 'created_on'
184 | if chart.time.to_s.empty?
185 | pie_chart scope.group_by_day(group).count
186 | else
187 | pie_chart scope.group_by_day(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
188 | pie_chart scope.joins(:time_entries).group_by_day(group).sum(:hours) if chart.time == 'spent_hours'
189 | end
190 | else
191 | if chart.time.to_s.empty?
192 | pie_chart scope.group(group).count
193 | else
194 | pie_chart scope.group(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
195 | pie_chart scope.joins(:time_entries).group(group).sum(:hours) if chart.time == 'spent_hours'
196 | end
197 | end
198 | elsif chart.chart_type == 'Column'
199 | if group == 'created_on'
200 | if chart.time.to_s.empty?
201 | column_chart scope.group_by_day(group).count
202 | else
203 | column_chart scope.group_by_day(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
204 | column_chart scope.joins(:time_entries).group_by_day(group).sum(:hours) if chart.time == 'spent_hours'
205 | end
206 | else
207 | if chart.time.to_s.empty?
208 | column_chart scope.group(group).count
209 | else
210 | column_chart scope.group(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
211 | column_chart scope.joins(:time_entries).group(group).sum(:hours) if chart.time == 'spent_hours'
212 | end
213 | end
214 | elsif chart.chart_type == 'Bar'
215 | if group == 'created_on'
216 | if chart.time.to_s.empty?
217 | bar_chart scope.group_by_day(group).count
218 | else
219 | bar_chart scope.group_by_day(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
220 | bar_chart scope.joins(:time_entries).group_by_day(group).sum(:hours) if chart.time == 'spent_hours'
221 | end
222 | else
223 | if chart.time.to_s.empty?
224 | bar_chart scope.group(group).count
225 | else
226 | bar_chart scope.group(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
227 | bar_chart scope.joins(:time_entries).group(group).sum(:hours) if chart.time == 'spent_hours'
228 | end
229 | end
230 | elsif chart.chart_type == 'Area'
231 | if group == 'created_on'
232 | if chart.time.to_s.empty?
233 | area_chart scope.group_by_day(group).count
234 | else
235 | area_chart scope.group_by_day(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
236 | area_chart scope.joins(:time_entries).group_by_day(group).sum(:hours) if chart.time == 'spent_hours'
237 | end
238 | else
239 | if chart.time.to_s.empty?
240 | area_chart scope.group(group).count
241 | else
242 | area_chart scope.group(group).sum(:estimated_hours) if chart.time == 'estimated_hours'
243 | area_chart scope.joins(:time_entries).group(group).sum(:hours) if chart.time == 'spent_hours'
244 | end
245 | end
246 | end
247 |
248 | elsif chart.chart_type == 'Created vs Closed Issues'
249 | created_issues = 0
250 | closed_issues = 0
251 | created_series = {}
252 | closed_series = {}
253 | scope.order(:created_on).each do |issue|
254 | created_issues += 1
255 | created_series[issue.created_on.to_date] = created_issues
256 | end
257 | scope.where('closed_on IS NOT NULL').order(:closed_on).each do |issue|
258 | closed_issues += 1
259 | closed_series[issue.closed_on.to_date] = closed_issues
260 | end
261 |
262 | closed_series.each do |cl|
263 | unless created_series.include? cl[0]
264 | created_series[cl[0]] = created_series.each.select{ |i| i[0] < cl[0] }.max.to_a[1].to_i
265 | end
266 | end
267 | created_series.each do |cr|
268 | unless closed_series.include? cr[0]
269 | closed_series[cr[0]] = closed_series.each.select{ |i| i[0] < cr[0] }.max.to_a[1].to_i
270 | end
271 | end
272 |
273 | area_chart [ { name: 'Created Issues', data: created_series }, { name: 'Closed Issues', data: closed_series } ], stacked: false, max: created_issues*1.1, colors: ['red', '#0a0']
274 | end
275 |
276 | rescue Exception => e
277 | flash[:error] = "Error loading chart '" + chart.name + "'. " + e.message
278 | return ''
279 | end
280 | end
281 |
282 | end
283 |
--------------------------------------------------------------------------------
/app/models/chart.rb:
--------------------------------------------------------------------------------
1 | class Chart < ActiveRecord::Base
2 | attr_writer :current_step
3 | include ChartsHelper
4 |
5 | validates_presence_of :name, :chart_type, :tracker_id, :range_integer
6 | validates_presence_of :group_by_field, :if => :not_predefined?
7 |
8 | def current_step
9 | @current_step || steps.first
10 | end
11 |
12 | def next_step
13 | self.current_step = steps[steps.index(current_step)+1]
14 | end
15 |
16 | def steps
17 | %w[chart_type chart_options]
18 | end
19 |
20 | def last_step?
21 | current_step == steps.last
22 | end
23 |
24 | def predefined?
25 | predefined_types.values.include? chart_type
26 | end
27 |
28 | def not_predefined?
29 | !predefined?
30 | end
31 |
32 | def projects
33 | project = Project.find(project_id)
34 | projects = [project_id]
35 | if Setting.display_subprojects_issues?
36 | projects += all_project_children(project)
37 | end
38 | return projects
39 | end
40 |
41 | end
42 |
--------------------------------------------------------------------------------
/app/views/charts/_action_menu.html.erb:
--------------------------------------------------------------------------------
1 |
9 | <%= f.text_field :name, required: true, value: params[:name], :id => 'name_field' %> 10 |
11 |12 | <%= f.select :chart_type, @chart_type_options, { required: true, selected: params[:chart_type], include_blank: true }, :id => 'chart_type_select' %> 13 |
14 | <% if User.current.allowed_to?(:create_public_charts, @project) %> 15 |16 | <%= f.check_box :is_public, checked: params[:is_public] == 'true', :id => 'public_checkbox' %> 17 |
18 | <% end %> 19 |20 | <%= f.select :tracker_id, @tracker_options, { required: true, selected: params[:tracker_id], include_blank: true }, :id => 'tracker_select' %> 21 |
22 | 23 |24 | <% params[:range_integer] ||= 30 %> 25 | <%= f.text_field :range_integer, required: true, value: params[:range_integer], :id => 'range_field' %> 26 | <%= f.select :range_type, @range_type_options, { selected: params[:range_type], include_blank: false, label: '' }, :id => 'date_range_type_select' %> 27 |
28 | 29 | <% unless @issue_status_options.nil? %> 30 |31 | <%= f.select :issue_status, @issue_status_options, { selected: params[:issue_status], include_blank: false }, :id => 'issue_status_select' %> 32 |
33 | <% end %> 34 | 35 | <% unless @time_options.nil? %> 36 |37 | <%= f.select :time, @time_options, { selected: params[:time], include_blank: true }, :id => 'time_tracking_select' %> 38 |
39 | <% end %> 40 | 41 | <% unless @group_by_field_options.nil? %> 42 |43 | <%= f.select :group_by_field, @group_by_field_options, { required: true, selected: params[:group_by_field], include_blank: true }, :id => 'group_by_select' %> 44 |
45 | <% end %> 46 |49 | <%= f.submit l(:button_submit) %> 50 |
51 | <% end %> -------------------------------------------------------------------------------- /app/views/charts/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= javascript_include_tag 'issue_charts_edit.js', :plugin => 'issue_charts' %> 2 | 3 |24 | | <%=l(:label_open_issues_plural)%> | 25 |<%=l(:label_closed_issues_plural)%> | 26 |<%=l(:label_total)%> | 27 |
---|---|---|---|
<%= link_to o, chart_issues_path(@chart, o, '*') %> | 35 |<%= link_to chart_issues_count(@chart, o, 'o'), chart_issues_path(@chart, o, 'o') %> | 36 |<%= link_to chart_issues_count(@chart, o, 'c'), chart_issues_path(@chart, o, 'c') %> | 37 |<%= link_to chart_issues_count(@chart, o, '*'), chart_issues_path(@chart, o, '*') %> | 38 |
<%= link_to o.name, chart_issues_path(@chart, o.id, '*') %> | 44 |<%= link_to chart_issues_count(@chart, o.id, 'o'), chart_issues_path(@chart, o.id, 'o') %> | 45 |<%= link_to chart_issues_count(@chart, o.id, 'c'), chart_issues_path(@chart, o.id, 'c') %> | 46 |<%= link_to chart_issues_count(@chart, o.id, '*'), chart_issues_path(@chart, o.id, '*') %> | 47 |