Project

General

Profile

Feature #22802 » manual_set_issue_position_in_versions.patch

Marius BĂLTEANU, 2016-09-06 01:55

View differences:

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
......
443 443
    'due_date',
444 444
    'done_ratio',
445 445
    'estimated_hours',
446
    'position',
446 447
    'custom_field_values',
447 448
    'custom_fields',
448 449
    'lock_version',
......
776 777

  
777 778
  # Returns the names of attributes that are journalized when updating the issue
778 779
  def journalized_attribute_names
779
    names = Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)
780
    names = Issue.column_names - %w(id root_id lft rgt lock_version position created_on updated_on closed_on)
780 781
    if tracker
781 782
      names -= tracker.disabled_core_fields
782 783
    end
......
1402 1403
    end
1403 1404
    Project.where(condition).having_trackers
1404 1405
  end
1405
 
1406

  
1406 1407
  # Returns a scope of trackers that user can assign the issue to
1407 1408
  def allowed_target_trackers(user=User.current)
1408 1409
    self.class.allowed_target_trackers(project, user, tracker_id_was)
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({}) 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' %>
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 %>
......
85 90
</ul>
86 91
<% if @completed_versions.present? %>
87 92
<p>
88
  <%= link_to_function l(:label_completed_versions), 
93
  <%= link_to_function l(:label_completed_versions),
89 94
                       '$("#toggle-completed-versions").toggleClass("collapsed"); $("#completed-versions").toggle()',
90 95
                       :id => 'toggle-completed-versions', :class => 'collapsible collapsed' %>
91 96
  <ul id = "completed-versions" style = "display:none;">
92 97
    <% @completed_versions.each do |version| %>
93 98
      <li><%= link_to_version version %></li>
94
    <% end %>  
99
    <% end %>
95 100
  </ul>
96 101
</p>
97 102
<% end %>
......
100 105
<% html_title(l(:label_roadmap)) %>
101 106

  
102 107
<%= context_menu issues_context_menu_path %>
108

  
109
<% if Setting.manual_issue_position_in_versions == '1' %>
110
  <%= javascript_tag do %>
111
    $(function() { $("table.related-issues tbody").positionedItems(); });
112
  <% end %>
113
<% end %>
114

  
app/views/versions/show.html.erb
38 38
<% if @issues.present? %>
39 39
<%= form_tag({}) 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? %>
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 issues_context_menu_path %>
......
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? %>
64
  <%= javascript_tag do %>
65
    $(function() { $("table.related-issues tbody").positionedItems(); });
66
  <% end %>
67
<% end %>
config/locales/en.yml
447 447
  setting_attachment_extensions_allowed: Allowed extensions
448 448
  setting_attachment_extensions_denied: Disallowed extensions
449 449
  setting_new_item_menu_tab: Project menu tab for creating new objects
450
  setting_manual_issue_position_in_versions: Enable manually set issue position in versions
450 451

  
451 452
  permission_add_project: Create project
452 453
  permission_add_subprojects: Create subprojects
config/settings.yml
169 169
  default: 'derived'
170 170
link_copied_issue:
171 171
  default: 'ask'
172
manual_issue_position_in_versions:
173
  default: 0
172 174
issue_group_assignment:
173 175
  default: 0
174 176
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
public/stylesheets/application.css
472 472

  
473 473
div#roadmap .related-issues { margin-bottom: 1em; }
474 474
div#roadmap .related-issues td.checkbox { display: none; }
475
div#roadmap .related-issues td.sortable { text-align: right; }
475 476
div#roadmap .wiki h1:first-child { display: none; }
476 477
div#roadmap .wiki h1 { font-size: 120%; }
477 478
div#roadmap .wiki h2 { font-size: 110%; }
test/fixtures/issues.yml
1
--- 
2
issues_001: 
1
---
2
issues_001:
3 3
  created_on: <%= 3.days.ago.to_s(:db) %>
4 4
  project_id: 1
5 5
  updated_on: <%= 1.day.ago.to_s(:db) %>
6 6
  priority_id: 4
7 7
  subject: Cannot print recipes
8 8
  id: 1
9
  fixed_version_id: 
9
  fixed_version_id:
10 10
  category_id: 1
11 11
  description: Unable to print recipes
12 12
  tracker_id: 1
13
  assigned_to_id: 
13
  assigned_to_id:
14 14
  author_id: 2
15 15
  status_id: 1
16 16
  start_date: <%= 1.day.ago.to_date.to_s(:db) %>
......
19 19
  lft: 1
20 20
  rgt: 2
21 21
  lock_version: 3
22
issues_002: 
22
issues_002:
23 23
  created_on: 2006-07-19 21:04:21 +02:00
24 24
  project_id: 1
25 25
  updated_on: 2006-07-19 21:09:50 +02:00
......
27 27
  subject: Add ingredients categories
28 28
  id: 2
