Project

General

Profile

Patch #19622 » journal_filters.diff

Jacek Grzybowski, 2015-04-14 14:30

View differences:

app/helpers/queries_helper.rb
26 26
    query.available_filters.map do |field, field_options|
27 27
      if field_options[:type] == :relation
28 28
        group = :label_related_issues
29
      elsif field =~ /_id_was\z/
30
        group = :label_history
29 31
      elsif field =~ /^(.+)\./
30 32
        # association filters
31 33
        group = "field_#{$1}"
app/models/issue_query.rb
168 168

  
169 169
    add_available_filter "tracker_id",
170 170
      :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
171

  
171 172
    add_available_filter "priority_id",
172 173
      :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
173 174

  
......
208 209
        :values => categories.collect{|s| [s.name, s.id.to_s] }
209 210
    end
210 211

  
212
    add_available_filter "status_id_was",
213
      :type => :list, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
214
    add_available_filter "priority_id_was",
215
      :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
216
    add_available_filter "tracker_id_was",
217
      :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
218
    add_available_filter "assigned_to_id_was",
219
      :type => :list, :values => assigned_to_values unless assigned_to_values.empty?
220

  
211 221
    add_available_filter "subject", :type => :text
212 222
    add_available_filter "created_on", :type => :date_past
213 223
    add_available_filter "updated_on", :type => :date_past
......
289 299
    end
290 300
  end
291 301

  
302
  def history_filters
303
    filters.select { |k,_| k =~ /_was\z/ }
304
  end
305

  
306
  def history_join_clause
307
    return nil if history_filters.blank?
308
    history_filters.keys.map do |f|
309
      "INNER JOIN journals AS j_#{f} ON j_#{f}.journalized_id = issues.id AND j_#{f}.journalized_type = 'Issue'" +
310
      "INNER JOIN journal_details AS jd_#{f} ON jd_#{f}.journal_id = j_#{f}.id"
311
    end.join(' ')
312
  end
313

  
292 314
  # Returns the issue count
293 315
  def issue_count
294
    Issue.visible.joins(:status, :project).where(statement).count
316
    Issue.visible.
317
      joins(:status, :project).
318
      joins(history_join_clause).
319
      where(statement).
320
      count
295 321
  rescue ::ActiveRecord::StatementInvalid => e
296 322
    raise StatementInvalid.new(e.message)
297 323
  end
......
304 330
        # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
305 331
        r = Issue.visible.
306 332
          joins(:status, :project).
333
          joins(history_join_clause).
307 334
          where(statement).
308 335
          joins(joins_for_order_statement(group_by_statement)).
309 336
          group(group_by_statement).
......
328 355

  
329 356
    scope = Issue.visible.
330 357
      joins(:status, :project).
358
      joins(history_join_clause).
331 359
      where(statement).
332 360
      includes(([:status, :project] + (options[:include] || [])).uniq).
333 361
      where(options[:conditions]).
......
360 388

  
361 389
    Issue.visible.
362 390
      joins(:status, :project).
391
      joins(history_join_clause).
363 392
      where(statement).
364 393
      includes(([:status, :project] + (options[:include] || [])).uniq).
365 394
      references(([:status, :project] + (options[:include] || [])).uniq).
......
401 430
    raise StatementInvalid.new(e.message)
402 431
  end
403 432

  
433
  # works for status_id_was & priority_id_was
434
  def sql_for_history_field(field, operator, values)
435
    field_name = field.sub('_was', '')
436
    select_values = values[0..-3]
437
    select_values = substitute_me_in_history_values(select_values) if field_name == 'assigned_to_id'
438
    date_from, date_to = values[-2..-1]
439
    journal_queries = []
440
    journal_queries << "jd_#{field}.prop_key = '#{field_name}'"
441
    journal_queries << sql_for_field(field_name, operator, select_values, "jd_#{field}", "old_value")
442
    date_range_query = sql_for_history_date_range(field, date_from, date_to)
443
    journal_queries << date_range_query if date_range_query
444
    journal_queries.join(" AND ")
445
  end
446

  
447
  def substitute_me_in_history_values(values)
448
    idx = values.index {|v| v == 'me'}
449
    return values unless idx
450
    values[idx] = User.current.logged? ? User.current.id.to_s : '0'
451
    values
452
  end
453

  
454
  def sql_for_history_date_range(field, from, to)
455
    from, to = parse_date(from), parse_date(to)
456
    from || to ? date_clause("j_#{field}", 'created_on', from, to, false) : nil
457
  end
458

  
404 459
  def sql_for_watcher_id_field(field, operator, value)
405 460
    db_table = Watcher.table_name
406 461
    "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
app/models/query.rb
584 584
      if field =~ /cf_(\d+)$/
585 585
        # custom field
586 586
        filters_clauses << sql_for_custom_field(field, operator, v, $1)
587
      elsif field =~ /_was\z/ && respond_to?("sql_for_history_field")
