Feature #22802 » manual_set_issue_position_in_versions_v3.patch
| app/controllers/issues_controller.rb | ||
|---|---|---|
| 192 | 192 | |
| 193 | 193 | 
    respond_to do |format|  | 
| 194 | 194 | 
            format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) }
   | 
| 195 | 
            format.js { render :nothing => true }
   | 
|
| 195 | 196 | 
            format.api  { render_api_ok }
   | 
| 196 | 197 | 
    end  | 
| 197 | 198 | 
    else  | 
| 198 | 199 | 
    respond_to do |format|  | 
| 199 | 200 | 
            format.html { render :action => 'edit' }
   | 
| 201 | 
            format.js { render :nothing => true, :status => 422 }
   | 
|
| 200 | 202 | 
            format.api  { render_validation_errors(@issue) }
   | 
| 201 | 203 | 
    end  | 
| 202 | 204 | 
    end  | 
| ... | ... | |
| 380 | 382 | 
    # Overrides Redmine::MenuManager::MenuController::ClassMethods for  | 
| 381 | 383 | 
    # when the "New issue" tab is enabled  | 
| 382 | 384 | 
    def current_menu_item  | 
| 383 | 
        if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym) 
   | 
|
| 385 | 
    if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym)  | 
|
| 384 | 386 | 
    :new_issue  | 
| 385 | 387 | 
    else  | 
| 386 | 388 | 
    super  | 
| app/controllers/versions_controller.rb | ||
|---|---|---|
| 46 | 46 | |
| 47 | 47 | 
            @issues_by_version = {}
   | 
| 48 | 48 | 
    if @selected_tracker_ids.any? && @versions.any?  | 
| 49 | ||
| 49 | 50 | 
    issues = Issue.visible.  | 
| 50 | 51 | 
    includes(:project, :tracker).  | 
| 51 | 52 | 
    preload(:status, :priority, :fixed_version).  | 
| 52 | 53 | 
    where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)).  | 
| 53 | 
                order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
   | 
|
| 54 | 
                order(order_issues_by)
   | 
|
| 54 | 55 | 
    @issues_by_version = issues.group_by(&:fixed_version)  | 
| 55 | 56 | 
    end  | 
| 56 | 57 | 
            @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
   | 
| ... | ... | |
| 67 | 68 | 
    @issues = @version.fixed_issues.visible.  | 
| 68 | 69 | 
    includes(:status, :tracker, :priority).  | 
| 69 | 70 | 
    preload(:project).  | 
| 70 | 
              reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
   | 
|
| 71 | 
              reorder(order_issues_by).
   | 
|
| 71 | 72 | 
    to_a  | 
| 72 | 73 | 
    }  | 
| 73 | 74 | 
    format.api  | 
| ... | ... | |
| 180 | 181 | 
          @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
   | 
| 181 | 182 | 
    end  | 
| 182 | 183 | 
    end  | 
| 184 | ||
| 185 | 
    def order_issues_by  | 
|
| 186 | 
    if Setting.manual_issue_position_in_versions == '1'  | 
|
| 187 | 
          return "COALESCE(#{Issue.table_name}.position, 999999)"
   | 
|
| 188 | 
    else  | 
|
| 189 | 
          return "#{Tracker.table_name}.position, #{Issue.table_name}.id"
   | 
|
| 190 | 
    end  | 
|
| 191 | 
    end  | 
|
| 183 | 192 | 
    end  | 
| app/models/issue.rb | ||
|---|---|---|
| 56 | 56 | |
| 57 | 57 | 
    acts_as_activity_provider :scope => preload(:project, :author, :tracker, :status),  | 
| 58 | 58 | 
    :author_key => :author_id  | 
| 59 | ||
| 59 | 
    acts_as_positioned :scope => [:fixed_version_id]  | 
|
| 60 | 60 | 
    DONE_RATIO_OPTIONS = %w(issue_field issue_status)  | 