29 29
  fixed_version_id: 2
30
  category_id: 
30
  category_id:
31 31
  description: Ingredients of the recipe should be classified by categories
32 32
  tracker_id: 2
33 33
  assigned_to_id: 3
34 34
  author_id: 2
35 35
  status_id: 2
36 36
  start_date: <%= 2.day.ago.to_date.to_s(:db) %>
37
  due_date: 
37
  due_date:
38 38
  root_id: 2
39 39
  lft: 1
40 40
  rgt: 2
41 41
  lock_version: 3
42 42
  done_ratio: 30
43
issues_003: 
43
  position: 1
44
issues_003:
44 45
  created_on: 2006-07-19 21:07:27 +02:00
45 46
  project_id: 1
46 47
  updated_on: 2006-07-19 21:07:27 +02:00
47 48
  priority_id: 4
48 49
  subject: Error 281 when updating a recipe
49 50
  id: 3
50
  fixed_version_id: 
51
  category_id: 
51
  fixed_version_id:
52
  category_id:
52 53
  description: Error 281 is encountered when saving a recipe
53 54
  tracker_id: 1
54 55
  assigned_to_id: 3
......
59 60
  root_id: 3
60 61
  lft: 1
61 62
  rgt: 2
62
issues_004: 
63
issues_004:
63 64
  created_on: <%= 5.days.ago.to_s(:db) %>
64 65
  project_id: 2
65 66
  updated_on: <%= 2.days.ago.to_s(:db) %>
66 67
  priority_id: 4
67 68
  subject: Issue on project 2
68 69
  id: 4
69
  fixed_version_id: 
70
  category_id: 
70
  fixed_version_id:
71
  category_id:
71 72
  description: Issue on project 2
72 73
  tracker_id: 1
73 74
  assigned_to_id: 2
......
76 77
  root_id: 4
77 78
  lft: 1
78 79
  rgt: 2
79
issues_005: 
80
issues_005:
80 81
  created_on: <%= 5.days.ago.to_s(:db) %>
81 82
  project_id: 3
82 83
  updated_on: <%= 2.days.ago.to_s(:db) %>
83 84
  priority_id: 4
84 85
  subject: Subproject issue
85 86
  id: 5
86
  fixed_version_id: 
87
  category_id: 
87
  fixed_version_id:
88
  category_id:
88 89
  description: This is an issue on a cookbook subproject
89 90
  tracker_id: 1
90
  assigned_to_id: 
91
  assigned_to_id:
91 92
  author_id: 2
92 93
  status_id: 1
93 94
  root_id: 5
94 95
  lft: 1
95 96
  rgt: 2
96
issues_006: 
97
issues_006:
97 98
  created_on: <%= 1.minute.ago.to_s(:db) %>
98 99
  project_id: 5
99 100
  updated_on: <%= 1.minute.ago.to_s(:db) %>
100 101
  priority_id: 4
101 102
  subject: Issue of a private subproject
102 103
  id: 6
103
  fixed_version_id: 
104
  category_id: 
104
  fixed_version_id:
105
  category_id:
105 106
  description: This is an issue of a private subproject of cookbook
106 107
  tracker_id: 1
107
  assigned_to_id: 
108
  assigned_to_id:
108 109
  author_id: 2
109 110
  status_id: 1
110 111
  start_date: <%= Date.today.to_s(:db) %>
......
112 113
  root_id: 6
113 114
  lft: 1
114 115
  rgt: 2
115
issues_007: 
116
issues_007:
116 117
  created_on: <%= 10.days.ago.to_s(:db) %>
117 118
  project_id: 1
118 119
  updated_on: <%= 10.days.ago.to_s(:db) %>
119 120
  priority_id: 5
120 121
  subject: Issue due today
121 122
  id: 7
122
  fixed_version_id: 
123
  category_id: 
123
  fixed_version_id:
124
  category_id:
124 125
  description: This is an issue that is due today
125 126
  tracker_id: 1
126
  assigned_to_id: 
127
  assigned_to_id:
127 128
  author_id: 2
128 129
  status_id: 1
129 130
  start_date: <%= 10.days.ago.to_s(:db) %>
......
132 133
  root_id: 7
133 134
  lft: 1
134 135
  rgt: 2
135
issues_008: 
136
issues_008:
136 137
  created_on: <%= 10.days.ago.to_s(:db) %>
137 138
  project_id: 1
138 139
  updated_on: <%= 10.days.ago.to_s(:db) %>
139 140
  priority_id: 5
140 141
  subject: Closed issue
141 142
  id: 8
142
  fixed_version_id: 
143
  category_id: 
143
  fixed_version_id:
144
  category_id:
144 145
  description: This is a closed issue.
145 146
  tracker_id: 1
146
  assigned_to_id: 
147
  assigned_to_id:
147 148
  author_id: 2
148 149
  status_id: 5
