Feature #5358 » shared_categories_redmine_13.patch
| app/controllers/issue_categories_controller.rb Wed Feb 22 14:09:19 2012 -0800 → app/controllers/issue_categories_controller.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 43 | 43 | 
    end  | 
| 44 | 44 | |
| 45 | 45 | 
    verify :method => :post, :only => :create  | 
| 46 | ||
| 47 | 
    helper :projects  | 
|
| 48 | ||
| 46 | 49 | 
    def create  | 
| 47 | 50 | 
    @category = @project.issue_categories.build(params[:issue_category])  | 
| 48 | 51 | 
    if @category.save  | 
| app/controllers/reports_controller.rb Wed Feb 22 14:09:19 2012 -0800 → app/controllers/reports_controller.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 23 | 23 | 
    @trackers = @project.trackers  | 
| 24 | 24 | 
    @versions = @project.shared_versions.sort  | 
| 25 | 25 | 
    @priorities = IssuePriority.all  | 
| 26 | 
        @categories = @project.issue_categories
   | 
|
| 26 | 
        @categories = @project.share_categories.sort
   | 
|
| 27 | 27 | 
    @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort  | 
| 28 | 28 | 
    @authors = @project.users.sort  | 
| 29 | 29 | 
    @subprojects = @project.descendants.visible  | 
| ... | ... | |
| 58 | 58 | 
    @report_title = l(:field_priority)  | 
| 59 | 59 | 
    when "category"  | 
| 60 | 60 | 
    @field = "category_id"  | 
| 61 | 
          @rows = @project.issue_categories
   | 
|
| 61 | 
          @rows = @project.shared_categories.sort
   | 
|
| 62 | 62 | 
    @data = Issue.by_category(@project)  | 
| 63 | 63 | 
    @report_title = l(:field_category)  | 
| 64 | 64 | 
    when "assigned_to"  | 
| app/helpers/projects_helper.rb Wed Feb 22 14:09:19 2012 -0800 → app/helpers/projects_helper.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 107 | 107 | 
    sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)  | 
| 108 | 108 | 
        l("label_version_sharing_#{sharing}")
   | 
| 109 | 109 | 
    end  | 
| 110 | ||
| 111 | 
    def format_category_sharing(category, project)  | 
|
| 112 | 
    sharing = IssueCategory::SHARINGS.include?(category.sharing) ? category.sharing : 'none'  | 
|
| 113 | 
        project_name = category.project != project ? ' (' + category.project.name + ')' : ''
   | 
|
| 114 | 
    # sharing = category.sharing  | 
|
| 115 | 
    # sharing = 'none' unless IssueCategory::SHARINGS.include?(category.sharing)  | 
|
| 116 | 
    # if category.project != project  | 
|
| 117 | 
        #  project_name = ' (' + category.project.name + ')'
   | 
|
| 118 | 
    # else  | 
|
| 119 | 
    # project_name = ''  | 
|
| 120 | 
    # end  | 
|
| 121 | 
        l("label_version_sharing_#{sharing}") + project_name
   | 
|
| 122 | 
    end  | 
|
| 110 | 123 | 
    end  | 
| app/models/issue.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/issue.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 168 | 168 | 
    unless new_project.shared_versions.include?(issue.fixed_version)  | 
| 169 | 169 | 
    issue.fixed_version = nil  | 
| 170 | 170 | 
    end  | 
| 171 | 
    # Keep the fixed_category if it's still valid in the new_project  | 
|
| 172 | 
    unless new_project.shared_categories.include?(issue.category)  | 
|
| 173 | 
    issue.category = nil  | 
|
| 174 | 
    end  | 
|
| 171 | 175 | 
    issue.project = new_project  | 
| 172 | 176 | 
    if issue.parent && issue.parent.project_id != issue.project_id  | 
| 173 | 177 | 
    issue.parent_issue_id = nil  | 
| ... | ... | |
| 359 | 363 | 
    end  | 
