git-svn-id: http://redmine.rubyforge.org/svn/trunk@95 e93f8b46-1217-0410-a6f0-8f06a7374b81pull/351/head
parent
236c735d08
commit
2b0142580f
@ -0,0 +1,49 @@ |
||||
# redMine - project management software |
||||
# Copyright (C) 2006 Jean-Philippe Lang |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
class QueriesController < ApplicationController |
||||
layout 'base' |
||||
before_filter :require_login, :find_query |
||||
|
||||
def edit |
||||
if request.post? |
||||
@query.filters = {} |
||||
params[:fields].each do |field| |
||||
@query.add_filter(field, params[:operators][field], params[:values][field]) |
||||
end if params[:fields] |
||||
@query.attributes = params[:query] |
||||
|
||||
if @query.save |
||||
flash[:notice] = l(:notice_successful_update) |
||||
redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query |
||||
end |
||||
end |
||||
end |
||||
|
||||
def destroy |
||||
@query.destroy if request.post? |
||||
redirect_to :controller => 'reports', :action => 'issue_report', :id => @project |
||||
end |
||||
|
||||
private |
||||
def find_query |
||||
@query = Query.find(params[:id]) |
||||
@project = @query.project |
||||
# check if user is allowed to manage queries (same permission as add_query) |
||||
authorize('projects', 'add_query') |
||||
end |
||||
end |
@ -0,0 +1,6 @@ |
||||
module QueriesHelper |
||||
|
||||
def operators_for_select(filter_type) |
||||
Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} |
||||
end |
||||
end |
@ -1,106 +0,0 @@ |
||||
# redMine - project management software |
||||
# Copyright (C) 2006 Jean-Philippe Lang |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
module SearchFilterHelper |
||||
|
||||
def search_filter_criteria(name, options = {}) |
||||
@search_filter ||= {} |
||||
@search_filter[name] ||= {} |
||||
@search_filter[name][:options] = [] |
||||
@search_filter[name][:conditions] = {} |
||||
yield.each { |c| |
||||
@search_filter[name][:options] << [c[0], c[1].to_s] |
||||
@search_filter[name][:conditions].store(c[1].to_s, c[2]) |
||||
} |
||||
end |
||||
|
||||
def search_filter_update |
||||
session[:search_filter] ||= {} |
||||
@search_filter.each_key {|field| session[:search_filter][field] = params[field] } |
||||
end |
||||
|
||||
def search_filter_clause |
||||
session[:search_filter] ||= {} |
||||
clause = ["1=1"] |
||||
@search_filter.each { |k, v| |
||||
filter_value = session[:search_filter][k] || v[:options][0][1] |
||||
if v[:conditions][filter_value] |
||||
clause[0] = clause[0] + " AND " + v[:conditions][filter_value].first |
||||
clause += v[:conditions][filter_value][1..-1] |
||||
end |
||||
} |
||||
clause |
||||
end |
||||
|
||||
def search_filter_tag(criteria, options = {}) |
||||
session[:search_filter] ||= {} |
||||
options[:name] = criteria |
||||
options[:class] += " active-filter" if session[:search_filter][criteria] and session[:search_filter][criteria] != @search_filter[criteria][:options][0][1] |
||||
content_tag("select", |
||||
options_for_select(@search_filter[criteria][:options], session[:search_filter][criteria]), |
||||
options |
||||
) |
||||
end |
||||
|
||||
def search_filter_init_list_issues |
||||
search_filter_criteria('status_id') { |
||||
[ [('['+l(:label_open_issues_plural)+']'), "O", ["issue_statuses.is_closed=?", false]], |
||||
[('['+l(:label_closed_issues_plural)+']'), "C", ["issue_statuses.is_closed=?", true]], |
||||
[('['+l(:label_all)+']'), "A", nil] |
||||
] + IssueStatus.find(:all).collect {|s| [s.name, s.id, ["issues.status_id=?", s.id]] } |
||||
} |
||||
|
||||
search_filter_criteria('tracker_id') { |
||||
[ [('['+l(:label_all)+']'), "A", nil] |
||||
] + Tracker.find(:all).collect {|s| [s.name, s.id, ["issues.tracker_id=?", s.id]] } |
||||
} |
||||
|
||||
search_filter_criteria('priority_id') { |
||||
[ [('['+l(:label_all)+']'), "A", nil] |
||||
] + Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect {|s| [s.name, s.id, ["issues.priority_id=?", s.id]] } |
||||
} |
||||
|
||||
search_filter_criteria('category_id') { |
||||
[ [('['+l(:label_all)+']'), "A", nil], |
||||
[('['+l(:label_none)+']'), "N", ["issues.category_id is null"]] |
||||
] + @project.issue_categories.find(:all).collect {|s| [s.name, s.id, ["issues.category_id=?", s.id]] } |
||||
} |
||||
|
||||
search_filter_criteria('fixed_version_id') { |
||||
[ [('['+l(:label_all)+']'), "A", nil], |
||||
[('['+l(:label_none)+']'), "N", ["issues.fixed_version_id is null"]] |
||||
] + @project.versions.collect {|s| [s.name, s.id, ["issues.fixed_version_id=?", s.id]] } |
||||
} |
||||
|
||||
search_filter_criteria('author_id') { |
||||
[ [('['+l(:label_all)+']'), "A", nil], |
||||
] + @project.users.collect {|s| [s.display_name, s.id, ["issues.author_id=?", s.id]] } |
||||
} |
||||
|
||||
search_filter_criteria('assigned_to_id') { |
||||
[ [('['+l(:label_all)+']'), "A", nil], |
||||
[('['+l(:label_none)+']'), "N", ["issues.assigned_to_id is null"]] |
||||
] + @project.users.collect {|s| [s.display_name, s.id, ["issues.assigned_to_id=?", s.id]] } |
||||
} |
||||
|
||||
search_filter_criteria('subproject_id') { |
||||
[ [('['+l(:label_none)+']'), "N", ["issues.project_id=?", @project.id]], |
||||
[('['+l(:label_all)+']'), "A", ["(issues.project_id=? or projects.parent_id=?)", @project.id, @project.id]] |
||||
] |
||||
} |
||||
end |
||||
end |
@ -0,0 +1,166 @@ |
||||
# redMine - project management software |
||||
# Copyright (C) 2006 Jean-Philippe Lang |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
class Query < ActiveRecord::Base |
||||
belongs_to :project |
||||
belongs_to :user |
||||
serialize :filters |
||||
|
||||
attr_protected :project, :user |
||||
|
||||
validates_presence_of :name, :on => :save |
||||
|
||||
@@operators = { "=" => :label_equals, |
||||
"!" => :label_not_equals, |
||||
"o" => :label_open_issues, |
||||
"c" => :label_closed_issues, |
||||
"!*" => :label_none, |
||||
"*" => :label_all, |
||||
"<t+" => :label_in_less_than, |
||||
">t+" => :label_in_more_than, |
||||
"t+" => :label_in, |
||||
"t" => :label_today, |
||||
">t-" => :label_less_than_ago, |
||||
"<t-" => :label_more_than_ago, |
||||
"t-" => :label_ago, |
||||
"~" => :label_contains, |
||||
"!~" => :label_not_contains } |
||||
|
||||
cattr_reader :operators |
||||
|
||||
@@operators_by_filter_type = { :list => [ "=", "!" ], |
||||
:list_status => [ "o", "=", "!", "c", "*" ], |
||||
:list_optional => [ "=", "!", "!*", "*" ], |
||||
:date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ], |
||||
:date_past => [ ">t-", "<t-", "t-", "t" ], |
||||
:text => [ "~", "!~" ] } |
||||
|
||||
cattr_reader :operators_by_filter_type |
||||
|
||||
def initialize(attributes = nil) |
||||
super attributes |
||||
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } |
||||
self.is_public = true |
||||
end |
||||
|
||||
def validate |
||||
filters.each_key do |field| |
||||
errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless |
||||
# filter requires one or more values |
||||
(values_for(field) and !values_for(field).first.empty?) or |
||||
# filter doesn't require any value |
||||
["o", "c", "!*", "*", "t"].include? operator_for(field) |
||||
end if filters |
||||
end |
||||
|
||||
def available_filters |
||||
return @available_filters if @available_filters |
||||
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all).collect{|s| [s.name, s.id.to_s] } }, |
||||
"tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all).collect{|s| [s.name, s.id.to_s] } }, |
||||
"priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } }, |
||||
"subject" => { :type => :text, :order => 7 }, |
||||
"created_on" => { :type => :date_past, :order => 8 }, |
||||
"updated_on" => { :type => :date_past, :order => 9 }, |
||||
"start_date" => { :type => :date, :order => 10 }, |
||||
"due_date" => { :type => :date, :order => 11 } } |
||||
unless project.nil? |
||||
# project specific filters |
||||
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => @project.users.collect{|s| [s.name, s.id.to_s] } } |
||||
@available_filters["author_id"] = { :type => :list, :order => 5, :values => @project.users.collect{|s| [s.name, s.id.to_s] } } |
||||
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } |
||||
# remove category filter if no category defined |
||||
@available_filters.delete "category_id" if @available_filters["category_id"][:values].empty? |
||||
end |
||||
@available_filters |
||||
end |
||||
|
||||
def add_filter(field, operator, values) |
||||
# values must be an array |
||||
return unless values and values.is_a? Array # and !values.first.empty? |
||||
# check if field is defined as an available filter |
||||
if available_filters.has_key? field |
||||
filter_options = available_filters[field] |
||||
# check if operator is allowed for that filter |
||||
#if @@operators_by_filter_type[filter_options[:type]].include? operator |
||||
# 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 |
||||
#end |
||||
filters[field] = {:operator => operator, :values => values } |
||||
end |
||||
end |
||||
|
||||
def add_short_filter(field, expression) |
||||
return unless expression |
||||
parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first |
||||
add_filter field, (parms[0] || "="), [parms[1] || ""] |
||||
end |
||||
|
||||
def has_filter?(field) |
||||
filters and filters[field] |
||||
end |
||||
|
||||
def operator_for(field) |
||||
has_filter?(field) ? filters[field][:operator] : nil |
||||
end |
||||
|
||||
def values_for(field) |
||||
has_filter?(field) ? filters[field][:values] : nil |
||||
end |
||||
|
||||
def statement |
||||
sql = "1=1" |
||||
sql << " AND issues.project_id=%d" % project.id if project |
||||
filters.each_key do |field| |
||||
v = values_for field |
||||
next unless v and !v.empty? |
||||
sql = sql + " AND " unless sql.empty? |
||||
case operator_for field |
||||
when "=" |
||||
sql = sql + "issues.#{field} IN (" + v.each(&:to_i).join(",") + ")" |
||||
when "!" |
||||
sql = sql + "issues.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")" |
||||
when "!*" |
||||
sql = sql + "issues.#{field} IS NULL" |
||||
when "*" |
||||
sql = sql + "issues.#{field} IS NOT NULL" |
||||
when "o" |
||||
sql = sql + "issue_statuses.is_closed=#{connection.quoted_false}" if field == "status_id" |
||||
when "c" |
||||
sql = sql + "issue_statuses.is_closed=#{connection.quoted_true}" if field == "status_id" |
||||
when ">t-" |
||||
sql = sql + "issues.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i) |
||||
when "<t-" |
||||
sql = sql + "issues.#{field} <= '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'" |
||||
when "t-" |
||||
sql = sql + "issues.#{field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'" |
||||
when ">t+" |
||||
sql = sql + "issues.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" |
||||
when "<t+" |
||||
sql = sql + "issues.#{field} <= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" |
||||
when "t+" |
||||
sql = sql + "issues.#{field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" |
||||
when "t" |
||||
sql = sql + "issues.#{field} = '%s'" % connection.quoted_date(Date.today) |
||||
when "~" |
||||
sql = sql + "issues.#{field} LIKE '%#{connection.quote_string(v.first)}%'" |
||||
when "!~" |
||||
sql = sql + "issues.#{field} NOT LIKE '%#{connection.quote_string(v.first)}%'" |
||||
end |
||||
end if filters and valid? |
||||
sql |
||||
end |
||||
end |
@ -0,0 +1,6 @@ |
||||
<h2><%= l(:label_query_new) %></h2> |
||||
|
||||
<%= start_form_tag :action => 'add_query', :id => @project %> |
||||
<%= render :partial => 'queries/form', :locals => {:query => @query} %> |
||||
<%= submit_tag l(:button_create) %> |
||||
<%= end_form_tag %> |
@ -0,0 +1,100 @@ |
||||
<script> |
||||
|
||||
function add_filter() { |
||||
select = $('add_filter_select'); |
||||
field = select.value |
||||
Element.show('tr_' + field); |
||||
check_box = $('cb_' + field); |
||||
check_box.checked = true; |
||||
toggle_filter(field); |
||||
select.selectedIndex = 0; |
||||
|
||||
for (i=0; i<select.options.length; i++) { |
||||
if (select.options[i].value == field) { |
||||
select.options[i].disabled = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function toggle_filter(field) { |
||||
check_box = $('cb_' + field); |
||||
|
||||
if (check_box.checked) { |
||||
Element.show("operators[" + field + "]"); |
||||
toggle_operator(field); |
||||
} else { |
||||
Element.hide("operators[" + field + "]"); |
||||
Element.hide("values_div[" + field + "]"); |
||||
} |
||||
} |
||||
|
||||
function toggle_operator(field) { |
||||
operator = $("operators[" + field + "]"); |
||||
switch (operator.value) { |
||||
case "!*": |
||||
case "*": |
||||
case "t": |
||||
case "o": |
||||
case "c": |
||||
Element.hide("values_div[" + field + "]"); |
||||
break; |
||||
default: |
||||
Element.show("values_div[" + field + "]"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
function toggle_multi_select(field) { |
||||
select = $('values[' + field + '][]'); |
||||
if (select.multiple == true) { |
||||
select.multiple = false; |
||||
} else { |
||||
select.multiple = true; |
||||
} |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<fieldset style="margin:0;"><legend><%= l(:label_filter_plural) %></legend> |
||||
<table width="100%" cellpadding=0 cellspacing=0> |
||||
<tr> |
||||
<td> |
||||
<table> |
||||
<% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %> |
||||
<% field = filter[0] |
||||
options = filter[1] %> |
||||
<tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>"> |
||||
<td valign="top" width="200"> |
||||
<%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> |
||||
<label for="cb_<%= field %>"><%= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label> |
||||
</td> |
||||
<td valign="top" width="150"> |
||||
<%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %> |
||||
</td> |
||||
<td valign="top"> |
||||
<div id="values_div[<%= field %>]"> |
||||
<% case options[:type] |
||||
when :list, :list_optional, :list_status %> |
||||
<select <%= "multiple=true" if query.values_for(field) and query.values_for(field).length > 1 %>" name="values[<%= field %>][]" id="values[<%= field %>][]" class="select-small" style="vertical-align: top;"> |
||||
<%= options_for_select options[:values], query.values_for(field) %> |
||||
</select> |
||||
<%= link_to_function image_tag('expand'), "toggle_multi_select('#{field}');" %> |
||||
<% when :date, :date_past %> |
||||
<%= text_field_tag "values[#{field}][]", query.values_for(field), :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %> |
||||
<% when :text %> |
||||
<%= text_field_tag "values[#{field}][]", query.values_for(field), :size => 30, :class => "select-small" %> |
||||
<% end %> |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
<script>toggle_filter('<%= field %>');</script> |
||||
<% end %> |
||||
</table> |
||||
</td> |
||||
<td align="right" valign="top"> |
||||
<%= l(:label_filter_add) %>: |
||||
<%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [l(("field_"+field[0].to_s.gsub(/\_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), :onchange => "add_filter();", :class => "select-small" %> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
</fieldset> |
@ -0,0 +1,12 @@ |
||||
<%= error_messages_for 'query' %> |
||||
|
||||
<!--[form:query]--> |
||||
<div class="box"> |
||||
<div class="tabular"> |
||||
<p><label for="query_name"><%=l(:field_name)%></label> |
||||
<%= text_field 'query', 'name', :size => 80 %></p> |
||||
</div> |
||||
|
||||
<%= render :partial => 'queries/filters', :locals => {:query => query}%> |
||||
</div> |
||||
<!--[eoform:query]--> |
@ -0,0 +1,6 @@ |
||||
<h2><%= l(:label_query) %></h2> |
||||
|
||||
<%= start_form_tag :action => 'edit', :id => @query %> |
||||
<%= render :partial => 'form', :locals => {:query => @query} %> |
||||
<%= submit_tag l(:button_save) %> |
||||
<%= end_form_tag %> |
@ -0,0 +1,15 @@ |
||||
class CreateQueries < ActiveRecord::Migration |
||||
def self.up |
||||
create_table :queries, :force => true do |t| |
||||
t.column "project_id", :integer |
||||
t.column "name", :string, :default => "", :null => false |
||||
t.column "filters", :text |
||||
t.column "user_id", :integer, :default => 0, :null => false |
||||
t.column "is_public", :boolean, :default => false, :null => false |
||||
end |
||||
end |
||||
|
||||
def self.down |
||||
drop_table :queries |
||||
end |
||||
end |
@ -0,0 +1,9 @@ |
||||
class AddQueriesPermissions < ActiveRecord::Migration |
||||
def self.up |
||||
Permission.create :controller => "projects", :action => "add_query", :description => "button_create", :sort => 600, :is_public => false, :mail_option => 0, :mail_enabled => 0 |
||||
end |
||||
|
||||
def self.down |
||||
Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'add_query']).destroy |
||||
end |
||||
end |
After Width: | Height: | Size: 222 B |
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 238 B |
After Width: | Height: | Size: 266 B |
Loading…
Reference in new issue