diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index a4a3a34..284a47f 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -192,11 +192,13 @@ class IssuesController < ApplicationController respond_to do |format| format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) } + format.js { render :nothing => true } format.api { render_api_ok } end else respond_to do |format| format.html { render :action => 'edit' } + format.js { render :nothing => true, :status => 422 } format.api { render_validation_errors(@issue) } end end @@ -380,7 +382,7 @@ class IssuesController < ApplicationController # Overrides Redmine::MenuManager::MenuController::ClassMethods for # when the "New issue" tab is enabled def current_menu_item - if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym) + if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym) :new_issue else super diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 84c7980..d990337 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -46,11 +46,12 @@ class VersionsController < ApplicationController @issues_by_version = {} if @selected_tracker_ids.any? && @versions.any? + issues = Issue.visible. includes(:project, :tracker). preload(:status, :priority, :fixed_version). where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)). - order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") + order(order_issues_by) @issues_by_version = issues.group_by(&:fixed_version) end @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} @@ -67,7 +68,7 @@ class VersionsController < ApplicationController @issues = @version.fixed_issues.visible. includes(:status, :tracker, :priority). preload(:project). - reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id"). + reorder(order_issues_by). to_a } format.api @@ -180,4 +181,12 @@ class VersionsController < ApplicationController @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } end end + + def order_issues_by + if Setting.manual_issue_position_in_versions == '1' + return "COALESCE(#{Issue.table_name}.position, 999999)" + else + return "#{Tracker.table_name}.position, #{Issue.table_name}.id" + end + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 92cfad3..5bd07ae 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -56,7 +56,7 @@ class Issue < ActiveRecord::Base acts_as_activity_provider :scope => preload(:project, :author, :tracker, :status), :author_key => :author_id - + acts_as_positioned :scope => [:fixed_version_id] DONE_RATIO_OPTIONS = %w(issue_field issue_status) attr_accessor :deleted_attachment_ids @@ -468,6 +468,9 @@ class Issue < ActiveRecord::Base :if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) && user.allowed_to?(:manage_subtasks, issue.project)} + safe_attributes 'position', + :if => lambda {|issue, user| user.allowed_to?(:change_issue_position_in_version, issue.project)} + safe_attributes 'deleted_attachment_ids', :if => lambda {|issue, user| issue.attachments_deletable?(user)} @@ -781,7 +784,7 @@ class Issue < ActiveRecord::Base # Returns the names of attributes that are journalized when updating the issue def journalized_attribute_names - names = Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on) + names = Issue.column_names - %w(id root_id lft rgt lock_version position created_on updated_on closed_on) if tracker names -= tracker.disabled_core_fields end diff --git a/app/views/settings/_issues.html.erb b/app/views/settings/_issues.html.erb index f29a6d4..610ea4a 100644 --- a/app/views/settings/_issues.html.erb +++ b/app/views/settings/_issues.html.erb @@ -13,6 +13,8 @@

<%= setting_check_box :display_subprojects_issues %>

+

<%= setting_check_box :manual_issue_position_in_versions %>

+

<%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %>

<%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %>

@@ -20,6 +22,7 @@

<%= setting_text_field :issues_export_limit, :size => 6 %>

<%= setting_text_field :gantt_items_limit, :size => 6 %>

