├── 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 |
2 | <%= link_to l(:label_new_chart), { :controller => 'charts', :action => 'new' }, :class => 'icon icon-add' if User.current.allowed_to?(:create_charts, @project) %> 3 |
-------------------------------------------------------------------------------- /app/views/charts/_chart.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/charts/_chart_menu.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to l(:button_edit), edit_chart_path(@chart), :class => 'icon icon-edit' if User.current.allowed_to?(:edit_public_charts, @project) || (User.current.allowed_to?(:edit_charts, @project) && !@chart.is_public?) %> 3 | <%= link_to l(:button_delete), chart_path(@chart), :method => :delete, :data => {:confirm => l(:text_are_you_sure)} ,:class => 'icon icon-del' if User.current.allowed_to?(:edit_public_charts, @project) || (User.current.allowed_to?(:edit_charts, @project) && !@chart.is_public?) %> 4 |
-------------------------------------------------------------------------------- /app/views/charts/_chart_options.html.erb: -------------------------------------------------------------------------------- 1 | <%= labelled_form_for @chart do |f| %> 2 | <%= error_messages_for 'chart' %> 3 | 4 | <%= f.hidden_field :project_id, :value => @project.id %> 5 | <%= f.hidden_field :user_id, :value => User.current.id %> 6 | 7 |
8 |

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 |
47 | 48 |

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 |

Edit <%= @chart.name %>

4 | 5 |
6 | <%= render partial: 'chart_options' %> 7 |
-------------------------------------------------------------------------------- /app/views/charts/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= javascript_include_tag 'https://www.google.com/jsapi', 'chartkick' %> 2 | 3 | <%= render :partial => 'action_menu' %> 4 | 5 |

<%= l(:label_my_charts) %>

6 | 7 |
8 | <% @my_charts.each do |chart| %> 9 |
"> 10 |

<%= link_to chart.name, chart_path(chart) %>

11 | <%= Tracker.find(chart.tracker_id).name + ' ' + l(:label_issue_plural).downcase if chart.tracker_id > 0 %> 12 | <%= l(:label_issues_visibility_all) if chart.tracker_id == 0 %> 13 | <%= '(' + l(:label_n_days, count: chart.range_integer) + ')' if chart.range_type == 'days' %> 14 | <%= '(' + l(:label_n_months, count: chart.range_integer) + ')' if chart.range_type == 'months' %> 15 | <%= '(' + l(:label_n_years, count: chart.range_integer) + ')' if chart.range_type == 'years' %> 16 | <%= render_chart(chart) %> 17 |
18 | <% end %> 19 |
20 | 21 |

<%= l(:label_public_charts) %>

22 | 23 |
24 | <% @public_charts.each do |chart| %> 25 |
"> 26 |

<%= link_to chart.name, chart_path(chart) %>

27 | <%= Tracker.find(chart.tracker_id).name + ' ' + l(:label_issue_plural).downcase if chart.tracker_id > 0 %> 28 | <%= l(:label_issues_visibility_all) if chart.tracker_id == 0 %> 29 | <%= '(' + l(:label_n_days, count: chart.range_integer) + ')' if chart.range_type == 'days' %> 30 | <%= '(' + l(:label_n_months, count: chart.range_integer) + ')' if chart.range_type == 'months' %> 31 | <%= '(' + l(:label_n_years, count: chart.range_integer) + ')' if chart.range_type == 'years' %> 32 | <%= render_chart(chart) %> 33 |
34 | <% end %> 35 |
36 | -------------------------------------------------------------------------------- /app/views/charts/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= javascript_include_tag 'issue_charts.js', :plugin => 'issue_charts' %> 2 | 3 |

New chart

4 | 5 |
6 | <%= render partial: 'chart_options' %> 7 |
-------------------------------------------------------------------------------- /app/views/charts/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= javascript_include_tag 'https://www.google.com/jsapi', 'chartkick' %> 2 | 3 | <%= render :partial => 'chart_menu' %> 4 | 5 |

