@@ -0,0 +1,49 | |||
|
1 | # redMine - project management software | |
|
2 | # Copyright (C) 2006 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 | class QueriesController < ApplicationController | |
|
19 | layout 'base' | |
|
20 | before_filter :require_login, :find_query | |
|
21 | ||
|
22 | def edit | |
|
23 | if request.post? | |
|
24 | @query.filters = {} | |
|
25 | params[:fields].each do |field| | |
|
26 | @query.add_filter(field, params[:operators][field], params[:values][field]) | |
|
27 | end if params[:fields] | |
|
28 | @query.attributes = params[:query] | |
|
29 | ||
|
30 | if @query.save | |
|
31 | flash[:notice] = l(:notice_successful_update) | |
|
32 | redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query | |
|
33 | end | |
|
34 | end | |
|
35 | end | |
|
36 | ||
|
37 | def destroy | |
|
38 | @query.destroy if request.post? | |
|
39 | redirect_to :controller => 'reports', :action => 'issue_report', :id => @project | |
|
40 | end | |
|
41 | ||
|
42 | private | |
|
43 | def find_query | |
|
44 | @query = Query.find(params[:id]) | |
|
45 | @project = @query.project | |
|
46 | # check if user is allowed to manage queries (same permission as add_query) | |
|
47 | authorize('projects', 'add_query') | |
|
48 | end | |
|
49 | end |
@@ -0,0 +1,6 | |||
|
1 | module QueriesHelper | |
|
2 | ||
|
3 | def operators_for_select(filter_type) | |
|
4 | Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} | |
|
5 | end | |
|
6 | end |
@@ -0,0 +1,166 | |||
|
1 | # redMine - project management software | |
|
2 | # Copyright (C) 2006 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 | class Query < ActiveRecord::Base | |
|
19 | belongs_to :project | |
|
20 | belongs_to :user | |
|
21 | serialize :filters | |
|
22 | ||
|
23 | attr_protected :project, :user | |
|
24 | ||
|
25 | validates_presence_of :name, :on => :save | |
|
26 | ||
|
27 | @@operators = { "=" => :label_equals, | |
|
28 | "!" => :label_not_equals, | |
|
29 | "o" => :label_open_issues, | |
|
30 | "c" => :label_closed_issues, | |
|
31 | "!*" => :label_none, | |
|
32 | "*" => :label_all, | |
|
33 | "<t+" => :label_in_less_than, | |
|
34 | ">t+" => :label_in_more_than, | |
|
35 | "t+" => :label_in, | |
|
36 | "t" => :label_today, | |
|
37 | ">t-" => :label_less_than_ago, | |
|
38 | "<t-" => :label_more_than_ago, | |
|
39 | "t-" => :label_ago, | |
|
40 | "~" => :label_contains, | |
|
41 | "!~" => :label_not_contains } | |
|
42 | ||
|
43 | cattr_reader :operators | |
|
44 | ||
|
45 | @@operators_by_filter_type = { :list => [ "=", "!" ], | |
|
46 | :list_status => [ "o", "=", "!", "c", "*" ], | |
|
47 | :list_optional => [ "=", "!", "!*", "*" ], | |
|
48 | :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ], | |
|
49 | :date_past => [ ">t-", "<t-", "t-", "t" ], | |
|
50 | :text => [ "~", "!~" ] } | |
|
51 | ||
|
52 | cattr_reader :operators_by_filter_type | |
|
53 | ||
|
54 | def initialize(attributes = nil) | |
|
55 | super attributes | |
|
56 | self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } | |
|
57 | self.is_public = true | |
|
58 | end | |
|
59 | ||
|
60 | def validate | |
|
61 | filters.each_key do |field| | |
|
62 | errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless | |
|
63 | # filter requires one or more values | |
|
64 | (values_for(field) and !values_for(field).first.empty?) or | |
|
65 | # filter doesn't require any value | |
|
66 | ["o", "c", "!*", "*", "t"].include? operator_for(field) | |
|
67 | end if filters | |
|
68 | end | |
|
69 | ||
|
70 | def available_filters | |
|
71 | return @available_filters if @available_filters | |
|
72 | @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all).collect{|s| [s.name, s.id.to_s] } }, | |
|
73 | "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all).collect{|s| [s.name, s.id.to_s] } }, | |
|
74 | "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } }, | |
|
75 | "subject" => { :type => :text, :order => 7 }, | |
|
76 | "created_on" => { :type => :date_past, :order => 8 }, | |
|
77 | "updated_on" => { :type => :date_past, :order => 9 }, | |
|
78 | "start_date" => { :type => :date, :order => 10 }, | |
|
79 | "due_date" => { :type => :date, :order => 11 } } | |
|
80 | unless project.nil? | |
|
81 | # project specific filters | |
|
82 | @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => @project.users.collect{|s| [s.name, s.id.to_s] } } | |
|
83 | @available_filters["author_id"] = { :type => :list, :order => 5, :values => @project.users.collect{|s| [s.name, s.id.to_s] } } | |
|
84 | @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } | |
|
85 | # remove category filter if no category defined | |
|
86 | @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty? | |
|
87 | end | |
|
88 | @available_filters | |
|
89 | end | |
|
90 | ||
|
91 | def add_filter(field, operator, values) | |
|
92 | # values must be an array | |
|
93 | return unless values and values.is_a? Array # and !values.first.empty? | |
|
94 | # check if field is defined as an available filter | |
|
95 | if available_filters.has_key? field | |
|
96 | filter_options = available_filters[field] | |
|
97 | # check if operator is allowed for that filter | |
|
98 | #if @@operators_by_filter_type[filter_options[:type]].include? operator | |
|
99 | # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) | |
|
100 | # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator | |
|
101 | #end | |
|
102 | filters[field] = {:operator => operator, :values => values } | |
|
103 | end | |
|
104 | end | |
|
105 | ||
|
106 | def add_short_filter(field, expression) | |
|
107 | return unless expression | |
|
108 | parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first | |
|
109 | add_filter field, (parms[0] || "="), [parms[1] || ""] | |
|
110 | end | |
|
111 | ||
|
112 | def has_filter?(field) | |
|
113 | filters and filters[field] | |
|
114 | end | |
|
115 | ||
|
116 | def operator_for(field) | |
|
117 | has_filter?(field) ? filters[field][:operator] : nil | |
|
118 | end | |
|
119 | ||
|
120 | def values_for(field) | |
|
121 | has_filter?(field) ? filters[field][:values] : nil | |
|
122 | end | |
|
123 | ||
|
124 | def statement | |
|
125 | sql = "1=1" | |
|
126 | sql << " AND issues.project_id=%d" % project.id if project | |
|
127 | filters.each_key do |field| | |
|
128 | v = values_for field | |
|
129 | next unless v and !v.empty? | |
|
130 | sql = sql + " AND " unless sql.empty? | |
|
131 | case operator_for field | |
|
132 | when "=" | |
|
133 | sql = sql + "issues.#{field} IN (" + v.each(&:to_i).join(",") + ")" | |
|
134 | when "!" | |
|
135 | sql = sql + "issues.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")" | |
|
136 | when "!*" | |
|
137 | sql = sql + "issues.#{field} IS NULL" | |
|
138 | when "*" | |
|
139 | sql = sql + "issues.#{field} IS NOT NULL" | |
|
140 | when "o" | |
|
141 | sql = sql + "issue_statuses.is_closed=#{connection.quoted_false}" if field == "status_id" | |
|
142 | when "c" | |
|
143 | sql = sql + "issue_statuses.is_closed=#{connection.quoted_true}" if field == "status_id" | |
|
144 | when ">t-" | |
|
145 | sql = sql + "issues.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i) | |
|
146 | when "<t-" | |
|
147 | sql = sql + "issues.#{field} <= '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'" | |
|
148 | when "t-" | |
|
149 | sql = sql + "issues.#{field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'" | |
|
150 | when ">t+" | |
|
151 | sql = sql + "issues.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" | |
|
152 | when "<t+" | |
|
153 | sql = sql + "issues.#{field} <= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" | |
|
154 | when "t+" | |
|
155 | sql = sql + "issues.#{field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" | |
|
156 | when "t" | |
|
157 | sql = sql + "issues.#{field} = '%s'" % connection.quoted_date(Date.today) | |
|
158 | when "~" | |
|
159 | sql = sql + "issues.#{field} LIKE '%#{connection.quote_string(v.first)}%'" | |
|
160 | when "!~" | |
|
161 | sql = sql + "issues.#{field} NOT LIKE '%#{connection.quote_string(v.first)}%'" | |
|
162 | end | |
|
163 | end if filters and valid? | |
|
164 | sql | |
|
165 | end | |
|
166 | end |
@@ -0,0 +1,6 | |||
|
1 | <h2><%= l(:label_query_new) %></h2> | |
|
2 | ||
|
3 | <%= start_form_tag :action => 'add_query', :id => @project %> | |
|
4 | <%= render :partial => 'queries/form', :locals => {:query => @query} %> | |
|
5 | <%= submit_tag l(:button_create) %> | |
|
6 | <%= end_form_tag %> No newline at end of file |
@@ -0,0 +1,100 | |||
|
1 | <script> | |
|
2 | ||
|
3 | function add_filter() { | |
|
4 | select = $('add_filter_select'); | |
|
5 | field = select.value | |
|
6 | Element.show('tr_' + field); | |
|
7 | check_box = $('cb_' + field); | |
|
8 | check_box.checked = true; | |
|
9 | toggle_filter(field); | |
|
10 | select.selectedIndex = 0; | |
|
11 | ||
|
12 | for (i=0; i<select.options.length; i++) { | |
|
13 | if (select.options[i].value == field) { | |
|
14 | select.options[i].disabled = true; | |
|
15 | } | |
|
16 | } | |
|
17 | } | |
|
18 | ||
|
19 | function toggle_filter(field) { | |
|
20 | check_box = $('cb_' + field); | |
|
21 | ||
|
22 | if (check_box.checked) { | |
|
23 | Element.show("operators[" + field + "]"); | |
|
24 | toggle_operator(field); | |
|
25 | } else { | |
|
26 | Element.hide("operators[" + field + "]"); | |
|
27 | Element.hide("values_div[" + field + "]"); | |
|
28 | } | |
|
29 | } | |
|
30 | ||
|
31 | function toggle_operator(field) { | |
|
32 | operator = $("operators[" + field + "]"); | |
|
33 | switch (operator.value) { | |
|
34 | case "!*": | |
|
35 | case "*": | |
|
36 | case "t": | |
|
37 | case "o": | |
|
38 | case "c": | |
|
39 | Element.hide("values_div[" + field + "]"); | |
|
40 | break; | |
|
41 | default: | |
|
42 | Element.show("values_div[" + field + "]"); | |
|
43 | break; | |
|
44 | } | |
|
45 | } | |
|
46 | ||
|
47 | function toggle_multi_select(field) { | |
|
48 | select = $('values[' + field + '][]'); | |
|
49 | if (select.multiple == true) { | |
|
50 | select.multiple = false; | |
|
51 | } else { | |
|
52 | select.multiple = true; | |
|
53 | } | |
|
54 | } | |
|
55 | ||
|
56 | </script> | |
|
57 | ||
|
58 | <fieldset style="margin:0;"><legend><%= l(:label_filter_plural) %></legend> | |
|
59 | <table width="100%" cellpadding=0 cellspacing=0> | |
|
60 | <tr> | |
|
61 | <td> | |
|
62 | <table> | |
|
63 | <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %> | |
|
64 | <% field = filter[0] | |
|
65 | options = filter[1] %> | |
|
66 | <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>"> | |
|
67 | <td valign="top" width="200"> | |
|
68 | <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> | |
|
69 | <label for="cb_<%= field %>"><%= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label> | |
|
70 | </td> | |
|
71 | <td valign="top" width="150"> | |
|
72 | <%= 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;" %> | |
|
73 | </td> | |
|
74 | <td valign="top"> | |
|
75 | <div id="values_div[<%= field %>]"> | |
|
76 | <% case options[:type] | |
|
77 | when :list, :list_optional, :list_status %> | |
|
78 | <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;"> | |
|
79 | <%= options_for_select options[:values], query.values_for(field) %> | |
|
80 | </select> | |
|
81 | <%= link_to_function image_tag('expand'), "toggle_multi_select('#{field}');" %> | |
|
82 | <% when :date, :date_past %> | |
|
83 | <%= text_field_tag "values[#{field}][]", query.values_for(field), :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %> | |
|
84 | <% when :text %> | |
|
85 | <%= text_field_tag "values[#{field}][]", query.values_for(field), :size => 30, :class => "select-small" %> | |
|
86 | <% end %> | |
|
87 | </div> | |
|
88 | </td> | |
|
89 | </tr> | |
|
90 | <script>toggle_filter('<%= field %>');</script> | |
|
91 | <% end %> | |
|
92 | </table> | |
|
93 | </td> | |
|
94 | <td align="right" valign="top"> | |
|
95 | <%= l(:label_filter_add) %>: | |
|
96 | <%= 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" %> | |
|
97 | </td> | |
|
98 | </tr> | |
|
99 | </table> | |
|
100 | </fieldset> No newline at end of file |
@@ -0,0 +1,12 | |||
|
1 | <%= error_messages_for 'query' %> | |
|
2 | ||
|
3 | <!--[form:query]--> | |
|
4 | <div class="box"> | |
|
5 | <div class="tabular"> | |
|
6 | <p><label for="query_name"><%=l(:field_name)%></label> | |
|
7 | <%= text_field 'query', 'name', :size => 80 %></p> | |
|
8 | </div> | |
|
9 | ||
|
10 | <%= render :partial => 'queries/filters', :locals => {:query => query}%> | |
|
11 | </div> | |
|
12 | <!--[eoform:query]--> No newline at end of file |
@@ -0,0 +1,6 | |||
|
1 | <h2><%= l(:label_query) %></h2> | |
|
2 | ||
|
3 | <%= start_form_tag :action => 'edit', :id => @query %> | |
|
4 | <%= render :partial => 'form', :locals => {:query => @query} %> | |
|
5 | <%= submit_tag l(:button_save) %> | |
|
6 | <%= end_form_tag %> No newline at end of file |
@@ -0,0 +1,15 | |||
|
1 | class CreateQueries < ActiveRecord::Migration | |
|
2 | def self.up | |
|
3 | create_table :queries, :force => true do |t| | |
|
4 | t.column "project_id", :integer | |
|
5 | t.column "name", :string, :default => "", :null => false | |
|
6 | t.column "filters", :text | |
|
7 | t.column "user_id", :integer, :default => 0, :null => false | |
|
8 | t.column "is_public", :boolean, :default => false, :null => false | |
|
9 | end | |
|
10 | end | |
|
11 | ||
|
12 | def self.down | |
|
13 | drop_table :queries | |
|
14 | end | |
|
15 | end |
@@ -0,0 +1,9 | |||
|
1 | class AddQueriesPermissions < ActiveRecord::Migration | |
|
2 | def self.up | |
|
3 | Permission.create :controller => "projects", :action => "add_query", :description => "button_create", :sort => 600, :is_public => false, :mail_option => 0, :mail_enabled => 0 | |
|
4 | end | |
|
5 | ||
|
6 | def self.down | |
|
7 | Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'add_query']).destroy | |
|
8 | end | |
|
9 | end |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
@@ -71,9 +71,9 class ApplicationController < ActionController::Base | |||
|
71 | 71 | end |
|
72 | 72 | |
|
73 | 73 | # authorizes the user for the requested action. |
|
74 | def authorize | |
|
74 | def authorize(ctrl = @params[:controller], action = @params[:action]) | |
|
75 | 75 | # check if action is allowed on public projects |
|
76 |
if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ |
|
|
76 | if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ ctrl, action ] | |
|
77 | 77 | return true |
|
78 | 78 | end |
|
79 | 79 | # if action is not public, force login |
@@ -82,7 +82,7 class ApplicationController < ActionController::Base | |||
|
82 | 82 | return true if self.logged_in_user.admin? |
|
83 | 83 | # if not admin, check membership permission |
|
84 | 84 | @user_membership ||= Member.find(:first, :conditions => ["user_id=? and project_id=?", self.logged_in_user.id, @project.id]) |
|
85 |
if @user_membership and Permission.allowed_to_role( "%s/%s" % [ |
|
|
85 | if @user_membership and Permission.allowed_to_role( "%s/%s" % [ ctrl, action ], @user_membership.role_id ) | |
|
86 | 86 | return true |
|
87 | 87 | end |
|
88 | 88 | render :nothing => true, :status => 403 |
@@ -16,19 +16,19 | |||
|
16 | 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
17 | 17 | |
|
18 | 18 | class ProjectsController < ApplicationController |
|
19 | layout 'base', :except => :export_issues_pdf | |
|
19 | layout 'base' | |
|
20 | 20 | before_filter :find_project, :authorize, :except => [ :index, :list, :add ] |
|
21 | 21 | before_filter :require_admin, :only => [ :add, :destroy ] |
|
22 | 22 | |
|
23 | 23 | helper :sort |
|
24 | 24 |
include SortHelper |
|
25 | helper :search_filter | |
|
26 | include SearchFilterHelper | |
|
27 | 25 | helper :custom_fields |
|
28 | 26 | include CustomFieldsHelper |
|
29 | 27 | helper :ifpdf |
|
30 | 28 | include IfpdfHelper |
|
31 | 29 | helper IssuesHelper |
|
30 | helper :queries | |
|
31 | include QueriesHelper | |
|
32 | 32 | |
|
33 | 33 | def index |
|
34 | 34 | list |
@@ -208,8 +208,7 class ProjectsController < ApplicationController | |||
|
208 | 208 | sort_init 'issues.id', 'desc' |
|
209 | 209 | sort_update |
|
210 | 210 | |
|
211 | search_filter_init_list_issues | |
|
212 | search_filter_update if params[:set_filter] | |
|
211 | retrieve_query | |
|
213 | 212 | |
|
214 | 213 | @results_per_page_options = [ 15, 25, 50, 100 ] |
|
215 | 214 | if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i |
@@ -219,14 +218,15 class ProjectsController < ApplicationController | |||
|
219 | 218 | @results_per_page = session[:results_per_page] || 25 |
|
220 | 219 | end |
|
221 | 220 | |
|
222 | @issue_count = Issue.count(:include => [:status, :project], :conditions => search_filter_clause) | |
|
221 | if @query.valid? | |
|
222 | @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) | |
|
223 | 223 | @issue_pages = Paginator.new self, @issue_count, @results_per_page, @params['page'] |
|
224 | 224 | @issues = Issue.find :all, :order => sort_clause, |
|
225 | 225 | :include => [ :author, :status, :tracker, :project ], |
|
226 |
:conditions => |
|
|
226 | :conditions => @query.statement, | |
|
227 | 227 | :limit => @issue_pages.items_per_page, |
|
228 | 228 | :offset => @issue_pages.current.offset |
|
229 | ||
|
229 | end | |
|
230 | 230 | render :layout => false if request.xhr? |
|
231 | 231 | end |
|
232 | 232 | |
@@ -235,11 +235,12 class ProjectsController < ApplicationController | |||
|
235 | 235 | sort_init 'issues.id', 'desc' |
|
236 | 236 | sort_update |
|
237 | 237 | |
|
238 | search_filter_init_list_issues | |
|
238 | retrieve_query | |
|
239 | render :action => 'list_issues' and return unless @query.valid? | |
|
239 | 240 | |
|
240 | 241 | @issues = Issue.find :all, :order => sort_clause, |
|
241 | 242 | :include => [ :author, :status, :tracker, :project, :custom_values ], |
|
242 |
:conditions => |
|
|
243 | :conditions => @query.statement | |
|
243 | 244 | |
|
244 | 245 | ic = Iconv.new('ISO-8859-1', 'UTF-8') |
|
245 | 246 | export = StringIO.new |
@@ -268,14 +269,16 class ProjectsController < ApplicationController | |||
|
268 | 269 | sort_init 'issues.id', 'desc' |
|
269 | 270 | sort_update |
|
270 | 271 | |
|
271 | search_filter_init_list_issues | |
|
272 | retrieve_query | |
|
273 | render :action => 'list_issues' and return unless @query.valid? | |
|
272 | 274 | |
|
273 | 275 | @issues = Issue.find :all, :order => sort_clause, |
|
274 | 276 | :include => [ :author, :status, :tracker, :project, :custom_values ], |
|
275 |
:conditions => |
|
|
277 | :conditions => @query.statement | |
|
276 | 278 | |
|
277 | 279 | @options_for_rfpdf ||= {} |
|
278 | 280 | @options_for_rfpdf[:file_name] = "export.pdf" |
|
281 | render :layout => false | |
|
279 | 282 | end |
|
280 | 283 | |
|
281 | 284 | def move_issues |
@@ -302,6 +305,22 class ProjectsController < ApplicationController | |||
|
302 | 305 | end |
|
303 | 306 | end |
|
304 | 307 | |
|
308 | def add_query | |
|
309 | @query = Query.new(params[:query]) | |
|
310 | @query.project = @project | |
|
311 | @query.user = logged_in_user | |
|
312 | ||
|
313 | params[:fields].each do |field| | |
|
314 | @query.add_filter(field, params[:operators][field], params[:values][field]) | |
|
315 | end if params[:fields] | |
|
316 | ||
|
317 | if request.post? and @query.save | |
|
318 | flash[:notice] = l(:notice_successful_create) | |
|
319 | redirect_to :controller => 'reports', :action => 'issue_report', :id => @project | |
|
320 | end | |
|
321 | render :layout => false if request.xhr? | |
|
322 | end | |
|
323 | ||
|
305 | 324 | # Add a news to @project |
|
306 | 325 | def add_news |
|
307 | 326 | @news = News.new(:project => @project) |
@@ -471,4 +490,29 private | |||
|
471 | 490 | rescue |
|
472 | 491 | redirect_to :action => 'list' |
|
473 | 492 | end |
|
493 | ||
|
494 | # Retrieve query from session or build a new query | |
|
495 | def retrieve_query | |
|
496 | if params[:query_id] | |
|
497 | @query = @project.queries.find(params[:query_id]) | |
|
498 | else | |
|
499 | if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id | |
|
500 | # Give it a name, required to be valid | |
|
501 | @query = Query.new(:name => "_") | |
|
502 | @query.project = @project | |
|
503 | if params[:fields] and params[:fields].is_a? Array | |
|
504 | params[:fields].each do |field| | |
|
505 | @query.add_filter(field, params[:operators][field], params[:values][field]) | |
|
506 | end | |
|
507 | else | |
|
508 | @query.available_filters.keys.each do |field| | |
|
509 | @query.add_short_filter(field, params[field]) if params[field] | |
|
510 | end | |
|
511 | end | |
|
512 | session[:query] = @query | |
|
513 | else | |
|
514 | @query = session[:query] | |
|
515 | end | |
|
516 | end | |
|
517 | end | |
|
474 | 518 | end |
@@ -48,6 +48,7 class ReportsController < ApplicationController | |||
|
48 | 48 | @report_title = l(:field_author) |
|
49 | 49 | render :template => "reports/issue_report_details" |
|
50 | 50 | else |
|
51 | @queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)] | |
|
51 | 52 | @trackers = Tracker.find(:all) |
|
52 | 53 | @priorities = Enumeration::get_values('IPRI') |
|
53 | 54 | @categories = @project.issue_categories |
@@ -25,6 +25,7 class Permission < ActiveRecord::Base | |||
|
25 | 25 | 200 => :label_member_plural, |
|
26 | 26 | 300 => :label_version_plural, |
|
27 | 27 | 400 => :label_issue_category_plural, |
|
28 | 600 => :label_query_plural, | |
|
28 | 29 | 1000 => :label_issue_plural, |
|
29 | 30 | 1100 => :label_news_plural, |
|
30 | 31 | 1200 => :label_document_plural, |
@@ -21,6 +21,7 class Project < ActiveRecord::Base | |||
|
21 | 21 | has_many :users, :through => :members |
|
22 | 22 | has_many :custom_values, :dependent => true, :as => :customized |
|
23 | 23 | has_many :issues, :dependent => true, :order => "issues.created_on DESC", :include => :status |
|
24 | has_many :queries, :dependent => true | |
|
24 | 25 | has_many :documents, :dependent => true |
|
25 | 26 | has_many :news, :dependent => true, :include => :author |
|
26 | 27 | has_many :issue_categories, :dependent => true, :order => "issue_categories.name" |
@@ -1,33 +1,39 | |||
|
1 | <div class="contextual"> | |
|
2 | <%= l(:label_export_to) %> | |
|
3 | <%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'pic picCsv' %>, | |
|
4 | <%= link_to 'PDF', {:action => 'export_issues_pdf', :id => @project}, :class => 'pic picPdf' %> | |
|
5 | </div> | |
|
6 | ||
|
1 | <% if @query.new_record? %> | |
|
7 | 2 | <h2><%=l(:label_issue_plural)%></h2> |
|
8 | 3 | |
|
9 |
<%= start_form_tag |
|
|
10 | <table cellpadding=2> | |
|
11 | <tr> | |
|
12 | <td valign="bottom"><small><%=l(:field_status)%>:</small><br /><%= search_filter_tag 'status_id', :class => 'select-small' %></td> | |
|
13 | <td valign="bottom"><small><%=l(:field_tracker)%>:</small><br /><%= search_filter_tag 'tracker_id', :class => 'select-small' %></td> | |
|
14 | <td valign="bottom"><small><%=l(:field_priority)%>:</small><br /><%= search_filter_tag 'priority_id', :class => 'select-small' %></td> | |
|
15 | <td valign="bottom"><small><%=l(:field_category)%>:</small><br /><%= search_filter_tag 'category_id', :class => 'select-small' %></td> | |
|
16 | <td valign="bottom"><small><%=l(:field_fixed_version)%>:</small><br /><%= search_filter_tag 'fixed_version_id', :class => 'select-small' %></td> | |
|
17 | <td valign="bottom"><small><%=l(:field_author)%>:</small><br /><%= search_filter_tag 'author_id', :class => 'select-small' %></td> | |
|
18 | <td valign="bottom"><small><%=l(:field_assigned_to)%>:</small><br /><%= search_filter_tag 'assigned_to_id', :class => 'select-small' %></td> | |
|
19 | <td valign="bottom"><small><%=l(:label_subproject_plural)%>:</small><br /><%= search_filter_tag 'subproject_id', :class => 'select-small' %></td> | |
|
20 | <td valign="bottom"> | |
|
21 | <%= hidden_field_tag 'set_filter', 1 %> | |
|
22 | <%= submit_tag l(:button_apply), :class => 'button-small' %> | |
|
23 | </td> | |
|
24 | <td valign="bottom"> | |
|
25 | <%= link_to l(:button_clear), :action => 'list_issues', :id => @project, :set_filter => 1 %> | |
|
26 | </td> | |
|
27 | </tr> | |
|
28 | </table> | |
|
4 | <%= start_form_tag({:action => 'list_issues'}, :id => 'query_form') %> | |
|
5 | <%= render :partial => 'queries/filters', :locals => {:query => @query} %> | |
|
29 | 6 | <%= end_form_tag %> |
|
7 | <div class="contextual"> | |
|
8 | <%= link_to_remote l(:button_apply), | |
|
9 | { :url => { :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 }, | |
|
10 | :update => "content", | |
|
11 | :with => "Form.serialize('query_form')" | |
|
12 | }, :class => 'pic picCheck' %> | |
|
30 | 13 | |
|
14 | <%= link_to l(:button_clear), {:controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1}, :class => 'pic picDelete' %> | |
|
15 | <% if authorize_for('projects', 'add_query') %> | |
|
16 | ||
|
17 | <%= link_to_remote l(:button_save), | |
|
18 | { :url => { :controller => 'projects', :action => "add_query", :id => @project }, | |
|
19 | :method => 'get', | |
|
20 | :update => "content", | |
|
21 | :with => "Form.serialize('query_form')" | |
|
22 | }, :class => 'pic picEdit' %> | |
|
23 | <% end %> | |
|
24 | </div> | |
|
25 | <br /> | |
|
26 | <% else %> | |
|
27 | <% if authorize_for('projects', 'add_query') %> | |
|
28 | <div class="contextual"> | |
|
29 | <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'pic picEdit' %> | |
|
30 | <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :post => true, :class => 'pic picDelete' %> | |
|
31 | </div> | |
|
32 | <% end %> | |
|
33 | <h2><%= @query.name %></h2> | |
|
34 | <% end %> | |
|
35 | <%= error_messages_for 'query' %> | |
|
36 | <% if @query.valid? %> | |
|
31 | 37 |
|
|
32 | 38 | <table class="listTableContent"> |
|
33 | 39 | <tr> |
@@ -67,9 +73,15 | |||
|
67 | 73 | </tr> |
|
68 | 74 | <% end %> |
|
69 | 75 | </table> |
|
76 | <div class="contextual"> | |
|
77 | <%= l(:label_export_to) %> | |
|
78 | <%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'pic picCsv' %>, | |
|
79 | <%= link_to 'PDF', {:action => 'export_issues_pdf', :id => @project}, :class => 'pic picPdf' %> | |
|
80 | </div> | |
|
70 | 81 | <p> |
|
71 | 82 | <%= pagination_links_full @issue_pages %> |
|
72 | 83 | [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ] |
|
73 | 84 | </p> |
|
74 | 85 | <%= submit_tag l(:button_move) %> |
|
75 | 86 | <%= end_form_tag %> |
|
87 | <% end %> No newline at end of file |
@@ -29,17 +29,17 | |||
|
29 | 29 | :controller => 'projects', :action => 'list_issues', :id => @project, |
|
30 | 30 | :set_filter => 1, |
|
31 | 31 | "#{field_name}" => row.id, |
|
32 |
"status_id" => " |
|
|
32 | "status_id" => "o" %></td> | |
|
33 | 33 | <td align="center"><%= link_to (aggregate data, { field_name => row.id, "closed" => 1 }), |
|
34 | 34 | :controller => 'projects', :action => 'list_issues', :id => @project, |
|
35 | 35 | :set_filter => 1, |
|
36 | 36 | "#{field_name}" => row.id, |
|
37 |
"status_id" => " |
|
|
37 | "status_id" => "c" %></td> | |
|
38 | 38 | <td align="center"><%= link_to (aggregate data, { field_name => row.id }), |
|
39 | 39 | :controller => 'projects', :action => 'list_issues', :id => @project, |
|
40 | 40 | :set_filter => 1, |
|
41 | 41 | "#{field_name}" => row.id, |
|
42 |
"status_id" => " |
|
|
42 | "status_id" => "*" %></td> | |
|
43 | 43 | <% end %> |
|
44 | 44 | </tr> |
|
45 | 45 | </table> |
@@ -18,17 +18,17 | |||
|
18 | 18 | :controller => 'projects', :action => 'list_issues', :id => @project, |
|
19 | 19 | :set_filter => 1, |
|
20 | 20 | "#{field_name}" => row.id, |
|
21 |
"status_id" => " |
|
|
21 | "status_id" => "o" %></td> | |
|
22 | 22 | <td align="center"><%= link_to (aggregate data, { field_name => row.id, "closed" => 1 }), |
|
23 | 23 | :controller => 'projects', :action => 'list_issues', :id => @project, |
|
24 | 24 | :set_filter => 1, |
|
25 | 25 | "#{field_name}" => row.id, |
|
26 |
"status_id" => " |
|
|
26 | "status_id" => "c" %></td> | |
|
27 | 27 | <td align="center"><%= link_to (aggregate data, { field_name => row.id }), |
|
28 | 28 | :controller => 'projects', :action => 'list_issues', :id => @project, |
|
29 | 29 | :set_filter => 1, |
|
30 | 30 | "#{field_name}" => row.id, |
|
31 |
"status_id" => " |
|
|
31 | "status_id" => "*" %></td> | |
|
32 | 32 | <% end %> |
|
33 | 33 | </tr> |
|
34 | 34 | </table> |
@@ -1,18 +1,30 | |||
|
1 | 1 | <h2><%=l(:label_report_plural)%></h2> |
|
2 | 2 | |
|
3 | <h3><%= l(:label_query_plural) %></h3> | |
|
4 | <div class="contextual"> | |
|
5 | <%= link_to_if_authorized l(:label_query_new), {:controller => 'projects', :action => 'add_query', :id => @project}, :class => 'pic picAdd' %> | |
|
6 | </div> | |
|
7 | ||
|
8 | <% if @queries.empty? %><p><i><%=l(:label_no_data)%></i></p><% end %> | |
|
9 | <ul> | |
|
10 | <% @queries.each do |query| %> | |
|
11 | <li><%= link_to query.name, :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => query %></li> | |
|
12 | <% end %> | |
|
13 | </ul> | |
|
14 | ||
|
3 | 15 | <div class="splitcontentleft"> |
|
4 |
<h3><%=l(:field_tracker)%> <%= link_to image_tag('details'), :detail => ' |
|
|
16 | <h3><%=l(:field_tracker)%> <%= link_to image_tag('details'), :detail => 'tracker' %></h3> | |
|
5 | 17 | <%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %> |
|
6 | 18 | <br /> |
|
7 | <h3><%=l(:field_priority)%> <%= link_to image_tag('details'), :detail => 'priority' %></h3> | |
|
8 | <%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %> | |
|
9 | <br /> | |
|
10 | 19 | <h3><%=l(:field_author)%> <%= link_to image_tag('details'), :detail => 'author' %></h3> |
|
11 | 20 | <%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %> |
|
12 | 21 | <br /> |
|
13 | 22 | </div> |
|
14 | 23 | |
|
15 | 24 | <div class="splitcontentright"> |
|
25 | <h3><%=l(:field_priority)%> <%= link_to image_tag('details'), :detail => 'priority' %></h3> | |
|
26 | <%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %> | |
|
27 | <br /> | |
|
16 | 28 | <h3><%=l(:field_category)%> <%= link_to image_tag('details'), :detail => 'category' %></h3> |
|
17 | 29 | <%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %> |
|
18 | 30 | <br /> |
@@ -265,6 +265,23 label_comment_plural: Anmerkungen | |||
|
265 | 265 | label_comment_add: Anmerkung addieren |
|
266 | 266 | label_comment_added: Anmerkung fügte hinzu |
|
267 | 267 | label_comment_delete: Anmerkungen löschen |
|
268 | label_query: Benutzerdefiniertes Frage | |
|
269 | label_query_plural: Benutzerdefinierte Fragen | |
|
270 | label_query_new: Neue Frage | |
|
271 | label_filter_add: Filter addieren | |
|
272 | label_filter_plural: Filter | |
|
273 | label_equals: ist | |
|
274 | label_not_equals: ist nicht | |
|
275 | label_in_less_than: an weniger als | |
|
276 | label_in_more_than: an mehr als | |
|
277 | label_in: an | |
|
278 | label_today: heute | |
|
279 | label_less_than_ago: vor weniger als | |
|
280 | label_more_than_ago: vor mehr als | |
|
281 | label_ago: vor | |
|
282 | label_contains: enthält | |
|
283 | label_not_contains: enthält nicht | |
|
284 | label_day_plural: Tage | |
|
268 | 285 | |
|
269 | 286 | button_login: Einloggen |
|
270 | 287 | button_submit: Einreichen |
@@ -265,6 +265,23 label_comment_plural: Comments | |||
|
265 | 265 | label_comment_add: Add a comment |
|
266 | 266 | label_comment_added: Comment added |
|
267 | 267 | label_comment_delete: Delete comments |
|
268 | label_query: Custom query | |
|
269 | label_query_plural: Custom queries | |
|
270 | label_query_new: New query | |
|
271 | label_filter_add: Add filter | |
|
272 | label_filter_plural: Filters | |
|
273 | label_equals: is | |
|
274 | label_not_equals: is not | |
|
275 | label_in_less_than: in less than | |
|
276 | label_in_more_than: in more than | |
|
277 | label_in: in | |
|
278 | label_today: today | |
|
279 | label_less_than_ago: less than days ago | |
|
280 | label_more_than_ago: more than days ago | |
|
281 | label_ago: days ago | |
|
282 | label_contains: contains | |
|
283 | label_not_contains: doesn't contain | |
|
284 | label_day_plural: days | |
|
268 | 285 | |
|
269 | 286 | button_login: Login |
|
270 | 287 | button_submit: Submit |
@@ -265,6 +265,23 label_comment_plural: Comentarios | |||
|
265 | 265 | label_comment_add: Agregar un comentario |
|
266 | 266 | label_comment_added: Comentario agregó |
|
267 | 267 | label_comment_delete: Suprimir comentarios |
|
268 | label_query: Pregunta personalizada | |
|
269 | label_query_plural: Preguntas personalizadas | |
|
270 | label_query_new: Nueva preguntas | |
|
271 | label_filter_add: Agregar el filtro | |
|
272 | label_filter_plural: Filtros | |
|
273 | label_equals: igual | |
|
274 | label_not_equals: no igual | |
|
275 | label_in_less_than: en menos que | |
|
276 | label_in_more_than: en más que | |
|
277 | label_in: en | |
|
278 | label_today: hoy | |
|
279 | label_less_than_ago: hace menos de | |
|
280 | label_more_than_ago: hace más de | |
|
281 | label_ago: hace | |
|
282 | label_contains: contiene | |
|
283 | label_not_contains: no contiene | |
|
284 | label_day_plural: días | |
|
268 | 285 | |
|
269 | 286 | button_login: Conexión |
|
270 | 287 | button_submit: Someter |
@@ -238,16 +238,16 label_confirmation: Confirmation | |||
|
238 | 238 | label_export_to: Exporter en |
|
239 | 239 | label_read: Lire... |
|
240 | 240 | label_public_projects: Projets publics |
|
241 |
label_open_issues: |
|
|
242 |
label_open_issues_plural: |
|
|
243 |
label_closed_issues: |
|
|
244 |
label_closed_issues_plural: |
|
|
241 | label_open_issues: ouvert | |
|
242 | label_open_issues_plural: ouverts | |
|
243 | label_closed_issues: fermé | |
|
244 | label_closed_issues_plural: fermés | |
|
245 | 245 | label_total: Total |
|
246 | 246 | label_permissions: Permissions |
|
247 | 247 | label_current_status: Statut actuel |
|
248 | 248 | label_new_statuses_allowed: Nouveaux statuts autorisés |
|
249 |
label_all: |
|
|
250 |
label_none: |
|
|
249 | label_all: tous | |
|
250 | label_none: aucun | |
|
251 | 251 | label_next: Suivant |
|
252 | 252 | label_previous: Précédent |
|
253 | 253 | label_used_by: Utilisé par |
@@ -266,6 +266,23 label_comment_plural: Commentaires | |||
|
266 | 266 | label_comment_add: Ajouter un commentaire |
|
267 | 267 | label_comment_added: Commentaire ajouté |
|
268 | 268 | label_comment_delete: Supprimer les commentaires |
|
269 | label_query: Rapport personnalisé | |
|
270 | label_query_plural: Rapports personnalisés | |
|
271 | label_query_new: Nouveau rapport | |
|
272 | label_filter_add: Ajouter le filtre | |
|
273 | label_filter_plural: Filtres | |
|
274 | label_equals: égal | |
|
275 | label_not_equals: différent | |
|
276 | label_in_less_than: dans moins de | |
|
277 | label_in_more_than: dans plus de | |
|
278 | label_in: dans | |
|
279 | label_today: aujourd'hui | |
|
280 | label_less_than_ago: il y a moins de | |
|
281 | label_more_than_ago: il y a plus de | |
|
282 | label_ago: il y a | |
|
283 | label_contains: contient | |
|
284 | label_not_contains: ne contient pas | |
|
285 | label_day_plural: jours | |
|
269 | 286 | |
|
270 | 287 | button_login: Connexion |
|
271 | 288 | button_submit: Soumettre |
|
1 | NO CONTENT: file renamed from public/images/edit_small.png to public/images/edit.png |
@@ -128,10 +128,11 background-color: #80b0da; | |||
|
128 | 128 | .picLogout { background: url(../images/logout.png) no-repeat 4px 50%; } |
|
129 | 129 | .picHelp { background: url(../images/help.png) no-repeat 4px 50%; } |
|
130 | 130 | |
|
131 |
.picEdit { background: url(../images/edit |
|
|
131 | .picEdit { background: url(../images/edit.png) no-repeat 4px 50%; } | |
|
132 | 132 | .picDelete { background: url(../images/delete.png) no-repeat 4px 50%; } |
|
133 | 133 | .picAdd { background: url(../images/add.png) no-repeat 4px 50%; } |
|
134 | 134 | .picMove { background: url(../images/move.png) no-repeat 4px 50%; } |
|
135 | .picCheck { background: url(../images/check.png) no-repeat 4px 70%; } | |
|
135 | 136 | .picPdf { background: url(../images/pdf.png) no-repeat 4px 50%;} |
|
136 | 137 | .picCsv { background: url(../images/csv.png) no-repeat 4px 50%;} |
|
137 | 138 | |
@@ -221,7 +222,7 select { | |||
|
221 | 222 | vertical-align: middle; |
|
222 | 223 | } |
|
223 | 224 | |
|
224 |
|
|
|
225 | .select-small | |
|
225 | 226 | { |
|
226 | 227 | border: 1px solid #7F9DB9; |
|
227 | 228 | padding: 1px; |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now