diff --git redmine-original/app/controllers/issues_controller.rb suissues-patch/app/controllers/issues_controller.rb index fc02794..850c604 100644 --- redmine-original/app/controllers/issues_controller.rb +++ suissues-patch/app/controllers/issues_controller.rb @@ -18,11 +18,11 @@ class IssuesController < ApplicationController menu_item :new_issue, :only => :new - before_filter :find_issue, :only => [:show, :edit, :reply] + before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment ] before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] - before_filter :find_project, :only => [:new, :update_form, :preview] - before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] - before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar] + before_filter :find_project, :only => [:new, :auto_complete_for_issue_parent, :add_subissue, :update_form, :preview ] + before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu, :auto_complete_for_issue_parent ] + before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar ] accept_key_auth :index, :changes helper :journals @@ -41,8 +41,47 @@ class IssuesController < ApplicationController include SortHelper include IssuesHelper helper :timelog + include ActionView::Helpers::PrototypeHelper include Redmine::Export::PDF + def auto_complete_for_issue_parent + @phrase = params[:issue_parent] + @candidates = [] + + # If cross project issue relations is allowed we should get + # candidates from every project + if Setting.cross_project_issue_relations? + projects_to_search = nil + else + projects_to_search = [ @project ] + @project.active_children + end + + # Try to find issue by id. + if @phrase.match(/^#?(\d+)$/) + if Setting.cross_project_issue_relations? + issue = Issue.find_by_id( $1) + else + issue = Issue.find_by_id_and_project_id( $1, projects_to_search.collect { |i| i.id}) + end + @candidates = [ issue ] if issue + end + + # If finding by id is fail, try to find by searching in subject + # and description. + if @candidates.empty? + # extract tokens from the question + # eg. hello "bye bye" => ["hello", "bye bye"] + tokens = @phrase.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} + # tokens must be at least 3 character long + tokens = tokens.uniq.select {|w| w.length > 2 } + like_tokens = tokens.collect {|w| "%#{w.downcase}%"} + + @candidates, count = Issue.search( like_tokens, projects_to_search, :before => true) + end + + render :inline => "<%= auto_complete_result_parent_issue( @candidates, @phrase) %>" + end + def index retrieve_query sort_init 'id', 'desc' @@ -58,11 +97,18 @@ class IssuesController < ApplicationController end @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) @issue_pages = Paginator.new self, @issue_count, limit, params['page'] - @issues = Issue.find :all, :order => sort_clause, - :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ], - :conditions => @query.statement, - :limit => limit, - :offset => @issue_pages.current.offset + @issues = Issue.find( :all, :order => sort_clause, + :include => [ :assigned_to, + :status, + :tracker, + :project, + :priority, + :category, + :fixed_version ], + :conditions => @query.statement, + :limit => limit, + :offset => @issue_pages.current.offset) + respond_to do |format| format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } @@ -136,7 +182,8 @@ class IssuesController < ApplicationController end @issue.status = default_status @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq - + @parent_issue = Issue.find( params[:issue_parent_issue_id]) unless params[:issue_parent_issue_id].blank? + if request.get? || request.xhr? @issue.start_date ||= Date.today else @@ -144,6 +191,7 @@ class IssuesController < ApplicationController # Check that the user is allowed to apply the requested status @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status if @issue.save + set_parent( @issue, params[:issue_parent_issue_id]) if params[:issue_parent_issue_id] attach_files(@issue, params[:attachments]) flash[:notice] = l(:notice_successful_create) Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') @@ -156,6 +204,18 @@ class IssuesController < ApplicationController @priorities = Enumeration::get_values('IPRI') render :layout => !request.xhr? end + + def add_subissue + if params[:issue_parent_issue_id].nil? + flash.now[:error] = 'No parent issue specified.' + render :nothing => true, :layout => true + return + else + redirect_to( :controller => 'issues', :action => 'new', + :issue_parent_issue_id => params[:issue_parent_issue_id]) + return + end + end # Attributes that can be updated on workflow transition (without :edit permission) # TODO: make it configurable (at least per role) @@ -186,6 +246,7 @@ class IssuesController < ApplicationController call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal}) if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save + set_parent(@issue, params[:issue_parent_issue_id]) if params[:issue_parent_issue_id] # Log spend time if current_role.allowed_to?(:log_time) @time_entry.save @@ -248,6 +309,7 @@ class IssuesController < ApplicationController call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) # Don't save any change to the issue if the user is not authorized to apply the requested status if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save + set_parent(issue, params[:issue_parent_issue_id]) unless params[:issue_parent_issue_id].blank? # Send notification for each issue (if changed) Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated') else @@ -412,6 +474,7 @@ class IssuesController < ApplicationController def update_form @issue = Issue.new(params[:issue]) + @parent_issue = @issue.parent render :action => :new, :layout => false end @@ -425,6 +488,7 @@ class IssuesController < ApplicationController private def find_issue @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) + @parent_issue = @issue.parent @project = @issue.project rescue ActiveRecord::RecordNotFound render_404 @@ -481,10 +545,22 @@ private @query.add_short_filter(field, params[field]) if params[field] end end - session[:query] = {:project_id => @query.project_id, :filters => @query.filters} + if params[:view_options] and params[:view_options].is_a? Hash + params[:view_options].each_pair do |name, value| + @query.set_view_option( name, value) + end + end + session[:query] = { + :project_id => @query.project_id, + :filters => @query.filters, + :view_options => @query.view_options + } else @query = Query.find_by_id(session[:query][:id]) if session[:query][:id] - @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters]) + @query ||= Query.new(:name => "_", + :project => @project, + :filters => session[:query][:filters], + :view_options => session[:query][:view_options]) @query.project = @project end end diff --git redmine-original/app/controllers/projects_controller.rb suissues-patch/app/controllers/projects_controller.rb index a75e412..8ab1d51 100644 --- redmine-original/app/controllers/projects_controller.rb +++ suissues-patch/app/controllers/projects_controller.rb @@ -46,6 +46,8 @@ class ProjectsController < ApplicationController helper :repositories include RepositoriesHelper include ProjectsHelper + helper :versions + include VersionsHelper # Lists visible projects def index @@ -293,4 +295,48 @@ private @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s } end end + + + def sort_as_tree(issues) + issues.sort!{|a,b| a.hierarchical_level <=> b.hierarchical_level} + @sorted_issues = [] + issues.each do |issue| + if @sorted_issues.empty? + @sorted_issues << issue + next + end + @time_to_stop = false #indicates when this task reaches its parent task (important because it has to stop between its parent task and the next aunt task + @sorted_issues.each do |sorted_issue| + #if same parent and smaller date, stop; if same parent, same date and smaller id, stop; after parent and before next parent, stop; + if ((sorted_issue.parent == issue.parent) && (sorted_issue.start_date > issue.start_date)) || + ((sorted_issue.parent == issue.parent) && (sorted_issue.start_date == issue.start_date) && (sorted_issue.id > issue.id)) || + (@time_to_stop && (sorted_issue.hierarchical_level < issue.hierarchical_level)) + @sorted_issues.insert(@sorted_issues.index(sorted_issue), issue) + break + end + @time_to_stop = true if sorted_issue == issue.parent + end + #if this issue's parent is the last element + @sorted_issues << issue if @time_to_stop + end + @sorted_issues + end + + #assumes that first level issues are ordered by date (sort_as_tree) + def integrate_versions_with_issues_tree(issues, versions) + versions.sort! {|x,y| x.start_date <=> y.start_date } + versions.each do |version| + issues << version if issues.empty? + issues.each do |issue| + if ((issue.is_a? Issue && issue.root?) || (issue.is_a? Version)) && version.start_date < issue.start_date + #insert version before a root task or another version whose date is immediately after this task's one + issues.insert(issues.index(issue), version) + elsif issue == issues.last + issues << version + end + end + end + issues + end + end diff --git redmine-original/app/controllers/queries_controller.rb suissues-patch/app/controllers/queries_controller.rb index 8500e85..7ab2b44 100644 --- redmine-original/app/controllers/queries_controller.rb +++ suissues-patch/app/controllers/queries_controller.rb @@ -30,7 +30,11 @@ class QueriesController < ApplicationController params[:fields].each do |field| @query.add_filter(field, params[:operators][field], params[:values][field]) end if params[:fields] - + + params[:view_options].each_pair do |name, value| + @query.set_view_option( name, value) + end if params[:view_options] + if request.post? && params[:confirm] && @query.save flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query @@ -45,6 +49,9 @@ class QueriesController < ApplicationController params[:fields].each do |field| @query.add_filter(field, params[:operators][field], params[:values][field]) end if params[:fields] + params[:view_options].each_pair do |name, value| + @query.set_view_option( name, value) + end if params[:view_options] @query.attributes = params[:query] @query.project = nil if params[:query_is_for_all] @query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin? diff --git redmine-original/app/controllers/versions_controller.rb suissues-patch/app/controllers/versions_controller.rb index c269432..73a4b9b 100644 --- redmine-original/app/controllers/versions_controller.rb +++ suissues-patch/app/controllers/versions_controller.rb @@ -20,6 +20,10 @@ class VersionsController < ApplicationController before_filter :find_project, :authorize def show + @issues = @version.fixed_issues.find(:all, + :include => [:status, :tracker], + :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") + @issues = Issue.find_with_parents( @issues.collect { |i| i.id}) end def edit diff --git redmine-original/app/helpers/issues_helper.rb suissues-patch/app/helpers/issues_helper.rb index b2b85ee..293743b 100644 --- redmine-original/app/helpers/issues_helper.rb +++ suissues-patch/app/helpers/issues_helper.rb @@ -19,6 +19,12 @@ require 'csv' module IssuesHelper include ApplicationHelper + + def issue_ancestors(issue=@issue) + ancestors = "" + return "" if issue.parent == nil + ancestors += "issue-#{issue.parent.id}-child " + issue_ancestors(issue.parent) + end def render_issue_tooltip(issue) @cached_label_start_date ||= l(:field_start_date) @@ -193,4 +199,31 @@ module IssuesHelper export.rewind export end + + def set_parent(issue, parent_id) + if (issue && parent_id.to_s.size > 0) + issue.relations_from.each do |relation| + if relation.relation_type == IssueRelation::TYPE_PARENTS + relation.destroy + end + end + issue.reload + IssueRelation.create do |relation| + relation.issue_from = issue + relation.issue_to = Issue.find(parent_id) + relation.relation_type = IssueRelation::TYPE_PARENTS + unless relation.save + flash[:error] = "Can't set ##{parent_id} as parent for the issue." + end + end unless parent_id == '0' + end + end + + def auto_complete_result_parent_issue(candidates, phrase) + return "" if candidates.empty? + candidates.map! do |c| + content_tag("li", highlight( c.to_s, phrase), :id => String( c[:id])) + end + content_tag("ul", candidates.uniq) + end end diff --git redmine-original/app/helpers/queries_helper.rb suissues-patch/app/helpers/queries_helper.rb index 63d6a53..a27bca9 100644 --- redmine-original/app/helpers/queries_helper.rb +++ suissues-patch/app/helpers/queries_helper.rb @@ -1,3 +1,4 @@ +# -*- coding: mule-utf-8 -*- # redMine - project management software # Copyright (C) 2006-2007 Jean-Philippe Lang # @@ -27,7 +28,7 @@ module QueriesHelper content_tag('th', column.caption) end - def column_content(column, issue) + def column_content(column, issue, query) if column.is_a?(QueryCustomFieldColumn) cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id} show_value(cv) @@ -40,8 +41,7 @@ module QueriesHelper else case column.name when :subject - h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') + - link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) + subject_in_tree(issue, value, query) when :done_ratio progress_bar(value, :width => '80px') when :fixed_version @@ -52,4 +52,47 @@ module QueriesHelper end end end + + def subject_in_tree(issue, value, query) + case query.view_options['show_parents'] + when Query::VIEW_OPTIONS_SHOW_PARENTS_NEVER + content_tag('div', subject_text(issue, value), :class=>'issue-subject') + else + content_tag('span', content_tag('div', subject_text(issue, value), :class=>'issue-subject'), :class=>"issue-subject-level-#{issue.hierarchical_level}") + end + end + + def subject_text(issue, value) + subject_text = link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) + h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') + subject_text + end + + def issue_content(issue, query, options = { }) + html = "" + html << "