588
        filters_clauses << sql_for_history_field(field, operator, v)
587 589
      elsif respond_to?("sql_for_#{field}_field")
588 590
        # specific statement
589 591
        filters_clauses << send("sql_for_#{field}_field", field, operator, v)
......
712 714
        end
713 715
      end
714 716
    when "o"
715
      sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false})" if field == "status_id"
717
      sql = "#{db_table}.#{db_field} IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false})" if field == "status_id"
716 718
    when "c"
717
      sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_true})" if field == "status_id"
719
      sql = "#{db_table}.#{db_field} IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_true})" if field == "status_id"
718 720
    when "><t-"
719 721
      # between today - n days and today
720 722
      sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0, is_custom_filter)
config/locales/en.yml
247 247
  field_project: Project
248 248
  field_issue: Issue
249 249
  field_status: Status
250
  field_status_id_was: Previous status
250 251
  field_notes: Notes
251 252
  field_is_closed: Issue closed
252 253
  field_is_default: Default value
253 254
  field_tracker: Tracker
255
  field_tracker_id_was: Previous tracker
254 256
  field_subject: Subject
255 257
  field_due_date: Due date
256 258
  field_assigned_to: Assignee
259
  field_assigned_to_id_was: Previous assignee
257 260
  field_priority: Priority
261
  field_priority_id_was: Previous priority
258 262
  field_fixed_version: Target version
259 263
  field_user: User
260 264
  field_principal: Principal
config/locales/pl.yml
224 224
  field_admin: Administrator
225 225
  field_assignable: Zagadnienia mogą być przypisane do tej roli
226 226
  field_assigned_to: Przypisany do
227
  field_assigned_to_id_was: Poprzednio przypisany do
227 228
  field_attr_firstname: Imię atrybut
228 229
  field_attr_lastname: Nazwisko atrybut
229 230
  field_attr_login: Login atrybut
......
282 283
  field_port: Port
283 284
  field_possible_values: Możliwe wartości
284 285
  field_priority: Priorytet
286
  field_priority_id_was: Poprzedni priorytet
285 287
  field_project: Projekt
286 288
  field_redirect_existing_links: Przekierowanie istniejących odnośników
287 289
  field_regexp: Wyrażenie regularne
......
291 293
  field_start_date: Data rozpoczęcia
292 294
  field_start_page: Strona startowa
293 295
  field_status: Status
296
  field_status_id_was: Poprzedni status
294 297
  field_subject: Temat
295 298
  field_subproject: Podprojekt
296 299
  field_summary: Podsumowanie
297 300
  field_time_zone: Strefa czasowa
298 301
  field_title: Tytuł
299 302
  field_tracker: Typ zagadnienia
303
  field_tracker_id_was: Poprzedni typ zagadnienia
300 304
  field_type: Typ
301 305
  field_updated_on: Data modyfikacji
302 306
  field_url: URL
public/javascripts/application.js
168 168
      ' <span class="toggle-multiselect">&nbsp;</span></span>'
169 169
    );
170 170
    select = tr.find('td.values select');
171
    if (values.length > 1) { select.attr('multiple', true); }
171
    if ( isHistoryFilter(field) ) {
172
      // history filters have 2 additional values in array
173
      // corresponding to `from` and `to` dates
174
      if (values.length > 3) { select.attr('multiple', true); }
175
    } else {
176
      if (values.length > 1) { select.attr('multiple', true); }
177
    }
178

  
172 179
    for (i = 0; i < filterValues.length; i++) {
173 180
      var filterValue = filterValues[i];
174 181
      var option = $('<option>');
......
225 232
    $('#values_'+fieldId+'_2').val(values[1]);
226 233
    break;
227 234
  }
235

  
236
  if ( isHistoryFilter(fieldId) ) {
237
    tr.find('td.values').append(
238
      '<a href="#" id="enable_daterange_'+field+'" class="enable_daterange"><img src="/images/calendar.png" alt="select daterange" title="select daterange"></a>' +
239
      '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_from" size="10" class="value date_value previous_date_range" /></span>' +
240
      '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_to" size="10" class="value date_value previous_date_range" /></span>' +
241
      '<a style="display:none;" class="disable_daterange icon icon-cancel" title="disable daterange" href="#" />'
242
    );
243
    date_values = values.slice(Math.max(values.length - 2, 1));
244
    $('#values_'+fieldId+'_from').val(date_values[0]).datepicker(datepickerOptions);
245
    $('#values_'+fieldId+'_to').val(date_values[1]).datepicker(datepickerOptions);
246
  }
228 247
}
229 248

  
230 249
function toggleFilter(field) {
......
234 253
    toggleOperator(field);
235 254
  } else {
236 255
    $("#operators_" + fieldId).hide().attr('disabled', true);
256
    if ( isHistoryFilter(field) ) {
257
      $('#values_'+fieldId+'_from').parent().hide();
258
      $('#values_'+fieldId+'_to').parent().hide();
259
      $('#values_'+fieldId+'_to').parent().siblings('a').hide();
260
    }
237 261
    enableValues(field, []);
238 262
  }