| 61 | 61 | |
| 62 | 62 | 
    attr_accessor :deleted_attachment_ids  | 
| ... | ... | |
| 468 | 468 | 
        :if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) &&
   | 
| 469 | 469 | 
    user.allowed_to?(:manage_subtasks, issue.project)}  | 
| 470 | 470 | |
| 471 | 
    safe_attributes 'position',  | 
|
| 472 | 
        :if => lambda {|issue, user| user.allowed_to?(:change_issue_position_in_version, issue.project)}
   | 
|
| 473 | ||
| 471 | 474 | 
    safe_attributes 'deleted_attachment_ids',  | 
| 472 | 475 | 
        :if => lambda {|issue, user| issue.attachments_deletable?(user)}
   | 
| 473 | 476 | |
| ... | ... | |
| 781 | 784 | |
| 782 | 785 | 
    # Returns the names of attributes that are journalized when updating the issue  | 
| 783 | 786 | 
    def journalized_attribute_names  | 
| 784 | 
    names = Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)  | 
|
| 787 | 
        names = Issue.column_names - %w(id root_id lft rgt lock_version position created_on updated_on closed_on)
   | 
|
| 785 | 788 | 
    if tracker  | 
| 786 | 789 | 
    names -= tracker.disabled_core_fields  | 
| 787 | 790 | 
    end  | 
| app/views/settings/_issues.html.erb | ||
|---|---|---|
| 13 | 13 | |
| 14 | 14 | 
    <p><%= setting_check_box :display_subprojects_issues %></p>  | 
| 15 | 15 | |
| 16 | 
    <p><%= setting_check_box :manual_issue_position_in_versions %></p>  | 
|
| 17 | ||
| 16 | 18 | 
    <p><%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %></p>
   | 
| 17 | 19 | |
| 18 | 20 | 
    <p><%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %></p>
   | 
| ... | ... | |
| 20 | 22 | 
    <p><%= setting_text_field :issues_export_limit, :size => 6 %></p>  | 
| 21 | 23 | |
| 22 | 24 | 
    <p><%= setting_text_field :gantt_items_limit, :size => 6 %></p>  | 
| 25 | ||
| 23 | 26 | 
    </div>  | 
| 24 | 27 | |
| 25 | 28 | 
    <fieldset class="box">  | 
| app/views/versions/index.html.erb | ||
|---|---|---|
| 26 | 26 | 
          <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
   | 
| 27 | 27 | 
    <table class="list related-issues">  | 
| 28 | 28 | 
    <caption><%= l(:label_related_issues) %></caption>  | 
| 29 | 
    <% issues.each do |issue| -%>  | 
|
| 30 | 
    <tr class="hascontextmenu">  | 
|
| 31 | 
    <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>  | 
|
| 32 | 
    <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>  | 
|
| 33 | 
    </tr>  | 
|
| 34 | 
    <% end -%>  | 
|
| 29 | 
    <tbody>  | 
|
| 30 | 
    <% issues.each do |issue| -%>  | 
|
| 31 | 
    <tr class="hascontextmenu">  | 
|
| 32 | 
    <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>  | 
|
| 33 | 
    <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>  | 
|
| 34 | 
    <% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, version.project) %>  | 
|
| 35 | 
    <td class="sortable"><%= reorder_handle(issue) %></td>  | 
|
| 36 | 
    <% end%>  | 
|
| 37 | 
    </tr>  | 
|
| 38 | 
    <% end -%>  | 
|
| 39 | 
    </tbody>  | 
|
| 35 | 40 | 
    </table>  | 
| 36 | 41 | 
    <% end %>  | 
| 37 | 42 | 
    <% end %>  | 
| ... | ... | |
| 100 | 105 | 
    <% html_title(l(:label_roadmap)) %>  | 
| 101 | 106 | |
| 102 | 107 | 
    <%= context_menu %>  | 
| 108 | ||
| 109 | 
    <% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, @project) %>  | 