149
  start_date: 
150
  due_date: 
150
  start_date:
151
  due_date:
151 152
  lock_version: 0
152 153
  root_id: 8
153 154
  lft: 1
154 155
  rgt: 2
155 156
  closed_on: <%= 3.days.ago.to_s(:db) %>
156
issues_009: 
157
issues_009:
157 158
  created_on: <%= 1.minute.ago.to_s(:db) %>
158 159
  project_id: 5
159 160
  updated_on: <%= 1.minute.ago.to_s(:db) %>
160 161
  priority_id: 5
161 162
  subject: Blocked Issue
162 163
  id: 9
163
  fixed_version_id: 
164
  category_id: 
164
  fixed_version_id:
165
  category_id:
165 166
  description: This is an issue that is blocked by issue #10
166 167
  tracker_id: 1
167
  assigned_to_id: 
168
  assigned_to_id:
168 169
  author_id: 2
169 170
  status_id: 1
170 171
  start_date: <%= Date.today.to_s(:db) %>
......
172 173
  root_id: 9
173 174
  lft: 1
174 175
  rgt: 2
175
issues_010: 
176
issues_010:
176 177
  created_on: <%= 1.minute.ago.to_s(:db) %>
177 178
  project_id: 5
178 179
  updated_on: <%= 1.minute.ago.to_s(:db) %>
179 180
  priority_id: 5
180 181
  subject: Issue Doing the Blocking
181 182
  id: 10
182
  fixed_version_id: 
183
  category_id: 
183
  fixed_version_id:
184
  category_id:
184 185
  description: This is an issue that blocks issue #9
185 186
  tracker_id: 1
186
  assigned_to_id: 
187
  assigned_to_id:
187 188
  author_id: 2
188 189
  status_id: 1
189 190
  start_date: <%= Date.today.to_s(:db) %>
......
191 192
  root_id: 10
192 193
  lft: 1
193 194
  rgt: 2
194
issues_011: 
195
issues_011:
195 196
  created_on: <%= 3.days.ago.to_s(:db) %>
196 197
  project_id: 1
197 198
  updated_on: <%= 1.day.ago.to_s(:db) %>
198 199
  priority_id: 5
199 200
  subject: Closed issue on a closed version
200 201
  id: 11
201
  fixed_version_id: 1 
202
  fixed_version_id: 1
202 203
  category_id: 1
203 204
  description:
204 205
  tracker_id: 1
205
  assigned_to_id: 
206
  assigned_to_id:
206 207
  author_id: 2
207 208
  status_id: 5
208 209
  start_date: <%= 1.day.ago.to_date.to_s(:db) %>
......
211 212
  lft: 1
212 213
  rgt: 2
213 214
  closed_on: <%= 1.day.ago.to_s(:db) %>
214
issues_012: 
215
issues_012:
215 216
  created_on: <%= 3.days.ago.to_s(:db) %>
216 217
  project_id: 1
217 218
  updated_on: <%= 1.day.ago.to_s(:db) %>
218 219
  priority_id: 5
219 220
  subject: Closed issue on a locked version
220 221
  id: 12
221
  fixed_version_id: 2 
222
  fixed_version_id: 2
222 223
  category_id: 1
223 224
  description:
224 225
  tracker_id: 1
225
  assigned_to_id: 
226
  assigned_to_id:
226 227
  author_id: 3
227 228
  status_id: 5
228 229
  start_date: <%= 1.day.ago.to_date.to_s(:db) %>
......
231 232
  lft: 1
232 233
  rgt: 2
233 234
  closed_on: <%= 1.day.ago.to_s(:db) %>
235
  position: 2
234 236
issues_013:
235 237
  created_on: <%= 5.days.ago.to_s(:db) %>
236 238
  project_id: 3
......
238 240
  priority_id: 4
239 241
  subject: Subproject issue two
240 242
  id: 13
241
  fixed_version_id: 
242
  category_id: 
243
  fixed_version_id:
244
  category_id:
243 245
  description: This is a second issue on a cookbook subproject
244 246
  tracker_id: 1
245
  assigned_to_id: 
247
  assigned_to_id:
246 248
  author_id: 2
247 249
  status_id: 1
248 250
  root_id: 13
......
255 257
  updated_on: <%= 15.days.ago.to_s(:db) %>
256 258
  priority_id: 5
257 259
  subject: Private issue on public project
258
  fixed_version_id: 
259
  category_id: 
260
  fixed_version_id:
261
  category_id:
260 262
  description: This is a private issue
261 263
  tracker_id: 1
262
  assigned_to_id: 
264
  assigned_to_id:
263 265
  author_id: 2
264 266
  status_id: 1
265 267
  is_private: true
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

  
80 120
  def test_index_should_prepend_shared_versions
81 121
    get :index, :params => {:project_id => 1}
82 122
    assert_response :success
(2-2/9)