| 360 | 364 | 
    end  | 
| 361 | 365 | |
| 366 | 
    if category  | 
|
| 367 | 
    if !assignable_categories.include?(category)  | 
|
| 368 | 
    errors.add :category_id, :inclusion  | 
|
| 369 | 
    end  | 
|
| 370 | 
    end  | 
|
| 371 | ||
| 362 | 372 | 
    # Checks that the issue can not be added/moved to a disabled tracker  | 
| 363 | 373 | 
    if project && (tracker_id_changed? || project_id_changed?)  | 
| 364 | 374 | 
    unless project.trackers.include?(tracker)  | 
| ... | ... | |
| 461 | 471 | 
    @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort  | 
| 462 | 472 | 
    end  | 
| 463 | 473 | |
| 474 | 
    # Categories that the issue can be assigned to  | 
|
| 475 | 
    def assignable_categories  | 
|
| 476 | 
    @assignable_categories ||= (project.shared_categories + [IssueCategory.find_by_id(category_id_was)]).compact.uniq.sort  | 
|
| 477 | 
    end  | 
|
| 478 | ||
| 464 | 479 | 
    # Returns true if this issue is blocked by another issue that is still open  | 
| 465 | 480 | 
    def blocked?  | 
| 466 | 481 | 
        !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
   | 
| ... | ... | |
| 647 | 662 | 
        update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
   | 
| 648 | 663 | 
    end  | 
| 649 | 664 | |
| 665 | 
    # Unassigns issues from +category+ if it's no longer shared with issue's project  | 
|
| 666 | 
    def self.update_categories_from_sharing_change(category)  | 
|
| 667 | 
    # Update issues assigned to the category  | 
|
| 668 | 
        update_categories(["#{Issue.table_name}.category_id = ?", category.id])
   | 
|
| 669 | 
    end  | 
|
| 670 | ||
| 650 | 671 | 
    # Unassigns issues from versions that are no longer shared  | 
| 651 | 672 | 
    # after +project+ was moved  | 
| 652 | 673 | 
    def self.update_versions_from_hierarchy_change(project)  | 
| ... | ... | |
| 655 | 676 | 
        Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
   | 
| 656 | 677 | 
    end  | 
| 657 | 678 | |
| 679 | 
    # Unassigns issues from categories that are no longer shared  | 
|
| 680 | 
    # after +project+ was moved  | 
|
| 681 | 
    def self.update_categories_from_hierarchy_change(project)  | 
|
| 682 | 
    moved_project_ids = project.self_and_descendants.reload.collect(&:id)  | 
|
| 683 | 
    # Update issues of the moved projects and issues assigned to a category of a moved project  | 
|
| 684 | 
        Issue.update_categories(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
   | 
|
| 685 | 
    end  | 
|
| 686 | ||
| 658 | 687 | 
    def parent_issue_id=(arg)  | 
| 659 | 688 | 
    parent_issue_id = arg.blank? ? nil : arg.to_i  | 
| 660 | 689 | 
    if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)  | 
| ... | ... | |
| 852 | 881 | 
    end  | 
| 853 | 882 | 
    end  | 
| 854 | 883 | |
| 884 | 
    # Update issues so their categories are not pointing to a  | 
|
| 885 | 
    # fixed_version that is not shared with the issue's project  | 
|
| 886 | 
    def self.update_categories(conditions=nil)  | 
|
| 887 | 
    # Only need to update issues with a fixed_version from  | 
|
| 888 | 
    # a different project and that is not systemwide shared  | 
|
| 889 | 
        Issue.all(:conditions => merge_conditions("#{Issue.table_name}.category_id IS NOT NULL" +
   | 
|
| 890 | 
                                                    " AND #{Issue.table_name}.project_id <> #{IssueCategory.table_name}.project_id" +
   | 
|
| 891 | 
                                                    " AND #{IssueCategory.table_name}.sharing <> 'system'",
   | 
|
| 892 | 
    conditions),  | 
|
| 893 | 
    :include => [:project, :category]  | 
|
| 894 | 
    ).each do |issue|  | 
