@@ -242,7 +242,9 class Query < ActiveRecord::Base | |||
|
242 | 242 | when :date, :date_past |
|
243 | 243 | case operator_for(field) |
|
244 | 244 | when "=", ">=", "<=", "><" |
|
245 |
add_filter_error(field, :invalid) if values_for(field).detect {|v| |
|
|
245 | add_filter_error(field, :invalid) if values_for(field).detect {|v| | |
|
246 | v.present? && (!v.match(/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){,2}(Z|\d{2}:?\d{2})?)?\z/) || parse_date(v).nil?) | |
|
247 | } | |
|
246 | 248 | when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-" |
|
247 | 249 | add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) } |
|
248 | 250 | end |
@@ -624,7 +626,7 class Query < ActiveRecord::Base | |||
|
624 | 626 | if value.any? |
|
625 | 627 | case type_for(field) |
|
626 | 628 | when :date, :date_past |
|
627 |
sql = date_clause(db_table, db_field, |
|
|
629 | sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first)) | |
|
628 | 630 | when :integer |
|
629 | 631 | if is_custom_filter |
|
630 | 632 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})" |
@@ -659,7 +661,7 class Query < ActiveRecord::Base | |||
|
659 | 661 | sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter |
|
660 | 662 | when ">=" |
|
661 | 663 | if [:date, :date_past].include?(type_for(field)) |
|
662 |
sql = date_clause(db_table, db_field, |
|
|
664 | sql = date_clause(db_table, db_field, parse_date(value.first), nil) | |
|
663 | 665 | else |
|
664 | 666 | if is_custom_filter |
|
665 | 667 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})" |
@@ -669,7 +671,7 class Query < ActiveRecord::Base | |||
|
669 | 671 | end |
|
670 | 672 | when "<=" |
|
671 | 673 | if [:date, :date_past].include?(type_for(field)) |
|
672 |
sql = date_clause(db_table, db_field, nil, |
|
|
674 | sql = date_clause(db_table, db_field, nil, parse_date(value.first)) | |
|
673 | 675 | else |
|
674 | 676 | if is_custom_filter |
|
675 | 677 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})" |
@@ -679,7 +681,7 class Query < ActiveRecord::Base | |||
|
679 | 681 | end |
|
680 | 682 | when "><" |
|
681 | 683 | if [:date, :date_past].include?(type_for(field)) |
|
682 |
sql = date_clause(db_table, db_field, |
|
|
684 | sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1])) | |
|
683 | 685 | else |
|
684 | 686 | if is_custom_filter |
|
685 | 687 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})" |
@@ -809,19 +811,23 class Query < ActiveRecord::Base | |||
|
809 | 811 | def date_clause(table, field, from, to) |
|
810 | 812 | s = [] |
|
811 | 813 | if from |
|
812 | from_yesterday = from - 1 | |
|
813 |
from |
|
|
814 | if from.is_a?(Date) | |
|
815 | from = Time.local(from.year, from.month, from.day).beginning_of_day | |
|
816 | end | |
|
814 | 817 | if self.class.default_timezone == :utc |
|
815 |
from |
|
|
818 | from = from.utc | |
|
816 | 819 | end |
|
817 | s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)]) | |
|
820 | from = from - 1 | |
|
821 | s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from)]) | |
|
818 | 822 | end |
|
819 | 823 | if to |
|
820 | to_time = Time.local(to.year, to.month, to.day) | |
|
824 | if to.is_a?(Date) | |
|
825 | to = Time.local(to.year, to.month, to.day).end_of_day | |
|
826 | end | |
|
821 | 827 | if self.class.default_timezone == :utc |
|
822 |
to |
|
|
828 | to = to.utc | |
|
823 | 829 | end |
|
824 |
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to |
|
|
830 | s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to)]) | |
|
825 | 831 | end |
|
826 | 832 | s.join(' AND ') |
|
827 | 833 | end |
@@ -831,6 +837,15 class Query < ActiveRecord::Base | |||
|
831 | 837 | date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil)) |
|
832 | 838 | end |
|
833 | 839 | |
|
840 | # Returns a Date or Time from the given filter value | |
|
841 | def parse_date(arg) | |
|
842 | if arg.to_s =~ /\A\d{4}-\d{2}-\d{2}T/ | |
|
843 | Time.parse(arg) rescue nil | |
|
844 | else | |
|
845 | Date.parse(arg) rescue nil | |
|
846 | end | |
|
847 | end | |
|
848 | ||
|
834 | 849 | # Additional joins required for the given sort options |
|
835 | 850 | def joins_for_order_statement(order_options) |
|
836 | 851 | joins = [] |
@@ -162,6 +162,29 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base | |||
|
162 | 162 | end |
|
163 | 163 | end |
|
164 | 164 | |
|
165 | def test_index_should_allow_timestamp_filtering | |
|
166 | Issue.delete_all | |
|
167 | Issue.generate!(:subject => '1').update_column(:updated_on, Time.parse("2014-01-02T10:25:00Z")) | |
|
168 | Issue.generate!(:subject => '2').update_column(:updated_on, Time.parse("2014-01-02T12:13:00Z")) | |
|
169 | ||
|
170 | get '/issues.xml', | |
|
171 | {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '<='}, | |
|
172 | :v => {:updated_on => ['2014-01-02T12:00:00Z']}} | |
|
173 | assert_select 'issues>issue', :count => 1 | |
|
174 | assert_select 'issues>issue>subject', :text => '1' | |
|
175 | ||
|
176 | get '/issues.xml', | |
|
177 | {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='}, | |
|
178 | :v => {:updated_on => ['2014-01-02T12:00:00Z']}} | |
|
179 | assert_select 'issues>issue', :count => 1 | |
|
180 | assert_select 'issues>issue>subject', :text => '2' | |
|
181 | ||
|
182 | get '/issues.xml', | |
|
183 | {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='}, | |
|
184 | :v => {:updated_on => ['2014-01-02T08:00:00Z']}} | |
|
185 | assert_select 'issues>issue', :count => 2 | |
|
186 | end | |
|
187 | ||
|
165 | 188 | context "/index.json" do |
|
166 | 189 | should_allow_api_authentication(:get, "/projects/private-child/issues.json") |
|
167 | 190 | end |
General Comments 0
You need to be logged in to leave comments.
Login now