git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@6226 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
7c505aaff3
commit
4507aa5014
@ -119,8 +119,8 @@ class Query < ActiveRecord::Base
|
|||||||
:list_status => [ "o", "=", "!", "c", "*" ],
|
:list_status => [ "o", "=", "!", "c", "*" ],
|
||||||
:list_optional => [ "=", "!", "!*", "*" ],
|
:list_optional => [ "=", "!", "!*", "*" ],
|
||||||
:list_subprojects => [ "*", "!*", "=" ],
|
:list_subprojects => [ "*", "!*", "=" ],
|
||||||
:date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
|
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
|
||||||
:date_past => [ ">t-", "<t-", "t-", "t", "w" ],
|
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w" ],
|
||||||
:string => [ "=", "~", "!", "!~" ],
|
:string => [ "=", "~", "!", "!~" ],
|
||||||
:text => [ "~", "!~" ],
|
:text => [ "~", "!~" ],
|
||||||
:integer => [ "=", ">=", "<=", "><", "!*", "*" ] }
|
:integer => [ "=", ">=", "<=", "><", "!*", "*" ] }
|
||||||
@ -268,7 +268,7 @@ class Query < ActiveRecord::Base
|
|||||||
|
|
||||||
def add_filter(field, operator, values)
|
def add_filter(field, operator, values)
|
||||||
# values must be an array
|
# values must be an array
|
||||||
return unless values and values.is_a? Array # and !values.first.empty?
|
return unless values.nil? || values.is_a?(Array)
|
||||||
# check if field is defined as an available filter
|
# check if field is defined as an available filter
|
||||||
if available_filters.has_key? field
|
if available_filters.has_key? field
|
||||||
filter_options = available_filters[field]
|
filter_options = available_filters[field]
|
||||||
@ -277,7 +277,7 @@ class Query < ActiveRecord::Base
|
|||||||
# allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
|
# allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
|
||||||
# filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
|
# filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
|
||||||
#end
|
#end
|
||||||
filters[field] = {:operator => operator, :values => values }
|
filters[field] = {:operator => operator, :values => (values || ['']) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -289,9 +289,9 @@ class Query < ActiveRecord::Base
|
|||||||
|
|
||||||
# Add multiple filters using +add_filter+
|
# Add multiple filters using +add_filter+
|
||||||
def add_filters(fields, operators, values)
|
def add_filters(fields, operators, values)
|
||||||
if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
|
if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
|
||||||
fields.each do |field|
|
fields.each do |field|
|
||||||
add_filter(field, operators[field], values[field])
|
add_filter(field, operators[field], values && values[field])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -299,6 +299,10 @@ class Query < ActiveRecord::Base
|
|||||||
def has_filter?(field)
|
def has_filter?(field)
|
||||||
filters and filters[field]
|
filters and filters[field]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def type_for(field)
|
||||||
|
available_filters[field][:type] if available_filters.has_key?(field)
|
||||||
|
end
|
||||||
|
|
||||||
def operator_for(field)
|
def operator_for(field)
|
||||||
has_filter?(field) ? filters[field][:operator] : nil
|
has_filter?(field) ? filters[field][:operator] : nil
|
||||||
@ -601,11 +605,15 @@ class Query < ActiveRecord::Base
|
|||||||
sql = ''
|
sql = ''
|
||||||
case operator
|
case operator
|
||||||
when "="
|
when "="
|
||||||
if value.any?
|
if [:date, :date_past].include?(type_for(field))
|
||||||
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
|
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
|
||||||
else
|
else
|
||||||
# IN an empty set
|
if value.any?
|
||||||
sql = "1=0"
|
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
|
||||||
|
else
|
||||||
|
# IN an empty set
|
||||||
|
sql = "1=0"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
when "!"
|
when "!"
|
||||||
if value.any?
|
if value.any?
|
||||||
@ -621,46 +629,58 @@ class Query < ActiveRecord::Base
|
|||||||
sql = "#{db_table}.#{db_field} IS NOT NULL"
|
sql = "#{db_table}.#{db_field} IS NOT NULL"
|
||||||
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
|
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
|
||||||
when ">="
|
when ">="
|
||||||
if is_custom_filter
|
if [:date, :date_past].include?(type_for(field))
|
||||||
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_i}"
|
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
|
||||||
else
|
else
|
||||||
sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
|
if is_custom_filter
|
||||||
|
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_i}"
|
||||||
|
else
|
||||||
|
sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
when "<="
|
when "<="
|
||||||
if is_custom_filter
|
if [:date, :date_past].include?(type_for(field))
|
||||||
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_i}"
|
sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
|
||||||
else
|
else
|
||||||
sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
|
if is_custom_filter
|
||||||
|
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_i}"
|
||||||
|
else
|
||||||
|
sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
when "><"
|
when "><"
|
||||||
if is_custom_filter
|
if [:date, :date_past].include?(type_for(field))
|
||||||
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
|
sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
|
||||||
else
|
else
|
||||||
sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
|
if is_custom_filter
|
||||||
|
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
|
||||||
|
else
|
||||||
|
sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
when "o"
|
when "o"
|
||||||
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
|
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
|
||||||
when "c"
|
when "c"
|
||||||
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
|
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
|
||||||
when ">t-"
|
when ">t-"
|
||||||
sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
|
sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
|
||||||
when "<t-"
|
when "<t-"
|
||||||
sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
|
sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
|
||||||
when "t-"
|
when "t-"
|
||||||
sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
|
sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
|
||||||
when ">t+"
|
when ">t+"
|
||||||
sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
|
sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
|
||||||
when "<t+"
|
when "<t+"
|
||||||
sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
|
sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
|
||||||
when "t+"
|
when "t+"
|
||||||
sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
|
sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
|
||||||
when "t"
|
when "t"
|
||||||
sql = date_range_clause(db_table, db_field, 0, 0)
|
sql = relative_date_clause(db_table, db_field, 0, 0)
|
||||||
when "w"
|
when "w"
|
||||||
first_day_of_week = l(:general_first_day_of_week).to_i
|
first_day_of_week = l(:general_first_day_of_week).to_i
|
||||||
day_of_week = Date.today.cwday
|
day_of_week = Date.today.cwday
|
||||||
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
|
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
|
||||||
sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6)
|
sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
|
||||||
when "~"
|
when "~"
|
||||||
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
|
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
|
||||||
when "!~"
|
when "!~"
|
||||||
@ -696,16 +716,21 @@ class Query < ActiveRecord::Base
|
|||||||
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
|
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a SQL clause for a date or datetime field.
|
# Returns a SQL clause for a date or datetime field.
|
||||||
def date_range_clause(table, field, from, to)
|
def date_clause(table, field, from, to)
|
||||||
s = []
|
s = []
|
||||||
if from
|
if from
|
||||||
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
|
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((from - 1).to_time.end_of_day)])
|
||||||
end
|
end
|
||||||
if to
|
if to
|
||||||
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
|
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to.to_time.end_of_day)])
|
||||||
end
|
end
|
||||||
s.join(' AND ')
|
s.join(' AND ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns a SQL clause for a date or datetime field using relative dates.
|
||||||
|
def relative_date_clause(table, field, days_from, days_to)
|
||||||
|
date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,37 +1,53 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//<![CDATA[
|
//<![CDATA[
|
||||||
function add_filter() {
|
function add_filter() {
|
||||||
select = $('add_filter_select');
|
select = $('add_filter_select');
|
||||||
field = select.value
|
field = select.value
|
||||||
Element.show('tr_' + field);
|
Element.show('tr_' + field);
|
||||||
check_box = $('cb_' + field);
|
check_box = $('cb_' + field);
|
||||||
check_box.checked = true;
|
check_box.checked = true;
|
||||||
toggle_filter(field);
|
toggle_filter(field);
|
||||||
select.selectedIndex = 0;
|
select.selectedIndex = 0;
|
||||||
|
|
||||||
for (i=0; i<select.options.length; i++) {
|
for (i=0; i<select.options.length; i++) {
|
||||||
if (select.options[i].value == field) {
|
if (select.options[i].value == field) {
|
||||||
select.options[i].disabled = true;
|
select.options[i].disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle_filter(field) {
|
function toggle_filter(field) {
|
||||||
check_box = $('cb_' + field);
|
check_box = $('cb_' + field);
|
||||||
|
if (check_box.checked) {
|
||||||
if (check_box.checked) {
|
Element.show("operators_" + field);
|
||||||
Element.show("operators_" + field);
|
Form.Element.enable("operators_" + field);
|
||||||
Form.Element.enable("operators_" + field);
|
toggle_operator(field);
|
||||||
$$(".values_" + field).each(function(el){ Form.Element.enable(el)});
|
} else {
|
||||||
toggle_operator(field);
|
Element.hide("operators_" + field);
|
||||||
} else {
|
Form.Element.disable("operators_" + field);
|
||||||
Element.hide("operators_" + field);
|
enableValues(field, []);
|
||||||
Element.hide("div_values_" + field);
|
|
||||||
Form.Element.disable("operators_" + field);
|
|
||||||
$$(".values_" + field).each(function(el){ Form.Element.disable(el)});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enableValues(field, indexes) {
|
||||||
|
var f = $$(".values_" + field);
|
||||||
|
for(var i=0;i<f.length;i++) {
|
||||||
|
if (indexes.include(i)) {
|
||||||
|
Form.Element.enable(f[i]);
|
||||||
|
f[i].up('span').show();
|
||||||
|
} else {
|
||||||
|
f[i].value = '';
|
||||||
|
Form.Element.disable(f[i]);
|
||||||
|
f[i].up('span').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (indexes.length > 0) {
|
||||||
|
Element.show("div_values_" + field);
|
||||||
|
} else {
|
||||||
|
Element.hide("div_values_" + field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggle_operator(field) {
|
function toggle_operator(field) {
|
||||||
operator = $("operators_" + field);
|
operator = $("operators_" + field);
|
||||||
switch (operator.value) {
|
switch (operator.value) {
|
||||||
@ -41,30 +57,32 @@ function toggle_operator(field) {
|
|||||||
case "w":
|
case "w":
|
||||||
case "o":
|
case "o":
|
||||||
case "c":
|
case "c":
|
||||||
Element.hide("div_values_" + field);
|
enableValues(field, []);
|
||||||
var v = $$(".values_" + field);
|
|
||||||
if (v.length > 1) {v[1].hide(); Form.Element.disable(v[1])}
|
|
||||||
break;
|
break;
|
||||||
case "><":
|
case "><":
|
||||||
Element.show("div_values_" + field);
|
enableValues(field, [0,1]);
|
||||||
var v = $$(".values_" + field);
|
break;
|
||||||
if (v.length > 1) {v[1].show(); Form.Element.enable(v[1])}
|
case "<t+":
|
||||||
|
case ">t+":
|
||||||
|
case "t+":
|
||||||
|
case ">t-":
|
||||||
|
case "<t-":
|
||||||
|
case "t-":
|
||||||
|
enableValues(field, [2]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Element.show("div_values_" + field);
|
enableValues(field, [0]);
|
||||||
var v = $$(".values_" + field);
|
|
||||||
if (v.length > 1) {v[1].hide(); Form.Element.disable(v[1])}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle_multi_select(field) {
|
function toggle_multi_select(el) {
|
||||||
select = $('values_' + field);
|
var select = $(el);
|
||||||
if (select.multiple == true) {
|
if (select.multiple == true) {
|
||||||
select.multiple = false;
|
select.multiple = false;
|
||||||
} else {
|
} else {
|
||||||
select.multiple = true;
|
select.multiple = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit_query_form(id) {
|
function submit_query_form(id) {
|
||||||
@ -102,15 +120,19 @@ Event.observe(document,"dom:loaded", apply_filters_observer);
|
|||||||
<div id="div_values_<%= field %>" style="display:none;">
|
<div id="div_values_<%= field %>" style="display:none;">
|
||||||
<% case options[:type]
|
<% case options[:type]
|
||||||
when :list, :list_optional, :list_status, :list_subprojects %>
|
when :list, :list_optional, :list_status, :list_subprojects %>
|
||||||
<%= select_tag "v[#{field}][]", options_for_select(options[:values], query.values_for(field)), :class => "values_#{field}", :multiple => (query.values_for(field) && query.values_for(field).length > 1) %>
|
<span class="span_values_<%= field %>">
|
||||||
<%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %>
|
<%= select_tag "v[#{field}][]", options_for_select(options[:values], query.values_for(field)), :class => "values_#{field}", :id => "values_#{field}_1", :multiple => (query.values_for(field) && query.values_for(field).length > 1) %>
|
||||||
|
<%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('values_#{field}_1');", :style => "vertical-align: bottom;" %>
|
||||||
|
</span>
|
||||||
<% when :date, :date_past %>
|
<% when :date, :date_past %>
|
||||||
<%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :size => 3 %> <%= l(:label_day_plural) %>
|
<span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :size => 10, :class => "values_#{field}", :id => "values_#{field}_1" %> <%= calendar_for "values_#{field}_1" %></span>
|
||||||
|
<span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field, 1), :size => 10, :class => "values_#{field}", :id => "values_#{field}_2" %> <%= calendar_for "values_#{field}_2" %></span>
|
||||||
|
<span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :size => 3, :class => "values_#{field}" %> <%= l(:label_day_plural) %></span>
|
||||||
<% when :string, :text %>
|
<% when :string, :text %>
|
||||||
<%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :size => 30 %>
|
<span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}", :size => 30 %></span>
|
||||||
<% when :integer %>
|
<% when :integer %>
|
||||||
<%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :size => 3 %>
|
<span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}_1", :size => 3 %></span>
|
||||||
<%= text_field_tag "v[#{field}][]", query.value_for(field, 1), :class => "values_#{field}", :size => 3 %>
|
<span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field, 1), :class => "values_#{field}", :id => "values_#{field}_2", :size => 3 %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">toggle_filter('<%= field %>');</script>
|
<script type="text/javascript">toggle_filter('<%= field %>');</script>
|
||||||
|
@ -146,6 +146,34 @@ class QueryTest < ActiveSupport::TestCase
|
|||||||
find_issues_with_query(query)
|
find_issues_with_query(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_operator_date_equals
|
||||||
|
query = Query.new(:name => '_')
|
||||||
|
query.add_filter('due_date', '=', ['2011-07-10'])
|
||||||
|
assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
|
||||||
|
find_issues_with_query(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_operator_date_lesser_than
|
||||||
|
query = Query.new(:name => '_')
|
||||||
|
query.add_filter('due_date', '<=', ['2011-07-10'])
|
||||||
|
assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
|
||||||
|
find_issues_with_query(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_operator_date_greater_than
|
||||||
|
query = Query.new(:name => '_')
|
||||||
|
query.add_filter('due_date', '>=', ['2011-07-10'])
|
||||||
|
assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
|
||||||
|
find_issues_with_query(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_operator_date_between
|
||||||
|
query = Query.new(:name => '_')
|
||||||
|
query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
|
||||||
|
assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
|
||||||
|
find_issues_with_query(query)
|
||||||
|
end
|
||||||
|
|
||||||
def test_operator_in_more_than
|
def test_operator_in_more_than
|
||||||
Issue.find(7).update_attribute(:due_date, (Date.today + 15))
|
Issue.find(7).update_attribute(:due_date, (Date.today + 15))
|
||||||
query = Query.new(:project => Project.find(1), :name => '_')
|
query = Query.new(:project => Project.find(1), :name => '_')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user