|
| 895 | 
    next if issue.project.nil? || issue.category.nil?  | 
|
| 896 | 
    unless issue.project.shared_categories.include?(issue.category)  | 
|
| 897 | 
    issue.init_journal(User.current)  | 
|
| 898 | 
    issue.category = nil  | 
|
| 899 | 
    issue.save  | 
|
| 900 | 
    end  | 
|
| 901 | 
    end  | 
|
| 902 | 
    end  | 
|
| 903 | ||
| 855 | 904 | 
    # Callback on attachment deletion  | 
| 856 | 905 | 
    def attachment_added(obj)  | 
| 857 | 906 | 
    if @current_journal && !obj.new_record?  | 
| app/models/issue_category.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/issue_category.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 16 | 16 | 
    # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  | 
| 17 | 17 | |
| 18 | 18 | 
    class IssueCategory < ActiveRecord::Base  | 
| 19 | 
    after_update :update_issues_from_sharing_change  | 
|
| 19 | 20 | 
    belongs_to :project  | 
| 20 | 21 | 
    belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'  | 
| 21 | 22 | 
    has_many :issues, :foreign_key => 'category_id', :dependent => :nullify  | 
| 22 | 23 | |
| 24 | 
    SHARINGS = %w(none descendants hierarchy tree system)  | 
|
| 25 | ||
| 23 | 26 | 
    validates_presence_of :name  | 
| 24 | 27 | 
    validates_uniqueness_of :name, :scope => [:project_id]  | 
| 25 | 28 | 
    validates_length_of :name, :maximum => 30  | 
| 26 | 
     | 
|
| 29 | 
    validates_inclusion_of :sharing, :in => SHARINGS  | 
|
| 30 | ||
| 27 | 31 | 
    attr_protected :project_id  | 
| 28 | 32 | |
| 29 | 33 | 
      named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
   | 
| ... | ... | |
| 44 | 48 | 
    end  | 
| 45 | 49 | |
| 46 | 50 | 
    def to_s; name end  | 
| 51 | 
    # Returns the sharings that +user+ can set the category to  | 
|
| 52 | 
    def allowed_sharings(user = User.current)  | 
|
| 53 | 
    SHARINGS.select do |s|  | 
|
| 54 | 
    if sharing == s  | 
|
| 55 | 
    true  | 
|
| 56 | 
    else  | 
|
| 57 | 
    case s  | 
|
| 58 | 
    when 'system'  | 
|
| 59 | 
    # Only admin users can set a systemwide sharing  | 
|
| 60 | 
    user.admin?  | 
|
| 61 | 
    when 'hierarchy', 'tree'  | 
|
| 62 | 
    # Only users allowed to manage categories of the root project can  | 
|
| 63 | 
    # set sharing to hierarchy or tree  | 
|
| 64 | 
    project.nil? || user.allowed_to?(:manage_categories, project.root)  | 
|
| 65 | 
    else  | 
|
| 66 | 
    true  | 
|
| 67 | 
    end  | 
|
| 68 | 
    end  | 
|
| 69 | 
    end  | 
|
| 70 | 
    end  | 
|
| 71 | ||
| 72 | 
    # Update the issue's fixed categories. Used if a category's sharing changes.  | 
|
| 73 | 
    def update_issues_from_sharing_change  | 
|
| 74 | 
    if sharing_changed?  | 
|
| 75 | 
    if SHARINGS.index(sharing_was).nil? ||  | 
|
| 76 | 
    SHARINGS.index(sharing).nil? ||  | 
|
| 77 | 
    SHARINGS.index(sharing_was) > SHARINGS.index(sharing)  | 
|
| 78 | 
    Issue.update_categories_from_sharing_change self  | 
|
| 79 | 
    end  | 
|
| 80 | 
    end  | 
|
| 81 | 
    end  | 
|
| 47 | 82 | 
    end  | 
