diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 779e851..773c778 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 4e32dce..3d9ccd7 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 @@ -443,6 +443,7 @@ class Issue < ActiveRecord::Base 'due_date', 'done_ratio', 'estimated_hours', + 'position', 'custom_field_values', 'custom_fields', 'lock_version', @@ -776,7 +777,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 @@ -1402,7 +1403,7 @@ class Issue < ActiveRecord::Base end Project.where(condition).having_trackers end - + # Returns a scope of trackers that user can assign the issue to def allowed_target_trackers(user=User.current) self.class.allowed_target_trackers(project, user, tracker_id_was) 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 index 0f1d0b8..3f7dc70 100644 --- a/app/views/versions/index.html.erb +++ b/app/views/versions/index.html.erb @@ -26,12 +26,17 @@ <%= form_tag({}) do -%> - <% issues.each do |issue| -%> - - - - - <% end -%> + + <% issues.each do |issue| -%> + + + + <% if Setting.manual_issue_position_in_versions == '1' %> + + <% end%> + + <% end -%> + <% end %> <% end %> @@ -85,13 +90,13 @@ <% if @completed_versions.present? %>

- <%= link_to_function l(:label_completed_versions), + <%= link_to_function l(:label_completed_versions), '$("#toggle-completed-versions").toggleClass("collapsed"); $("#completed-versions").toggle()', :id => 'toggle-completed-versions', :class => 'collapsible collapsed' %>

