##// END OF EJS Templates
Allow filtering with timestamp (#8842)....
Jean-Philippe Lang -
r12202:a4d3da988a3e
parent child
Show More
@@ -242,7 +242,9 class Query < ActiveRecord::Base
242 when :date, :date_past
242 when :date, :date_past
243 case operator_for(field)
243 case operator_for(field)
244 when "=", ">=", "<=", "><"
244 when "=", ">=", "<=", "><"
245 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
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 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
248 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
247 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
249 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
248 end
250 end
@@ -624,7 +626,7 class Query < ActiveRecord::Base
624 if value.any?
626 if value.any?
625 case type_for(field)
627 case type_for(field)
626 when :date, :date_past
628 when :date, :date_past
627 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
629 sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first))
628 when :integer
630 when :integer
629 if is_custom_filter
631 if is_custom_filter
630 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})"
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 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
661 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
660 when ">="
662 when ">="
661 if [:date, :date_past].include?(type_for(field))
663 if [:date, :date_past].include?(type_for(field))
662 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
664 sql = date_clause(db_table, db_field, parse_date(value.first), nil)
663 else
665 else
664 if is_custom_filter
666 if is_custom_filter
665 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})"
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 end
671 end
670 when "<="
672 when "<="
671 if [:date, :date_past].include?(type_for(field))
673 if [:date, :date_past].include?(type_for(field))
672 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
674 sql = date_clause(db_table, db_field, nil, parse_date(value.first))
673 else
675 else
674 if is_custom_filter
676 if is_custom_filter
675 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})"
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 end
681 end
680 when "><"
682 when "><"
681 if [:date, :date_past].include?(type_for(field))
683 if [:date, :date_past].include?(type_for(field))
682 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
684 sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]))
683 else
685 else
684 if is_custom_filter
686 if is_custom_filter
685 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})"
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 def date_clause(table, field, from, to)
811 def date_clause(table, field, from, to)
810 s = []
812 s = []
811 if from
813 if from
812 from_yesterday = from - 1
814 if from.is_a?(Date)
813 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
815 from = Time.local(from.year, from.month, from.day).beginning_of_day
816 end
814 if self.class.default_timezone == :utc
817 if self.class.default_timezone == :utc
815 from_yesterday_time = from_yesterday_time.utc
818 from = from.utc
816 end
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 end
822 end
819 if to
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 if self.class.default_timezone == :utc
827 if self.class.default_timezone == :utc
822 to_time = to_time.utc
828 to = to.utc
823 end
829 end
824 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
830 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to)])
825 end
831 end
826 s.join(' AND ')
832 s.join(' AND ')
827 end
833 end
@@ -831,6 +837,15 class Query < ActiveRecord::Base
831 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
837 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
832 end
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 # Additional joins required for the given sort options
849 # Additional joins required for the given sort options
835 def joins_for_order_statement(order_options)
850 def joins_for_order_statement(order_options)
836 joins = []
851 joins = []
@@ -162,6 +162,29 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
162 end
162 end
163 end
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 context "/index.json" do
188 context "/index.json" do
166 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
189 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
167 end
190 end
General Comments 0
You need to be logged in to leave comments. Login now