|
| 110 | 
    <%= javascript_tag do %>  | 
|
| 111 | 
        $(function() { $("table.related-issues tbody").positionedItems(); });
   | 
|
| 112 | 
    <% end %>  | 
|
| 113 | 
    <% end %>  | 
|
| app/views/versions/show.html.erb | ||
|---|---|---|
| 38 | 38 | 
    <% if @issues.present? %>  | 
| 39 | 39 | 
    <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
   | 
| 40 | 40 | 
    <table class="list related-issues">  | 
| 41 | 
    <caption><%= l(:label_related_issues) %></caption>  | 
|
| 42 | 
    <%- @issues.each do |issue| -%>  | 
|
| 43 | 
    <tr class="issue hascontextmenu">  | 
|
| 44 | 
    <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>  | 
|
| 45 | 
    <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>  | 
|
| 46 | 
    </tr>  | 
|
| 47 | 
    <% end %>  | 
|
| 41 | 
    <caption><%= l(:label_related_issues) %></caption>  | 
|
| 42 | 
    <tbody>  | 
|
| 43 | 
    <%- @issues.each do |issue| -%>  | 
|
| 44 | 
    <tr class="issue hascontextmenu">  | 
|
| 45 | 
    <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>  | 
|
| 46 | 
    <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>  | 
|
| 47 | 
    <% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %>  | 
|
| 48 | 
    <td class="sortable"><%= reorder_handle(issue) %></td>  | 
|
| 49 | 
    <% end%>  | 
|
| 50 | 
    </tr>  | 
|
| 51 | 
    <% end %>  | 
|
| 52 | 
    </tbody>  | 
|
| 48 | 53 | 
    </table>  | 
| 49 | 54 | 
    <% end %>  | 
| 50 | 55 | 
    <%= context_menu %>  | 
| ... | ... | |
| 54 | 59 | 
    <%= call_hook :view_versions_show_bottom, :version => @version %>  | 
| 55 | 60 | |
| 56 | 61 | 
    <% html_title @version.name %>  | 
| 62 | ||
| 63 | 
    <% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %>  | 
|
| 64 | 
    <%= javascript_tag do %>  | 
|
| 65 | 
        $(function() { $("table.related-issues tbody").positionedItems(); });
   | 
|
| 66 | 
    <% end %>  | 
|
| 67 | 
    <% end %>  | 
|
| config/locales/en.yml | ||
|---|---|---|
| 448 | 448 | 
    setting_attachment_extensions_allowed: Allowed extensions  | 
| 449 | 449 | 
    setting_attachment_extensions_denied: Disallowed extensions  | 
| 450 | 450 | 
    setting_new_item_menu_tab: Project menu tab for creating new objects  | 
| 451 | 
    setting_manual_issue_position_in_versions: Enable manually set issue position in versions  | 
|
| 451 | 452 | |
| 452 | 453 | 
    permission_add_project: Create project  | 
| 453 | 454 | 
    permission_add_subprojects: Create subprojects  | 
| ... | ... | |
| 514 | 515 | 
    permission_manage_subtasks: Manage subtasks  | 
| 515 | 516 | 
    permission_manage_related_issues: Manage related issues  | 
| 516 | 517 | 
    permission_import_issues: Import issues  | 
| 518 | 
    permission_change_issue_position_in_version: Change issue position in version  | 
|
| 517 | 519 | |
| 518 | 520 | 
    project_module_issue_tracking: Issue tracking  | 
| 519 | 521 | 
    project_module_time_tracking: Time tracking  | 
| config/settings.yml | ||
|---|---|---|
| 171 | 171 | 
    default: 'derived'  | 
| 172 | 172 | 
    link_copied_issue:  | 
| 173 | 173 | 
    default: 'ask'  | 
| 174 | 
    manual_issue_position_in_versions:  | 
|
| 175 | 
    default: 0  | 
|
| 174 | 176 | 
    issue_group_assignment:  | 
| 175 | 177 | 
    default: 0  | 
