##// END OF EJS Templates
Makes custom queries available through the REST API (#5737)....
Jean-Philippe Lang -
r6066:d48ea908761d
parent child
Show More
@@ -0,0 +1,10
1 api.array :queries, api_meta(:total_count => @query_count, :offset => @offset, :limit => @limit) do
2 @queries.each do |query|
3 api.query do
4 api.id query.id
5 api.name query.name
6 api.is_public query.is_public
7 api.project_id query.project_id
8 end
9 end
10 end
@@ -0,0 +1,55
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 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 require File.expand_path('../../../test_helper', __FILE__)
19
20 class ApiTest::QueriesTest < ActionController::IntegrationTest
21 fixtures :all
22
23 def setup
24 Setting.rest_api_enabled = '1'
25 end
26
27 context "/queries" do
28 context "GET" do
29
30 should "return queries" do
31 get '/queries.xml'
32
33 assert_response :success
34 assert_equal 'application/xml', @response.content_type
35 assert_tag :tag => 'queries',
36 :attributes => {:type => 'array'},
37 :child => {
38 :tag => 'query',
39 :child => {
40 :tag => 'id',
41 :content => '4',
42 :sibling => {
43 :tag => 'name',
44 :content => 'Public query for all projects'
45 }
46 }
47 }
48 end
49 end
50 end
51
52 def credentials(user, password=nil)
53 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
54 end
55 end
@@ -1,80 +1,100
1 # redMine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueriesController < ApplicationController
18 class QueriesController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20 before_filter :find_query, :except => :new
20 before_filter :find_query, :except => [:new, :index]
21 before_filter :find_optional_project, :only => :new
21 before_filter :find_optional_project, :only => :new
22
22
23 accept_key_auth :index
24
25 def index
26 case params[:format]
27 when 'xml', 'json'
28 @offset, @limit = api_offset_and_limit
29 else
30 @limit = per_page_option
31 end
32
33 @query_count = Query.visible.count
34 @query_pages = Paginator.new self, @query_count, @limit, params['page']
35 @queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
36
37 respond_to do |format|
38 format.html { render :nothing => true }
39 format.api
40 end
41 end
42
23 def new
43 def new
24 @query = Query.new(params[:query])
44 @query = Query.new(params[:query])
25 @query.project = params[:query_is_for_all] ? nil : @project
45 @query.project = params[:query_is_for_all] ? nil : @project
26 @query.user = User.current
46 @query.user = User.current
27 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
47 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
28
48
29 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
49 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
30 @query.group_by ||= params[:group_by]
50 @query.group_by ||= params[:group_by]
31 @query.column_names = params[:c] if params[:c]
51 @query.column_names = params[:c] if params[:c]
32 @query.column_names = nil if params[:default_columns]
52 @query.column_names = nil if params[:default_columns]
33
53
34 if request.post? && params[:confirm] && @query.save
54 if request.post? && params[:confirm] && @query.save
35 flash[:notice] = l(:notice_successful_create)
55 flash[:notice] = l(:notice_successful_create)
36 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
56 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
37 return
57 return
38 end
58 end
39 render :layout => false if request.xhr?
59 render :layout => false if request.xhr?
40 end
60 end
41
61
42 def edit
62 def edit
43 if request.post?
63 if request.post?
44 @query.filters = {}
64 @query.filters = {}
45 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
65 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
46 @query.attributes = params[:query]
66 @query.attributes = params[:query]
47 @query.project = nil if params[:query_is_for_all]
67 @query.project = nil if params[:query_is_for_all]
48 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
68 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
49 @query.group_by ||= params[:group_by]
69 @query.group_by ||= params[:group_by]
50 @query.column_names = params[:c] if params[:c]
70 @query.column_names = params[:c] if params[:c]
51 @query.column_names = nil if params[:default_columns]
71 @query.column_names = nil if params[:default_columns]
52
72
53 if @query.save
73 if @query.save
54 flash[:notice] = l(:notice_successful_update)
74 flash[:notice] = l(:notice_successful_update)
55 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
75 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
56 end
76 end
57 end
77 end
58 end
78 end
59
79
60 def destroy
80 def destroy
61 @query.destroy if request.post?
81 @query.destroy if request.post?
62 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
82 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
63 end
83 end
64
84
65 private
85 private
66 def find_query
86 def find_query
67 @query = Query.find(params[:id])
87 @query = Query.find(params[:id])
68 @project = @query.project
88 @project = @query.project
69 render_403 unless @query.editable_by?(User.current)
89 render_403 unless @query.editable_by?(User.current)
70 rescue ActiveRecord::RecordNotFound
90 rescue ActiveRecord::RecordNotFound
71 render_404
91 render_404
72 end
92 end
73
93
74 def find_optional_project
94 def find_optional_project
75 @project = Project.find(params[:project_id]) if params[:project_id]
95 @project = Project.find(params[:project_id]) if params[:project_id]
76 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
96 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
77 rescue ActiveRecord::RecordNotFound
97 rescue ActiveRecord::RecordNotFound
78 render_404
98 render_404
79 end
99 end
80 end
100 end
@@ -1,678 +1,688
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !sortable.nil?
39 !sortable.nil?
40 end
40 end
41
41
42 def value(issue)
42 def value(issue)
43 issue.send name
43 issue.send name
44 end
44 end
45
45
46 def css_classes
46 def css_classes
47 name
47 name
48 end
48 end
49 end
49 end
50
50
51 class QueryCustomFieldColumn < QueryColumn
51 class QueryCustomFieldColumn < QueryColumn
52
52
53 def initialize(custom_field)
53 def initialize(custom_field)
54 self.name = "cf_#{custom_field.id}".to_sym
54 self.name = "cf_#{custom_field.id}".to_sym
55 self.sortable = custom_field.order_statement || false
55 self.sortable = custom_field.order_statement || false
56 if %w(list date bool int).include?(custom_field.field_format)
56 if %w(list date bool int).include?(custom_field.field_format)
57 self.groupable = custom_field.order_statement
57 self.groupable = custom_field.order_statement
58 end
58 end
59 self.groupable ||= false
59 self.groupable ||= false
60 @cf = custom_field
60 @cf = custom_field
61 end
61 end
62
62
63 def caption
63 def caption
64 @cf.name
64 @cf.name
65 end
65 end
66
66
67 def custom_field
67 def custom_field
68 @cf
68 @cf
69 end
69 end
70
70
71 def value(issue)
71 def value(issue)
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
73 cv && @cf.cast_value(cv.value)
73 cv && @cf.cast_value(cv.value)
74 end
74 end
75
75
76 def css_classes
76 def css_classes
77 @css_classes ||= "#{name} #{@cf.field_format}"
77 @css_classes ||= "#{name} #{@cf.field_format}"
78 end
78 end
79 end
79 end
80
80
81 class Query < ActiveRecord::Base
81 class Query < ActiveRecord::Base
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
83 end
83 end
84
84
85 belongs_to :project
85 belongs_to :project
86 belongs_to :user
86 belongs_to :user
87 serialize :filters
87 serialize :filters
88 serialize :column_names
88 serialize :column_names
89 serialize :sort_criteria, Array
89 serialize :sort_criteria, Array
90
90
91 attr_protected :project_id, :user_id
91 attr_protected :project_id, :user_id
92
92
93 validates_presence_of :name, :on => :save
93 validates_presence_of :name, :on => :save
94 validates_length_of :name, :maximum => 255
94 validates_length_of :name, :maximum => 255
95
95
96 @@operators = { "=" => :label_equals,
96 @@operators = { "=" => :label_equals,
97 "!" => :label_not_equals,
97 "!" => :label_not_equals,
98 "o" => :label_open_issues,
98 "o" => :label_open_issues,
99 "c" => :label_closed_issues,
99 "c" => :label_closed_issues,
100 "!*" => :label_none,
100 "!*" => :label_none,
101 "*" => :label_all,
101 "*" => :label_all,
102 ">=" => :label_greater_or_equal,
102 ">=" => :label_greater_or_equal,
103 "<=" => :label_less_or_equal,
103 "<=" => :label_less_or_equal,
104 "<t+" => :label_in_less_than,
104 "<t+" => :label_in_less_than,
105 ">t+" => :label_in_more_than,
105 ">t+" => :label_in_more_than,
106 "t+" => :label_in,
106 "t+" => :label_in,
107 "t" => :label_today,
107 "t" => :label_today,
108 "w" => :label_this_week,
108 "w" => :label_this_week,
109 ">t-" => :label_less_than_ago,
109 ">t-" => :label_less_than_ago,
110 "<t-" => :label_more_than_ago,
110 "<t-" => :label_more_than_ago,
111 "t-" => :label_ago,
111 "t-" => :label_ago,
112 "~" => :label_contains,
112 "~" => :label_contains,
113 "!~" => :label_not_contains }
113 "!~" => :label_not_contains }
114
114
115 cattr_reader :operators
115 cattr_reader :operators
116
116
117 @@operators_by_filter_type = { :list => [ "=", "!" ],
117 @@operators_by_filter_type = { :list => [ "=", "!" ],
118 :list_status => [ "o", "=", "!", "c", "*" ],
118 :list_status => [ "o", "=", "!", "c", "*" ],
119 :list_optional => [ "=", "!", "!*", "*" ],
119 :list_optional => [ "=", "!", "!*", "*" ],
120 :list_subprojects => [ "*", "!*", "=" ],
120 :list_subprojects => [ "*", "!*", "=" ],
121 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
121 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
122 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
122 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
123 :string => [ "=", "~", "!", "!~" ],
123 :string => [ "=", "~", "!", "!~" ],
124 :text => [ "~", "!~" ],
124 :text => [ "~", "!~" ],
125 :integer => [ "=", ">=", "<=", "!*", "*" ] }
125 :integer => [ "=", ">=", "<=", "!*", "*" ] }
126
126
127 cattr_reader :operators_by_filter_type
127 cattr_reader :operators_by_filter_type
128
128
129 @@available_columns = [
129 @@available_columns = [
130 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
130 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
131 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
131 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
132 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
132 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
133 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
133 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
134 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
134 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
135 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
135 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
136 QueryColumn.new(:author),
136 QueryColumn.new(:author),
137 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
137 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
138 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
138 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
139 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
139 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
140 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
140 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
141 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
141 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
142 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
142 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
143 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
143 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
144 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
144 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
145 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
145 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
146 ]
146 ]
147 cattr_reader :available_columns
147 cattr_reader :available_columns
148
148
149 named_scope :visible, lambda {|*args|
150 user = args.shift || User.current
151 base = Project.allowed_to_condition(user, :view_issues, *args)
152 user_id = user.logged? ? user.id : 0
153 {
154 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
155 :include => :project
156 }
157 }
158
149 def initialize(attributes = nil)
159 def initialize(attributes = nil)
150 super attributes
160 super attributes
151 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
161 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
152 end
162 end
153
163
154 def after_initialize
164 def after_initialize
155 # Store the fact that project is nil (used in #editable_by?)
165 # Store the fact that project is nil (used in #editable_by?)
156 @is_for_all = project.nil?
166 @is_for_all = project.nil?
157 end
167 end
158
168
159 def validate
169 def validate
160 filters.each_key do |field|
170 filters.each_key do |field|
161 errors.add label_for(field), :blank unless
171 errors.add label_for(field), :blank unless
162 # filter requires one or more values
172 # filter requires one or more values
163 (values_for(field) and !values_for(field).first.blank?) or
173 (values_for(field) and !values_for(field).first.blank?) or
164 # filter doesn't require any value
174 # filter doesn't require any value
165 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
175 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
166 end if filters
176 end if filters
167 end
177 end
168
178
169 # Returns true if the query is visible to +user+ or the current user.
179 # Returns true if the query is visible to +user+ or the current user.
170 def visible?(user=User.current)
180 def visible?(user=User.current)
171 self.is_public? || self.user_id == user.id
181 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
172 end
182 end
173
183
174 def editable_by?(user)
184 def editable_by?(user)
175 return false unless user
185 return false unless user
176 # Admin can edit them all and regular users can edit their private queries
186 # Admin can edit them all and regular users can edit their private queries
177 return true if user.admin? || (!is_public && self.user_id == user.id)
187 return true if user.admin? || (!is_public && self.user_id == user.id)
178 # Members can not edit public queries that are for all project (only admin is allowed to)
188 # Members can not edit public queries that are for all project (only admin is allowed to)
179 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
189 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
180 end
190 end
181
191
182 def available_filters
192 def available_filters
183 return @available_filters if @available_filters
193 return @available_filters if @available_filters
184
194
185 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
195 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
186
196
187 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
197 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
188 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
198 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
189 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
199 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
190 "subject" => { :type => :text, :order => 8 },
200 "subject" => { :type => :text, :order => 8 },
191 "created_on" => { :type => :date_past, :order => 9 },
201 "created_on" => { :type => :date_past, :order => 9 },
192 "updated_on" => { :type => :date_past, :order => 10 },
202 "updated_on" => { :type => :date_past, :order => 10 },
193 "start_date" => { :type => :date, :order => 11 },
203 "start_date" => { :type => :date, :order => 11 },
194 "due_date" => { :type => :date, :order => 12 },
204 "due_date" => { :type => :date, :order => 12 },
195 "estimated_hours" => { :type => :integer, :order => 13 },
205 "estimated_hours" => { :type => :integer, :order => 13 },
196 "done_ratio" => { :type => :integer, :order => 14 }}
206 "done_ratio" => { :type => :integer, :order => 14 }}
197
207
198 user_values = []
208 user_values = []
199 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
209 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
200 if project
210 if project
201 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
211 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
202 else
212 else
203 all_projects = Project.visible.all
213 all_projects = Project.visible.all
204 if all_projects.any?
214 if all_projects.any?
205 # members of visible projects
215 # members of visible projects
206 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] }
216 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] }
207
217
208 # project filter
218 # project filter
209 project_values = []
219 project_values = []
210 Project.project_tree(all_projects) do |p, level|
220 Project.project_tree(all_projects) do |p, level|
211 prefix = (level > 0 ? ('--' * level + ' ') : '')
221 prefix = (level > 0 ? ('--' * level + ' ') : '')
212 project_values << ["#{prefix}#{p.name}", p.id.to_s]
222 project_values << ["#{prefix}#{p.name}", p.id.to_s]
213 end
223 end
214 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
224 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
215 end
225 end
216 end
226 end
217 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
227 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
218 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
228 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
219
229
220 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
230 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
221 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
231 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
222
232
223 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
233 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
224 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
234 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
225
235
226 if User.current.logged?
236 if User.current.logged?
227 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
237 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
228 end
238 end
229
239
230 if project
240 if project
231 # project specific filters
241 # project specific filters
232 categories = @project.issue_categories.all
242 categories = @project.issue_categories.all
233 unless categories.empty?
243 unless categories.empty?
234 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
244 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
235 end
245 end
236 versions = @project.shared_versions.all
246 versions = @project.shared_versions.all
237 unless versions.empty?
247 unless versions.empty?
238 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
248 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
239 end
249 end
240 unless @project.leaf?
250 unless @project.leaf?
241 subprojects = @project.descendants.visible.all
251 subprojects = @project.descendants.visible.all
242 unless subprojects.empty?
252 unless subprojects.empty?
243 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
253 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
244 end
254 end
245 end
255 end
246 add_custom_fields_filters(@project.all_issue_custom_fields)
256 add_custom_fields_filters(@project.all_issue_custom_fields)
247 else
257 else
248 # global filters for cross project issue list
258 # global filters for cross project issue list
249 system_shared_versions = Version.visible.find_all_by_sharing('system')
259 system_shared_versions = Version.visible.find_all_by_sharing('system')
250 unless system_shared_versions.empty?
260 unless system_shared_versions.empty?
251 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
261 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
252 end
262 end
253 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
263 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
254 end
264 end
255 @available_filters
265 @available_filters
256 end
266 end
257
267
258 def add_filter(field, operator, values)
268 def add_filter(field, operator, values)
259 # values must be an array
269 # values must be an array
260 return unless values and values.is_a? Array # and !values.first.empty?
270 return unless values and values.is_a? Array # and !values.first.empty?
261 # check if field is defined as an available filter
271 # check if field is defined as an available filter
262 if available_filters.has_key? field
272 if available_filters.has_key? field
263 filter_options = available_filters[field]
273 filter_options = available_filters[field]
264 # check if operator is allowed for that filter
274 # check if operator is allowed for that filter
265 #if @@operators_by_filter_type[filter_options[:type]].include? operator
275 #if @@operators_by_filter_type[filter_options[:type]].include? operator
266 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
276 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
267 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
277 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
268 #end
278 #end
269 filters[field] = {:operator => operator, :values => values }
279 filters[field] = {:operator => operator, :values => values }
270 end
280 end
271 end
281 end
272
282
273 def add_short_filter(field, expression)
283 def add_short_filter(field, expression)
274 return unless expression
284 return unless expression
275 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
285 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
276 add_filter field, (parms[0] || "="), [parms[1] || ""]
286 add_filter field, (parms[0] || "="), [parms[1] || ""]
277 end
287 end
278
288
279 # Add multiple filters using +add_filter+
289 # Add multiple filters using +add_filter+
280 def add_filters(fields, operators, values)
290 def add_filters(fields, operators, values)
281 if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
291 if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
282 fields.each do |field|
292 fields.each do |field|
283 add_filter(field, operators[field], values[field])
293 add_filter(field, operators[field], values[field])
284 end
294 end
285 end
295 end
286 end
296 end
287
297
288 def has_filter?(field)
298 def has_filter?(field)
289 filters and filters[field]
299 filters and filters[field]
290 end
300 end
291
301
292 def operator_for(field)
302 def operator_for(field)
293 has_filter?(field) ? filters[field][:operator] : nil
303 has_filter?(field) ? filters[field][:operator] : nil
294 end
304 end
295
305
296 def values_for(field)
306 def values_for(field)
297 has_filter?(field) ? filters[field][:values] : nil
307 has_filter?(field) ? filters[field][:values] : nil
298 end
308 end
299
309
300 def label_for(field)
310 def label_for(field)
301 label = available_filters[field][:name] if available_filters.has_key?(field)
311 label = available_filters[field][:name] if available_filters.has_key?(field)
302 label ||= field.gsub(/\_id$/, "")
312 label ||= field.gsub(/\_id$/, "")
303 end
313 end
304
314
305 def available_columns
315 def available_columns
306 return @available_columns if @available_columns
316 return @available_columns if @available_columns
307 @available_columns = Query.available_columns
317 @available_columns = Query.available_columns
308 @available_columns += (project ?
318 @available_columns += (project ?
309 project.all_issue_custom_fields :
319 project.all_issue_custom_fields :
310 IssueCustomField.find(:all)
320 IssueCustomField.find(:all)
311 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
321 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
312 end
322 end
313
323
314 def self.available_columns=(v)
324 def self.available_columns=(v)
315 self.available_columns = (v)
325 self.available_columns = (v)
316 end
326 end
317
327
318 def self.add_available_column(column)
328 def self.add_available_column(column)
319 self.available_columns << (column) if column.is_a?(QueryColumn)
329 self.available_columns << (column) if column.is_a?(QueryColumn)
320 end
330 end
321
331
322 # Returns an array of columns that can be used to group the results
332 # Returns an array of columns that can be used to group the results
323 def groupable_columns
333 def groupable_columns
324 available_columns.select {|c| c.groupable}
334 available_columns.select {|c| c.groupable}
325 end
335 end
326
336
327 # Returns a Hash of columns and the key for sorting
337 # Returns a Hash of columns and the key for sorting
328 def sortable_columns
338 def sortable_columns
329 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
339 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
330 h[column.name.to_s] = column.sortable
340 h[column.name.to_s] = column.sortable
331 h
341 h
332 })
342 })
333 end
343 end
334
344
335 def columns
345 def columns
336 if has_default_columns?
346 if has_default_columns?
337 available_columns.select do |c|
347 available_columns.select do |c|
338 # Adds the project column by default for cross-project lists
348 # Adds the project column by default for cross-project lists
339 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
349 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
340 end
350 end
341 else
351 else
342 # preserve the column_names order
352 # preserve the column_names order
343 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
353 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
344 end
354 end
345 end
355 end
346
356
347 def column_names=(names)
357 def column_names=(names)
348 if names
358 if names
349 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
359 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
350 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
360 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
351 # Set column_names to nil if default columns
361 # Set column_names to nil if default columns
352 if names.map(&:to_s) == Setting.issue_list_default_columns
362 if names.map(&:to_s) == Setting.issue_list_default_columns
353 names = nil
363 names = nil
354 end
364 end
355 end
365 end
356 write_attribute(:column_names, names)
366 write_attribute(:column_names, names)
357 end
367 end
358
368
359 def has_column?(column)
369 def has_column?(column)
360 column_names && column_names.include?(column.name)
370 column_names && column_names.include?(column.name)
361 end
371 end
362
372
363 def has_default_columns?
373 def has_default_columns?
364 column_names.nil? || column_names.empty?
374 column_names.nil? || column_names.empty?
365 end
375 end
366
376
367 def sort_criteria=(arg)
377 def sort_criteria=(arg)
368 c = []
378 c = []
369 if arg.is_a?(Hash)
379 if arg.is_a?(Hash)
370 arg = arg.keys.sort.collect {|k| arg[k]}
380 arg = arg.keys.sort.collect {|k| arg[k]}
371 end
381 end
372 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
382 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
373 write_attribute(:sort_criteria, c)
383 write_attribute(:sort_criteria, c)
374 end
384 end
375
385
376 def sort_criteria
386 def sort_criteria
377 read_attribute(:sort_criteria) || []
387 read_attribute(:sort_criteria) || []
378 end
388 end
379
389
380 def sort_criteria_key(arg)
390 def sort_criteria_key(arg)
381 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
391 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
382 end
392 end
383
393
384 def sort_criteria_order(arg)
394 def sort_criteria_order(arg)
385 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
395 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
386 end
396 end
387
397
388 # Returns the SQL sort order that should be prepended for grouping
398 # Returns the SQL sort order that should be prepended for grouping
389 def group_by_sort_order
399 def group_by_sort_order
390 if grouped? && (column = group_by_column)
400 if grouped? && (column = group_by_column)
391 column.sortable.is_a?(Array) ?
401 column.sortable.is_a?(Array) ?
392 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
402 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
393 "#{column.sortable} #{column.default_order}"
403 "#{column.sortable} #{column.default_order}"
394 end
404 end
395 end
405 end
396
406
397 # Returns true if the query is a grouped query
407 # Returns true if the query is a grouped query
398 def grouped?
408 def grouped?
399 !group_by_column.nil?
409 !group_by_column.nil?
400 end
410 end
401
411
402 def group_by_column
412 def group_by_column
403 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
413 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
404 end
414 end
405
415
406 def group_by_statement
416 def group_by_statement
407 group_by_column.try(:groupable)
417 group_by_column.try(:groupable)
408 end
418 end
409
419
410 def project_statement
420 def project_statement
411 project_clauses = []
421 project_clauses = []
412 if project && !@project.descendants.active.empty?
422 if project && !@project.descendants.active.empty?
413 ids = [project.id]
423 ids = [project.id]
414 if has_filter?("subproject_id")
424 if has_filter?("subproject_id")
415 case operator_for("subproject_id")
425 case operator_for("subproject_id")
416 when '='
426 when '='
417 # include the selected subprojects
427 # include the selected subprojects
418 ids += values_for("subproject_id").each(&:to_i)
428 ids += values_for("subproject_id").each(&:to_i)
419 when '!*'
429 when '!*'
420 # main project only
430 # main project only
421 else
431 else
422 # all subprojects
432 # all subprojects
423 ids += project.descendants.collect(&:id)
433 ids += project.descendants.collect(&:id)
424 end
434 end
425 elsif Setting.display_subprojects_issues?
435 elsif Setting.display_subprojects_issues?
426 ids += project.descendants.collect(&:id)
436 ids += project.descendants.collect(&:id)
427 end
437 end
428 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
438 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
429 elsif project
439 elsif project
430 project_clauses << "#{Project.table_name}.id = %d" % project.id
440 project_clauses << "#{Project.table_name}.id = %d" % project.id
431 end
441 end
432 project_clauses.any? ? project_clauses.join(' AND ') : nil
442 project_clauses.any? ? project_clauses.join(' AND ') : nil
433 end
443 end
434
444
435 def statement
445 def statement
436 # filters clauses
446 # filters clauses
437 filters_clauses = []
447 filters_clauses = []
438 filters.each_key do |field|
448 filters.each_key do |field|
439 next if field == "subproject_id"
449 next if field == "subproject_id"
440 v = values_for(field).clone
450 v = values_for(field).clone
441 next unless v and !v.empty?
451 next unless v and !v.empty?
442 operator = operator_for(field)
452 operator = operator_for(field)
443
453
444 # "me" value subsitution
454 # "me" value subsitution
445 if %w(assigned_to_id author_id watcher_id).include?(field)
455 if %w(assigned_to_id author_id watcher_id).include?(field)
446 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
456 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
447 end
457 end
448
458
449 sql = ''
459 sql = ''
450 if field =~ /^cf_(\d+)$/
460 if field =~ /^cf_(\d+)$/
451 # custom field
461 # custom field
452 db_table = CustomValue.table_name
462 db_table = CustomValue.table_name
453 db_field = 'value'
463 db_field = 'value'
454 is_custom_filter = true
464 is_custom_filter = true
455 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
465 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
456 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
466 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
457 elsif field == 'watcher_id'
467 elsif field == 'watcher_id'
458 db_table = Watcher.table_name
468 db_table = Watcher.table_name
459 db_field = 'user_id'
469 db_field = 'user_id'
460 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
470 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
461 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
471 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
462 elsif field == "member_of_group" # named field
472 elsif field == "member_of_group" # named field
463 if operator == '*' # Any group
473 if operator == '*' # Any group
464 groups = Group.all
474 groups = Group.all
465 operator = '=' # Override the operator since we want to find by assigned_to
475 operator = '=' # Override the operator since we want to find by assigned_to
466 elsif operator == "!*"
476 elsif operator == "!*"
467 groups = Group.all
477 groups = Group.all
468 operator = '!' # Override the operator since we want to find by assigned_to
478 operator = '!' # Override the operator since we want to find by assigned_to
469 else
479 else
470 groups = Group.find_all_by_id(v)
480 groups = Group.find_all_by_id(v)
471 end
481 end
472 groups ||= []
482 groups ||= []
473
483
474 members_of_groups = groups.inject([]) {|user_ids, group|
484 members_of_groups = groups.inject([]) {|user_ids, group|
475 if group && group.user_ids.present?
485 if group && group.user_ids.present?
476 user_ids << group.user_ids
486 user_ids << group.user_ids
477 end
487 end
478 user_ids.flatten.uniq.compact
488 user_ids.flatten.uniq.compact
479 }.sort.collect(&:to_s)
489 }.sort.collect(&:to_s)
480
490
481 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
491 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
482
492
483 elsif field == "assigned_to_role" # named field
493 elsif field == "assigned_to_role" # named field
484 if operator == "*" # Any Role
494 if operator == "*" # Any Role
485 roles = Role.givable
495 roles = Role.givable
486 operator = '=' # Override the operator since we want to find by assigned_to
496 operator = '=' # Override the operator since we want to find by assigned_to
487 elsif operator == "!*" # No role
497 elsif operator == "!*" # No role
488 roles = Role.givable
498 roles = Role.givable
489 operator = '!' # Override the operator since we want to find by assigned_to
499 operator = '!' # Override the operator since we want to find by assigned_to
490 else
500 else
491 roles = Role.givable.find_all_by_id(v)
501 roles = Role.givable.find_all_by_id(v)
492 end
502 end
493 roles ||= []
503 roles ||= []
494
504
495 members_of_roles = roles.inject([]) {|user_ids, role|
505 members_of_roles = roles.inject([]) {|user_ids, role|
496 if role && role.members
506 if role && role.members
497 user_ids << role.members.collect(&:user_id)
507 user_ids << role.members.collect(&:user_id)
498 end
508 end
499 user_ids.flatten.uniq.compact
509 user_ids.flatten.uniq.compact
500 }.sort.collect(&:to_s)
510 }.sort.collect(&:to_s)
501
511
502 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
512 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
503 else
513 else
504 # regular field
514 # regular field
505 db_table = Issue.table_name
515 db_table = Issue.table_name
506 db_field = field
516 db_field = field
507 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
517 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
508 end
518 end
509 filters_clauses << sql
519 filters_clauses << sql
510
520
511 end if filters and valid?
521 end if filters and valid?
512
522
513 filters_clauses << project_statement
523 filters_clauses << project_statement
514 filters_clauses.reject!(&:blank?)
524 filters_clauses.reject!(&:blank?)
515
525
516 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
526 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
517 end
527 end
518
528
519 # Returns the issue count
529 # Returns the issue count
520 def issue_count
530 def issue_count
521 Issue.count(:include => [:status, :project], :conditions => statement)
531 Issue.count(:include => [:status, :project], :conditions => statement)
522 rescue ::ActiveRecord::StatementInvalid => e
532 rescue ::ActiveRecord::StatementInvalid => e
523 raise StatementInvalid.new(e.message)
533 raise StatementInvalid.new(e.message)
524 end
534 end
525
535
526 # Returns the issue count by group or nil if query is not grouped
536 # Returns the issue count by group or nil if query is not grouped
527 def issue_count_by_group
537 def issue_count_by_group
528 r = nil
538 r = nil
529 if grouped?
539 if grouped?
530 begin
540 begin
531 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
541 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
532 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
542 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
533 rescue ActiveRecord::RecordNotFound
543 rescue ActiveRecord::RecordNotFound
534 r = {nil => issue_count}
544 r = {nil => issue_count}
535 end
545 end
536 c = group_by_column
546 c = group_by_column
537 if c.is_a?(QueryCustomFieldColumn)
547 if c.is_a?(QueryCustomFieldColumn)
538 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
548 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
539 end
549 end
540 end
550 end
541 r
551 r
542 rescue ::ActiveRecord::StatementInvalid => e
552 rescue ::ActiveRecord::StatementInvalid => e
543 raise StatementInvalid.new(e.message)
553 raise StatementInvalid.new(e.message)
544 end
554 end
545
555
546 # Returns the issues
556 # Returns the issues
547 # Valid options are :order, :offset, :limit, :include, :conditions
557 # Valid options are :order, :offset, :limit, :include, :conditions
548 def issues(options={})
558 def issues(options={})
549 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
559 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
550 order_option = nil if order_option.blank?
560 order_option = nil if order_option.blank?
551
561
552 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
562 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
553 :conditions => Query.merge_conditions(statement, options[:conditions]),
563 :conditions => Query.merge_conditions(statement, options[:conditions]),
554 :order => order_option,
564 :order => order_option,
555 :limit => options[:limit],
565 :limit => options[:limit],
556 :offset => options[:offset]
566 :offset => options[:offset]
557 rescue ::ActiveRecord::StatementInvalid => e
567 rescue ::ActiveRecord::StatementInvalid => e
558 raise StatementInvalid.new(e.message)
568 raise StatementInvalid.new(e.message)
559 end
569 end
560
570
561 # Returns the journals
571 # Returns the journals
562 # Valid options are :order, :offset, :limit
572 # Valid options are :order, :offset, :limit
563 def journals(options={})
573 def journals(options={})
564 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
574 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
565 :conditions => statement,
575 :conditions => statement,
566 :order => options[:order],
576 :order => options[:order],
567 :limit => options[:limit],
577 :limit => options[:limit],
568 :offset => options[:offset]
578 :offset => options[:offset]
569 rescue ::ActiveRecord::StatementInvalid => e
579 rescue ::ActiveRecord::StatementInvalid => e
570 raise StatementInvalid.new(e.message)
580 raise StatementInvalid.new(e.message)
571 end
581 end
572
582
573 # Returns the versions
583 # Returns the versions
574 # Valid options are :conditions
584 # Valid options are :conditions
575 def versions(options={})
585 def versions(options={})
576 Version.visible.find :all, :include => :project,
586 Version.visible.find :all, :include => :project,
577 :conditions => Query.merge_conditions(project_statement, options[:conditions])
587 :conditions => Query.merge_conditions(project_statement, options[:conditions])
578 rescue ::ActiveRecord::StatementInvalid => e
588 rescue ::ActiveRecord::StatementInvalid => e
579 raise StatementInvalid.new(e.message)
589 raise StatementInvalid.new(e.message)
580 end
590 end
581
591
582 private
592 private
583
593
584 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
594 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
585 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
595 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
586 sql = ''
596 sql = ''
587 case operator
597 case operator
588 when "="
598 when "="
589 if value.any?
599 if value.any?
590 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
600 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
591 else
601 else
592 # IN an empty set
602 # IN an empty set
593 sql = "1=0"
603 sql = "1=0"
594 end
604 end
595 when "!"
605 when "!"
596 if value.any?
606 if value.any?
597 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
607 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
598 else
608 else
599 # NOT IN an empty set
609 # NOT IN an empty set
600 sql = "1=1"
610 sql = "1=1"
601 end
611 end
602 when "!*"
612 when "!*"
603 sql = "#{db_table}.#{db_field} IS NULL"
613 sql = "#{db_table}.#{db_field} IS NULL"
604 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
614 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
605 when "*"
615 when "*"
606 sql = "#{db_table}.#{db_field} IS NOT NULL"
616 sql = "#{db_table}.#{db_field} IS NOT NULL"
607 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
617 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
608 when ">="
618 when ">="
609 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
619 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
610 when "<="
620 when "<="
611 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
621 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
612 when "o"
622 when "o"
613 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
623 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
614 when "c"
624 when "c"
615 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
625 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
616 when ">t-"
626 when ">t-"
617 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
627 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
618 when "<t-"
628 when "<t-"
619 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
629 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
620 when "t-"
630 when "t-"
621 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
631 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
622 when ">t+"
632 when ">t+"
623 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
633 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
624 when "<t+"
634 when "<t+"
625 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
635 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
626 when "t+"
636 when "t+"
627 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
637 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
628 when "t"
638 when "t"
629 sql = date_range_clause(db_table, db_field, 0, 0)
639 sql = date_range_clause(db_table, db_field, 0, 0)
630 when "w"
640 when "w"
631 first_day_of_week = l(:general_first_day_of_week).to_i
641 first_day_of_week = l(:general_first_day_of_week).to_i
632 day_of_week = Date.today.cwday
642 day_of_week = Date.today.cwday
633 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
643 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
634 sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6)
644 sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6)
635 when "~"
645 when "~"
636 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
646 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
637 when "!~"
647 when "!~"
638 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
648 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
639 end
649 end
640
650
641 return sql
651 return sql
642 end
652 end
643
653
644 def add_custom_fields_filters(custom_fields)
654 def add_custom_fields_filters(custom_fields)
645 @available_filters ||= {}
655 @available_filters ||= {}
646
656
647 custom_fields.select(&:is_filter?).each do |field|
657 custom_fields.select(&:is_filter?).each do |field|
648 case field.field_format
658 case field.field_format
649 when "text"
659 when "text"
650 options = { :type => :text, :order => 20 }
660 options = { :type => :text, :order => 20 }
651 when "list"
661 when "list"
652 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
662 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
653 when "date"
663 when "date"
654 options = { :type => :date, :order => 20 }
664 options = { :type => :date, :order => 20 }
655 when "bool"
665 when "bool"
656 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
666 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
657 when "user", "version"
667 when "user", "version"
658 next unless project
668 next unless project
659 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
669 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
660 else
670 else
661 options = { :type => :string, :order => 20 }
671 options = { :type => :string, :order => 20 }
662 end
672 end
663 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
673 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
664 end
674 end
665 end
675 end
666
676
667 # Returns a SQL clause for a date or datetime field.
677 # Returns a SQL clause for a date or datetime field.
668 def date_range_clause(table, field, from, to)
678 def date_range_clause(table, field, from, to)
669 s = []
679 s = []
670 if from
680 if from
671 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
681 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
672 end
682 end
673 if to
683 if to
674 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
684 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
675 end
685 end
676 s.join(' AND ')
686 s.join(' AND ')
677 end
687 end
678 end
688 end
@@ -1,255 +1,256
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome'
9 map.home '', :controller => 'welcome'
10
10
11 map.signin 'login', :controller => 'account', :action => 'login'
11 map.signin 'login', :controller => 'account', :action => 'login'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
13
13
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
16
16
17 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
17 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
18 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report'
18 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report'
19 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format'
19 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format'
20 time_report.connect 'projects/:project_id/time_entries/report'
20 time_report.connect 'projects/:project_id/time_entries/report'
21 time_report.connect 'projects/:project_id/time_entries/report.:format'
21 time_report.connect 'projects/:project_id/time_entries/report.:format'
22 time_report.connect 'time_entries/report'
22 time_report.connect 'time_entries/report'
23 time_report.connect 'time_entries/report.:format'
23 time_report.connect 'time_entries/report.:format'
24 end
24 end
25
25
26 map.bulk_edit_time_entry 'time_entries/bulk_edit',
26 map.bulk_edit_time_entry 'time_entries/bulk_edit',
27 :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
27 :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
28 map.bulk_update_time_entry 'time_entries/bulk_edit',
28 map.bulk_update_time_entry 'time_entries/bulk_edit',
29 :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
29 :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
30 map.time_entries_context_menu '/time_entries/context_menu',
30 map.time_entries_context_menu '/time_entries/context_menu',
31 :controller => 'context_menus', :action => 'time_entries'
31 :controller => 'context_menus', :action => 'time_entries'
32 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
32 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
33 map.resources :time_entries, :controller => 'timelog'
33 map.resources :time_entries, :controller => 'timelog'
34
34
35 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
35 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
36 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
36 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
37 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
37 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
38
38
39 map.with_options :controller => 'messages' do |messages_routes|
39 map.with_options :controller => 'messages' do |messages_routes|
40 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
40 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
41 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
41 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
42 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
42 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
43 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
43 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
44 end
44 end
45 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
45 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
46 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
46 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
47 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
47 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
48 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
48 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
49 end
49 end
50 end
50 end
51
51
52 map.with_options :controller => 'boards' do |board_routes|
52 map.with_options :controller => 'boards' do |board_routes|
53 board_routes.with_options :conditions => {:method => :get} do |board_views|
53 board_routes.with_options :conditions => {:method => :get} do |board_views|
54 board_views.connect 'projects/:project_id/boards', :action => 'index'
54 board_views.connect 'projects/:project_id/boards', :action => 'index'
55 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
55 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
56 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
56 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
57 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
57 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
58 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
58 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
59 end
59 end
60 board_routes.with_options :conditions => {:method => :post} do |board_actions|
60 board_routes.with_options :conditions => {:method => :post} do |board_actions|
61 board_actions.connect 'projects/:project_id/boards', :action => 'new'
61 board_actions.connect 'projects/:project_id/boards', :action => 'new'
62 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
62 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
63 end
63 end
64 end
64 end
65
65
66 map.with_options :controller => 'documents' do |document_routes|
66 map.with_options :controller => 'documents' do |document_routes|
67 document_routes.with_options :conditions => {:method => :get} do |document_views|
67 document_routes.with_options :conditions => {:method => :get} do |document_views|
68 document_views.connect 'projects/:project_id/documents', :action => 'index'
68 document_views.connect 'projects/:project_id/documents', :action => 'index'
69 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
69 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
70 document_views.connect 'documents/:id', :action => 'show'
70 document_views.connect 'documents/:id', :action => 'show'
71 document_views.connect 'documents/:id/edit', :action => 'edit'
71 document_views.connect 'documents/:id/edit', :action => 'edit'
72 end
72 end
73 document_routes.with_options :conditions => {:method => :post} do |document_actions|
73 document_routes.with_options :conditions => {:method => :post} do |document_actions|
74 document_actions.connect 'projects/:project_id/documents', :action => 'new'
74 document_actions.connect 'projects/:project_id/documents', :action => 'new'
75 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
75 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
76 end
76 end
77 end
77 end
78
78
79 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
79 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
80 map.resources :queries, :only => [:index]
80
81
81 # Misc issue routes. TODO: move into resources
82 # Misc issue routes. TODO: move into resources
82 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
83 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
83 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
84 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
84 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
85 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
85 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
86 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
86 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
87 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
87 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
88 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
88 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
89 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
89 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
90 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
90
91
91 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
92 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
92 gantts_routes.connect '/projects/:project_id/issues/gantt'
93 gantts_routes.connect '/projects/:project_id/issues/gantt'
93 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
94 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
94 gantts_routes.connect '/issues/gantt.:format'
95 gantts_routes.connect '/issues/gantt.:format'
95 end
96 end
96
97
97 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
98 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
98 calendars_routes.connect '/projects/:project_id/issues/calendar'
99 calendars_routes.connect '/projects/:project_id/issues/calendar'
99 calendars_routes.connect '/issues/calendar'
100 calendars_routes.connect '/issues/calendar'
100 end
101 end
101
102
102 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
103 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
103 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
104 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
104 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
105 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
105 end
106 end
106
107
107 # Following two routes conflict with the resources because #index allows POST
108 # Following two routes conflict with the resources because #index allows POST
108 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
109 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
109 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
110 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
110
111
111 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
112 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
112 issues.resources :time_entries, :controller => 'timelog'
113 issues.resources :time_entries, :controller => 'timelog'
113 issues.resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
114 issues.resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
114 end
115 end
115
116
116 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
117 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
117 issues.resources :time_entries, :controller => 'timelog'
118 issues.resources :time_entries, :controller => 'timelog'
118 end
119 end
119
120
120 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
121 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
121
122
122 map.with_options :controller => 'users' do |users|
123 map.with_options :controller => 'users' do |users|
123 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
124 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
124
125
125 users.with_options :conditions => {:method => :post} do |user_actions|
126 users.with_options :conditions => {:method => :post} do |user_actions|
126 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
127 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
127 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
128 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
128 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
129 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
129 end
130 end
130 end
131 end
131
132
132 map.resources :users, :member => {
133 map.resources :users, :member => {
133 :edit_membership => :post,
134 :edit_membership => :post,
134 :destroy_membership => :post
135 :destroy_membership => :post
135 }
136 }
136
137
137 # For nice "roadmap" in the url for the index action
138 # For nice "roadmap" in the url for the index action
138 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
139 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
139
140
140 map.all_news 'news', :controller => 'news', :action => 'index'
141 map.all_news 'news', :controller => 'news', :action => 'index'
141 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
142 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
142 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
143 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
143 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
144 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
144 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
145 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
145
146
146 map.resources :projects, :member => {
147 map.resources :projects, :member => {
147 :copy => [:get, :post],
148 :copy => [:get, :post],
148 :settings => :get,
149 :settings => :get,
149 :modules => :post,
150 :modules => :post,
150 :archive => :post,
151 :archive => :post,
151 :unarchive => :post
152 :unarchive => :post
152 } do |project|
153 } do |project|
153 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
154 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
154 project.resources :files, :only => [:index, :new, :create]
155 project.resources :files, :only => [:index, :new, :create]
155 project.resources :versions, :shallow => true, :collection => {:close_completed => :put}, :member => {:status_by => :post}
156 project.resources :versions, :shallow => true, :collection => {:close_completed => :put}, :member => {:status_by => :post}
156 project.resources :news, :shallow => true
157 project.resources :news, :shallow => true
157 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
158 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
158
159
159 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
160 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
160 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
161 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
161 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
162 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
162 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
163 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
163 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
164 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
164 project.resources :wiki, :except => [:new, :create], :member => {
165 project.resources :wiki, :except => [:new, :create], :member => {
165 :rename => [:get, :post],
166 :rename => [:get, :post],
166 :history => :get,
167 :history => :get,
167 :preview => :any,
168 :preview => :any,
168 :protect => :post,
169 :protect => :post,
169 :add_attachment => :post
170 :add_attachment => :post
170 }, :collection => {
171 }, :collection => {
171 :export => :get,
172 :export => :get,
172 :date_index => :get
173 :date_index => :get
173 }
174 }
174
175
175 end
176 end
176
177
177 # Destroy uses a get request to prompt the user before the actual DELETE request
178 # Destroy uses a get request to prompt the user before the actual DELETE request
178 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
179 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
179
180
180 # TODO: port to be part of the resources route(s)
181 # TODO: port to be part of the resources route(s)
181 map.with_options :controller => 'projects' do |project_mapper|
182 map.with_options :controller => 'projects' do |project_mapper|
182 project_mapper.with_options :conditions => {:method => :get} do |project_views|
183 project_mapper.with_options :conditions => {:method => :get} do |project_views|
183 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
184 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
184 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
185 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
185 end
186 end
186 end
187 end
187
188
188 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
189 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
189 activity.connect 'projects/:id/activity'
190 activity.connect 'projects/:id/activity'
190 activity.connect 'projects/:id/activity.:format'
191 activity.connect 'projects/:id/activity.:format'
191 activity.connect 'activity', :id => nil
192 activity.connect 'activity', :id => nil
192 activity.connect 'activity.:format', :id => nil
193 activity.connect 'activity.:format', :id => nil
193 end
194 end
194
195
195
196
196 map.with_options :controller => 'issue_categories' do |categories|
197 map.with_options :controller => 'issue_categories' do |categories|
197 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
198 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
198 end
199 end
199
200
200 map.with_options :controller => 'repositories' do |repositories|
201 map.with_options :controller => 'repositories' do |repositories|
201 repositories.with_options :conditions => {:method => :get} do |repository_views|
202 repositories.with_options :conditions => {:method => :get} do |repository_views|
202 repository_views.connect 'projects/:id/repository', :action => 'show'
203 repository_views.connect 'projects/:id/repository', :action => 'show'
203 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
204 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
204 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
205 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
205 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
206 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
206 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
207 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
207 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
208 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
208 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
209 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
209 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
210 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
210 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
211 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
211 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
212 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
212 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
213 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
213 # TODO: why the following route is required?
214 # TODO: why the following route is required?
214 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
215 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
215 repository_views.connect 'projects/:id/repository/:action/*path'
216 repository_views.connect 'projects/:id/repository/:action/*path'
216 end
217 end
217
218
218 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
219 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
219 end
220 end
220
221
221 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
222 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
222 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
223 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
223 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
224 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
224
225
225 map.resources :groups
226 map.resources :groups
226
227
227 #left old routes at the bottom for backwards compat
228 #left old routes at the bottom for backwards compat
228 map.connect 'projects/:project_id/queries/:action', :controller => 'queries'
229 map.connect 'projects/:project_id/queries/:action', :controller => 'queries'
229 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
230 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
230 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
231 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
231 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
232 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
232 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
233 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
233 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
234 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
234 map.connect 'projects/:project_id/news/:action', :controller => 'news'
235 map.connect 'projects/:project_id/news/:action', :controller => 'news'
235 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
236 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
236 map.with_options :controller => 'repositories' do |omap|
237 map.with_options :controller => 'repositories' do |omap|
237 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
238 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
238 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
239 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
239 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
240 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
240 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
241 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
241 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
242 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
242 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
243 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
243 end
244 end
244
245
245 map.with_options :controller => 'sys' do |sys|
246 map.with_options :controller => 'sys' do |sys|
246 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
247 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
247 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
248 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
248 end
249 end
249
250
250 # Install the default route as the lowest priority.
251 # Install the default route as the lowest priority.
251 map.connect ':controller/:action/:id'
252 map.connect ':controller/:action/:id'
252 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
253 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
253 # Used for OpenID
254 # Used for OpenID
254 map.root :controller => 'account', :action => 'login'
255 map.root :controller => 'account', :action => 'login'
255 end
256 end
@@ -1,392 +1,395
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class RoutingTest < ActionController::IntegrationTest
20 class RoutingTest < ActionController::IntegrationTest
21 context "activities" do
21 context "activities" do
22 should_route :get, "/activity", :controller => 'activities', :action => 'index', :id => nil
22 should_route :get, "/activity", :controller => 'activities', :action => 'index', :id => nil
23 should_route :get, "/activity.atom", :controller => 'activities', :action => 'index', :id => nil, :format => 'atom'
23 should_route :get, "/activity.atom", :controller => 'activities', :action => 'index', :id => nil, :format => 'atom'
24 end
24 end
25
25
26 context "attachments" do
26 context "attachments" do
27 should_route :get, "/attachments/1", :controller => 'attachments', :action => 'show', :id => '1'
27 should_route :get, "/attachments/1", :controller => 'attachments', :action => 'show', :id => '1'
28 should_route :get, "/attachments/1/filename.ext", :controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext'
28 should_route :get, "/attachments/1/filename.ext", :controller => 'attachments', :action => 'show', :id => '1', :filename => 'filename.ext'
29 should_route :get, "/attachments/download/1", :controller => 'attachments', :action => 'download', :id => '1'
29 should_route :get, "/attachments/download/1", :controller => 'attachments', :action => 'download', :id => '1'
30 should_route :get, "/attachments/download/1/filename.ext", :controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext'
30 should_route :get, "/attachments/download/1/filename.ext", :controller => 'attachments', :action => 'download', :id => '1', :filename => 'filename.ext'
31 end
31 end
32
32
33 context "boards" do
33 context "boards" do
34 should_route :get, "/projects/world_domination/boards", :controller => 'boards', :action => 'index', :project_id => 'world_domination'
34 should_route :get, "/projects/world_domination/boards", :controller => 'boards', :action => 'index', :project_id => 'world_domination'
35 should_route :get, "/projects/world_domination/boards/new", :controller => 'boards', :action => 'new', :project_id => 'world_domination'
35 should_route :get, "/projects/world_domination/boards/new", :controller => 'boards', :action => 'new', :project_id => 'world_domination'
36 should_route :get, "/projects/world_domination/boards/44", :controller => 'boards', :action => 'show', :project_id => 'world_domination', :id => '44'
36 should_route :get, "/projects/world_domination/boards/44", :controller => 'boards', :action => 'show', :project_id => 'world_domination', :id => '44'
37 should_route :get, "/projects/world_domination/boards/44.atom", :controller => 'boards', :action => 'show', :project_id => 'world_domination', :id => '44', :format => 'atom'
37 should_route :get, "/projects/world_domination/boards/44.atom", :controller => 'boards', :action => 'show', :project_id => 'world_domination', :id => '44', :format => 'atom'
38 should_route :get, "/projects/world_domination/boards/44/edit", :controller => 'boards', :action => 'edit', :project_id => 'world_domination', :id => '44'
38 should_route :get, "/projects/world_domination/boards/44/edit", :controller => 'boards', :action => 'edit', :project_id => 'world_domination', :id => '44'
39
39
40 should_route :post, "/projects/world_domination/boards/new", :controller => 'boards', :action => 'new', :project_id => 'world_domination'
40 should_route :post, "/projects/world_domination/boards/new", :controller => 'boards', :action => 'new', :project_id => 'world_domination'
41 should_route :post, "/projects/world_domination/boards/44/edit", :controller => 'boards', :action => 'edit', :project_id => 'world_domination', :id => '44'
41 should_route :post, "/projects/world_domination/boards/44/edit", :controller => 'boards', :action => 'edit', :project_id => 'world_domination', :id => '44'
42 should_route :post, "/projects/world_domination/boards/44/destroy", :controller => 'boards', :action => 'destroy', :project_id => 'world_domination', :id => '44'
42 should_route :post, "/projects/world_domination/boards/44/destroy", :controller => 'boards', :action => 'destroy', :project_id => 'world_domination', :id => '44'
43
43
44 end
44 end
45
45
46 context "documents" do
46 context "documents" do
47 should_route :get, "/projects/567/documents", :controller => 'documents', :action => 'index', :project_id => '567'
47 should_route :get, "/projects/567/documents", :controller => 'documents', :action => 'index', :project_id => '567'
48 should_route :get, "/projects/567/documents/new", :controller => 'documents', :action => 'new', :project_id => '567'
48 should_route :get, "/projects/567/documents/new", :controller => 'documents', :action => 'new', :project_id => '567'
49 should_route :get, "/documents/22", :controller => 'documents', :action => 'show', :id => '22'
49 should_route :get, "/documents/22", :controller => 'documents', :action => 'show', :id => '22'
50 should_route :get, "/documents/22/edit", :controller => 'documents', :action => 'edit', :id => '22'
50 should_route :get, "/documents/22/edit", :controller => 'documents', :action => 'edit', :id => '22'
51
51
52 should_route :post, "/projects/567/documents/new", :controller => 'documents', :action => 'new', :project_id => '567'
52 should_route :post, "/projects/567/documents/new", :controller => 'documents', :action => 'new', :project_id => '567'
53 should_route :post, "/documents/567/edit", :controller => 'documents', :action => 'edit', :id => '567'
53 should_route :post, "/documents/567/edit", :controller => 'documents', :action => 'edit', :id => '567'
54 should_route :post, "/documents/567/destroy", :controller => 'documents', :action => 'destroy', :id => '567'
54 should_route :post, "/documents/567/destroy", :controller => 'documents', :action => 'destroy', :id => '567'
55 end
55 end
56
56
57 context "issues" do
57 context "issues" do
58 # REST actions
58 # REST actions
59 should_route :get, "/issues", :controller => 'issues', :action => 'index'
59 should_route :get, "/issues", :controller => 'issues', :action => 'index'
60 should_route :get, "/issues.pdf", :controller => 'issues', :action => 'index', :format => 'pdf'
60 should_route :get, "/issues.pdf", :controller => 'issues', :action => 'index', :format => 'pdf'
61 should_route :get, "/issues.atom", :controller => 'issues', :action => 'index', :format => 'atom'
61 should_route :get, "/issues.atom", :controller => 'issues', :action => 'index', :format => 'atom'
62 should_route :get, "/issues.xml", :controller => 'issues', :action => 'index', :format => 'xml'
62 should_route :get, "/issues.xml", :controller => 'issues', :action => 'index', :format => 'xml'
63 should_route :get, "/projects/23/issues", :controller => 'issues', :action => 'index', :project_id => '23'
63 should_route :get, "/projects/23/issues", :controller => 'issues', :action => 'index', :project_id => '23'
64 should_route :get, "/projects/23/issues.pdf", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
64 should_route :get, "/projects/23/issues.pdf", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
65 should_route :get, "/projects/23/issues.atom", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
65 should_route :get, "/projects/23/issues.atom", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
66 should_route :get, "/projects/23/issues.xml", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'xml'
66 should_route :get, "/projects/23/issues.xml", :controller => 'issues', :action => 'index', :project_id => '23', :format => 'xml'
67 should_route :get, "/issues/64", :controller => 'issues', :action => 'show', :id => '64'
67 should_route :get, "/issues/64", :controller => 'issues', :action => 'show', :id => '64'
68 should_route :get, "/issues/64.pdf", :controller => 'issues', :action => 'show', :id => '64', :format => 'pdf'
68 should_route :get, "/issues/64.pdf", :controller => 'issues', :action => 'show', :id => '64', :format => 'pdf'
69 should_route :get, "/issues/64.atom", :controller => 'issues', :action => 'show', :id => '64', :format => 'atom'
69 should_route :get, "/issues/64.atom", :controller => 'issues', :action => 'show', :id => '64', :format => 'atom'
70 should_route :get, "/issues/64.xml", :controller => 'issues', :action => 'show', :id => '64', :format => 'xml'
70 should_route :get, "/issues/64.xml", :controller => 'issues', :action => 'show', :id => '64', :format => 'xml'
71
71
72 should_route :get, "/projects/23/issues/new", :controller => 'issues', :action => 'new', :project_id => '23'
72 should_route :get, "/projects/23/issues/new", :controller => 'issues', :action => 'new', :project_id => '23'
73 should_route :post, "/projects/23/issues", :controller => 'issues', :action => 'create', :project_id => '23'
73 should_route :post, "/projects/23/issues", :controller => 'issues', :action => 'create', :project_id => '23'
74 should_route :post, "/issues.xml", :controller => 'issues', :action => 'create', :format => 'xml'
74 should_route :post, "/issues.xml", :controller => 'issues', :action => 'create', :format => 'xml'
75
75
76 should_route :get, "/issues/64/edit", :controller => 'issues', :action => 'edit', :id => '64'
76 should_route :get, "/issues/64/edit", :controller => 'issues', :action => 'edit', :id => '64'
77 # TODO: Should use PUT
77 # TODO: Should use PUT
78 should_route :post, "/issues/64/edit", :controller => 'issues', :action => 'edit', :id => '64'
78 should_route :post, "/issues/64/edit", :controller => 'issues', :action => 'edit', :id => '64'
79 should_route :put, "/issues/1.xml", :controller => 'issues', :action => 'update', :id => '1', :format => 'xml'
79 should_route :put, "/issues/1.xml", :controller => 'issues', :action => 'update', :id => '1', :format => 'xml'
80
80
81 # TODO: Should use DELETE
81 # TODO: Should use DELETE
82 should_route :post, "/issues/64/destroy", :controller => 'issues', :action => 'destroy', :id => '64'
82 should_route :post, "/issues/64/destroy", :controller => 'issues', :action => 'destroy', :id => '64'
83 should_route :delete, "/issues/1.xml", :controller => 'issues', :action => 'destroy', :id => '1', :format => 'xml'
83 should_route :delete, "/issues/1.xml", :controller => 'issues', :action => 'destroy', :id => '1', :format => 'xml'
84
84
85 # Extra actions
85 # Extra actions
86 should_route :get, "/projects/23/issues/64/copy", :controller => 'issues', :action => 'new', :project_id => '23', :copy_from => '64'
86 should_route :get, "/projects/23/issues/64/copy", :controller => 'issues', :action => 'new', :project_id => '23', :copy_from => '64'
87
87
88 should_route :get, "/issues/move/new", :controller => 'issue_moves', :action => 'new'
88 should_route :get, "/issues/move/new", :controller => 'issue_moves', :action => 'new'
89 should_route :post, "/issues/move", :controller => 'issue_moves', :action => 'create'
89 should_route :post, "/issues/move", :controller => 'issue_moves', :action => 'create'
90
90
91 should_route :post, "/issues/1/quoted", :controller => 'journals', :action => 'new', :id => '1'
91 should_route :post, "/issues/1/quoted", :controller => 'journals', :action => 'new', :id => '1'
92
92
93 should_route :get, "/issues/calendar", :controller => 'calendars', :action => 'show'
93 should_route :get, "/issues/calendar", :controller => 'calendars', :action => 'show'
94 should_route :get, "/projects/project-name/issues/calendar", :controller => 'calendars', :action => 'show', :project_id => 'project-name'
94 should_route :get, "/projects/project-name/issues/calendar", :controller => 'calendars', :action => 'show', :project_id => 'project-name'
95
95
96 should_route :get, "/issues/gantt", :controller => 'gantts', :action => 'show'
96 should_route :get, "/issues/gantt", :controller => 'gantts', :action => 'show'
97 should_route :get, "/issues/gantt.pdf", :controller => 'gantts', :action => 'show', :format => 'pdf'
97 should_route :get, "/issues/gantt.pdf", :controller => 'gantts', :action => 'show', :format => 'pdf'
98 should_route :get, "/projects/project-name/issues/gantt", :controller => 'gantts', :action => 'show', :project_id => 'project-name'
98 should_route :get, "/projects/project-name/issues/gantt", :controller => 'gantts', :action => 'show', :project_id => 'project-name'
99 should_route :get, "/projects/project-name/issues/gantt.pdf", :controller => 'gantts', :action => 'show', :project_id => 'project-name', :format => 'pdf'
99 should_route :get, "/projects/project-name/issues/gantt.pdf", :controller => 'gantts', :action => 'show', :project_id => 'project-name', :format => 'pdf'
100
100
101 should_route :get, "/issues/auto_complete", :controller => 'auto_completes', :action => 'issues'
101 should_route :get, "/issues/auto_complete", :controller => 'auto_completes', :action => 'issues'
102
102
103 should_route :get, "/issues/preview/123", :controller => 'previews', :action => 'issue', :id => '123'
103 should_route :get, "/issues/preview/123", :controller => 'previews', :action => 'issue', :id => '123'
104 should_route :post, "/issues/preview/123", :controller => 'previews', :action => 'issue', :id => '123'
104 should_route :post, "/issues/preview/123", :controller => 'previews', :action => 'issue', :id => '123'
105 should_route :get, "/issues/context_menu", :controller => 'context_menus', :action => 'issues'
105 should_route :get, "/issues/context_menu", :controller => 'context_menus', :action => 'issues'
106 should_route :post, "/issues/context_menu", :controller => 'context_menus', :action => 'issues'
106 should_route :post, "/issues/context_menu", :controller => 'context_menus', :action => 'issues'
107
107
108 should_route :get, "/issues/changes", :controller => 'journals', :action => 'index'
108 should_route :get, "/issues/changes", :controller => 'journals', :action => 'index'
109
109
110 should_route :get, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_edit'
110 should_route :get, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_edit'
111 should_route :post, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_update'
111 should_route :post, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_update'
112 end
112 end
113
113
114 context "issue categories" do
114 context "issue categories" do
115 should_route :get, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
115 should_route :get, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
116
116
117 should_route :post, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
117 should_route :post, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
118 end
118 end
119
119
120 context "issue relations" do
120 context "issue relations" do
121 should_route :get, "/issues/1/relations", :controller => 'issue_relations', :action => 'index', :issue_id => '1'
121 should_route :get, "/issues/1/relations", :controller => 'issue_relations', :action => 'index', :issue_id => '1'
122 should_route :get, "/issues/1/relations.xml", :controller => 'issue_relations', :action => 'index', :issue_id => '1', :format => 'xml'
122 should_route :get, "/issues/1/relations.xml", :controller => 'issue_relations', :action => 'index', :issue_id => '1', :format => 'xml'
123 should_route :get, "/issues/1/relations.json", :controller => 'issue_relations', :action => 'index', :issue_id => '1', :format => 'json'
123 should_route :get, "/issues/1/relations.json", :controller => 'issue_relations', :action => 'index', :issue_id => '1', :format => 'json'
124
124
125 should_route :post, "/issues/1/relations", :controller => 'issue_relations', :action => 'create', :issue_id => '1'
125 should_route :post, "/issues/1/relations", :controller => 'issue_relations', :action => 'create', :issue_id => '1'
126 should_route :post, "/issues/1/relations.xml", :controller => 'issue_relations', :action => 'create', :issue_id => '1', :format => 'xml'
126 should_route :post, "/issues/1/relations.xml", :controller => 'issue_relations', :action => 'create', :issue_id => '1', :format => 'xml'
127 should_route :post, "/issues/1/relations.json", :controller => 'issue_relations', :action => 'create', :issue_id => '1', :format => 'json'
127 should_route :post, "/issues/1/relations.json", :controller => 'issue_relations', :action => 'create', :issue_id => '1', :format => 'json'
128
128
129 should_route :get, "/relations/23", :controller => 'issue_relations', :action => 'show', :id => '23'
129 should_route :get, "/relations/23", :controller => 'issue_relations', :action => 'show', :id => '23'
130 should_route :get, "/relations/23.xml", :controller => 'issue_relations', :action => 'show', :id => '23', :format => 'xml'
130 should_route :get, "/relations/23.xml", :controller => 'issue_relations', :action => 'show', :id => '23', :format => 'xml'
131 should_route :get, "/relations/23.json", :controller => 'issue_relations', :action => 'show', :id => '23', :format => 'json'
131 should_route :get, "/relations/23.json", :controller => 'issue_relations', :action => 'show', :id => '23', :format => 'json'
132
132
133 should_route :delete, "/relations/23", :controller => 'issue_relations', :action => 'destroy', :id => '23'
133 should_route :delete, "/relations/23", :controller => 'issue_relations', :action => 'destroy', :id => '23'
134 should_route :delete, "/relations/23.xml", :controller => 'issue_relations', :action => 'destroy', :id => '23', :format => 'xml'
134 should_route :delete, "/relations/23.xml", :controller => 'issue_relations', :action => 'destroy', :id => '23', :format => 'xml'
135 should_route :delete, "/relations/23.json", :controller => 'issue_relations', :action => 'destroy', :id => '23', :format => 'json'
135 should_route :delete, "/relations/23.json", :controller => 'issue_relations', :action => 'destroy', :id => '23', :format => 'json'
136 end
136 end
137
137
138 context "issue reports" do
138 context "issue reports" do
139 should_route :get, "/projects/567/issues/report", :controller => 'reports', :action => 'issue_report', :id => '567'
139 should_route :get, "/projects/567/issues/report", :controller => 'reports', :action => 'issue_report', :id => '567'
140 should_route :get, "/projects/567/issues/report/assigned_to", :controller => 'reports', :action => 'issue_report_details', :id => '567', :detail => 'assigned_to'
140 should_route :get, "/projects/567/issues/report/assigned_to", :controller => 'reports', :action => 'issue_report_details', :id => '567', :detail => 'assigned_to'
141 end
141 end
142
142
143 context "members" do
143 context "members" do
144 should_route :post, "/projects/5234/members/new", :controller => 'members', :action => 'new', :id => '5234'
144 should_route :post, "/projects/5234/members/new", :controller => 'members', :action => 'new', :id => '5234'
145 end
145 end
146
146
147 context "messages" do
147 context "messages" do
148 should_route :get, "/boards/22/topics/2", :controller => 'messages', :action => 'show', :id => '2', :board_id => '22'
148 should_route :get, "/boards/22/topics/2", :controller => 'messages', :action => 'show', :id => '2', :board_id => '22'
149 should_route :get, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
149 should_route :get, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
150 should_route :get, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
150 should_route :get, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
151
151
152 should_route :post, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
152 should_route :post, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
153 should_route :post, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
153 should_route :post, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
154 should_route :post, "/boards/22/topics/555/replies", :controller => 'messages', :action => 'reply', :id => '555', :board_id => '22'
154 should_route :post, "/boards/22/topics/555/replies", :controller => 'messages', :action => 'reply', :id => '555', :board_id => '22'
155 should_route :post, "/boards/22/topics/555/destroy", :controller => 'messages', :action => 'destroy', :id => '555', :board_id => '22'
155 should_route :post, "/boards/22/topics/555/destroy", :controller => 'messages', :action => 'destroy', :id => '555', :board_id => '22'
156 end
156 end
157
157
158 context "news" do
158 context "news" do
159 should_route :get, "/news", :controller => 'news', :action => 'index'
159 should_route :get, "/news", :controller => 'news', :action => 'index'
160 should_route :get, "/news.atom", :controller => 'news', :action => 'index', :format => 'atom'
160 should_route :get, "/news.atom", :controller => 'news', :action => 'index', :format => 'atom'
161 should_route :get, "/news.xml", :controller => 'news', :action => 'index', :format => 'xml'
161 should_route :get, "/news.xml", :controller => 'news', :action => 'index', :format => 'xml'
162 should_route :get, "/news.json", :controller => 'news', :action => 'index', :format => 'json'
162 should_route :get, "/news.json", :controller => 'news', :action => 'index', :format => 'json'
163 should_route :get, "/projects/567/news", :controller => 'news', :action => 'index', :project_id => '567'
163 should_route :get, "/projects/567/news", :controller => 'news', :action => 'index', :project_id => '567'
164 should_route :get, "/projects/567/news.atom", :controller => 'news', :action => 'index', :format => 'atom', :project_id => '567'
164 should_route :get, "/projects/567/news.atom", :controller => 'news', :action => 'index', :format => 'atom', :project_id => '567'
165 should_route :get, "/projects/567/news.xml", :controller => 'news', :action => 'index', :format => 'xml', :project_id => '567'
165 should_route :get, "/projects/567/news.xml", :controller => 'news', :action => 'index', :format => 'xml', :project_id => '567'
166 should_route :get, "/projects/567/news.json", :controller => 'news', :action => 'index', :format => 'json', :project_id => '567'
166 should_route :get, "/projects/567/news.json", :controller => 'news', :action => 'index', :format => 'json', :project_id => '567'
167 should_route :get, "/news/2", :controller => 'news', :action => 'show', :id => '2'
167 should_route :get, "/news/2", :controller => 'news', :action => 'show', :id => '2'
168 should_route :get, "/projects/567/news/new", :controller => 'news', :action => 'new', :project_id => '567'
168 should_route :get, "/projects/567/news/new", :controller => 'news', :action => 'new', :project_id => '567'
169 should_route :get, "/news/234", :controller => 'news', :action => 'show', :id => '234'
169 should_route :get, "/news/234", :controller => 'news', :action => 'show', :id => '234'
170 should_route :get, "/news/567/edit", :controller => 'news', :action => 'edit', :id => '567'
170 should_route :get, "/news/567/edit", :controller => 'news', :action => 'edit', :id => '567'
171 should_route :get, "/news/preview", :controller => 'previews', :action => 'news'
171 should_route :get, "/news/preview", :controller => 'previews', :action => 'news'
172
172
173 should_route :post, "/projects/567/news", :controller => 'news', :action => 'create', :project_id => '567'
173 should_route :post, "/projects/567/news", :controller => 'news', :action => 'create', :project_id => '567'
174 should_route :post, "/news/567/comments", :controller => 'comments', :action => 'create', :id => '567'
174 should_route :post, "/news/567/comments", :controller => 'comments', :action => 'create', :id => '567'
175
175
176 should_route :put, "/news/567", :controller => 'news', :action => 'update', :id => '567'
176 should_route :put, "/news/567", :controller => 'news', :action => 'update', :id => '567'
177
177
178 should_route :delete, "/news/567", :controller => 'news', :action => 'destroy', :id => '567'
178 should_route :delete, "/news/567", :controller => 'news', :action => 'destroy', :id => '567'
179 should_route :delete, "/news/567/comments/15", :controller => 'comments', :action => 'destroy', :id => '567', :comment_id => '15'
179 should_route :delete, "/news/567/comments/15", :controller => 'comments', :action => 'destroy', :id => '567', :comment_id => '15'
180 end
180 end
181
181
182 context "projects" do
182 context "projects" do
183 should_route :get, "/projects", :controller => 'projects', :action => 'index'
183 should_route :get, "/projects", :controller => 'projects', :action => 'index'
184 should_route :get, "/projects.atom", :controller => 'projects', :action => 'index', :format => 'atom'
184 should_route :get, "/projects.atom", :controller => 'projects', :action => 'index', :format => 'atom'
185 should_route :get, "/projects.xml", :controller => 'projects', :action => 'index', :format => 'xml'
185 should_route :get, "/projects.xml", :controller => 'projects', :action => 'index', :format => 'xml'
186 should_route :get, "/projects/new", :controller => 'projects', :action => 'new'
186 should_route :get, "/projects/new", :controller => 'projects', :action => 'new'
187 should_route :get, "/projects/test", :controller => 'projects', :action => 'show', :id => 'test'
187 should_route :get, "/projects/test", :controller => 'projects', :action => 'show', :id => 'test'
188 should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
188 should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
189 should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223'
189 should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223'
190 should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
190 should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
191 should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :project_id => '33'
191 should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :project_id => '33'
192 should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :project_id => '33'
192 should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :project_id => '33'
193 should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33'
193 should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33'
194 should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33'
194 should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33'
195 should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom'
195 should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom'
196
196
197 should_route :post, "/projects", :controller => 'projects', :action => 'create'
197 should_route :post, "/projects", :controller => 'projects', :action => 'create'
198 should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml'
198 should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml'
199 should_route :post, "/projects/33/files", :controller => 'files', :action => 'create', :project_id => '33'
199 should_route :post, "/projects/33/files", :controller => 'files', :action => 'create', :project_id => '33'
200 should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64'
200 should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64'
201 should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64'
201 should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64'
202
202
203 should_route :put, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'update', :project_id => '64'
203 should_route :put, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'update', :project_id => '64'
204 should_route :put, "/projects/4223", :controller => 'projects', :action => 'update', :id => '4223'
204 should_route :put, "/projects/4223", :controller => 'projects', :action => 'update', :id => '4223'
205 should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml'
205 should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml'
206
206
207 should_route :delete, "/projects/64", :controller => 'projects', :action => 'destroy', :id => '64'
207 should_route :delete, "/projects/64", :controller => 'projects', :action => 'destroy', :id => '64'
208 should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
208 should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
209 should_route :delete, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'destroy', :project_id => '64'
209 should_route :delete, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'destroy', :project_id => '64'
210 end
210 end
211
211
212 context "queries" do
212 context "queries" do
213 should_route :get, "/queries.xml", :controller => 'queries', :action => 'index', :format => 'xml'
214 should_route :get, "/queries.json", :controller => 'queries', :action => 'index', :format => 'json'
215
213 should_route :get, "/queries/new", :controller => 'queries', :action => 'new'
216 should_route :get, "/queries/new", :controller => 'queries', :action => 'new'
214 should_route :get, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
217 should_route :get, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
215
218
216 should_route :post, "/queries/new", :controller => 'queries', :action => 'new'
219 should_route :post, "/queries/new", :controller => 'queries', :action => 'new'
217 should_route :post, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
220 should_route :post, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
218 end
221 end
219
222
220 context "repositories" do
223 context "repositories" do
221 should_route :get, "/projects/redmine/repository", :controller => 'repositories', :action => 'show', :id => 'redmine'
224 should_route :get, "/projects/redmine/repository", :controller => 'repositories', :action => 'show', :id => 'redmine'
222 should_route :get, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
225 should_route :get, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
223 should_route :get, "/projects/redmine/repository/revisions", :controller => 'repositories', :action => 'revisions', :id => 'redmine'
226 should_route :get, "/projects/redmine/repository/revisions", :controller => 'repositories', :action => 'revisions', :id => 'redmine'
224 should_route :get, "/projects/redmine/repository/revisions.atom", :controller => 'repositories', :action => 'revisions', :id => 'redmine', :format => 'atom'
227 should_route :get, "/projects/redmine/repository/revisions.atom", :controller => 'repositories', :action => 'revisions', :id => 'redmine', :format => 'atom'
225 should_route :get, "/projects/redmine/repository/revisions/2457", :controller => 'repositories', :action => 'revision', :id => 'redmine', :rev => '2457'
228 should_route :get, "/projects/redmine/repository/revisions/2457", :controller => 'repositories', :action => 'revision', :id => 'redmine', :rev => '2457'
226 should_route :get, "/projects/redmine/repository/revisions/2457/diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457'
229 should_route :get, "/projects/redmine/repository/revisions/2457/diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457'
227 should_route :get, "/projects/redmine/repository/revisions/2457/diff.diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457', :format => 'diff'
230 should_route :get, "/projects/redmine/repository/revisions/2457/diff.diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457', :format => 'diff'
228 should_route :get, "/projects/redmine/repository/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c]
231 should_route :get, "/projects/redmine/repository/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c]
229 should_route :get, "/projects/redmine/repository/revisions/2/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c], :rev => '2'
232 should_route :get, "/projects/redmine/repository/revisions/2/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c], :rev => '2'
230 should_route :get, "/projects/redmine/repository/browse/path/to/file.c", :controller => 'repositories', :action => 'browse', :id => 'redmine', :path => %w[path to file.c]
233 should_route :get, "/projects/redmine/repository/browse/path/to/file.c", :controller => 'repositories', :action => 'browse', :id => 'redmine', :path => %w[path to file.c]
231 should_route :get, "/projects/redmine/repository/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c]
234 should_route :get, "/projects/redmine/repository/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c]
232 should_route :get, "/projects/redmine/repository/revisions/2/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :rev => '2'
235 should_route :get, "/projects/redmine/repository/revisions/2/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :rev => '2'
233 should_route :get, "/projects/redmine/repository/raw/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :format => 'raw'
236 should_route :get, "/projects/redmine/repository/raw/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :format => 'raw'
234 should_route :get, "/projects/redmine/repository/revisions/2/raw/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :rev => '2', :format => 'raw'
237 should_route :get, "/projects/redmine/repository/revisions/2/raw/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c], :rev => '2', :format => 'raw'
235 should_route :get, "/projects/redmine/repository/annotate/path/to/file.c", :controller => 'repositories', :action => 'annotate', :id => 'redmine', :path => %w[path to file.c]
238 should_route :get, "/projects/redmine/repository/annotate/path/to/file.c", :controller => 'repositories', :action => 'annotate', :id => 'redmine', :path => %w[path to file.c]
236 should_route :get, "/projects/redmine/repository/changes/path/to/file.c", :controller => 'repositories', :action => 'changes', :id => 'redmine', :path => %w[path to file.c]
239 should_route :get, "/projects/redmine/repository/changes/path/to/file.c", :controller => 'repositories', :action => 'changes', :id => 'redmine', :path => %w[path to file.c]
237 should_route :get, "/projects/redmine/repository/statistics", :controller => 'repositories', :action => 'stats', :id => 'redmine'
240 should_route :get, "/projects/redmine/repository/statistics", :controller => 'repositories', :action => 'stats', :id => 'redmine'
238
241
239
242
240 should_route :post, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
243 should_route :post, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
241 end
244 end
242
245
243 context "timelogs (global)" do
246 context "timelogs (global)" do
244 should_route :get, "/time_entries", :controller => 'timelog', :action => 'index'
247 should_route :get, "/time_entries", :controller => 'timelog', :action => 'index'
245 should_route :get, "/time_entries.csv", :controller => 'timelog', :action => 'index', :format => 'csv'
248 should_route :get, "/time_entries.csv", :controller => 'timelog', :action => 'index', :format => 'csv'
246 should_route :get, "/time_entries.atom", :controller => 'timelog', :action => 'index', :format => 'atom'
249 should_route :get, "/time_entries.atom", :controller => 'timelog', :action => 'index', :format => 'atom'
247 should_route :get, "/time_entries/new", :controller => 'timelog', :action => 'new'
250 should_route :get, "/time_entries/new", :controller => 'timelog', :action => 'new'
248 should_route :get, "/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22'
251 should_route :get, "/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22'
249
252
250 should_route :post, "/time_entries", :controller => 'timelog', :action => 'create'
253 should_route :post, "/time_entries", :controller => 'timelog', :action => 'create'
251
254
252 should_route :put, "/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22'
255 should_route :put, "/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22'
253
256
254 should_route :delete, "/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55'
257 should_route :delete, "/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55'
255 end
258 end
256
259
257 context "timelogs (scoped under project)" do
260 context "timelogs (scoped under project)" do
258 should_route :get, "/projects/567/time_entries", :controller => 'timelog', :action => 'index', :project_id => '567'
261 should_route :get, "/projects/567/time_entries", :controller => 'timelog', :action => 'index', :project_id => '567'
259 should_route :get, "/projects/567/time_entries.csv", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'csv'
262 should_route :get, "/projects/567/time_entries.csv", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'csv'
260 should_route :get, "/projects/567/time_entries.atom", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'atom'
263 should_route :get, "/projects/567/time_entries.atom", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'atom'
261 should_route :get, "/projects/567/time_entries/new", :controller => 'timelog', :action => 'new', :project_id => '567'
264 should_route :get, "/projects/567/time_entries/new", :controller => 'timelog', :action => 'new', :project_id => '567'
262 should_route :get, "/projects/567/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :project_id => '567'
265 should_route :get, "/projects/567/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :project_id => '567'
263
266
264 should_route :post, "/projects/567/time_entries", :controller => 'timelog', :action => 'create', :project_id => '567'
267 should_route :post, "/projects/567/time_entries", :controller => 'timelog', :action => 'create', :project_id => '567'
265
268
266 should_route :put, "/projects/567/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :project_id => '567'
269 should_route :put, "/projects/567/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :project_id => '567'
267
270
268 should_route :delete, "/projects/567/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :project_id => '567'
271 should_route :delete, "/projects/567/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :project_id => '567'
269 end
272 end
270
273
271 context "timelogs (scoped under issues)" do
274 context "timelogs (scoped under issues)" do
272 should_route :get, "/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234'
275 should_route :get, "/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234'
273 should_route :get, "/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'csv'
276 should_route :get, "/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'csv'
274 should_route :get, "/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'atom'
277 should_route :get, "/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'atom'
275 should_route :get, "/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234'
278 should_route :get, "/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234'
276 should_route :get, "/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234'
279 should_route :get, "/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234'
277
280
278 should_route :post, "/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234'
281 should_route :post, "/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234'
279
282
280 should_route :put, "/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234'
283 should_route :put, "/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234'
281
284
282 should_route :delete, "/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234'
285 should_route :delete, "/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234'
283 end
286 end
284
287
285 context "timelogs (scoped under project and issues)" do
288 context "timelogs (scoped under project and issues)" do
286 should_route :get, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook'
289 should_route :get, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook'
287 should_route :get, "/projects/ecookbook/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'csv'
290 should_route :get, "/projects/ecookbook/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'csv'
288 should_route :get, "/projects/ecookbook/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'atom'
291 should_route :get, "/projects/ecookbook/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'atom'
289 should_route :get, "/projects/ecookbook/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234', :project_id => 'ecookbook'
292 should_route :get, "/projects/ecookbook/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234', :project_id => 'ecookbook'
290 should_route :get, "/projects/ecookbook/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
293 should_route :get, "/projects/ecookbook/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
291
294
292 should_route :post, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234', :project_id => 'ecookbook'
295 should_route :post, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234', :project_id => 'ecookbook'
293
296
294 should_route :put, "/projects/ecookbook/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
297 should_route :put, "/projects/ecookbook/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
295
298
296 should_route :delete, "/projects/ecookbook/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234', :project_id => 'ecookbook'
299 should_route :delete, "/projects/ecookbook/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234', :project_id => 'ecookbook'
297 end
300 end
298
301
299 context "time_entry_reports" do
302 context "time_entry_reports" do
300 should_route :get, "/time_entries/report", :controller => 'time_entry_reports', :action => 'report'
303 should_route :get, "/time_entries/report", :controller => 'time_entry_reports', :action => 'report'
301 should_route :get, "/projects/567/time_entries/report", :controller => 'time_entry_reports', :action => 'report', :project_id => '567'
304 should_route :get, "/projects/567/time_entries/report", :controller => 'time_entry_reports', :action => 'report', :project_id => '567'
302 should_route :get, "/projects/567/time_entries/report.csv", :controller => 'time_entry_reports', :action => 'report', :project_id => '567', :format => 'csv'
305 should_route :get, "/projects/567/time_entries/report.csv", :controller => 'time_entry_reports', :action => 'report', :project_id => '567', :format => 'csv'
303 end
306 end
304
307
305 context "users" do
308 context "users" do
306 should_route :get, "/users", :controller => 'users', :action => 'index'
309 should_route :get, "/users", :controller => 'users', :action => 'index'
307 should_route :get, "/users.xml", :controller => 'users', :action => 'index', :format => 'xml'
310 should_route :get, "/users.xml", :controller => 'users', :action => 'index', :format => 'xml'
308 should_route :get, "/users/44", :controller => 'users', :action => 'show', :id => '44'
311 should_route :get, "/users/44", :controller => 'users', :action => 'show', :id => '44'
309 should_route :get, "/users/44.xml", :controller => 'users', :action => 'show', :id => '44', :format => 'xml'
312 should_route :get, "/users/44.xml", :controller => 'users', :action => 'show', :id => '44', :format => 'xml'
310 should_route :get, "/users/current", :controller => 'users', :action => 'show', :id => 'current'
313 should_route :get, "/users/current", :controller => 'users', :action => 'show', :id => 'current'
311 should_route :get, "/users/current.xml", :controller => 'users', :action => 'show', :id => 'current', :format => 'xml'
314 should_route :get, "/users/current.xml", :controller => 'users', :action => 'show', :id => 'current', :format => 'xml'
312 should_route :get, "/users/new", :controller => 'users', :action => 'new'
315 should_route :get, "/users/new", :controller => 'users', :action => 'new'
313 should_route :get, "/users/444/edit", :controller => 'users', :action => 'edit', :id => '444'
316 should_route :get, "/users/444/edit", :controller => 'users', :action => 'edit', :id => '444'
314 should_route :get, "/users/222/edit/membership", :controller => 'users', :action => 'edit', :id => '222', :tab => 'membership'
317 should_route :get, "/users/222/edit/membership", :controller => 'users', :action => 'edit', :id => '222', :tab => 'membership'
315
318
316 should_route :post, "/users", :controller => 'users', :action => 'create'
319 should_route :post, "/users", :controller => 'users', :action => 'create'
317 should_route :post, "/users.xml", :controller => 'users', :action => 'create', :format => 'xml'
320 should_route :post, "/users.xml", :controller => 'users', :action => 'create', :format => 'xml'
318 should_route :post, "/users/123/memberships", :controller => 'users', :action => 'edit_membership', :id => '123'
321 should_route :post, "/users/123/memberships", :controller => 'users', :action => 'edit_membership', :id => '123'
319 should_route :post, "/users/123/memberships/55", :controller => 'users', :action => 'edit_membership', :id => '123', :membership_id => '55'
322 should_route :post, "/users/123/memberships/55", :controller => 'users', :action => 'edit_membership', :id => '123', :membership_id => '55'
320 should_route :post, "/users/567/memberships/12/destroy", :controller => 'users', :action => 'destroy_membership', :id => '567', :membership_id => '12'
323 should_route :post, "/users/567/memberships/12/destroy", :controller => 'users', :action => 'destroy_membership', :id => '567', :membership_id => '12'
321
324
322 should_route :put, "/users/444", :controller => 'users', :action => 'update', :id => '444'
325 should_route :put, "/users/444", :controller => 'users', :action => 'update', :id => '444'
323 should_route :put, "/users/444.xml", :controller => 'users', :action => 'update', :id => '444', :format => 'xml'
326 should_route :put, "/users/444.xml", :controller => 'users', :action => 'update', :id => '444', :format => 'xml'
324
327
325 should_route :delete, "/users/44", :controller => 'users', :action => 'destroy', :id => '44'
328 should_route :delete, "/users/44", :controller => 'users', :action => 'destroy', :id => '44'
326 should_route :delete, "/users/44.xml", :controller => 'users', :action => 'destroy', :id => '44', :format => 'xml'
329 should_route :delete, "/users/44.xml", :controller => 'users', :action => 'destroy', :id => '44', :format => 'xml'
327 end
330 end
328
331
329 context "versions" do
332 context "versions" do
330 # /projects/foo/versions is /projects/foo/roadmap
333 # /projects/foo/versions is /projects/foo/roadmap
331 should_route :get, "/projects/foo/versions.xml", :controller => 'versions', :action => 'index', :project_id => 'foo', :format => 'xml'
334 should_route :get, "/projects/foo/versions.xml", :controller => 'versions', :action => 'index', :project_id => 'foo', :format => 'xml'
332 should_route :get, "/projects/foo/versions.json", :controller => 'versions', :action => 'index', :project_id => 'foo', :format => 'json'
335 should_route :get, "/projects/foo/versions.json", :controller => 'versions', :action => 'index', :project_id => 'foo', :format => 'json'
333
336
334 should_route :get, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo'
337 should_route :get, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo'
335
338
336 should_route :post, "/projects/foo/versions", :controller => 'versions', :action => 'create', :project_id => 'foo'
339 should_route :post, "/projects/foo/versions", :controller => 'versions', :action => 'create', :project_id => 'foo'
337 should_route :post, "/projects/foo/versions.xml", :controller => 'versions', :action => 'create', :project_id => 'foo', :format => 'xml'
340 should_route :post, "/projects/foo/versions.xml", :controller => 'versions', :action => 'create', :project_id => 'foo', :format => 'xml'
338 should_route :post, "/projects/foo/versions.json", :controller => 'versions', :action => 'create', :project_id => 'foo', :format => 'json'
341 should_route :post, "/projects/foo/versions.json", :controller => 'versions', :action => 'create', :project_id => 'foo', :format => 'json'
339
342
340 should_route :get, "/versions/1", :controller => 'versions', :action => 'show', :id => '1'
343 should_route :get, "/versions/1", :controller => 'versions', :action => 'show', :id => '1'
341 should_route :get, "/versions/1.xml", :controller => 'versions', :action => 'show', :id => '1', :format => 'xml'
344 should_route :get, "/versions/1.xml", :controller => 'versions', :action => 'show', :id => '1', :format => 'xml'
342 should_route :get, "/versions/1.json", :controller => 'versions', :action => 'show', :id => '1', :format => 'json'
345 should_route :get, "/versions/1.json", :controller => 'versions', :action => 'show', :id => '1', :format => 'json'
343
346
344 should_route :get, "/versions/1/edit", :controller => 'versions', :action => 'edit', :id => '1'
347 should_route :get, "/versions/1/edit", :controller => 'versions', :action => 'edit', :id => '1'
345
348
346 should_route :put, "/versions/1", :controller => 'versions', :action => 'update', :id => '1'
349 should_route :put, "/versions/1", :controller => 'versions', :action => 'update', :id => '1'
347 should_route :put, "/versions/1.xml", :controller => 'versions', :action => 'update', :id => '1', :format => 'xml'
350 should_route :put, "/versions/1.xml", :controller => 'versions', :action => 'update', :id => '1', :format => 'xml'
348 should_route :put, "/versions/1.json", :controller => 'versions', :action => 'update', :id => '1', :format => 'json'
351 should_route :put, "/versions/1.json", :controller => 'versions', :action => 'update', :id => '1', :format => 'json'
349
352
350 should_route :delete, "/versions/1", :controller => 'versions', :action => 'destroy', :id => '1'
353 should_route :delete, "/versions/1", :controller => 'versions', :action => 'destroy', :id => '1'
351 should_route :delete, "/versions/1.xml", :controller => 'versions', :action => 'destroy', :id => '1', :format => 'xml'
354 should_route :delete, "/versions/1.xml", :controller => 'versions', :action => 'destroy', :id => '1', :format => 'xml'
352 should_route :delete, "/versions/1.json", :controller => 'versions', :action => 'destroy', :id => '1', :format => 'json'
355 should_route :delete, "/versions/1.json", :controller => 'versions', :action => 'destroy', :id => '1', :format => 'json'
353
356
354 should_route :put, "/projects/foo/versions/close_completed", :controller => 'versions', :action => 'close_completed', :project_id => 'foo'
357 should_route :put, "/projects/foo/versions/close_completed", :controller => 'versions', :action => 'close_completed', :project_id => 'foo'
355 should_route :post, "/versions/1/status_by", :controller => 'versions', :action => 'status_by', :id => '1'
358 should_route :post, "/versions/1/status_by", :controller => 'versions', :action => 'status_by', :id => '1'
356 end
359 end
357
360
358 context "wiki (singular, project's pages)" do
361 context "wiki (singular, project's pages)" do
359 should_route :get, "/projects/567/wiki", :controller => 'wiki', :action => 'show', :project_id => '567'
362 should_route :get, "/projects/567/wiki", :controller => 'wiki', :action => 'show', :project_id => '567'
360 should_route :get, "/projects/567/wiki/lalala", :controller => 'wiki', :action => 'show', :project_id => '567', :id => 'lalala'
363 should_route :get, "/projects/567/wiki/lalala", :controller => 'wiki', :action => 'show', :project_id => '567', :id => 'lalala'
361 should_route :get, "/projects/567/wiki/my_page/edit", :controller => 'wiki', :action => 'edit', :project_id => '567', :id => 'my_page'
364 should_route :get, "/projects/567/wiki/my_page/edit", :controller => 'wiki', :action => 'edit', :project_id => '567', :id => 'my_page'
362 should_route :get, "/projects/1/wiki/CookBook_documentation/history", :controller => 'wiki', :action => 'history', :project_id => '1', :id => 'CookBook_documentation'
365 should_route :get, "/projects/1/wiki/CookBook_documentation/history", :controller => 'wiki', :action => 'history', :project_id => '1', :id => 'CookBook_documentation'
363 should_route :get, "/projects/1/wiki/CookBook_documentation/diff", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation'
366 should_route :get, "/projects/1/wiki/CookBook_documentation/diff", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation'
364 should_route :get, "/projects/1/wiki/CookBook_documentation/diff/2", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
367 should_route :get, "/projects/1/wiki/CookBook_documentation/diff/2", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
365 should_route :get, "/projects/1/wiki/CookBook_documentation/diff/2/vs/1", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation', :version => '2', :version_from => '1'
368 should_route :get, "/projects/1/wiki/CookBook_documentation/diff/2/vs/1", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation', :version => '2', :version_from => '1'
366 should_route :get, "/projects/1/wiki/CookBook_documentation/annotate/2", :controller => 'wiki', :action => 'annotate', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
369 should_route :get, "/projects/1/wiki/CookBook_documentation/annotate/2", :controller => 'wiki', :action => 'annotate', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
367 should_route :get, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
370 should_route :get, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
368 should_route :get, "/projects/567/wiki/index", :controller => 'wiki', :action => 'index', :project_id => '567'
371 should_route :get, "/projects/567/wiki/index", :controller => 'wiki', :action => 'index', :project_id => '567'
369 should_route :get, "/projects/567/wiki/date_index", :controller => 'wiki', :action => 'date_index', :project_id => '567'
372 should_route :get, "/projects/567/wiki/date_index", :controller => 'wiki', :action => 'date_index', :project_id => '567'
370 should_route :get, "/projects/567/wiki/export", :controller => 'wiki', :action => 'export', :project_id => '567'
373 should_route :get, "/projects/567/wiki/export", :controller => 'wiki', :action => 'export', :project_id => '567'
371
374
372 should_route :post, "/projects/567/wiki/CookBook_documentation/preview", :controller => 'wiki', :action => 'preview', :project_id => '567', :id => 'CookBook_documentation'
375 should_route :post, "/projects/567/wiki/CookBook_documentation/preview", :controller => 'wiki', :action => 'preview', :project_id => '567', :id => 'CookBook_documentation'
373 should_route :post, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
376 should_route :post, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
374 should_route :post, "/projects/22/wiki/ladida/protect", :controller => 'wiki', :action => 'protect', :project_id => '22', :id => 'ladida'
377 should_route :post, "/projects/22/wiki/ladida/protect", :controller => 'wiki', :action => 'protect', :project_id => '22', :id => 'ladida'
375 should_route :post, "/projects/22/wiki/ladida/add_attachment", :controller => 'wiki', :action => 'add_attachment', :project_id => '22', :id => 'ladida'
378 should_route :post, "/projects/22/wiki/ladida/add_attachment", :controller => 'wiki', :action => 'add_attachment', :project_id => '22', :id => 'ladida'
376
379
377 should_route :put, "/projects/567/wiki/my_page", :controller => 'wiki', :action => 'update', :project_id => '567', :id => 'my_page'
380 should_route :put, "/projects/567/wiki/my_page", :controller => 'wiki', :action => 'update', :project_id => '567', :id => 'my_page'
378
381
379 should_route :delete, "/projects/22/wiki/ladida", :controller => 'wiki', :action => 'destroy', :project_id => '22', :id => 'ladida'
382 should_route :delete, "/projects/22/wiki/ladida", :controller => 'wiki', :action => 'destroy', :project_id => '22', :id => 'ladida'
380 end
383 end
381
384
382 context "wikis (plural, admin setup)" do
385 context "wikis (plural, admin setup)" do
383 should_route :get, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
386 should_route :get, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
384
387
385 should_route :post, "/projects/ladida/wiki", :controller => 'wikis', :action => 'edit', :id => 'ladida'
388 should_route :post, "/projects/ladida/wiki", :controller => 'wikis', :action => 'edit', :id => 'ladida'
386 should_route :post, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
389 should_route :post, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
387 end
390 end
388
391
389 context "administration panel" do
392 context "administration panel" do
390 should_route :get, "/admin/projects", :controller => 'admin', :action => 'projects'
393 should_route :get, "/admin/projects", :controller => 'admin', :action => 'projects'
391 end
394 end
392 end
395 end
@@ -1,607 +1,617
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class QueryTest < ActiveSupport::TestCase
20 class QueryTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :watchers, :custom_fields, :custom_values, :versions, :queries
21 fixtures :projects, :enabled_modules, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :watchers, :custom_fields, :custom_values, :versions, :queries
22
22
23 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
23 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
24 query = Query.new(:project => nil, :name => '_')
24 query = Query.new(:project => nil, :name => '_')
25 assert query.available_filters.has_key?('cf_1')
25 assert query.available_filters.has_key?('cf_1')
26 assert !query.available_filters.has_key?('cf_3')
26 assert !query.available_filters.has_key?('cf_3')
27 end
27 end
28
28
29 def test_system_shared_versions_should_be_available_in_global_queries
29 def test_system_shared_versions_should_be_available_in_global_queries
30 Version.find(2).update_attribute :sharing, 'system'
30 Version.find(2).update_attribute :sharing, 'system'
31 query = Query.new(:project => nil, :name => '_')
31 query = Query.new(:project => nil, :name => '_')
32 assert query.available_filters.has_key?('fixed_version_id')
32 assert query.available_filters.has_key?('fixed_version_id')
33 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
33 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
34 end
34 end
35
35
36 def test_project_filter_in_global_queries
36 def test_project_filter_in_global_queries
37 query = Query.new(:project => nil, :name => '_')
37 query = Query.new(:project => nil, :name => '_')
38 project_filter = query.available_filters["project_id"]
38 project_filter = query.available_filters["project_id"]
39 assert_not_nil project_filter
39 assert_not_nil project_filter
40 project_ids = project_filter[:values].map{|p| p[1]}
40 project_ids = project_filter[:values].map{|p| p[1]}
41 assert project_ids.include?("1") #public project
41 assert project_ids.include?("1") #public project
42 assert !project_ids.include?("2") #private project user cannot see
42 assert !project_ids.include?("2") #private project user cannot see
43 end
43 end
44
44
45 def find_issues_with_query(query)
45 def find_issues_with_query(query)
46 Issue.find :all,
46 Issue.find :all,
47 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
47 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
48 :conditions => query.statement
48 :conditions => query.statement
49 end
49 end
50
50
51 def assert_find_issues_with_query_is_successful(query)
51 def assert_find_issues_with_query_is_successful(query)
52 assert_nothing_raised do
52 assert_nothing_raised do
53 find_issues_with_query(query)
53 find_issues_with_query(query)
54 end
54 end
55 end
55 end
56
56
57 def assert_query_statement_includes(query, condition)
57 def assert_query_statement_includes(query, condition)
58 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
58 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
59 end
59 end
60
60
61 def test_query_should_allow_shared_versions_for_a_project_query
61 def test_query_should_allow_shared_versions_for_a_project_query
62 subproject_version = Version.find(4)
62 subproject_version = Version.find(4)
63 query = Query.new(:project => Project.find(1), :name => '_')
63 query = Query.new(:project => Project.find(1), :name => '_')
64 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
64 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
65
65
66 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
66 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
67 end
67 end
68
68
69 def test_query_with_multiple_custom_fields
69 def test_query_with_multiple_custom_fields
70 query = Query.find(1)
70 query = Query.find(1)
71 assert query.valid?
71 assert query.valid?
72 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
72 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
73 issues = find_issues_with_query(query)
73 issues = find_issues_with_query(query)
74 assert_equal 1, issues.length
74 assert_equal 1, issues.length
75 assert_equal Issue.find(3), issues.first
75 assert_equal Issue.find(3), issues.first
76 end
76 end
77
77
78 def test_operator_none
78 def test_operator_none
79 query = Query.new(:project => Project.find(1), :name => '_')
79 query = Query.new(:project => Project.find(1), :name => '_')
80 query.add_filter('fixed_version_id', '!*', [''])
80 query.add_filter('fixed_version_id', '!*', [''])
81 query.add_filter('cf_1', '!*', [''])
81 query.add_filter('cf_1', '!*', [''])
82 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
82 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
83 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
83 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
84 find_issues_with_query(query)
84 find_issues_with_query(query)
85 end
85 end
86
86
87 def test_operator_none_for_integer
87 def test_operator_none_for_integer
88 query = Query.new(:project => Project.find(1), :name => '_')
88 query = Query.new(:project => Project.find(1), :name => '_')
89 query.add_filter('estimated_hours', '!*', [''])
89 query.add_filter('estimated_hours', '!*', [''])
90 issues = find_issues_with_query(query)
90 issues = find_issues_with_query(query)
91 assert !issues.empty?
91 assert !issues.empty?
92 assert issues.all? {|i| !i.estimated_hours}
92 assert issues.all? {|i| !i.estimated_hours}
93 end
93 end
94
94
95 def test_operator_all
95 def test_operator_all
96 query = Query.new(:project => Project.find(1), :name => '_')
96 query = Query.new(:project => Project.find(1), :name => '_')
97 query.add_filter('fixed_version_id', '*', [''])
97 query.add_filter('fixed_version_id', '*', [''])
98 query.add_filter('cf_1', '*', [''])
98 query.add_filter('cf_1', '*', [''])
99 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
99 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
100 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
100 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
101 find_issues_with_query(query)
101 find_issues_with_query(query)
102 end
102 end
103
103
104 def test_operator_greater_than
104 def test_operator_greater_than
105 query = Query.new(:project => Project.find(1), :name => '_')
105 query = Query.new(:project => Project.find(1), :name => '_')
106 query.add_filter('done_ratio', '>=', ['40'])
106 query.add_filter('done_ratio', '>=', ['40'])
107 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40")
107 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40")
108 find_issues_with_query(query)
108 find_issues_with_query(query)
109 end
109 end
110
110
111 def test_operator_in_more_than
111 def test_operator_in_more_than
112 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
112 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
113 query = Query.new(:project => Project.find(1), :name => '_')
113 query = Query.new(:project => Project.find(1), :name => '_')
114 query.add_filter('due_date', '>t+', ['15'])
114 query.add_filter('due_date', '>t+', ['15'])
115 issues = find_issues_with_query(query)
115 issues = find_issues_with_query(query)
116 assert !issues.empty?
116 assert !issues.empty?
117 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
117 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
118 end
118 end
119
119
120 def test_operator_in_less_than
120 def test_operator_in_less_than
121 query = Query.new(:project => Project.find(1), :name => '_')
121 query = Query.new(:project => Project.find(1), :name => '_')
122 query.add_filter('due_date', '<t+', ['15'])
122 query.add_filter('due_date', '<t+', ['15'])
123 issues = find_issues_with_query(query)
123 issues = find_issues_with_query(query)
124 assert !issues.empty?
124 assert !issues.empty?
125 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
125 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
126 end
126 end
127
127
128 def test_operator_less_than_ago
128 def test_operator_less_than_ago
129 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
129 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
130 query = Query.new(:project => Project.find(1), :name => '_')
130 query = Query.new(:project => Project.find(1), :name => '_')
131 query.add_filter('due_date', '>t-', ['3'])
131 query.add_filter('due_date', '>t-', ['3'])
132 issues = find_issues_with_query(query)
132 issues = find_issues_with_query(query)
133 assert !issues.empty?
133 assert !issues.empty?
134 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
134 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
135 end
135 end
136
136
137 def test_operator_more_than_ago
137 def test_operator_more_than_ago
138 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
138 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
139 query = Query.new(:project => Project.find(1), :name => '_')
139 query = Query.new(:project => Project.find(1), :name => '_')
140 query.add_filter('due_date', '<t-', ['10'])
140 query.add_filter('due_date', '<t-', ['10'])
141 assert query.statement.include?("#{Issue.table_name}.due_date <=")
141 assert query.statement.include?("#{Issue.table_name}.due_date <=")
142 issues = find_issues_with_query(query)
142 issues = find_issues_with_query(query)
143 assert !issues.empty?
143 assert !issues.empty?
144 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
144 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
145 end
145 end
146
146
147 def test_operator_in
147 def test_operator_in
148 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
148 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
149 query = Query.new(:project => Project.find(1), :name => '_')
149 query = Query.new(:project => Project.find(1), :name => '_')
150 query.add_filter('due_date', 't+', ['2'])
150 query.add_filter('due_date', 't+', ['2'])
151 issues = find_issues_with_query(query)
151 issues = find_issues_with_query(query)
152 assert !issues.empty?
152 assert !issues.empty?
153 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
153 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
154 end
154 end
155
155
156 def test_operator_ago
156 def test_operator_ago
157 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
157 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
158 query = Query.new(:project => Project.find(1), :name => '_')
158 query = Query.new(:project => Project.find(1), :name => '_')
159 query.add_filter('due_date', 't-', ['3'])
159 query.add_filter('due_date', 't-', ['3'])
160 issues = find_issues_with_query(query)
160 issues = find_issues_with_query(query)
161 assert !issues.empty?
161 assert !issues.empty?
162 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
162 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
163 end
163 end
164
164
165 def test_operator_today
165 def test_operator_today
166 query = Query.new(:project => Project.find(1), :name => '_')
166 query = Query.new(:project => Project.find(1), :name => '_')
167 query.add_filter('due_date', 't', [''])
167 query.add_filter('due_date', 't', [''])
168 issues = find_issues_with_query(query)
168 issues = find_issues_with_query(query)
169 assert !issues.empty?
169 assert !issues.empty?
170 issues.each {|issue| assert_equal Date.today, issue.due_date}
170 issues.each {|issue| assert_equal Date.today, issue.due_date}
171 end
171 end
172
172
173 def test_operator_this_week_on_date
173 def test_operator_this_week_on_date
174 query = Query.new(:project => Project.find(1), :name => '_')
174 query = Query.new(:project => Project.find(1), :name => '_')
175 query.add_filter('due_date', 'w', [''])
175 query.add_filter('due_date', 'w', [''])
176 find_issues_with_query(query)
176 find_issues_with_query(query)
177 end
177 end
178
178
179 def test_operator_this_week_on_datetime
179 def test_operator_this_week_on_datetime
180 query = Query.new(:project => Project.find(1), :name => '_')
180 query = Query.new(:project => Project.find(1), :name => '_')
181 query.add_filter('created_on', 'w', [''])
181 query.add_filter('created_on', 'w', [''])
182 find_issues_with_query(query)
182 find_issues_with_query(query)
183 end
183 end
184
184
185 def test_operator_contains
185 def test_operator_contains
186 query = Query.new(:project => Project.find(1), :name => '_')
186 query = Query.new(:project => Project.find(1), :name => '_')
187 query.add_filter('subject', '~', ['uNable'])
187 query.add_filter('subject', '~', ['uNable'])
188 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
188 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
189 result = find_issues_with_query(query)
189 result = find_issues_with_query(query)
190 assert result.empty?
190 assert result.empty?
191 result.each {|issue| assert issue.subject.downcase.include?('unable') }
191 result.each {|issue| assert issue.subject.downcase.include?('unable') }
192 end
192 end
193
193
194 def test_range_for_this_week_with_week_starting_on_monday
194 def test_range_for_this_week_with_week_starting_on_monday
195 I18n.locale = :fr
195 I18n.locale = :fr
196 assert_equal '1', I18n.t(:general_first_day_of_week)
196 assert_equal '1', I18n.t(:general_first_day_of_week)
197
197
198 Date.stubs(:today).returns(Date.parse('2011-04-29'))
198 Date.stubs(:today).returns(Date.parse('2011-04-29'))
199
199
200 query = Query.new(:project => Project.find(1), :name => '_')
200 query = Query.new(:project => Project.find(1), :name => '_')
201 query.add_filter('due_date', 'w', [''])
201 query.add_filter('due_date', 'w', [''])
202 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
202 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
203 I18n.locale = :en
203 I18n.locale = :en
204 end
204 end
205
205
206 def test_range_for_this_week_with_week_starting_on_sunday
206 def test_range_for_this_week_with_week_starting_on_sunday
207 I18n.locale = :en
207 I18n.locale = :en
208 assert_equal '7', I18n.t(:general_first_day_of_week)
208 assert_equal '7', I18n.t(:general_first_day_of_week)
209
209
210 Date.stubs(:today).returns(Date.parse('2011-04-29'))
210 Date.stubs(:today).returns(Date.parse('2011-04-29'))
211
211
212 query = Query.new(:project => Project.find(1), :name => '_')
212 query = Query.new(:project => Project.find(1), :name => '_')
213 query.add_filter('due_date', 'w', [''])
213 query.add_filter('due_date', 'w', [''])
214 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
214 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
215 end
215 end
216
216
217 def test_operator_does_not_contains
217 def test_operator_does_not_contains
218 query = Query.new(:project => Project.find(1), :name => '_')
218 query = Query.new(:project => Project.find(1), :name => '_')
219 query.add_filter('subject', '!~', ['uNable'])
219 query.add_filter('subject', '!~', ['uNable'])
220 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
220 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
221 find_issues_with_query(query)
221 find_issues_with_query(query)
222 end
222 end
223
223
224 def test_filter_watched_issues
224 def test_filter_watched_issues
225 User.current = User.find(1)
225 User.current = User.find(1)
226 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
226 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
227 result = find_issues_with_query(query)
227 result = find_issues_with_query(query)
228 assert_not_nil result
228 assert_not_nil result
229 assert !result.empty?
229 assert !result.empty?
230 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
230 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
231 User.current = nil
231 User.current = nil
232 end
232 end
233
233
234 def test_filter_unwatched_issues
234 def test_filter_unwatched_issues
235 User.current = User.find(1)
235 User.current = User.find(1)
236 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
236 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
237 result = find_issues_with_query(query)
237 result = find_issues_with_query(query)
238 assert_not_nil result
238 assert_not_nil result
239 assert !result.empty?
239 assert !result.empty?
240 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
240 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
241 User.current = nil
241 User.current = nil
242 end
242 end
243
243
244 def test_statement_should_be_nil_with_no_filters
244 def test_statement_should_be_nil_with_no_filters
245 q = Query.new(:name => '_')
245 q = Query.new(:name => '_')
246 q.filters = {}
246 q.filters = {}
247
247
248 assert q.valid?
248 assert q.valid?
249 assert_nil q.statement
249 assert_nil q.statement
250 end
250 end
251
251
252 def test_default_columns
252 def test_default_columns
253 q = Query.new
253 q = Query.new
254 assert !q.columns.empty?
254 assert !q.columns.empty?
255 end
255 end
256
256
257 def test_set_column_names
257 def test_set_column_names
258 q = Query.new
258 q = Query.new
259 q.column_names = ['tracker', :subject, '', 'unknonw_column']
259 q.column_names = ['tracker', :subject, '', 'unknonw_column']
260 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
260 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
261 c = q.columns.first
261 c = q.columns.first
262 assert q.has_column?(c)
262 assert q.has_column?(c)
263 end
263 end
264
264
265 def test_groupable_columns_should_include_custom_fields
265 def test_groupable_columns_should_include_custom_fields
266 q = Query.new
266 q = Query.new
267 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}
267 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}
268 end
268 end
269
269
270 def test_grouped_with_valid_column
270 def test_grouped_with_valid_column
271 q = Query.new(:group_by => 'status')
271 q = Query.new(:group_by => 'status')
272 assert q.grouped?
272 assert q.grouped?
273 assert_not_nil q.group_by_column
273 assert_not_nil q.group_by_column
274 assert_equal :status, q.group_by_column.name
274 assert_equal :status, q.group_by_column.name
275 assert_not_nil q.group_by_statement
275 assert_not_nil q.group_by_statement
276 assert_equal 'status', q.group_by_statement
276 assert_equal 'status', q.group_by_statement
277 end
277 end
278
278
279 def test_grouped_with_invalid_column
279 def test_grouped_with_invalid_column
280 q = Query.new(:group_by => 'foo')
280 q = Query.new(:group_by => 'foo')
281 assert !q.grouped?
281 assert !q.grouped?
282 assert_nil q.group_by_column
282 assert_nil q.group_by_column
283 assert_nil q.group_by_statement
283 assert_nil q.group_by_statement
284 end
284 end
285
285
286 def test_default_sort
286 def test_default_sort
287 q = Query.new
287 q = Query.new
288 assert_equal [], q.sort_criteria
288 assert_equal [], q.sort_criteria
289 end
289 end
290
290
291 def test_set_sort_criteria_with_hash
291 def test_set_sort_criteria_with_hash
292 q = Query.new
292 q = Query.new
293 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
293 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
294 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
294 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
295 end
295 end
296
296
297 def test_set_sort_criteria_with_array
297 def test_set_sort_criteria_with_array
298 q = Query.new
298 q = Query.new
299 q.sort_criteria = [['priority', 'desc'], 'tracker']
299 q.sort_criteria = [['priority', 'desc'], 'tracker']
300 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
300 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
301 end
301 end
302
302
303 def test_create_query_with_sort
303 def test_create_query_with_sort
304 q = Query.new(:name => 'Sorted')
304 q = Query.new(:name => 'Sorted')
305 q.sort_criteria = [['priority', 'desc'], 'tracker']
305 q.sort_criteria = [['priority', 'desc'], 'tracker']
306 assert q.save
306 assert q.save
307 q.reload
307 q.reload
308 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
308 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
309 end
309 end
310
310
311 def test_sort_by_string_custom_field_asc
311 def test_sort_by_string_custom_field_asc
312 q = Query.new
312 q = Query.new
313 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
313 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
314 assert c
314 assert c
315 assert c.sortable
315 assert c.sortable
316 issues = Issue.find :all,
316 issues = Issue.find :all,
317 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
317 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
318 :conditions => q.statement,
318 :conditions => q.statement,
319 :order => "#{c.sortable} ASC"
319 :order => "#{c.sortable} ASC"
320 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
320 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
321 assert !values.empty?
321 assert !values.empty?
322 assert_equal values.sort, values
322 assert_equal values.sort, values
323 end
323 end
324
324
325 def test_sort_by_string_custom_field_desc
325 def test_sort_by_string_custom_field_desc
326 q = Query.new
326 q = Query.new
327 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
327 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
328 assert c
328 assert c
329 assert c.sortable
329 assert c.sortable
330 issues = Issue.find :all,
330 issues = Issue.find :all,
331 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
331 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
332 :conditions => q.statement,
332 :conditions => q.statement,
333 :order => "#{c.sortable} DESC"
333 :order => "#{c.sortable} DESC"
334 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
334 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
335 assert !values.empty?
335 assert !values.empty?
336 assert_equal values.sort.reverse, values
336 assert_equal values.sort.reverse, values
337 end
337 end
338
338
339 def test_sort_by_float_custom_field_asc
339 def test_sort_by_float_custom_field_asc
340 q = Query.new
340 q = Query.new
341 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
341 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
342 assert c
342 assert c
343 assert c.sortable
343 assert c.sortable
344 issues = Issue.find :all,
344 issues = Issue.find :all,
345 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
345 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
346 :conditions => q.statement,
346 :conditions => q.statement,
347 :order => "#{c.sortable} ASC"
347 :order => "#{c.sortable} ASC"
348 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
348 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
349 assert !values.empty?
349 assert !values.empty?
350 assert_equal values.sort, values
350 assert_equal values.sort, values
351 end
351 end
352
352
353 def test_invalid_query_should_raise_query_statement_invalid_error
353 def test_invalid_query_should_raise_query_statement_invalid_error
354 q = Query.new
354 q = Query.new
355 assert_raise Query::StatementInvalid do
355 assert_raise Query::StatementInvalid do
356 q.issues(:conditions => "foo = 1")
356 q.issues(:conditions => "foo = 1")
357 end
357 end
358 end
358 end
359
359
360 def test_issue_count_by_association_group
360 def test_issue_count_by_association_group
361 q = Query.new(:name => '_', :group_by => 'assigned_to')
361 q = Query.new(:name => '_', :group_by => 'assigned_to')
362 count_by_group = q.issue_count_by_group
362 count_by_group = q.issue_count_by_group
363 assert_kind_of Hash, count_by_group
363 assert_kind_of Hash, count_by_group
364 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
364 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
365 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
365 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
366 assert count_by_group.has_key?(User.find(3))
366 assert count_by_group.has_key?(User.find(3))
367 end
367 end
368
368
369 def test_issue_count_by_list_custom_field_group
369 def test_issue_count_by_list_custom_field_group
370 q = Query.new(:name => '_', :group_by => 'cf_1')
370 q = Query.new(:name => '_', :group_by => 'cf_1')
371 count_by_group = q.issue_count_by_group
371 count_by_group = q.issue_count_by_group
372 assert_kind_of Hash, count_by_group
372 assert_kind_of Hash, count_by_group
373 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
373 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
374 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
374 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
375 assert count_by_group.has_key?('MySQL')
375 assert count_by_group.has_key?('MySQL')
376 end
376 end
377
377
378 def test_issue_count_by_date_custom_field_group
378 def test_issue_count_by_date_custom_field_group
379 q = Query.new(:name => '_', :group_by => 'cf_8')
379 q = Query.new(:name => '_', :group_by => 'cf_8')
380 count_by_group = q.issue_count_by_group
380 count_by_group = q.issue_count_by_group
381 assert_kind_of Hash, count_by_group
381 assert_kind_of Hash, count_by_group
382 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
382 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
383 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
383 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
384 end
384 end
385
385
386 def test_label_for
386 def test_label_for
387 q = Query.new
387 q = Query.new
388 assert_equal 'assigned_to', q.label_for('assigned_to_id')
388 assert_equal 'assigned_to', q.label_for('assigned_to_id')
389 end
389 end
390
390
391 def test_editable_by
391 def test_editable_by
392 admin = User.find(1)
392 admin = User.find(1)
393 manager = User.find(2)
393 manager = User.find(2)
394 developer = User.find(3)
394 developer = User.find(3)
395
395
396 # Public query on project 1
396 # Public query on project 1
397 q = Query.find(1)
397 q = Query.find(1)
398 assert q.editable_by?(admin)
398 assert q.editable_by?(admin)
399 assert q.editable_by?(manager)
399 assert q.editable_by?(manager)
400 assert !q.editable_by?(developer)
400 assert !q.editable_by?(developer)
401
401
402 # Private query on project 1
402 # Private query on project 1
403 q = Query.find(2)
403 q = Query.find(2)
404 assert q.editable_by?(admin)
404 assert q.editable_by?(admin)
405 assert !q.editable_by?(manager)
405 assert !q.editable_by?(manager)
406 assert q.editable_by?(developer)
406 assert q.editable_by?(developer)
407
407
408 # Private query for all projects
408 # Private query for all projects
409 q = Query.find(3)
409 q = Query.find(3)
410 assert q.editable_by?(admin)
410 assert q.editable_by?(admin)
411 assert !q.editable_by?(manager)
411 assert !q.editable_by?(manager)
412 assert q.editable_by?(developer)
412 assert q.editable_by?(developer)
413
413
414 # Public query for all projects
414 # Public query for all projects
415 q = Query.find(4)
415 q = Query.find(4)
416 assert q.editable_by?(admin)
416 assert q.editable_by?(admin)
417 assert !q.editable_by?(manager)
417 assert !q.editable_by?(manager)
418 assert !q.editable_by?(developer)
418 assert !q.editable_by?(developer)
419 end
419 end
420
421 def test_visible_scope
422 query_ids = Query.visible(User.anonymous).map(&:id)
423
424 assert query_ids.include?(1), 'public query on public project was not visible'
425 assert query_ids.include?(4), 'public query for all projects was not visible'
426 assert !query_ids.include?(2), 'private query on public project was visible'
427 assert !query_ids.include?(3), 'private query for all projects was visible'
428 assert !query_ids.include?(7), 'public query on private project was visible'
429 end
420
430
421 context "#available_filters" do
431 context "#available_filters" do
422 setup do
432 setup do
423 @query = Query.new(:name => "_")
433 @query = Query.new(:name => "_")
424 end
434 end
425
435
426 should "include users of visible projects in cross-project view" do
436 should "include users of visible projects in cross-project view" do
427 users = @query.available_filters["assigned_to_id"]
437 users = @query.available_filters["assigned_to_id"]
428 assert_not_nil users
438 assert_not_nil users
429 assert users[:values].map{|u|u[1]}.include?("3")
439 assert users[:values].map{|u|u[1]}.include?("3")
430 end
440 end
431
441
432 should "include visible projects in cross-project view" do
442 should "include visible projects in cross-project view" do
433 projects = @query.available_filters["project_id"]
443 projects = @query.available_filters["project_id"]
434 assert_not_nil projects
444 assert_not_nil projects
435 assert projects[:values].map{|u|u[1]}.include?("1")
445 assert projects[:values].map{|u|u[1]}.include?("1")
436 end
446 end
437
447
438 context "'member_of_group' filter" do
448 context "'member_of_group' filter" do
439 should "be present" do
449 should "be present" do
440 assert @query.available_filters.keys.include?("member_of_group")
450 assert @query.available_filters.keys.include?("member_of_group")
441 end
451 end
442
452
443 should "be an optional list" do
453 should "be an optional list" do
444 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
454 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
445 end
455 end
446
456
447 should "have a list of the groups as values" do
457 should "have a list of the groups as values" do
448 Group.destroy_all # No fixtures
458 Group.destroy_all # No fixtures
449 group1 = Group.generate!.reload
459 group1 = Group.generate!.reload
450 group2 = Group.generate!.reload
460 group2 = Group.generate!.reload
451
461
452 expected_group_list = [
462 expected_group_list = [
453 [group1.name, group1.id.to_s],
463 [group1.name, group1.id.to_s],
454 [group2.name, group2.id.to_s]
464 [group2.name, group2.id.to_s]
455 ]
465 ]
456 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
466 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
457 end
467 end
458
468
459 end
469 end
460
470
461 context "'assigned_to_role' filter" do
471 context "'assigned_to_role' filter" do
462 should "be present" do
472 should "be present" do
463 assert @query.available_filters.keys.include?("assigned_to_role")
473 assert @query.available_filters.keys.include?("assigned_to_role")
464 end
474 end
465
475
466 should "be an optional list" do
476 should "be an optional list" do
467 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
477 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
468 end
478 end
469
479
470 should "have a list of the Roles as values" do
480 should "have a list of the Roles as values" do
471 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
481 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
472 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
482 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
473 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
483 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
474 end
484 end
475
485
476 should "not include the built in Roles as values" do
486 should "not include the built in Roles as values" do
477 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
487 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
478 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
488 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
479 end
489 end
480
490
481 end
491 end
482
492
483 end
493 end
484
494
485 context "#statement" do
495 context "#statement" do
486 context "with 'member_of_group' filter" do
496 context "with 'member_of_group' filter" do
487 setup do
497 setup do
488 Group.destroy_all # No fixtures
498 Group.destroy_all # No fixtures
489 @user_in_group = User.generate!
499 @user_in_group = User.generate!
490 @second_user_in_group = User.generate!
500 @second_user_in_group = User.generate!
491 @user_in_group2 = User.generate!
501 @user_in_group2 = User.generate!
492 @user_not_in_group = User.generate!
502 @user_not_in_group = User.generate!
493
503
494 @group = Group.generate!.reload
504 @group = Group.generate!.reload
495 @group.users << @user_in_group
505 @group.users << @user_in_group
496 @group.users << @second_user_in_group
506 @group.users << @second_user_in_group
497
507
498 @group2 = Group.generate!.reload
508 @group2 = Group.generate!.reload
499 @group2.users << @user_in_group2
509 @group2.users << @user_in_group2
500
510
501 end
511 end
502
512
503 should "search assigned to for users in the group" do
513 should "search assigned to for users in the group" do
504 @query = Query.new(:name => '_')
514 @query = Query.new(:name => '_')
505 @query.add_filter('member_of_group', '=', [@group.id.to_s])
515 @query.add_filter('member_of_group', '=', [@group.id.to_s])
506
516
507 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
517 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
508 assert_find_issues_with_query_is_successful @query
518 assert_find_issues_with_query_is_successful @query
509 end
519 end
510
520
511 should "search not assigned to any group member (none)" do
521 should "search not assigned to any group member (none)" do
512 @query = Query.new(:name => '_')
522 @query = Query.new(:name => '_')
513 @query.add_filter('member_of_group', '!*', [''])
523 @query.add_filter('member_of_group', '!*', [''])
514
524
515 # Users not in a group
525 # Users not in a group
516 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
526 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
517 assert_find_issues_with_query_is_successful @query
527 assert_find_issues_with_query_is_successful @query
518 end
528 end
519
529
520 should "search assigned to any group member (all)" do
530 should "search assigned to any group member (all)" do
521 @query = Query.new(:name => '_')
531 @query = Query.new(:name => '_')
522 @query.add_filter('member_of_group', '*', [''])
532 @query.add_filter('member_of_group', '*', [''])
523
533
524 # Only users in a group
534 # Only users in a group
525 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
535 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
526 assert_find_issues_with_query_is_successful @query
536 assert_find_issues_with_query_is_successful @query
527 end
537 end
528
538
529 should "return an empty set with = empty group" do
539 should "return an empty set with = empty group" do
530 @empty_group = Group.generate!
540 @empty_group = Group.generate!
531 @query = Query.new(:name => '_')
541 @query = Query.new(:name => '_')
532 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
542 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
533
543
534 assert_equal [], find_issues_with_query(@query)
544 assert_equal [], find_issues_with_query(@query)
535 end
545 end
536
546
537 should "return issues with ! empty group" do
547 should "return issues with ! empty group" do
538 @empty_group = Group.generate!
548 @empty_group = Group.generate!
539 @query = Query.new(:name => '_')
549 @query = Query.new(:name => '_')
540 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
550 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
541
551
542 assert_find_issues_with_query_is_successful @query
552 assert_find_issues_with_query_is_successful @query
543 end
553 end
544 end
554 end
545
555
546 context "with 'assigned_to_role' filter" do
556 context "with 'assigned_to_role' filter" do
547 setup do
557 setup do
548 # No fixtures
558 # No fixtures
549 MemberRole.delete_all
559 MemberRole.delete_all
550 Member.delete_all
560 Member.delete_all
551 Role.delete_all
561 Role.delete_all
552
562
553 @manager_role = Role.generate!(:name => 'Manager')
563 @manager_role = Role.generate!(:name => 'Manager')
554 @developer_role = Role.generate!(:name => 'Developer')
564 @developer_role = Role.generate!(:name => 'Developer')
555
565
556 @project = Project.generate!
566 @project = Project.generate!
557 @manager = User.generate!
567 @manager = User.generate!
558 @developer = User.generate!
568 @developer = User.generate!
559 @boss = User.generate!
569 @boss = User.generate!
560 User.add_to_project(@manager, @project, @manager_role)
570 User.add_to_project(@manager, @project, @manager_role)
561 User.add_to_project(@developer, @project, @developer_role)
571 User.add_to_project(@developer, @project, @developer_role)
562 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
572 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
563 end
573 end
564
574
565 should "search assigned to for users with the Role" do
575 should "search assigned to for users with the Role" do
566 @query = Query.new(:name => '_')
576 @query = Query.new(:name => '_')
567 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
577 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
568
578
569 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@boss.id}')"
579 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@boss.id}')"
570 assert_find_issues_with_query_is_successful @query
580 assert_find_issues_with_query_is_successful @query
571 end
581 end
572
582
573 should "search assigned to for users not assigned to any Role (none)" do
583 should "search assigned to for users not assigned to any Role (none)" do
574 @query = Query.new(:name => '_')
584 @query = Query.new(:name => '_')
575 @query.add_filter('assigned_to_role', '!*', [''])
585 @query.add_filter('assigned_to_role', '!*', [''])
576
586
577 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
587 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
578 assert_find_issues_with_query_is_successful @query
588 assert_find_issues_with_query_is_successful @query
579 end
589 end
580
590
581 should "search assigned to for users assigned to any Role (all)" do
591 should "search assigned to for users assigned to any Role (all)" do
582 @query = Query.new(:name => '_')
592 @query = Query.new(:name => '_')
583 @query.add_filter('assigned_to_role', '*', [''])
593 @query.add_filter('assigned_to_role', '*', [''])
584
594
585 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
595 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
586 assert_find_issues_with_query_is_successful @query
596 assert_find_issues_with_query_is_successful @query
587 end
597 end
588
598
589 should "return an empty set with empty role" do
599 should "return an empty set with empty role" do
590 @empty_role = Role.generate!
600 @empty_role = Role.generate!
591 @query = Query.new(:name => '_')
601 @query = Query.new(:name => '_')
592 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
602 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
593
603
594 assert_equal [], find_issues_with_query(@query)
604 assert_equal [], find_issues_with_query(@query)
595 end
605 end
596
606
597 should "return issues with ! empty role" do
607 should "return issues with ! empty role" do
598 @empty_role = Role.generate!
608 @empty_role = Role.generate!
599 @query = Query.new(:name => '_')
609 @query = Query.new(:name => '_')
600 @query.add_filter('member_of_group', '!', [@empty_role.id.to_s])
610 @query.add_filter('member_of_group', '!', [@empty_role.id.to_s])
601
611
602 assert_find_issues_with_query_is_successful @query
612 assert_find_issues_with_query_is_successful @query
603 end
613 end
604 end
614 end
605 end
615 end
606
616
607 end
617 end
General Comments 0
You need to be logged in to leave comments. Login now