| app/models/mail_handler.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/mail_handler.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 267 | 267 | 
    'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),  | 
| 268 | 268 | 
    'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),  | 
| 269 | 269 | 
    'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),  | 
| 270 | 
          'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
   | 
|
| 270 | 
          'category_id' => (k = get_keyword(:category)) && issue.project.shared_categories.named(k).first.try(:id),
   | 
|
| 271 | 271 | 
    'assigned_to_id' => assigned_to.try(:id),  | 
| 272 | 272 | 
    'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.named(k).first.try(:id),  | 
| 273 | 273 | 
          'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
   | 
| app/models/project.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/project.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 354 | 354 | 
    move_to_child_of(p)  | 
| 355 | 355 | 
    end  | 
| 356 | 356 | 
    Issue.update_versions_from_hierarchy_change(self)  | 
| 357 | 
    Issue.update_categories_from_hierarchy_change(self)  | 
|
| 357 | 358 | 
    true  | 
| 358 | 359 | 
    else  | 
| 359 | 360 | 
    # Can not move to the given target  | 
| ... | ... | |
| 402 | 403 | 
    "))")  | 
| 403 | 404 | 
    end  | 
| 404 | 405 | 
    end  | 
| 406 | 
    # Returns a scope of the categories used by the project  | 
|
| 407 | 
    def shared_categories  | 
|
| 408 | 
    @shared_categories ||=  | 
|
| 409 | 
    IssueCategory.find(:all, :include => :project,  | 
|
| 410 | 
                         :conditions => "#{Project.table_name}.id = #{id}" +
   | 
|
| 411 | 
                                        " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
   | 
|
| 412 | 
                                              " #{IssueCategory.table_name}.sharing = 'system'" +
   | 
|
| 413 | 
                                              " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{IssueCategory.table_name}.sharing = 'tree')" +
   | 
|
| 414 | 
                                              " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{IssueCategory.table_name}.sharing IN ('hierarchy', 'descendants'))" +
   | 
|
| 415 | 
                                              " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{IssueCategory.table_name}.sharing = 'hierarchy')" +
   | 
|
| 416 | 
    "))")  | 
|
| 417 | 
    end  | 
|
| 405 | 418 | |
| 406 | 419 | 
    # Returns a hash of project users grouped by role  | 
| 407 | 420 | 
    def users_by_role  | 
| app/models/query.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/query.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 270 | 270 | |
| 271 | 271 | 
    if project  | 
| 272 | 272 | 
    # project specific filters  | 
| 273 | 
          categories = project.issue_categories.all
   | 
|
| 273 | 
          categories = project.shared_categories
   | 
|
| 274 | 274 | 
    unless categories.empty?  | 
| 275 | 
            @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
   | 
|
| 275 | 
            @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
   | 
|
| 276 | 276 | 
    end  | 
| 277 | 277 | 
    versions = project.shared_versions.all  | 
| 278 | 278 | 
    unless versions.empty?  | 
| app/views/context_menus/issues.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/context_menus/issues.html.erb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 70 | 70 | 
    </ul>  | 
| 71 | 71 | 
    </li>  | 
| 72 | 72 | 
    <% end %>  | 
| 73 | 
      <% unless @project.nil? || @project.issue_categories.empty? -%>
   | 
|
| 73 | 
      <% unless @project.nil? || @project.shared_categories.empty? -%>
   | 
|
| 74 | 74 | 
    <li class="folder">  | 
| 75 | 75 | 
    <a href="#" class="submenu"><%= l(:field_category) %></a>  | 
| 76 | 76 | 
    <ul>  | 
| 77 | 
        <% @project.issue_categories.each do |u| -%>
   | 
|
| 77 | 
        <% @project.shared_categories.each do |u| -%>
   | 