| 176 | 178 | 
    default_issue_start_date_to_creation_date:  | 
| db/migrate/20160901154541_add_issue_position.rb | ||
|---|---|---|
| 1 | 
    class AddIssuePosition < ActiveRecord::Migration  | 
|
| 2 | 
    def up  | 
|
| 3 | 
    add_column :issues, :position, :integer  | 
|
| 4 | 
    end  | 
|
| 5 | ||
| 6 | 
    def down  | 
|
| 7 | 
    remove_column :issues, :position  | 
|
| 8 | 
    end  | 
|
| 9 | 
    end  | 
|
| lib/redmine.rb | ||
|---|---|---|
| 110 | 110 | 
        map.permission :view_private_notes, {}, :read => true, :require => :member
   | 
| 111 | 111 | 
        map.permission :set_notes_private, {}, :require => :member
   | 
| 112 | 112 | 
        map.permission :delete_issues, {:issues => :destroy}, :require => :member
   | 
| 113 | 
        map.permission :change_issue_position_in_version, {}
   | 
|
| 113 | 114 | 
    # Queries  | 
| 114 | 115 | 
        map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
   | 
| 115 | 116 | 
        map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
   | 
| public/stylesheets/application.css | ||
|---|---|---|
| 442 | 442 | |
| 443 | 443 | 
    div#roadmap .related-issues { margin-bottom: 1em; }
   | 
| 444 | 444 | 
    div#roadmap .related-issues td.checkbox { display: none; }
   | 
| 445 | 
    div#roadmap .related-issues td.sortable { text-align: right; }
   | 
|
| 445 | 446 | 
    div#roadmap .wiki h1:first-child { display: none; }
   | 
| 446 | 447 | 
    div#roadmap .wiki h1 { font-size: 120%; }
   | 
| 447 | 448 | 
    div#roadmap .wiki h2 { font-size: 110%; }
   | 
| test/fixtures/issues.yml | ||
|---|---|---|
| 40 | 40 | 
    rgt: 2  | 
| 41 | 41 | 
    lock_version: 3  | 
| 42 | 42 | 
    done_ratio: 30  | 
| 43 | 
    position: 1  | 
|
| 43 | 44 | 
    issues_003:  | 
| 44 | 45 | 
    created_on: 2006-07-19 21:07:27 +02:00  | 
| 45 | 46 | 
    project_id: 1  | 
| test/functional/versions_controller_test.rb | ||
|---|---|---|
| 77 | 77 | 
    assert_select 'h3', :text => /Subproject version/  | 
| 78 | 78 | 
    end  | 
| 79 | 79 | |
| 80 | 
    def test_issues_order_when_manual_issue_position_in_versions_is_disabled  | 
|
| 81 | 
    with_settings :manual_issue_position_in_versions => '0' do  | 
|
| 82 | 
          get :index, :params => {:project_id => 1}
   | 
|
| 83 | 
    assert_response :success  | 
|
| 84 | ||
| 85 | 
    assert_select '#roadmap article:first-child' do  | 
|
| 86 | 
    assert_select '.related-issues tr:nth-child(1) a[href=?]', '/issues/12', :text => 'Bug #12'  | 
|
| 87 | 
    assert_select '.related-issues tr:nth-child(2) a[href=?]', '/issues/2', :text => 'Feature request #2'  | 
|
| 88 | 
    end  | 
|
| 89 | ||
| 90 | 
          get :show, :params => { :id => 2 }
   | 
|
| 91 | 
    assert_response :success  | 
|
| 92 | ||
| 93 | 
    assert_select '.related-issues' do  | 
|
| 94 | 
    assert_select 'tr:nth-child(1) a[href=?]', '/issues/12', :text => 'Bug #12'  | 
|
| 95 | 
    assert_select 'tr:nth-child(2) a[href=?]', '/issues/2', :text => 'Feature request #2'  | 
|
| 96 | 
    end  | 