<% end %> @@ -100,3 +105,10 @@ <% html_title(l(:label_roadmap)) %> <%= context_menu issues_context_menu_path %> + +<% if Setting.manual_issue_position_in_versions == '1' %> + <%= 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 f018c2d..6e43358 100644 --- a/app/views/versions/show.html.erb +++ b/app/views/versions/show.html.erb @@ -38,13 +38,18 @@ <% if @issues.present? %> <%= form_tag({}) do -%> - - <%- @issues.each do |issue| -%> - - - - - <% end %> + + + <%- @issues.each do |issue| -%> + + + + <% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? %> + + <% end%> + + <% end %> + <% end %> <%= context_menu issues_context_menu_path %> @@ -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? %> + <%= javascript_tag do %> + $(function() { $("table.related-issues tbody").positionedItems(); }); + <% end %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 56a06c7..67ac04f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -447,6 +447,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 diff --git a/config/settings.yml b/config/settings.yml index 807f9b7..6a77ffa 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -169,6 +169,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/public/stylesheets/application.css b/public/stylesheets/application.css index 1f47dd4..904a002 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -472,6 +472,7 @@ dt.time-entry { background-image: url(../images/time.png); } 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 index fb2216b..3f5e2a8 100644 --- a/test/fixtures/issues.yml +++ b/test/fixtures/issues.yml @@ -1,16 +1,16 @@ ---- -issues_001: +--- +issues_001: created_on: <%= 3.days.ago.to_s(:db) %> project_id: 1 updated_on: <%= 1.day.ago.to_s(:db) %> priority_id: 4 subject: Cannot print recipes id: 1 - fixed_version_id: + fixed_version_id: category_id: 1 description: Unable to print recipes tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 start_date: <%= 1.day.ago.to_date.to_s(:db) %> @@ -19,7 +19,7 @@ issues_001: lft: 1 rgt: 2 lock_version: 3 -issues_002: +issues_002: created_on: 2006-07-19 21:04:21 +02:00 project_id: 1 updated_on: 2006-07-19 21:09:50 +02:00 @@ -27,28 +27,29 @@ issues_002: subject: Add ingredients categories id: 2 fixed_version_id: 2 - category_id: + category_id: description: Ingredients of the recipe should be classified by categories tracker_id: 2 assigned_to_id: 3 author_id: 2 status_id: 2 start_date: <%= 2.day.ago.to_date.to_s(:db) %> - due_date: + due_date: root_id: 2 lft: 1 rgt: 2 lock_version: 3 done_ratio: 30 -issues_003: + position: 1 +issues_003: created_on: 2006-07-19 21:07:27 +02:00 project_id: 1 updated_on: 2006-07-19 21:07:27 +02:00 priority_id: 4 subject: Error 281 when updating a recipe id: 3 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: Error 281 is encountered when saving a recipe tracker_id: 1 assigned_to_id: 3 @@ -59,15 +60,15 @@ issues_003: root_id: 3 lft: 1 rgt: 2 -issues_004: +issues_004: created_on: <%= 5.days.ago.to_s(:db) %> project_id: 2 updated_on: <%= 2.days.ago.to_s(:db) %> priority_id: 4 subject: Issue on project 2 id: 4 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: Issue on project 2 tracker_id: 1 assigned_to_id: 2 @@ -76,35 +77,35 @@ issues_004: root_id: 4 lft: 1 rgt: 2 -issues_005: +issues_005: created_on: <%= 5.days.ago.to_s(:db) %> project_id: 3 updated_on: <%= 2.days.ago.to_s(:db) %> priority_id: 4 subject: Subproject issue id: 5 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is an issue on a cookbook subproject tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 root_id: 5 lft: 1 rgt: 2 -issues_006: +issues_006: created_on: <%= 1.minute.ago.to_s(:db) %> project_id: 5 updated_on: <%= 1.minute.ago.to_s(:db) %> priority_id: 4 subject: Issue of a private subproject id: 6 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is an issue of a private subproject of cookbook tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 start_date: <%= Date.today.to_s(:db) %> @@ -112,18 +113,18 @@ issues_006: root_id: 6 lft: 1 rgt: 2 -issues_007: +issues_007: created_on: <%= 10.days.ago.to_s(:db) %> project_id: 1 updated_on: <%= 10.days.ago.to_s(:db) %> priority_id: 5 subject: Issue due today id: 7 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is an issue that is due today tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 start_date: <%= 10.days.ago.to_s(:db) %> @@ -132,39 +133,39 @@ issues_007: root_id: 7 lft: 1 rgt: 2 -issues_008: +issues_008: created_on: <%= 10.days.ago.to_s(:db) %> project_id: 1 updated_on: <%= 10.days.ago.to_s(:db) %> priority_id: 5 subject: Closed issue id: 8 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is a closed issue. tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 5 - start_date: - due_date: + start_date: + due_date: lock_version: 0 root_id: 8 lft: 1 rgt: 2 closed_on: <%= 3.days.ago.to_s(:db) %> -issues_009: +issues_009: created_on: <%= 1.minute.ago.to_s(:db) %> project_id: 5 updated_on: <%= 1.minute.ago.to_s(:db) %> priority_id: 5 subject: Blocked Issue id: 9 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is an issue that is blocked by issue #10 tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 start_date: <%= Date.today.to_s(:db) %> @@ -172,18 +173,18 @@ issues_009: root_id: 9 lft: 1 rgt: 2 -issues_010: +issues_010: created_on: <%= 1.minute.ago.to_s(:db) %> project_id: 5 updated_on: <%= 1.minute.ago.to_s(:db) %> priority_id: 5 subject: Issue Doing the Blocking id: 10 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is an issue that blocks issue #9 tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 start_date: <%= Date.today.to_s(:db) %> @@ -191,18 +192,18 @@ issues_010: root_id: 10 lft: 1 rgt: 2 -issues_011: +issues_011: created_on: <%= 3.days.ago.to_s(:db) %> project_id: 1 updated_on: <%= 1.day.ago.to_s(:db) %> priority_id: 5 subject: Closed issue on a closed version id: 11 - fixed_version_id: 1 + fixed_version_id: 1 category_id: 1 description: tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 5 start_date: <%= 1.day.ago.to_date.to_s(:db) %> @@ -211,18 +212,18 @@ issues_011: lft: 1 rgt: 2 closed_on: <%= 1.day.ago.to_s(:db) %> -issues_012: +issues_012: created_on: <%= 3.days.ago.to_s(:db) %> project_id: 1 updated_on: <%= 1.day.ago.to_s(:db) %> priority_id: 5 subject: Closed issue on a locked version id: 12 - fixed_version_id: 2 + fixed_version_id: 2 category_id: 1 description: tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 3 status_id: 5 start_date: <%= 1.day.ago.to_date.to_s(:db) %> @@ -231,6 +232,7 @@ issues_012: lft: 1 rgt: 2 closed_on: <%= 1.day.ago.to_s(:db) %> + position: 2 issues_013: created_on: <%= 5.days.ago.to_s(:db) %> project_id: 3 @@ -238,11 +240,11 @@ issues_013: priority_id: 4 subject: Subproject issue two id: 13 - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is a second issue on a cookbook subproject tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 root_id: 13 @@ -255,11 +257,11 @@ issues_014: updated_on: <%= 15.days.ago.to_s(:db) %> priority_id: 5 subject: Private issue on public project - fixed_version_id: - category_id: + fixed_version_id: + category_id: description: This is a private issue tracker_id: 1 - assigned_to_id: + assigned_to_id: author_id: 2 status_id: 1 is_private: true diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index d794cf3..1efb1c7 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -77,6 +77,46 @@ 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_index_should_prepend_shared_versions get :index, :params => {:project_id => 1} assert_response :success