diff --git a/app/controllers/gantts_controller.rb b/app/controllers/gantts_controller.rb index 571fe1d..217c90d 100644 --- a/app/controllers/gantts_controller.rb +++ b/app/controllers/gantts_controller.rb @@ -34,7 +34,6 @@ class GanttsController < ApplicationController @gantt = Redmine::Helpers::Gantt.new(params) @gantt.project = @project retrieve_query - @query.group_by = nil @gantt.query = @query if @query.valid? basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb index 0cebd0a..0a6aa6d 100644 --- a/app/views/gantts/show.html.erb +++ b/app/views/gantts/show.html.erb @@ -3,7 +3,9 @@ <%= form_tag({:controller => 'gantts', :action => 'show', :project_id => @project, :month => params[:month], - :year => params[:year], :months => params[:months]}, + :year => params[:year], :months => params[:months], + :not_detailed_groups => params[:not_detailed_groups], + :version_groups => params[:version_groups]}, :method => :get, :id => 'query_form') do %> <%= hidden_field_tag 'set_filter', '1' %>
"> @@ -13,6 +15,50 @@
+ + +

<%= gantt_zoom_link(@gantt, :in) %> <%= gantt_zoom_link(@gantt, :out) %> @@ -29,6 +75,11 @@ :class => 'icon icon-checked' %> <%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, :class => 'icon icon-reload' %> +<% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> + <%= link_to_function l(:button_save), + "$('query_form').action='#{ @project ? new_project_query_path(@project) : new_query_path }'; submit_query_form('query_form')", + :class => 'icon icon-save' %> +<% end %>