239 263
}
......
241 265
function enableValues(field, indexes) {
242 266
  var fieldId = field.replace('.', '_');
243 267
  $('#tr_'+fieldId+' td.values .value').each(function(index) {
268
    if ( isHistoryFilter(this.id) ) return;
244 269
    if ($.inArray(index, indexes) >= 0) {
245 270
      $(this).removeAttr('disabled');
246 271
      $(this).parents('span').first().show();
......
298 323
      enableValues(field, [0]);
299 324
      break;
300 325
  }
326
  showHistoryFiltersDateRange();
301 327
}
302 328

  
303 329
function toggleMultiSelect(el) {
......
313 339
  }
314 340
}
315 341

  
342
function isHistoryFilter(filter) {
343
  return filter.match(/_was$|_was_from$|_was_to$/);
344
}
345

  
346
function showHistoryFiltersDateRange() {
347
  $('a.enable_daterange').each(function(){
348
    var date_inputs = $(this).siblings().find('.previous_date_range');
349
    var date_values = date_inputs.map(function(){
350
                        return $(this).val();
351
                      }).get();
352
    var matches = $.grep(date_values, function(val){
353
      return val.match(/^\d{4}-\d{2}-\d{2}$/);
354
    });
355
    if ( matches.length > 0 ) {
356
      $(this).hide();
357
      date_inputs.closest('span').show();
358
      $(this).siblings('a.disable_daterange').show();
359
    } else {
360
      $(this).show();
361
      $(this).siblings('a.disable_daterange').hide();
362
    }
363
  });
364
}
365

  
316 366
function showTab(name, url) {
317 367
  $('div#content .tab-content').hide();
318 368
  $('div.tabs a').removeClass('selected');
......
651 701
$(document).ready(defaultFocus);
652 702

  
653 703
$(function(){
704
  $(document).on('click', 'a.enable_daterange', function(){
705
    $(this).siblings().find('.previous_date_range').closest('span').show();
706
    $(this).siblings('a.disable_daterange').show();
707
    $(this).hide();
708
  });
709

  
710
  $(document).on('click', 'a.disable_daterange', function(){
711
    $(this).siblings().find('.previous_date_range').val('').closest('span').hide();
712
    $(this).siblings('a.enable_daterange').show();
713
    $(this).hide();
714
  });
715
});
716

  
717
$(function(){
654 718
  if ($('#issue_tree .issues').length){
655 719
    var fuzzyOptions = {
656 720
      searchClass: "fuzzy-search",
public/stylesheets/application.css
1237 1237
  z-index: 999  !important;
1238 1238
}
1239 1239

  
1240
.previous_date_range {
1241
  margin-left:15px;
1242
}
1243

  
1244
a.enable_daterange {
1245
  margin-left:10px;
1246
}
1247

  
1248
a.enable_daterange img {
1249
  cursor: pointer;
1250
  vertical-align: middle;
1251
  margin-left: 4px;
1252
}
1253

  
1254
a.disable_daterange {
1255
  margin-left:5px;
1256
}
1257

  
1258

  
1240 1259
/* js fuzzy search */
1241 1260

  
1242 1261
input.fuzzy-search{
......
1279 1298
   color: #ccc;
1280 1299
}
1281 1300

  
1282

  
1283 1301
table.team_time_sheet tr td{
1284 1302
  max-width: 200px;
1285 1303
  overflow: hidden;
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
issues_003:
44 44
  created_on: 2006-07-19 21:07:27 +02:00
45 45
  project_id: 1
46 46
  updated_on: 2006-07-19 21:07:27 +02:00
47 47
  priority_id: 4
48 48
  subject: Error 281 when updating a recipe
49 49
  id: 3
50
  fixed_version_id: 
51
  category_id: 
50
  fixed_version_id:
51
  category_id:
52 52
  description: Error 281 is encountered when saving a recipe
53 53
  tracker_id: 1
54 54
  assigned_to_id: 3
......
59 59
  root_id: 3
60 60
  lft: 1
61 61
  rgt: 2
62
issues_004: 
62
issues_004:
63 63
  created_on: <%= 5.days.ago.to_s(:db) %>
64 64
  project_id: 2
65 65
  updated_on: <%= 2.days.ago.to_s(:db) %>
66 66
  priority_id: 4
67 67
  subject: Issue on project 2
68 68
  id: 4
69
  fixed_version_id: 
70
  category_id: 
69
  fixed_version_id:
70
  category_id:
71 71
  description: Issue on project 2
72 72
  tracker_id: 1
73 73
  assigned_to_id: 2
......
76 76
  root_id: 4
77 77
  lft: 1
78 78
  rgt: 2
79
issues_005: 
79
issues_005:
80 80
  created_on: <%= 5.days.ago.to_s(:db) %>
81 81
  project_id: 3
82 82
  updated_on: <%= 2.days.ago.to_s(:db) %>
83 83
  priority_id: 4
84 84
  subject: Subproject issue
85 85
  id: 5
86
  fixed_version_id: 
87
  category_id: 
86
  fixed_version_id:
87
  category_id:
88 88
  description: This is an issue on a cookbook subproject
89 89
  tracker_id: 1
90
  assigned_to_id: 
90
  assigned_to_id:
91 91
  author_id: 2
92 92
  status_id: 1
93 93
  root_id: 5
94 94
  lft: 1
95 95
  rgt: 2
96
issues_006: 
96
issues_006:
97 97
  created_on: <%= 1.minute.ago.to_s(:db) %>
98 98
  project_id: 5
99 99
  updated_on: <%= 1.minute.ago.to_s(:db) %>
100 100
  priority_id: 4
101 101
  subject: Issue of a private subproject
102 102
  id: 6
103
  fixed_version_id: 
104
  category_id: 
103
  fixed_version_id:
104
  category_id:
105 105
  description: This is an issue of a private subproject of cookbook
106 106
  tracker_id: 1
107
  assigned_to_id: 
107
  assigned_to_id:
108 108
  author_id: 2
109 109
  status_id: 1
110 110
  start_date: <%= Date.today.to_s(:db) %>
......
112 112
  root_id: 6
113 113
  lft: 1
114 114
  rgt: 2
115
issues_007: 
115
issues_007:
116 116
  created_on: <%= 10.days.ago.to_s(:db) %>
117 117
  project_id: 1
118 118
  updated_on: <%= 10.days.ago.to_s(:db) %>
119 119
  priority_id: 5
120 120
  subject: Issue due today
121 121
  id: 7
122
  fixed_version_id: 
123
  category_id: 
122
  fixed_version_id:
123
  category_id:
124 124
  description: This is an issue that is due today
125 125
  tracker_id: 1
126
  assigned_to_id: 
126
  assigned_to_id:
127 127
  author_id: 2
128 128
  status_id: 1
129 129
  start_date: <%= 10.days.ago.to_s(:db) %>
......
132 132
  root_id: 7
133 133
  lft: 1
134 134
  rgt: 2
135
issues_008: 
135
issues_008:
136 136
  created_on: <%= 10.days.ago.to_s(:db) %>
137 137
  project_id: 1
138 138
  updated_on: <%= 10.days.ago.to_s(:db) %>
139 139
  priority_id: 5
140 140
  subject: Closed issue
141 141
  id: 8
142
  fixed_version_id: 
143
  category_id: 
142
  fixed_version_id:
143
  category_id:
144 144
  description: This is a closed issue.
145 145
  tracker_id: 1
146
  assigned_to_id: 
146
  assigned_to_id:
147 147
  author_id: 2
148 148
  status_id: 5
149
  start_date: 
150
  due_date: 
149
  start_date:
150
  due_date:
151 151
  lock_version: 0
152 152
  root_id: 8
153 153
  lft: 1
154 154
  rgt: 2
155 155
  closed_on: <%= 3.days.ago.to_s(:db) %>
156
issues_009: 
156
issues_009:
157 157
  created_on: <%= 1.minute.ago.to_s(:db) %>
158 158
  project_id: 5
159 159
  updated_on: <%= 1.minute.ago.to_s(:db) %>
160 160
  priority_id: 5
161 161
  subject: Blocked Issue
162 162
  id: 9
163
  fixed_version_id: 
164
  category_id: 
163
  fixed_version_id:
164
  category_id:
165 165
  description: This is an issue that is blocked by issue #10
166 166
  tracker_id: 1
167
  assigned_to_id: 
167
  assigned_to_id:
168 168
  author_id: 2
169 169
  status_id: 1
170 170
  start_date: <%= Date.today.to_s(:db) %>
......
172 172
  root_id: 9
173 173
  lft: 1
174 174
  rgt: 2
175
issues_010: 
175
issues_010:
176 176
  created_on: <%= 1.minute.ago.to_s(:db) %>
177 177
  project_id: 5
178 178
  updated_on: <%= 1.minute.ago.to_s(:db) %>
179 179
  priority_id: 5
180 180
  subject: Issue Doing the Blocking
181 181
  id: 10
182
  fixed_version_id: 
183
  category_id: 
182
  fixed_version_id:
183
  category_id:
184 184
  description: This is an issue that blocks issue #9
185 185
  tracker_id: 1
186
  assigned_to_id: 
186
  assigned_to_id:
187 187
  author_id: 2
188 188
  status_id: 1
189 189
  start_date: <%= Date.today.to_s(:db) %>
......
191 191
  root_id: 10
192 192
  lft: 1
193 193
  rgt: 2
194
issues_011: 
194
issues_011:
195 195
  created_on: <%= 3.days.ago.to_s(:db) %>
196 196
  project_id: 1
197 197
  updated_on: <%= 1.day.ago.to_s(:db) %>
198 198
  priority_id: 5
199 199
  subject: Closed issue on a closed version
200 200
  id: 11
201
  fixed_version_id: 1 
201
  fixed_version_id: 1
202 202
  category_id: 1
203 203
  description:
204 204
  tracker_id: 1
205
  assigned_to_id: 
205
  assigned_to_id:
206 206
  author_id: 2
207 207
  status_id: 5
208 208
  start_date: <%= 1.day.ago.to_date.to_s(:db) %>
......
211 211
  lft: 1
212 212
  rgt: 2
213 213
  closed_on: <%= 1.day.ago.to_s(:db) %>
214
issues_012: 
214
issues_012:
215 215
  created_on: <%= 3.days.ago.to_s(:db) %>
216 216
  project_id: 1
217 217
  updated_on: <%= 1.day.ago.to_s(:db) %>
218 218
  priority_id: 5
219 219
  subject: Closed issue on a locked version
220 220
  id: 12
221
  fixed_version_id: 2 
221
  fixed_version_id: 2
222 222
  category_id: 1
223 223
  description:
224 224
  tracker_id: 1
225
  assigned_to_id: 
225
  assigned_to_id:
226 226
  author_id: 3
227 227
  status_id: 5
228 228
  start_date: <%= 1.day.ago.to_date.to_s(:db) %>
......
238 238
  priority_id: 4
239 239
  subject: Subproject issue two
240 240
  id: 13
241
  fixed_version_id: 
242
  category_id: 
241
  fixed_version_id:
242
  category_id:
243 243
  description: This is a second issue on a cookbook subproject
244 244
  tracker_id: 1
245
  assigned_to_id: 
245
  assigned_to_id:
246 246
  author_id: 2
247 247
  status_id: 1
248 248
  root_id: 13
......
255 255
  updated_on: <%= 15.days.ago.to_s(:db) %>
256 256
  priority_id: 5
257 257
  subject: Private issue on public project
258
  fixed_version_id: 
259
  category_id: 
258
  fixed_version_id:
259
  category_id:
260
  description: This is a private issue
261
  tracker_id: 1
262
  assigned_to_id:
263
  author_id: 2
264
  status_id: 1
265
  is_private: true
266
  root_id: 14
267
  lft: 1
268
  rgt: 2
269
issues_015:
270
  id: 15
271
  created_on: <%= 15.days.ago.to_s(:db) %>
272
  project_id: 1
273
  updated_on: <%= 15.days.ago.to_s(:db) %>
274
  priority_id: 5
275
  subject: Private issue on public project
276
  fixed_version_id:
277
  category_id:
278
  description: This is a private issue
279
  tracker_id: 1
280
  assigned_to_id:
281
  author_id: 2
282
  status_id: 1
283
  is_private: true
284
  root_id: 14
285
  lft: 1
286
  rgt: 2
287
issues_016:
288
  id: 16
289
  created_on: <%= 15.days.ago.to_s(:db) %>
290
  project_id: 1
291
  updated_on: <%= 15.days.ago.to_s(:db) %>
292
  priority_id: 5
293
  subject: Private issue on public project
294
  fixed_version_id:
295
  category_id:
296
  description: This is a private issue
297
  tracker_id: 1
298
  assigned_to_id:
299
  author_id: 2
300
  status_id: 1
301
  is_private: true
302
  root_id: 14
303
  lft: 1
304
  rgt: 2
305
issues_015:
306
  id: 15
307
  created_on: <%= 15.days.ago.to_s(:db) %>
308
  project_id: 1
309
  updated_on: <%= 15.days.ago.to_s(:db) %>
310
  priority_id: 5
311
  subject: Private issue on public project
312
  fixed_version_id:
313
  category_id:
314
  description: This is a private issue
315
  tracker_id: 1
316
  assigned_to_id:
317
  author_id: 2
318
  status_id: 1
319
  is_private: true
320
  root_id: 14
321
  lft: 1
322
  rgt: 2
323
issues_016:
324
  id: 16
325
  created_on: <%= 15.days.ago.to_s(:db) %>
326
  project_id: 1
327
  updated_on: <%= 15.days.ago.to_s(:db) %>
328
  priority_id: 5
329
  subject: Private issue on public project
330
  fixed_version_id:
331
  category_id:
260 332
  description: This is a private issue
261 333
  tracker_id: 1
262
  assigned_to_id: 
334
  assigned_to_id:
263 335
  author_id: 2
264 336
  status_id: 1
265 337
  is_private: true
test/fixtures/journal_details.yml
1
--- 
2
journal_details_001: 
1
---
2
journal_details_001:
3 3
  old_value: "1"
4 4
  property: attr
5 5
  id: 1
6 6
  value: "2"
7 7
  prop_key: status_id
8 8
  journal_id: 1
9
journal_details_002: 
9
journal_details_002:
10 10
  old_value: "40"
11 11
  property: attr
12 12
  id: 2
......
41 41
  value: 060719210727_picture.jpg
42 42
  prop_key: 4
43 43
  journal_id: 3
44
journal_details_007:
45
  old_value: '1'
46
  value: '4'
47
  property: attr
48
  id: 7
49
  prop_key: status_id
50
  journal_id: 6
51
journal_details_008:
52
  old_value: '4'
53
  value: '3'
54
  property: attr
55
  id: 8
56
  prop_key: status_id
57
  journal_id: 7
58
journal_details_009:
59
  old_value: '4'
60
  value: '1'
61
  property: attr
62
  id: 9
63
  prop_key: priority_id
64
  journal_id: 8
65
journal_details_010:
66
  old_value: '4'
67
  value: '1'
68
  property: attr
69
  id: 10
70
  prop_key: priority_id
71
  journal_id: 9
72
journal_details_011:
73
  old_value: ''
74
  value: '2'
75
  property: attr
76
  id: 11
77
  prop_key: assigned_to_id
78
  journal_id: 10
79
journal_details_012:
80
  old_value: '2'
81
  value: '3'
82
  property: attr
83
  id: 12
84
  prop_key: assigned_to_id
85
  journal_id: 11
86
journal_details_013:
87
  old_value: '1'
88
  value: '2'
89
  property: attr
90
  id: 13
91
  prop_key: tracker_id
92
  journal_id: 12
test/fixtures/journals.yml
1
--- 
1
---
2 2
journals_001:
3 3
  created_on: <%= 2.days.ago.to_date.to_s(:db) %>
4 4
  notes: "Journal notes"
......
6 6
  journalized_type: Issue
7 7
  user_id: 1
8 8
  journalized_id: 1
9
journals_002: 
9
journals_002:
10 10
  created_on: <%= 1.days.ago.to_date.to_s(:db) %>
11 11
  notes: "Some notes with Redmine links: #2, r2."
12 12
  id: 2
......
34 34
  user_id: 2
35 35
  journalized_type: Issue
36 36
  journalized_id: 14
37
journals_006:
38
  id: 6
39
  created_on: <%= 14.days.ago.to_date.to_s(:db) %>
40
  notes: "Change status"
41
  user_id: 2
42
  journalized_type: Issue
43
  journalized_id: 15
44
journals_007:
45
  id: 7
46
  created_on: <%= 13.days.ago.to_date.to_s(:db) %>
47
  notes: "Change status"
48
  user_id: 2
49
  journalized_type: Issue
50
  journalized_id: 15
51
journals_008:
52
  id: 8
53
  created_on: <%= 12.days.ago.to_date.to_s(:db) %>
54
  notes: "Change priority"
55
  user_id: 2
56
  journalized_type: Issue
57
  journalized_id: 15
58
journals_009:
59
  id: 9
60
  created_on: <%= 5.days.ago.to_date.to_s(:db) %>
61
  notes: "Change priority"
62
  user_id: 2
63
  journalized_type: Issue
64
  journalized_id: 16
65
journals_010:
66
  id: 10
67
  created_on: <%= 8.days.ago.to_date.to_s(:db) %>
68
  notes: "Change asignee"
69
  user_id: 2
70
  journalized_type: Issue
71
  journalized_id: 16
72
journals_011:
73
  id: 11
74
  created_on: <%= 6.days.ago.to_date.to_s(:db) %>
75
  notes: "Change asignee"
76
  user_id: 2
77
  journalized_type: Issue
78
  journalized_id: 16
79
journals_012:
80
  id: 12
81
  created_on: <%= 4.days.ago.to_date.to_s(:db) %>
82
  notes: "Change tracker"
83
  user_id: 2
84
  journalized_type: Issue
85
  journalized_id: 16
test/unit/query_issue_history_test.rb
1
# Redmine - project management software
2
# Copyright (C) 2006-2015  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.expand_path('../../test_helper', __FILE__)
19

  
20
class QueryTest < ActiveSupport::TestCase
21
  include Redmine::I18n
22

  
23
  fixtures :projects, :enabled_modules, :users, :members,
24
           :member_roles, :roles, :trackers, :issue_statuses,
25
           :issue_categories, :enumerations, :issues,
26
           :watchers, :custom_fields, :custom_values, :versions,
27
           :queries,
28
           :projects_trackers,
29
           :custom_fields_trackers,
30
           :workflows,
31
           :journals,
32
           :journal_details
33

  
34
  def setup
35
    User.current = nil
36
  end
37

  
38
  def test_available_filters_includes_status_id_was
39
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
40
    assert query.available_filters.has_key? 'status_id_was'
41
    assert_equal :list, query.available_filters['status_id_was'][:type]
42
    assert_equal query.available_filters['status_id'][:values], query.available_filters['status_id_was'][:values]
43
  end
44

  
45
  def test_available_filters_includes_priority_id_was
46
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
47
    assert query.available_filters.has_key? 'priority_id_was'
48
    assert_equal :list, query.available_filters['priority_id_was'][:type]
49
    assert_equal query.available_filters['priority_id'][:values], query.available_filters['priority_id_was'][:values]
50
  end
51

  
52
  def test_status_id_was_is_added_as_filter
53
    add_filter_to_query('status_id_was', '=', ['2', '', ''])
54
    assert @query.available_filters.has_key? @field
55
    assert @query.filters['status_id_was'] == { operator: @operator, values: @values }
56
  end
57

  
58
  def test_priority_id_was_is_added_as_filter
59
    add_filter_to_query('priority_id_was', '=', ['1', '', ''])
60
    assert @query.available_filters.has_key? @field
61
    assert @query.filters['priority_id_was'] == { operator: @operator, values: @values }
62
  end
63

  
64
  def test_status_id_was_with_dates_is_added_as_filter
65
    add_filter_to_query('status_id_was', '=', ['2', '2012-02-02', '2015-03-03'])
66
    assert @query.available_filters.has_key? @field
67
    assert @query.filters['status_id_was'] == { operator: @operator, values: @values }
68
  end
69

  
70
  def test_priority_id_was_with_dates_is_added_as_filter
71
    add_filter_to_query('priority_id_was', '=', ['2', '2012-02-02', '2015-03-03'])
72
    assert @query.available_filters.has_key? @field
73
    assert @query.filters['priority_id_was'] == { operator: @operator, values: @values }
74
  end
75

  
76
  def test_status_id_was_gets_proper_sql_query_with_no_dates
77
    add_filter_to_query('status_id_was', '=', ['2', '', ''])
78

  
79
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
80
    expected_query = "jd_status_id_was.prop_key = 'status_id' AND jd_status_id_was.old_value IN ('2')"
81
    assert_equal expected_query, generated_query
82
    assert_match expected_query, @query.statement, 'query not included in statement'
83
  end
84

  
85
  def test_priority_id_was_gets_proper_sql_query_with_no_dates
86
    add_filter_to_query('priority_id_was', '=', ['2', '', ''])
87

  
88
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
89
    expected_query = "jd_priority_id_was.prop_key = 'priority_id' AND jd_priority_id_was.old_value IN ('2')"
90
    assert_equal expected_query, generated_query
91
    assert_match expected_query, @query.statement, 'query not included in statement'
92
  end
93

  
94
  def test_status_id_was_gets_proper_sql_query_with_both_dates
95
    add_filter_to_query('status_id_was', '=', ['2', '2012-02-02', '2015-03-03'])
96
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
97
    expected_query = "jd_status_id_was.prop_key = 'status_id' AND jd_status_id_was.old_value IN ('2') AND j_status_id_was.created_on > '2012-02-01 23:59:59.999999' AND j_status_id_was.created_on <= '2015-03-03 23:59:59.999999'"
98
    assert_equal expected_query, generated_query
99
    assert_match expected_query, @query.statement, 'query not included in statement'
100
  end
101

  
102
  def test_status_id_was_gets_proper_sql_query_with_both_dates
103
    add_filter_to_query('priority_id_was', '=', ['2', '2012-02-02', '2015-03-03'])
104
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
105
    expected_query = "jd_priority_id_was.prop_key = 'priority_id' AND jd_priority_id_was.old_value IN ('2') AND j_priority_id_was.created_on > '2012-02-01 23:59:59.999999' AND j_priority_id_was.created_on <= '2015-03-03 23:59:59.999999'"
106
    assert_equal expected_query, generated_query
107
    assert_match expected_query, @query.statement, 'query not included in statement'
108
  end
109

  
110
  def test_status_id_was_gets_proper_sql_query_with_from_date
111
    add_filter_to_query('status_id_was', '=', ['2', '2012-02-02', ''])
112
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
113
    expected_query = "jd_status_id_was.prop_key = 'status_id' AND jd_status_id_was.old_value IN ('2') AND j_status_id_was.created_on > '2012-02-01 23:59:59.999999'"
114
    assert_equal expected_query, generated_query
115
    assert_match expected_query, @query.statement, 'query not included in statement'
116
  end
117

  
118
  def test_priority_id_was_gets_proper_sql_query_with_from_date
119
    add_filter_to_query('priority_id_was', '=', ['2', '2012-02-02', ''])
120
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
121
    expected_query = "jd_priority_id_was.prop_key = 'priority_id' AND jd_priority_id_was.old_value IN ('2') AND j_priority_id_was.created_on > '2012-02-01 23:59:59.999999'"
122
    assert_equal expected_query, generated_query
123
    assert_match expected_query, @query.statement, 'query not included in statement'
124
  end
125

  
126
  def test_status_id_was_gets_proper_sql_query_with_to_date
127
    add_filter_to_query('status_id_was', '=', ['2', '', '2012-02-02'])
128
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
129
    expected_query = "jd_status_id_was.prop_key = 'status_id' AND jd_status_id_was.old_value IN ('2') AND j_status_id_was.created_on <= '2012-02-02 23:59:59.999999'"
130
    assert_equal expected_query, generated_query
131
    assert_match expected_query, @query.statement, 'query not included in statement'
132
  end
133

  
134
  def test_status_id_was_gets_proper_sql_query_with_to_date
135
    add_filter_to_query('priority_id_was', '=', ['2', '', '2012-02-02'])
136
    generated_query = @query.sql_for_history_field(@field, @operator, @values)
137
    expected_query = "jd_priority_id_was.prop_key = 'priority_id' AND jd_priority_id_was.old_value IN ('2') AND j_priority_id_was.created_on <= '2012-02-02 23:59:59.999999'"
138
    assert_equal expected_query, generated_query
139
    assert_match expected_query, @query.statement, 'query not included in statement'
140
  end
141

  
142
  def test_status_id_issues_return_proper_issue_without_date
143
    add_filter_to_query('status_id_was', '=', ['4', '', ''])
144
    assert_include Issue.find(15), @query.issues
145
    assert_equal 1, @query.issues.count
146
  end
147

  
148
  def test_status_id_issues_return_proper_issue_with_date
149
    add_filter_to_query('status_id_was', '=', ['4', (Date.today-15.days).to_s, (Date.today-10.days).to_s])
150
    assert_include Issue.find(15), @query.issues
151
    assert_equal 1, @query.issues.count
152
  end
153

  
154
  def test_status_id_shouldnt_return_any_issue_for_a_specific_range
155
    add_filter_to_query('status_id_was', '=', ['4', (Date.today-8.days).to_s, (Date.today-7.days).to_s])
156
    assert_not_include Issue.find(15), @query.issues
157
  end
158

  
159
  def test_status_id_issues_return_proper_issues_without_date
160
    add_filter_to_query('priority_id_was', '=', ['4', '', ''])
161
    assert_include Issue.find(15), @query.issues
162
    assert_include Issue.find(16), @query.issues
163
    assert_equal 2, @query.issues.count
164
  end
165

  
166
  def test_status_id_issues_return_proper_issues_without_date
167
    add_filter_to_query('priority_id_was', '=', ['4', (Date.today-7.days).to_s, (Date.today-3.days).to_s])
168
    assert_not_include Issue.find(15), @query.issues
169
    assert_include Issue.find(16), @query.issues
170
  end
171

  
172
  def test_assigned_to_id_issues_return_previous_asignee_with_dates
173
    add_filter_to_query('assigned_to_id_was', '=', ['2', (Date.today-10.days).to_s, (Date.today-3.days).to_s])
174
    assert_include Issue.find(16), @query.issues
175
    assert_equal 1, @query.issues.count
176
  end
177

  
178
  def test_assigned_to_id_issues_return_previous_asignee_without_dates
179
    add_filter_to_query('assigned_to_id_was', '=', ['2', '', ''])
180
    assert_include Issue.find(16), @query.issues
181
    assert_equal 1, @query.issues.count
182
  end
183

  
184
  def test_assigned_to_id_issues_return_proper_issues_for_me_value
185
    User.current = User.find(2)
186
    add_filter_to_query('assigned_to_id_was', '=', ['me', (Date.today-10.days).to_s, (Date.today-3.days).to_s])
187
    assert_include Issue.find(16), @query.issues
188
    assert_equal 1, @query.issues.count
189
  end
190

  
191
  def test_tracker_id_issues_return_proper_issue_with_dates
192
    add_filter_to_query('tracker_id_was', '=', ['1', (Date.today-10.days).to_s, (Date.today-3.days).to_s])
193
    assert_include Issue.find(16), @query.issues
194
    assert_equal 1, @query.issues.count
195
  end
196

  
197
  def test_filters_return_issue_for_a_combined_history_query
198
    add_filter_to_query('priority_id_was', '=', ['4', (Date.today-7.days).to_s, (Date.today-3.days).to_s])
199
    add_filter_to_query('tracker_id_was', '=', ['1', (Date.today-10.days).to_s, (Date.today-3.days).to_s])
200
    add_filter_to_query('assigned_to_id_was', '=', ['2', (Date.today-10.days).to_s, (Date.today-3.days).to_s])
201
    assert_include Issue.find(16), @query.issues
202
    assert_equal 1, @query.issues.count
203
  end
204

  
205
  private
206

  
207
  def add_filter_to_query(field, operator, values)
208
    @query ||= IssueQuery.new(:project => Project.find(1), :name => '_')
209
    @field, @operator, @values = field, operator, values
210
    @query.add_filter(field, operator, values)
211
    assert @query.valid?
212
  end
213
end
    (1-1/1)