<%= @chart.name %>

6 | 7 | <%= Tracker.find(@chart.tracker_id).name + ' ' + l(:label_issue_plural).downcase if @chart.tracker_id > 0 %> 8 | <%= l(:label_issues_visibility_all) if @chart.tracker_id == 0 %> 9 | <%= '(' + l(:label_n_days, count: @chart.range_integer) + ')' if @chart.range_type == 'days' %> 10 | <%= '(' + l(:label_n_months, count: @chart.range_integer) + ')' if @chart.range_type == 'months' %> 11 | <%= '(' + l(:label_n_years, count: @chart.range_integer) + ')' if @chart.range_type == 'years' %> 12 | 13 | <%= render_chart(@chart) %> 14 | 15 | <% unless @chart.predefined? %> 16 | 17 | <% objects = render_link_objects(@chart) %> 18 | 19 | <% unless objects.empty? %> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | <% if ('0' + @chart.group_by_field.to_s).to_i > 0 %> 32 | <% objects.each do |o| %> 33 | "> 34 | 35 | 36 | 37 | 38 | 39 | <% end %> 40 | <% else %> 41 | <% objects.each do |o| %> 42 | "> 43 | 44 | 45 | 46 | 47 | 48 | <% end %> 49 | <% end %> 50 | 51 | 52 |
<%=l(:label_open_issues_plural)%><%=l(:label_closed_issues_plural)%><%=l(:label_total)%>
<%= link_to o, chart_issues_path(@chart, o, '*') %><%= link_to chart_issues_count(@chart, o, 'o'), chart_issues_path(@chart, o, 'o') %><%= link_to chart_issues_count(@chart, o, 'c'), chart_issues_path(@chart, o, 'c') %><%= link_to chart_issues_count(@chart, o, '*'), chart_issues_path(@chart, o, '*') %>
<%= link_to o.name, chart_issues_path(@chart, o.id, '*') %><%= link_to chart_issues_count(@chart, o.id, 'o'), chart_issues_path(@chart, o.id, 'o') %><%= link_to chart_issues_count(@chart, o.id, 'c'), chart_issues_path(@chart, o.id, 'c') %><%= link_to chart_issues_count(@chart, o.id, '*'), chart_issues_path(@chart, o.id, '*') %>
53 | <% end %> 54 | 55 | <% end %> 56 | -------------------------------------------------------------------------------- /app/views/charts/update_edit_options.js.erb: -------------------------------------------------------------------------------- 1 | $("#chart_options").html("<%= escape_javascript(render partial: 'chart_options') %>"); -------------------------------------------------------------------------------- /app/views/charts/update_options.js.erb: -------------------------------------------------------------------------------- 1 | $("#chart_options").html("<%= escape_javascript(render partial: 'chart_options') %>"); -------------------------------------------------------------------------------- /assets/javascripts/issue_charts.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | return $(document).on('change', '#chart_type_select', function(evt) { 3 | return $.ajax('update_options', { 4 | type: 'GET', 5 | dataType: 'script', 6 | data: { 7 | 'chart_type': $("#chart_type_select option:selected").val(), 8 | 'name': $("#name_field").val(), 9 | 'is_public': $("#public_checkbox").is(":checked"), 10 | 'tracker_id': $("#tracker_select option:selected").val(), 11 | 'time': $("#time_tracking_select option:selected").val(), 12 | 'range_integer': $("#range_field").val(), 13 | 'range_type': $("#date_range_type_select option:selected").val(), 14 | 'group_by_field': $("#group_by_select option:selected").val(), 15 | 'issue_status': $("#issue_status_select option:selected").val() 16 | }, 17 | error: function(jqXHR, textStatus, errorThrown) { 18 | return console.log("AJAX Error: " + textStatus); 19 | }, 20 | success: function(data, textStatus, jqXHR) { 21 | return console.log("Chart type selected"); 22 | } 23 | }); 24 | }); 25 | }); 26 | 27 | $(function() { 28 | return $(document).on('change', '#tracker_select', function(evt) { 29 | return $.ajax('update_options', { 30 | type: 'GET', 31 | dataType: 'script', 32 | data: { 33 | 'chart_type': $("#chart_type_select option:selected").val(), 34 | 'name': $("#name_field").val(), 35 | 'is_public': $("#public_checkbox").is(":checked"), 36 | 'tracker_id': $("#tracker_select option:selected").val(), 37 | 'time': $("#time_tracking_select option:selected").val(), 38 | 'range_integer': $("#range_field").val(), 39 | 'range_type': $("#date_range_type_select option:selected").val(), 40 | 'group_by_field': $("#group_by_select option:selected").val(), 41 | 'issue_status': $("#issue_status_select option:selected").val() 42 | }, 43 | error: function(jqXHR, textStatus, errorThrown) { 44 | return console.log("AJAX Error: " + textStatus); 45 | }, 46 | success: function(data, textStatus, jqXHR) { 47 | return console.log("Chart type selected"); 48 | } 49 | }); 50 | }); 51 | }); 52 | 53 | $(function() { 54 | return $(document).on('change', '#time_tracking_select', function(evt) { 55 | return $.ajax('update_options', { 56 | type: 'GET', 57 | dataType: 'script', 58 | data: { 59 | 'chart_type': $("#chart_type_select option:selected").val(), 60 | 'name': $("#name_field").val(), 61 | 'is_public': $("#public_checkbox").is(":checked"), 62 | 'tracker_id': $("#tracker_select option:selected").val(), 63 | 'time': $("#time_tracking_select option:selected").val(), 64 | 'range_integer': $("#range_field").val(), 65 | 'range_type': $("#date_range_type_select option:selected").val(), 66 | 'group_by_field': $("#group_by_select option:selected").val(), 67 | 'issue_status': $("#issue_status_select option:selected").val() 68 | }, 69 | error: function(jqXHR, textStatus, errorThrown) { 70 | return console.log("AJAX Error: " + textStatus); 71 | }, 72 | success: function(data, textStatus, jqXHR) { 73 | return console.log("Chart type selected"); 74 | } 75 | }); 76 | }); 77 | }); -------------------------------------------------------------------------------- /assets/javascripts/issue_charts_edit.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | return $(document).on('change', '#chart_type_select', function(evt) { 3 | return $.ajax('update_edit_options', { 4 | type: 'GET', 5 | dataType: 'script', 6 | data: { 7 | 'chart_type': $("#chart_type_select option:selected").val(), 8 | 'name': $("#name_field").val(), 9 | 'is_public': $("#public_checkbox").is(":checked"), 10 | 'tracker_id': $("#tracker_select option:selected").val(), 11 | 'time': $("#time_tracking_select option:selected").val(), 12 | 'range_integer': $("#range_field").val(), 13 | 'range_type': $("#date_range_type_select option:selected").val(), 14 | 'group_by_field': $("#group_by_select option:selected").val(), 15 | 'issue_status': $("#issue_status_select option:selected").val() 16 | }, 17 | error: function(jqXHR, textStatus, errorThrown) { 18 | return console.log("AJAX Error: " + textStatus); 19 | }, 20 | success: function(data, textStatus, jqXHR) { 21 | return console.log("Chart type selected"); 22 | } 23 | }); 24 | }); 25 | }); 26 | 27 | $(function() { 28 | return $(document).on('change', '#tracker_select', function(evt) { 29 | return $.ajax('update_edit_options', { 30 | type: 'GET', 31 | dataType: 'script', 32 | data: { 33 | 'chart_type': $("#chart_type_select option:selected").val(), 34 | 'name': $("#name_field").val(), 35 | 'is_public': $("#public_checkbox").is(":checked"), 36 | 'tracker_id': $("#tracker_select option:selected").val(), 37 | 'time': $("#time_tracking_select option:selected").val(), 38 | 'range_integer': $("#range_field").val(), 39 | 'range_type': $("#date_range_type_select option:selected").val(), 40 | 'group_by_field': $("#group_by_select option:selected").val(), 41 | 'issue_status': $("#issue_status_select option:selected").val() 42 | }, 43 | error: function(jqXHR, textStatus, errorThrown) { 44 | return console.log("AJAX Error: " + textStatus); 45 | }, 46 | success: function(data, textStatus, jqXHR) { 47 | return console.log("Chart type selected"); 48 | } 49 | }); 50 | }); 51 | }); 52 | 53 | $(function() { 54 | return $(document).on('change', '#time_tracking_select', function(evt) { 55 | return $.ajax('update_edit_options', { 56 | type: 'GET', 57 | dataType: 'script', 58 | data: { 59 | 'chart_type': $("#chart_type_select option:selected").val(), 60 | 'name': $("#name_field").val(), 61 | 'is_public': $("#public_checkbox").is(":checked"), 62 | 'tracker_id': $("#tracker_select option:selected").val(), 63 | 'time': $("#time_tracking_select option:selected").val(), 64 | 'range_integer': $("#range_field").val(), 65 | 'range_type': $("#date_range_type_select option:selected").val(), 66 | 'group_by_field': $("#group_by_select option:selected").val(), 67 | 'issue_status': $("#issue_status_select option:selected").val() 68 | }, 69 | error: function(jqXHR, textStatus, errorThrown) { 70 | return console.log("AJAX Error: " + textStatus); 71 | }, 72 | success: function(data, textStatus, jqXHR) { 73 | return console.log("Chart type selected"); 74 | } 75 | }); 76 | }); 77 | }); -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | label_charts: Diagramme 3 | field_chart_type: Diagrammtyp 4 | label_month_plural: Monaten 5 | label_year_plural: Jahren 6 | label_n_days: 7 | one: "letzter Tag" 8 | other: "letzte %{count} Tage" 9 | label_n_months: 10 | one: "letzter Monat" 11 | other: "letzte %{count} Monate" 12 | label_n_years: 13 | one: "letztes Jahr" 14 | other: "letzte %{count} Jahre" 15 | label_tracker_all: Alle Tracker 16 | label_new_chart: Neues Diagramm 17 | label_my_charts: Meine Diagramme 18 | label_public_charts: Öffentliche Diagramme 19 | label_line_chart: Liniendiagramm 20 | label_pie_chart: Kreisdiagramm 21 | label_column_chart: Säulendiagramm 22 | label_bar_chart: Balkendiagramm 23 | label_area_chart: Flächendiagramm 24 | label_created_vs_closed_issues: Erstellte vs. geschlossene Tickets 25 | 26 | permission_view_charts: Diagramme anzeigen 27 | permission_create_charts: Diagramme erstellen 28 | permission_create_public_charts: Öffentliche Diagramme erstellen 29 | permission_edit_charts: Diagramme bearbeiten 30 | permission_edit_public_charts: Öffentliche Diagramme bearbeiten 31 | field_range_integer: :label_in_the_past_days 32 | field_issue_status: :label_issue_status 33 | field_time: :label_time_tracking 34 | field_group_by_field: :field_group_by -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | label_charts: Charts 3 | field_chart_type: Chart type 4 | label_month_plural: months 5 | label_year_plural: years 6 | label_n_days: 7 | one: "last 1 day" 8 | other: "last %{count} days" 9 | label_n_months: 10 | one: "last 1 month" 11 | other: "last %{count} months" 12 | label_n_years: 13 | one: "last 1 year" 14 | other: "last %{count} years" 15 | label_tracker_all: All trackers 16 | label_new_chart: New chart 17 | label_my_charts: My charts 18 | label_public_charts: Public charts 19 | label_line_chart: Line chart 20 | label_pie_chart: Pie chart 21 | label_column_chart: Column chart 22 | label_bar_chart: Bar chart 23 | label_area_chart: Area chart 24 | label_created_vs_closed_issues: Created vs closed issues 25 | 26 | permission_view_charts: View charts 27 | permission_create_charts: Create charts 28 | permission_create_public_charts: Create public charts 29 | permission_edit_charts: Edit charts 30 | permission_edit_public_charts: Edit public charts 31 | 32 | #references 33 | field_range_integer: :label_in_the_past_days 34 | field_issue_status: :label_issue_status 35 | field_time: :label_time_tracking 36 | field_group_by_field: :field_group_by 37 | -------------------------------------------------------------------------------- /config/locales/es.yml: -------------------------------------------------------------------------------- 1 | es: 2 | label_charts: Gráficos 3 | field_chart_type: Tipo de gráfico 4 | label_month_plural: meses 5 | label_year_plural: años 6 | label_n_days: 7 | one: "1 día" 8 | other: "%{count} días" 9 | label_n_months: 10 | one: "1 mes" 11 | other: "%{count} meses" 12 | label_n_years: 13 | one: "1 año" 14 | other: "%{count} años" 15 | label_tracker_all: Todos los tipos de peticiones 16 | label_new_chart: Nuevo gráfico 17 | label_my_charts: Mis gráficos 18 | label_public_charts: Gráficos públicos 19 | label_line_chart: Línea 20 | label_pie_chart: Circular 21 | label_column_chart: Columnas 22 | label_bar_chart: Barras 23 | label_area_chart: Área 24 | label_created_vs_closed_issues: Todas las peticiones y peticiones cerrados 25 | 26 | permission_view_charts: Ver gráficos 27 | permission_create_charts: Crear gráficos 28 | permission_create_public_charts: Crear gráficos públicos 29 | permission_edit_charts: Editar gráficos 30 | permission_edit_public_charts: Editar gráficos públicos 31 | field_range_integer: :label_in_the_past_days 32 | field_issue_status: :label_issue_status 33 | field_time: :label_time_tracking 34 | field_group_by_field: :field_group_by 35 | -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | #Laurent HADJADJ - 11/09/2016 2 | fr: 3 | label_charts: Graphiques 4 | field_chart_type: Type de graphique 5 | label_month_plural: mois 6 | label_year_plural: années 7 | label_n_days: 8 | one: "dernier jour" 9 | other: "%{count} derniers jours" 10 | label_n_months: 11 | one: "dernier mois" 12 | other: "%{count} dernier mois" 13 | label_n_years: 14 | one: "année courrante" 15 | other: "%{count} dernières années" 16 | label_tracker_all: tous les sujets 17 | label_new_chart: Nouveau graphique 18 | label_my_charts: Mes graphiques 19 | label_public_charts: Graphiques publics 20 | label_line_chart: Courbe 21 | label_pie_chart: Diagramme circulaire 22 | label_column_chart: Histogramme 23 | label_bar_chart: Diagramme à bandes 24 | label_area_chart: Zone graphique 25 | label_created_vs_closed_issues: Demandes créées vs fermées 26 | 27 | permission_view_charts: Voir les graphiques 28 | permission_create_charts: Créer un graphique 29 | permission_create_public_charts: Créer un graphique public 30 | permission_edit_charts: Editer un graphique 31 | permission_edit_public_charts: Editer un graphique public 32 | field_range_integer: :label_in_the_past_days 33 | field_issue_status: :label_issue_status 34 | field_time: :label_time_tracking 35 | field_group_by_field: :field_group_by 36 | -------------------------------------------------------------------------------- /config/locales/it.yml: -------------------------------------------------------------------------------- 1 | it: 2 | label_charts: Grafici 3 | field_chart_type: Tipo di Grafico 4 | label_month_plural: mesi 5 | label_year_plural: anni 6 | label_n_days: 7 | one: "ultimo giorno" 8 | other: "ultimi %{count} giorni" 9 | label_n_months: 10 | one: "ultimo mese" 11 | other: "ultimi %{count} mesi" 12 | label_n_years: 13 | one: "ultimo anno" 14 | other: "ultimi %{count} anni" 15 | label_tracker_all: Tutti i tracker 16 | label_new_chart: Nuovo grafico 17 | label_my_charts: I miei grafici 18 | label_public_charts: Grafici pubblici 19 | label_line_chart: a linee 20 | label_pie_chart: a torta 21 | label_column_chart: a colonne 22 | label_bar_chart: a barre 23 | label_area_chart: ad area 24 | label_created_vs_closed_issues: Segnalazioni aperte vs chiuse 25 | 26 | permission_view_charts: Visualizza grafici 27 | permission_create_charts: Crea grafici 28 | permission_create_public_charts: Crea grafici pubblici 29 | permission_edit_charts: Modifica grafici 30 | permission_edit_public_charts: Modifica grafici pubblici 31 | 32 | #references 33 | field_range_integer: :label_in_the_past_days 34 | field_issue_status: :label_issue_status 35 | field_time: :label_time_tracking 36 | field_group_by_field: :field_group_by 37 | -------------------------------------------------------------------------------- /config/locales/sv.yml: -------------------------------------------------------------------------------- 1 | sv: 2 | label_charts: Diagram 3 | field_chart_type: Diagramtyp 4 | label_month_plural: månader 5 | label_year_plural: år 6 | label_n_days: 7 | one: "senaste dagen" 8 | other: "senaste %{count} dagarna" 9 | label_n_months: 10 | one: "senaste månaden" 11 | other: "senaste %{count} månaderna" 12 | label_n_years: 13 | one: "senaste året" 14 | other: "senaste %{count} åren" 15 | label_tracker_all: Alla ärendetyper 16 | label_new_chart: Nytt diagram 17 | label_my_charts: Mina diagram 18 | label_public_charts: Publika diagram 19 | label_line_chart: Linjediagram 20 | label_pie_chart: Tårtdiagram 21 | label_column_chart: Spaltdiagram 22 | label_bar_chart: Stapeldiagram 23 | label_area_chart: Ytdiagram 24 | label_created_vs_closed_issues: Skapade kontra stängda ärenden 25 | 26 | permission_view_charts: Visa diagram 27 | permission_create_charts: Skapa diagram 28 | permission_create_public_charts: Skapa publika diagram 29 | permission_edit_charts: Redigera diagram 30 | permission_edit_public_charts: Redigera publika diagram 31 | 32 | #references 33 | field_range_integer: :label_in_the_past_days 34 | field_issue_status: :label_issue_status 35 | field_time: :label_time_tracking 36 | field_group_by_field: :field_group_by 37 | -------------------------------------------------------------------------------- /config/locales/zh.yml: -------------------------------------------------------------------------------- 1 | zh: 2 | label_charts: Issue Charts 3 | field_chart_type: 图表类型 4 | label_month_plural: 月份 5 | label_year_plural: 年 6 | label_n_days: 7 | one: "昨天" 8 | other: "过去 %{count} 天" 9 | label_n_months: 10 | one: "上个月" 11 | other: "过去 %{count} 个月" 12 | label_n_years: 13 | one: "去年" 14 | other: "过去 %{count} 年" 15 | label_tracker_all: All trackers 16 | label_new_chart: 新建图表 17 | label_my_charts: 我的图表 18 | label_public_charts: 公共图表 19 | label_line_chart: 折线图 20 | label_pie_chart: 饼图 21 | label_column_chart: 柱形图 22 | label_bar_chart: 条形图 23 | label_area_chart: 面积图 24 | label_created_vs_closed_issues: 已创建 VS 已关闭 25 | 26 | permission_view_charts: 查看图表 27 | permission_create_charts: 创建图表 28 | permission_create_public_charts: 创建公共图表 29 | permission_edit_charts: 编辑图表 30 | permission_edit_public_charts: 编辑公共图表 31 | field_range_integer: :label_in_the_past_days 32 | field_issue_status: :label_issue_status 33 | field_time: :label_time_tracking 34 | field_group_by_field: :field_group_by 35 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | 2 | match 'projects/:project_id/charts/update_options', to: 'charts#update_options', via: :get 3 | match 'charts/:id/update_edit_options', to: 'charts#update_edit_options', via: :get 4 | 5 | match 'projects/:project_id/charts', to: 'charts#index', via: :get 6 | match 'projects/:project_id/charts/new', to: 'charts#new', via: :get 7 | match 'projects/:project_id/charts', to: 'charts#create', via: :post 8 | 9 | resources :charts -------------------------------------------------------------------------------- /db/migrate/001_create_charts.rb: -------------------------------------------------------------------------------- 1 | class CreateCharts < ActiveRecord::Migration 2 | def change 3 | create_table :charts do |t| 4 | t.string :name 5 | t.integer :project_id 6 | t.integer :tracker_id 7 | t.string :chart_type 8 | t.string :group_by_field 9 | t.integer :group_by_custom_field 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/002_add_user_id_to_charts.rb: -------------------------------------------------------------------------------- 1 | class AddUserIdToCharts < ActiveRecord::Migration 2 | def change 3 | add_column :charts, :user_id, :integer 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/003_add_public_to_charts.rb: -------------------------------------------------------------------------------- 1 | class AddPublicToCharts < ActiveRecord::Migration 2 | def change 3 | add_column :charts, :public, :boolean, :default => false 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/004_add_columns_to_charts.rb: -------------------------------------------------------------------------------- 1 | class AddColumnsToCharts < ActiveRecord::Migration 2 | def change 3 | add_column :charts, :range_integer, :integer, :default => 30 4 | add_column :charts, :range_type, :string, :default => 'days' 5 | end 6 | end -------------------------------------------------------------------------------- /db/migrate/005_add_time_to_charts.rb: -------------------------------------------------------------------------------- 1 | class AddTimeToCharts < ActiveRecord::Migration 2 | def change 3 | add_column :charts, :time, :string, :default => '' 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/006_add_issue_status_to_charts.rb: -------------------------------------------------------------------------------- 1 | class AddIssueStatusToCharts < ActiveRecord::Migration 2 | def change 3 | add_column :charts, :issue_status, :string, :default => 'o' 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/007_rename_columns.rb: -------------------------------------------------------------------------------- 1 | class RenameColumns < ActiveRecord::Migration 2 | def change 3 | rename_column :charts, :public, :is_public 4 | end 5 | end -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | Redmine::Plugin.register :issue_charts do 2 | 3 | name 'Issue Charts plugin' 4 | author 'Mike Sweetman' 5 | description 'Provides the capability to create charts and graphs for project issues.' 6 | version '2.0.0' 7 | url 'https://github.com/masweetman/issue_charts' 8 | author_url 'https://github.com/masweetman' 9 | 10 | project_module :charts do 11 | permission :view_charts, :charts => [:index, :show] 12 | permission :create_charts, :charts => [:new, :create, :destroy] 13 | permission :create_public_charts, :charts => [:new, :create, :destroy] 14 | permission :edit_charts, :charts => [:edit, :update] 15 | permission :edit_public_charts, :charts => [:edit, :update] 16 | end 17 | 18 | menu :project_menu, :charts, { :controller => 'charts', :action => 'index' }, :caption => :label_charts, :after => :issues, :param => :project_id 19 | 20 | end 21 | -------------------------------------------------------------------------------- /test/functional/charts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class ChartsControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | def test_truth 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Load the Redmine helper 2 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') 3 | -------------------------------------------------------------------------------- /test/unit/chart_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class ChartTest < ActiveSupport::TestCase 4 | 5 | # Replace this with your real tests. 6 | def test_truth 7 | assert true 8 | end 9 | end 10 | --------------------------------------------------------------------------------