|
| 78 | 78 | 
            <li><%= context_menu_link h(u.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post,
   | 
| 79 | 79 | 
    :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %></li>  | 
| 80 | 80 | 
    <% end -%>  | 
| app/views/issue_categories/_form.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/issue_categories/_form.html.erb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 3 | 3 | 
    <div class="box">  | 
| 4 | 4 | 
    <p><%= f.text_field :name, :size => 30, :required => true %></p>  | 
| 5 | 5 | 
    <p><%= f.select :assigned_to_id, principals_options_for_select(@project.assignable_users, @category.assigned_to), :include_blank => true %></p>  | 
| 6 | 
    <p><%= f.select :sharing, @category.allowed_sharings.collect{|c| [format_category_sharing(c), c]} %></p>
   | 
|
| 6 | 7 | 
    </div>  | 
| app/views/issues/_attributes.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/issues/_attributes.html.erb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 9 | 9 | |
| 10 | 10 | 
    <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
   | 
| 11 | 11 | 
    <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>  | 
| 12 | 
    <% unless @project.issue_categories.empty? %>
   | 
|
| 13 | 
    <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
   | 
|
| 12 | 
    <% unless @issue.assignable_categories.empty? %>
   | 
|
| 13 | 
    <p><%= f.select :category_id, (@issue.assignable_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
   | 
|
| 14 | 14 | 
    <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
   | 
| 15 | 15 | 
    l(:label_issue_category_new),  | 
| 16 | 16 | 
    'issue_category[name]',  | 
| app/views/projects/settings/_issue_categories.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/projects/settings/_issue_categories.html.erb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 1 | 
    <% if @project.issue_categories.any? %>
   | 
|
| 1 | 
    <% if @project.shared_categories.any? %>
   | 
|
| 2 | 2 | 
    <table class="list">  | 
| 3 | 3 | 
    <thead><tr>  | 
| 4 | 4 | 
    <th><%= l(:label_issue_category) %></th>  | 
| 5 | 5 | 
    <th><%= l(:field_assigned_to) %></th>  | 
| 6 | 
    <th><%= l(:field_sharing) %></th>  | 
|
| 6 | 7 | 
    <th></th>  | 
| 7 | 8 | 
    </tr></thead>  | 
| 8 | 9 | 
    <tbody>  | 
| 9 | 
    <% for category in @project.issue_categories %>
   | 
|
| 10 | 
    <% for category in @project.shared_categories %>
   | 
|
| 10 | 11 | 
    <% unless category.new_record? %>  | 
| 11 | 
    <tr class="<%= cycle 'odd', 'even' %>">  | 
|
| 12 | 
      <tr class="<%= cycle 'odd', 'even' %> <%= 'shared' if category.project != @project %>">
   | 
|
| 12 | 13 | 
    <td><%=h(category.name) %></td>  | 
| 13 | 14 | 
    <td><%=h(category.assigned_to.name) if category.assigned_to %></td>  | 
| 15 | 
    <td class="sharing"><%=h format_category_sharing(category, @project) %></td>  | 
|
| 14 | 16 | 
    <td class="buttons">  | 
| 15 | 17 | 
    <% if User.current.allowed_to?(:manage_categories, @project) %>  | 
| 16 | 18 | 
    <%= link_to l(:button_edit), edit_issue_category_path(category), :class => 'icon icon-edit' %>  | 
| /dev/null Thu Jan 01 00:00:00 1970 +0000 → db/migrate/20120222000000_add_issue_category_sharing.rb Wed Feb 22 16:48:47 2012 -0800 | ||
|---|---|---|
| 1 | 
    class AddIssueCategorySharing < ActiveRecord::Migration  | 
|
| 2 | 
    def self.up  | 
|
| 3 | 
    add_column :issue_categories, :sharing, :string, :default => 'none', :null => false  | 
|
| 4 | 
    add_index :issue_categories, :sharing  | 
|
| 5 | 
    end  | 
|
| 6 | ||
| 7 | 
    def self.down  | 
|
| 8 | 
    remove_column :issue_categories, :sharing  | 
|
| 9 | 
    end  | 
|
| 10 | 
    end  | 
|