<% end %> diff --git a/config/locales/de.yml b/config/locales/de.yml index 9eb469a..14ae90b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -585,6 +585,8 @@ de: label_per_page: Pro Seite label_calendar: Kalender label_months_from: Monate ab + label_hide_group_details: Verstecke Gruppendetail + label_keep_version_groups: Behalte Versiongruppen label_gantt: Gantt-Diagramm label_internal: Intern label_last_changes: "%{count} letzte Ă„nderungen" diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index d7a425f..f14768c 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -591,6 +591,8 @@ en-GB: label_per_page: Per page label_calendar: Calendar label_months_from: months from + label_hide_group_details: Hide group details + label_keep_version_groups: Keep version groups label_gantt: Gantt label_internal: Internal label_last_changes: "last %{count} changes" diff --git a/config/locales/en.yml b/config/locales/en.yml index 1e359f6..63d1f64 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -632,6 +632,8 @@ en: label_per_page: Per page label_calendar: Calendar label_months_from: months from + label_hide_group_details: Hide group details + label_keep_version_groups: Keep version groups label_gantt: Gantt label_internal: Internal label_last_changes: "last %{count} changes" diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index c71eec1..e55d8e3 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -36,6 +36,7 @@ module Redmine end attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows + attr_reader :detailed_groups, :version_groups attr_accessor :query attr_accessor :project attr_accessor :view @@ -57,10 +58,24 @@ module Redmine @zoom = (zoom > 0 && zoom < 5) ? zoom : 2 months = (options[:months] || User.current.pref[:gantt_months]).to_i @months = (months > 0 && months < 25) ? months : 6 + detailed_groups = User.current.pref[:gantt_detailed_groups] + if options[:set_filter] == '1' + detailed_groups = (options[:not_detailed_groups].to_s == 'true') ? false : true + end + @detailed_groups = (detailed_groups.to_s == 'true') ? true : false + version_groups = User.current.pref[:gantt_version_groups].to_s + if options[:set_filter] == '1' + version_groups = options[:version_groups].to_s + end + @version_groups = (version_groups == 'true') ? true : false # Save gantt parameters as user preference (zoom and months count) if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || - @months != User.current.pref[:gantt_months])) + @months != User.current.pref[:gantt_months]) || + @detailed_groups != User.current.pref[:gantt_detailed_groups] || + @version_groups != User.current.pref[:gantt_version_groups]) User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months + User.current.pref[:gantt_detailed_groups] = @detailed_groups + User.current.pref[:gantt_version_groups] = @version_groups User.current.preference.save end @date_from = Date.civil(@year_from, @month_from, 1) @@ -83,19 +98,25 @@ module Redmine def params common_params.merge({:zoom => zoom, :year => year_from, - :month => month_from, :months => months}) + :month => month_from, :months => months, + :not_detailed_groups => !detailed_groups, + :version_groups => version_groups}) end def params_previous common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, - :zoom => zoom, :months => months}) + :zoom => zoom, :months => months, + :not_detailed_groups => !detailed_groups, + :version_groups => version_groups}) end def params_next common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, - :zoom => zoom, :months => months}) + :zoom => zoom, :months => months, + :not_detailed_groups => !detailed_groups, + :version_groups => version_groups}) end # Returns the number of rows that will be rendered on the Gantt chart @@ -168,6 +189,13 @@ module Redmine project_issues(project).select {|issue| issue.fixed_version == version} end + # Returns the issues that belong to +project+ and are grouped by +group+ + def group_issues!(group, issues) + result = issues.take_while {|issue| group_name(issue) == group} + issues.reject! {|issue| group_name(issue) == group} + result + end + def render(options={}) options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, @@ -193,9 +221,12 @@ module Redmine options[:indent] += options[:indent_increment] @number_of_rows += 1 return if abort? - issues = project_issues(project).select {|i| i.fixed_version.nil?} - sort_issues!(issues) - if issues + issues = project_issues(project).select {|i| i.fixed_version.nil? || (grouped? && !@version_groups) } + if issues && grouped? + render_groups(issues, options) + return if abort? + else + sort_issues!(issues) render_issues(issues, options) return if abort? end @@ -207,33 +238,94 @@ module Redmine options[:indent] -= options[:indent_increment] end - def render_issues(issues, options={}) - @issue_ancestors = [] - issues.each do |i| - subject_for_issue(i, options) unless options[:only] == :lines - line_for_issue(i, options) unless options[:only] == :subjects - options[:top] += options[:top_increment] - @number_of_rows += 1 - break if abort? - end - options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) - end - def render_version(project, version, options={}) # Version header subject_for_version(version, options) unless options[:only] == :lines line_for_version(version, options) unless options[:only] == :subjects options[:top] += options[:top_increment] + options[:indent] += options[:indent_increment] @number_of_rows += 1 - return if abort? + return if abort? || (grouped? && !@version_groups) issues = version_issues(project, version) - if issues + if issues && grouped? + render_groups(issues, options) + return if abort? + else sort_issues!(issues) - # Indent issues - options[:indent] += options[:indent_increment] render_issues(issues, options) - options[:indent] -= options[:indent_increment] + return if abort? + end + # Remove indent to hit the next sibling + options[:indent] -= options[:indent_increment] + end + + def render_groups(issues, options={}) + while !issues.empty? + # Group header + group = group_name(issues[0]) + subject_for_group(group, options) unless options[:only] == :lines + options[:top] += options[:top_increment] + @number_of_rows += 1 + break if abort? + gr_issues = group_issues!(group, issues) + if gr_issues + sort_issues!(gr_issues) + # Indent issues + options[:indent] += options[:indent_increment] + render_issues(gr_issues, options) + options[:indent] -= options[:indent_increment] + end + break if abort? + end + end + + def render_issues(issues, options={}) + @issue_ancestors = [] + if !grouped? || @detailed_groups + issues.each do |i| + if i.due_before == nil + i.due_date = i.start_date + end + subject_for_issue(i, options) unless options[:only] == :lines + line_for_issue(i, options) unless options[:only] == :subjects + options[:top] += options[:top_increment] + @number_of_rows += 1 + break if abort? + end + else + group_start = options[:top] - options[:top_increment] + group_max_line = 0 + group_write_line = group_max_line + dates_in_line = [[]] + @number_of_rows += 1 + issues.each do |i| + if i.leaf? + if i.due_before == nil + i.due_date = i.start_date + end + group_write_line = -1 + dates_in_line.each_with_index do |dates, line| + if dates.find {|e| (i.start_date >= e[0] && i.start_date <= e[1]) || (i.start_date <= e[0] && i.due_before >= e[1]) } == nil + group_write_line = line + break + end + end + if group_write_line == -1 + group_max_line += 1 + group_write_line = group_max_line + dates_in_line.push([]) + @number_of_rows += 1 + end + options[:top] = group_start + (group_write_line * options[:top_increment]) + subject_for_issue(i, options) unless options[:only] == :lines + line_for_issue(i, options) unless options[:only] == :subjects + dates_in_line[group_write_line].push([i.start_date, i.due_before]) + end + break if abort? + end + options[:top] = group_start + ((group_max_line+1) * options[:top_increment]) end + options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) end def render_end(options={}) @@ -262,7 +354,7 @@ module Redmine end def line_for_project(project, options) - # Skip versions that don't have a start_date or due date + # Skip projects that don't have a start_date or due date if project.is_a?(Project) && project.start_date && project.due_date options[:zoom] ||= 1 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] @@ -282,6 +374,23 @@ module Redmine end end + def subject_for_group(group, options) + case options[:format] + when :html + html_class = "" + html_class << 'icon icon-package ' + s = group.html_safe + subject = view.content_tag(:span, s, + :class => html_class).html_safe + html_subject(options, subject, :css => "version-name") + when :image + image_subject(options, group) + when :pdf + pdf_new_page?(options) + pdf_subject(options, group) + end + end + def subject_for_version(version, options) case options[:format] when :html @@ -332,27 +441,31 @@ module Redmine end output = case options[:format] when :html - css_classes = '' - css_classes << ' issue-overdue' if issue.overdue? - css_classes << ' issue-behind-schedule' if issue.behind_schedule? - css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to - s = "".html_safe - if issue.assigned_to.present? - assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name - s << view.avatar(issue.assigned_to, - :class => 'gravatar icon-gravatar', - :size => 10, - :title => assigned_string).to_s.html_safe - end - s << view.link_to_issue(issue).html_safe - subject = view.content_tag(:span, s, :class => css_classes).html_safe - html_subject(options, subject, :css => "issue-subject", - :title => issue.subject) + "\n" + if !grouped? || @detailed_groups + css_classes = '' + css_classes << ' issue-overdue' if issue.overdue? + css_classes << ' issue-behind-schedule' if issue.behind_schedule? + css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to + s = "".html_safe + if issue.assigned_to.present? + assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name + s << view.avatar(issue.assigned_to, + :class => 'gravatar icon-gravatar', + :size => 10, + :title => assigned_string).to_s.html_safe + end + s << view.link_to_issue(issue).html_safe + subject = view.content_tag(:span, s, :class => css_classes).html_safe + html_subject(options, subject, :css => "issue-subject", + :title => issue.subject) + "\n" + end when :image - image_subject(options, issue.subject) + if !grouped? || @detailed_groups + image_subject(options, issue.subject) + end when :pdf pdf_new_page?(options) - pdf_subject(options, issue.subject) + pdf_subject(options, (grouped? || @detailed_groups) ? ' ' : issue.subject) end unless issue.leaf? @issue_ancestors << issue @@ -365,7 +478,10 @@ module Redmine # Skip issues that don't have a due_before (due_date or version's due_date) if issue.is_a?(Issue) && issue.due_before coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom]) - label = "#{issue.status.name} #{issue.done_ratio}%" + label = " " + if !grouped? || @detailed_groups + label = "#{issue.status.name} #{issue.done_ratio}%" + end case options[:format] when :html html_task(options, coords, @@ -666,6 +782,33 @@ module Redmine end end + def grouped? + @query.grouped? + end + + def new_group?(x, y) + if grouped? + value_x = @query.group_by_column.value(x) + value_y = @query.group_by_column.value(y) + value_x != value_y + else + true + end + end + + def group_name(issue) + if grouped? + result = @query.group_by_column.value(issue) + if result == nil || result == '' + 'None' + else + result.to_s + end + else + issue.subject + end + end + def pdf_new_page?(options) if options[:top] > 180 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])