|
| 97 | 
    end  | 
|
| 98 | 
    end  | 
|
| 99 | ||
| 100 | 
    def test_issues_order_when_manual_issue_position_in_versions_is_enabled  | 
|
| 101 | 
    with_settings :manual_issue_position_in_versions => '1' do  | 
|
| 102 | 
          get :index, :params => {:project_id => 1}
   | 
|
| 103 | 
    assert_response :success  | 
|
| 104 | ||
| 105 | 
    assert_select '#roadmap article:first-child' do  | 
|
| 106 | 
    assert_select '.related-issues tr:nth-child(1) a[href=?]', '/issues/2', :text => 'Feature request #2'  | 
|
| 107 | 
    assert_select '.related-issues tr:nth-child(2) a[href=?]', '/issues/12', :text => 'Bug #12'  | 
|
| 108 | 
    end  | 
|
| 109 | ||
| 110 | 
          get :show, :params => { :id => 2 }
   | 
|
| 111 | 
    assert_response :success  | 
|
| 112 | ||
| 113 | 
    assert_select '.related-issues' do  | 
|
| 114 | 
    assert_select 'tr:nth-child(1) a[href=?]', '/issues/2', :text => 'Feature request #2'  | 
|
| 115 | 
    assert_select 'tr:nth-child(2) a[href=?]', '/issues/12', :text => 'Bug #12'  | 
|
| 116 | 
    end  | 
|
| 117 | 
    end  | 
|
| 118 | 
    end  | 
|
| 119 | ||
| 120 | 
    def test_user_with_permission_can_change_issue_position  | 
|
| 121 | 
    role = Role.find(1)  | 
|
| 122 | 
    role.add_permission! :change_issue_position_in_version  | 
|
| 123 | ||
| 124 | 
    @request.session[:user_id] = 2  | 
|
| 125 | 
    with_settings :manual_issue_position_in_versions => '1' do  | 
|
| 126 | 
          get :index, :params => {:project_id => 1}
   | 
|
| 127 | 
    assert_response :success  | 
|
| 128 | ||
| 129 | 
    assert_select '#roadmap article' do  | 
|
| 130 | 
    assert_select '.related-issues tr td.sortable', 2  | 
|
| 131 | 
    end  | 
|
| 132 | ||
| 133 | 
          get :show, :params => { :id => 2 }
   | 
|
| 134 | 
    assert_response :success  | 
|
| 135 | ||
| 136 | 
    assert_select '.related-issues' do  | 
|
| 137 | 
    assert_select '.related-issues tr td.sortable', 2  | 
|
| 138 | 
    end  | 
|
| 139 | 
    end  | 
|
| 140 | 
    end  | 
|
| 141 | ||
| 142 | 
    def test_user_without_permission_cannot_change_issue_position  | 
|
| 143 | 
    @request.session[:user_id] = 2  | 
|
| 144 | 
    with_settings :manual_issue_position_in_versions => '1' do  | 
|
| 145 | 
          get :index, :params => {:project_id => 1}
   | 
|
| 146 | 
    assert_response :success  | 
|
| 147 | ||
| 148 | 
    assert_select '#roadmap article' do  | 
|
| 149 | 
    assert_select '.related-issues tr td.sortable', 0  | 
|
| 150 | 
    end  | 
|
| 151 | ||
| 152 | 
          get :show, :params => { :id => 2 }
   | 
|
| 153 | 
    assert_response :success  | 
|
| 154 | ||
| 155 | 
    assert_select '.related-issues' do  | 
|
| 156 | 
    assert_select '.related-issues tr td.sortable', 0  | 
|
| 157 | 
    end  | 
|
| 158 | 
    end  | 
|
| 159 | 
    end  | 
|
| 160 | ||
| 80 | 161 | 
    def test_index_should_prepend_shared_versions  | 
| 81 | 162 | 
        get :index, :params => {:project_id => 1}
   | 
| 82 | 163 | 
    assert_response :success  |