+
diff --git a/app/views/versions/index.html.erb b/app/views/versions/index.html.erb old mode 100644 new mode 100755 index 2914a6d..b818b99 --- a/app/views/versions/index.html.erb +++ b/app/views/versions/index.html.erb @@ -26,12 +26,17 @@ <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%> - <% issues.each do |issue| -%> - - - - - <% end -%> + + <% issues.each do |issue| -%> + + + + <% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, version.project) %> + + <% end%> + + <% end -%> + <% end %> <% end %> @@ -100,3 +105,9 @@ <% html_title(l(:label_roadmap)) %> <%= context_menu %> + +<% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, @project) %> + <%= javascript_tag do %> + $(function() { $("table.related-issues tbody").positionedItems(); }); + <% end %> +<% end %> diff --git a/app/views/versions/show.html.erb b/app/views/versions/show.html.erb index 638a17a..343b3f5 100644 --- a/app/views/versions/show.html.erb +++ b/app/views/versions/show.html.erb @@ -38,13 +38,18 @@ <% if @issues.present? %> <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%> - - <%- @issues.each do |issue| -%> - - - - - <% end %> + + + <%- @issues.each do |issue| -%> + + + + <% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %> + + <% end%> + + <% end %> + <% end %> <%= context_menu %> @@ -54,3 +59,9 @@ <%= call_hook :view_versions_show_bottom, :version => @version %> <% html_title @version.name %> + +<% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %> + <%= javascript_tag do %> + $(function() { $("table.related-issues tbody").positionedItems(); }); + <% end %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index eae1469..230005f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -448,6 +448,7 @@ en: setting_attachment_extensions_allowed: Allowed extensions setting_attachment_extensions_denied: Disallowed extensions setting_new_item_menu_tab: Project menu tab for creating new objects + setting_manual_issue_position_in_versions: Enable manually set issue position in versions permission_add_project: Create project permission_add_subprojects: Create subprojects @@ -514,6 +515,7 @@ en: permission_manage_subtasks: Manage subtasks permission_manage_related_issues: Manage related issues permission_import_issues: Import issues + permission_change_issue_position_in_version: Change issue position in version project_module_issue_tracking: Issue tracking project_module_time_tracking: Time tracking diff --git a/config/settings.yml b/config/settings.yml index b9a8258..fc06fd3 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -171,6 +171,8 @@ parent_issue_done_ratio: default: 'derived' link_copied_issue: default: 'ask' +manual_issue_position_in_versions: + default: 0 issue_group_assignment: default: 0 default_issue_start_date_to_creation_date: diff --git a/db/migrate/20160901154541_add_issue_position.rb b/db/migrate/20160901154541_add_issue_position.rb new file mode 100644 index 0000000..53ad10c --- /dev/null +++ b/db/migrate/20160901154541_add_issue_position.rb @@ -0,0 +1,9 @@ +class AddIssuePosition < ActiveRecord::Migration + def up + add_column :issues, :position, :integer + end + + def down + remove_column :issues, :position + end +end diff --git a/lib/redmine.rb b/lib/redmine.rb index 193d5d1..fbc2fb9 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -110,6 +110,7 @@ Redmine::AccessControl.map do |map| map.permission :view_private_notes, {}, :read => true, :require => :member map.permission :set_notes_private, {}, :require => :member map.permission :delete_issues, {:issues => :destroy}, :require => :member + map.permission :change_issue_position_in_version, {} # Queries map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 4f60ac3..3bf5630 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -442,6 +442,7 @@ div#search-results-counts li { list-style-type:none; float: left; margin-left: div#roadmap .related-issues { margin-bottom: 1em; } div#roadmap .related-issues td.checkbox { display: none; } +div#roadmap .related-issues td.sortable { text-align: right; } div#roadmap .wiki h1:first-child { display: none; } div#roadmap .wiki h1 { font-size: 120%; } div#roadmap .wiki h2 { font-size: 110%; } diff --git a/test/fixtures/issues.yml b/test/fixtures/issues.yml old mode 100644 new mode 100755 index 05d5bff..edde09a --- a/test/fixtures/issues.yml +++ b/test/fixtures/issues.yml @@ -40,6 +40,7 @@ issues_002: rgt: 2 lock_version: 3 done_ratio: 30 + position: 1 issues_003: created_on: 2006-07-19 21:07:27 +02:00 project_id: 1 diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index c2f054e..2d4feeb 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -77,6 +77,87 @@ class VersionsControllerTest < Redmine::ControllerTest assert_select 'h3', :text => /Subproject version/ end + def test_issues_order_when_manual_issue_position_in_versions_is_disabled + with_settings :manual_issue_position_in_versions => '0' do + get :index, :params => {:project_id => 1} + assert_response :success + + assert_select '#roadmap article:first-child' do + assert_select '.related-issues tr:nth-child(1) a[href=?]', '/issues/12', :text => 'Bug #12' + assert_select '.related-issues tr:nth-child(2) a[href=?]', '/issues/2', :text => 'Feature request #2' + end + + get :show, :params => { :id => 2 } + assert_response :success + + assert_select '.related-issues' do + assert_select 'tr:nth-child(1) a[href=?]', '/issues/12', :text => 'Bug #12' + assert_select 'tr:nth-child(2) a[href=?]', '/issues/2', :text => 'Feature request #2' + end + end + end + + def test_issues_order_when_manual_issue_position_in_versions_is_enabled + with_settings :manual_issue_position_in_versions => '1' do + get :index, :params => {:project_id => 1} + assert_response :success + + assert_select '#roadmap article:first-child' do + assert_select '.related-issues tr:nth-child(1) a[href=?]', '/issues/2', :text => 'Feature request #2' + assert_select '.related-issues tr:nth-child(2) a[href=?]', '/issues/12', :text => 'Bug #12' + end + + get :show, :params => { :id => 2 } + assert_response :success + + assert_select '.related-issues' do + assert_select 'tr:nth-child(1) a[href=?]', '/issues/2', :text => 'Feature request #2' + assert_select 'tr:nth-child(2) a[href=?]', '/issues/12', :text => 'Bug #12' + end + end + end + + def test_user_with_permission_can_change_issue_position + role = Role.find(1) + role.add_permission! :change_issue_position_in_version + + @request.session[:user_id] = 2 + with_settings :manual_issue_position_in_versions => '1' do + get :index, :params => {:project_id => 1} + assert_response :success + + assert_select '#roadmap article' do + assert_select '.related-issues tr td.sortable', 2 + end + + get :show, :params => { :id => 2 } + assert_response :success + + assert_select '.related-issues' do + assert_select '.related-issues tr td.sortable', 2 + end + end + end + + def test_user_without_permission_cannot_change_issue_position + @request.session[:user_id] = 2 + with_settings :manual_issue_position_in_versions => '1' do + get :index, :params => {:project_id => 1} + assert_response :success + + assert_select '#roadmap article' do + assert_select '.related-issues tr td.sortable', 0 + end + + get :show, :params => { :id => 2 } + assert_response :success + + assert_select '.related-issues' do + assert_select '.related-issues tr td.sortable', 0 + end + end + end + def test_index_should_prepend_shared_versions get :index, :params => {:project_id => 1} assert_response :success