##// END OF EJS Templates
Adds REST API for issue relations (#7366)....
Jean-Philippe Lang -
r6056:21b37187445f
parent child
Show More
@@ -0,0 +1,7
1 api.relation do
2 api.id @relation.id
3 api.issue_id @relation.issue_from_id
4 api.issue_to_id @relation.issue_to_id
5 api.relation_type @relation.relation_type_for(@issue)
6 api.delay @relation.delay
7 end
@@ -0,0 +1,83
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::IssueRelationsTest < ActionController::IntegrationTest
21 fixtures :all
22
23 def setup
24 Setting.rest_api_enabled = '1'
25 end
26
27 context "/issues/:issue_id/relations" do
28 context "POST" do
29 should "create a relation" do
30 assert_difference('IssueRelation.count') do
31 post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'relates'}}, :authorization => credentials('jsmith')
32 end
33
34 relation = IssueRelation.first(:order => 'id DESC')
35 assert_equal 2, relation.issue_from_id
36 assert_equal 7, relation.issue_to_id
37 assert_equal 'relates', relation.relation_type
38
39 assert_response :created
40 assert_equal 'application/xml', @response.content_type
41 assert_tag 'relation', :child => {:tag => 'id', :content => relation.id.to_s}
42 end
43
44 context "with failure" do
45 should "return the errors" do
46 assert_no_difference('IssueRelation.count') do
47 post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'foo'}}, :authorization => credentials('jsmith')
48 end
49
50 assert_response :unprocessable_entity
51 assert_tag :errors, :child => {:tag => 'error', :content => 'relation_type is not included in the list'}
52 end
53 end
54 end
55 end
56
57 context "/issues/:issue_id/relations/:id" do
58 context "GET" do
59 should "return the relation" do
60 get '/issues/3/relations/2.xml', {}, :authorization => credentials('jsmith')
61
62 assert_response :success
63 assert_equal 'application/xml', @response.content_type
64 assert_tag 'relation', :child => {:tag => 'id', :content => '2'}
65 end
66 end
67
68 context "DELETE" do
69 should "delete the relation" do
70 assert_difference('IssueRelation.count', -1) do
71 delete '/issues/3/relations/2.xml', {}, :authorization => credentials('jsmith')
72 end
73
74 assert_response :ok
75 assert_nil IssueRelation.find_by_id(2)
76 end
77 end
78 end
79
80 def credentials(user, password=nil)
81 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
82 end
83 end
@@ -1,64 +1,87
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 IssueRelationsController < ApplicationController
18 class IssueRelationsController < ApplicationController
19 before_filter :find_issue, :find_project_from_association, :authorize
19 before_filter :find_issue, :find_project_from_association, :authorize
20 accept_key_auth :show, :create, :destroy
20
21
21 def new
22 def show
23 @relation = @issue.find_relation(params[:id])
24
25 respond_to do |format|
26 format.html { render :nothing => true }
27 format.api
28 end
29 rescue ActiveRecord::RecordNotFound
30 render_404
31 end
32
33 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
34 def create
22 @relation = IssueRelation.new(params[:relation])
35 @relation = IssueRelation.new(params[:relation])
23 @relation.issue_from = @issue
36 @relation.issue_from = @issue
24 if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
37 if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
25 @relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
38 @relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
26 end
39 end
27 @relation.save if request.post?
40 saved = @relation.save
41
28 respond_to do |format|
42 respond_to do |format|
29 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
43 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
30 format.js do
44 format.js do
31 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
45 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
32 render :update do |page|
46 render :update do |page|
33 page.replace_html "relations", :partial => 'issues/relations'
47 page.replace_html "relations", :partial => 'issues/relations'
34 if @relation.errors.empty?
48 if @relation.errors.empty?
35 page << "$('relation_delay').value = ''"
49 page << "$('relation_delay').value = ''"
36 page << "$('relation_issue_to_id').value = ''"
50 page << "$('relation_issue_to_id').value = ''"
37 end
51 end
38 end
52 end
39 end
53 end
54 format.api {
55 if saved
56 render :action => 'show', :status => :created, :location => issue_relation_url(@issue, @relation)
57 else
58 render_validation_errors(@relation)
59 end
60 }
40 end
61 end
41 end
62 end
42
63
64 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
43 def destroy
65 def destroy
44 relation = IssueRelation.find(params[:id])
66 relation = @issue.find_relation(params[:id])
45 if request.post? && @issue.relations.include?(relation)
46 relation.destroy
67 relation.destroy
47 @issue.reload
68
48 end
49 respond_to do |format|
69 respond_to do |format|
50 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
70 format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
51 format.js {
71 format.js {
52 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
72 @relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
53 render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'}
73 render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'}
54 }
74 }
75 format.api { head :ok }
55 end
76 end
77 rescue ActiveRecord::RecordNotFound
78 render_404
56 end
79 end
57
80
58 private
81 private
59 def find_issue
82 def find_issue
60 @issue = @object = Issue.find(params[:issue_id])
83 @issue = @object = Issue.find(params[:issue_id])
61 rescue ActiveRecord::RecordNotFound
84 rescue ActiveRecord::RecordNotFound
62 render_404
85 render_404
63 end
86 end
64 end
87 end
@@ -1,930 +1,935
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 Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20
20
21 belongs_to :project
21 belongs_to :project
22 belongs_to :tracker
22 belongs_to :tracker
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
25 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
25 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
29
29
30 has_many :journals, :as => :journalized, :dependent => :destroy
30 has_many :journals, :as => :journalized, :dependent => :destroy
31 has_many :time_entries, :dependent => :delete_all
31 has_many :time_entries, :dependent => :delete_all
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
33
33
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36
36
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
38 acts_as_attachable :after_remove => :attachment_removed
38 acts_as_attachable :after_remove => :attachment_removed
39 acts_as_customizable
39 acts_as_customizable
40 acts_as_watchable
40 acts_as_watchable
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
42 :include => [:project, :journals],
42 :include => [:project, :journals],
43 # sort by id so that limited eager loading doesn't break with postgresql
43 # sort by id so that limited eager loading doesn't break with postgresql
44 :order_column => "#{table_name}.id"
44 :order_column => "#{table_name}.id"
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
48
48
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
50 :author_key => :author_id
50 :author_key => :author_id
51
51
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
53
53
54 attr_reader :current_journal
54 attr_reader :current_journal
55
55
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
57
57
58 validates_length_of :subject, :maximum => 255
58 validates_length_of :subject, :maximum => 255
59 validates_inclusion_of :done_ratio, :in => 0..100
59 validates_inclusion_of :done_ratio, :in => 0..100
60 validates_numericality_of :estimated_hours, :allow_nil => true
60 validates_numericality_of :estimated_hours, :allow_nil => true
61
61
62 named_scope :visible, lambda {|*args| { :include => :project,
62 named_scope :visible, lambda {|*args| { :include => :project,
63 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
63 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
64
64
65 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
65 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
66
66
67 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
67 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
68 named_scope :with_limit, lambda { |limit| { :limit => limit} }
68 named_scope :with_limit, lambda { |limit| { :limit => limit} }
69 named_scope :on_active_project, :include => [:status, :project, :tracker],
69 named_scope :on_active_project, :include => [:status, :project, :tracker],
70 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
70 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
71
71
72 named_scope :without_version, lambda {
72 named_scope :without_version, lambda {
73 {
73 {
74 :conditions => { :fixed_version_id => nil}
74 :conditions => { :fixed_version_id => nil}
75 }
75 }
76 }
76 }
77
77
78 named_scope :with_query, lambda {|query|
78 named_scope :with_query, lambda {|query|
79 {
79 {
80 :conditions => Query.merge_conditions(query.statement)
80 :conditions => Query.merge_conditions(query.statement)
81 }
81 }
82 }
82 }
83
83
84 before_create :default_assign
84 before_create :default_assign
85 before_save :close_duplicates, :update_done_ratio_from_issue_status
85 before_save :close_duplicates, :update_done_ratio_from_issue_status
86 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
86 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
87 after_destroy :update_parent_attributes
87 after_destroy :update_parent_attributes
88
88
89 # Returns a SQL conditions string used to find all issues visible by the specified user
89 # Returns a SQL conditions string used to find all issues visible by the specified user
90 def self.visible_condition(user, options={})
90 def self.visible_condition(user, options={})
91 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
91 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
92 case role.issues_visibility
92 case role.issues_visibility
93 when 'all'
93 when 'all'
94 nil
94 nil
95 when 'default'
95 when 'default'
96 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
96 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
97 when 'own'
97 when 'own'
98 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
98 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
99 else
99 else
100 '1=0'
100 '1=0'
101 end
101 end
102 end
102 end
103 end
103 end
104
104
105 # Returns true if usr or current user is allowed to view the issue
105 # Returns true if usr or current user is allowed to view the issue
106 def visible?(usr=nil)
106 def visible?(usr=nil)
107 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
107 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
108 case role.issues_visibility
108 case role.issues_visibility
109 when 'all'
109 when 'all'
110 true
110 true
111 when 'default'
111 when 'default'
112 !self.is_private? || self.author == user || self.assigned_to == user
112 !self.is_private? || self.author == user || self.assigned_to == user
113 when 'own'
113 when 'own'
114 self.author == user || self.assigned_to == user
114 self.author == user || self.assigned_to == user
115 else
115 else
116 false
116 false
117 end
117 end
118 end
118 end
119 end
119 end
120
120
121 def after_initialize
121 def after_initialize
122 if new_record?
122 if new_record?
123 # set default values for new records only
123 # set default values for new records only
124 self.status ||= IssueStatus.default
124 self.status ||= IssueStatus.default
125 self.priority ||= IssuePriority.default
125 self.priority ||= IssuePriority.default
126 end
126 end
127 end
127 end
128
128
129 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
129 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
130 def available_custom_fields
130 def available_custom_fields
131 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
131 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
132 end
132 end
133
133
134 def copy_from(arg)
134 def copy_from(arg)
135 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
135 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
136 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
136 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
137 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
137 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
138 self.status = issue.status
138 self.status = issue.status
139 self
139 self
140 end
140 end
141
141
142 # Moves/copies an issue to a new project and tracker
142 # Moves/copies an issue to a new project and tracker
143 # Returns the moved/copied issue on success, false on failure
143 # Returns the moved/copied issue on success, false on failure
144 def move_to_project(*args)
144 def move_to_project(*args)
145 ret = Issue.transaction do
145 ret = Issue.transaction do
146 move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
146 move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
147 end || false
147 end || false
148 end
148 end
149
149
150 def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
150 def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
151 options ||= {}
151 options ||= {}
152 issue = options[:copy] ? self.class.new.copy_from(self) : self
152 issue = options[:copy] ? self.class.new.copy_from(self) : self
153
153
154 if new_project && issue.project_id != new_project.id
154 if new_project && issue.project_id != new_project.id
155 # delete issue relations
155 # delete issue relations
156 unless Setting.cross_project_issue_relations?
156 unless Setting.cross_project_issue_relations?
157 issue.relations_from.clear
157 issue.relations_from.clear
158 issue.relations_to.clear
158 issue.relations_to.clear
159 end
159 end
160 # issue is moved to another project
160 # issue is moved to another project
161 # reassign to the category with same name if any
161 # reassign to the category with same name if any
162 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
162 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
163 issue.category = new_category
163 issue.category = new_category
164 # Keep the fixed_version if it's still valid in the new_project
164 # Keep the fixed_version if it's still valid in the new_project
165 unless new_project.shared_versions.include?(issue.fixed_version)
165 unless new_project.shared_versions.include?(issue.fixed_version)
166 issue.fixed_version = nil
166 issue.fixed_version = nil
167 end
167 end
168 issue.project = new_project
168 issue.project = new_project
169 if issue.parent && issue.parent.project_id != issue.project_id
169 if issue.parent && issue.parent.project_id != issue.project_id
170 issue.parent_issue_id = nil
170 issue.parent_issue_id = nil
171 end
171 end
172 end
172 end
173 if new_tracker
173 if new_tracker
174 issue.tracker = new_tracker
174 issue.tracker = new_tracker
175 issue.reset_custom_values!
175 issue.reset_custom_values!
176 end
176 end
177 if options[:copy]
177 if options[:copy]
178 issue.author = User.current
178 issue.author = User.current
179 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
179 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
180 issue.status = if options[:attributes] && options[:attributes][:status_id]
180 issue.status = if options[:attributes] && options[:attributes][:status_id]
181 IssueStatus.find_by_id(options[:attributes][:status_id])
181 IssueStatus.find_by_id(options[:attributes][:status_id])
182 else
182 else
183 self.status
183 self.status
184 end
184 end
185 end
185 end
186 # Allow bulk setting of attributes on the issue
186 # Allow bulk setting of attributes on the issue
187 if options[:attributes]
187 if options[:attributes]
188 issue.attributes = options[:attributes]
188 issue.attributes = options[:attributes]
189 end
189 end
190 if issue.save
190 if issue.save
191 if options[:copy]
191 if options[:copy]
192 if current_journal && current_journal.notes.present?
192 if current_journal && current_journal.notes.present?
193 issue.init_journal(current_journal.user, current_journal.notes)
193 issue.init_journal(current_journal.user, current_journal.notes)
194 issue.current_journal.notify = false
194 issue.current_journal.notify = false
195 issue.save
195 issue.save
196 end
196 end
197 else
197 else
198 # Manually update project_id on related time entries
198 # Manually update project_id on related time entries
199 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
199 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
200
200
201 issue.children.each do |child|
201 issue.children.each do |child|
202 unless child.move_to_project_without_transaction(new_project)
202 unless child.move_to_project_without_transaction(new_project)
203 # Move failed and transaction was rollback'd
203 # Move failed and transaction was rollback'd
204 return false
204 return false
205 end
205 end
206 end
206 end
207 end
207 end
208 else
208 else
209 return false
209 return false
210 end
210 end
211 issue
211 issue
212 end
212 end
213
213
214 def status_id=(sid)
214 def status_id=(sid)
215 self.status = nil
215 self.status = nil
216 write_attribute(:status_id, sid)
216 write_attribute(:status_id, sid)
217 end
217 end
218
218
219 def priority_id=(pid)
219 def priority_id=(pid)
220 self.priority = nil
220 self.priority = nil
221 write_attribute(:priority_id, pid)
221 write_attribute(:priority_id, pid)
222 end
222 end
223
223
224 def tracker_id=(tid)
224 def tracker_id=(tid)
225 self.tracker = nil
225 self.tracker = nil
226 result = write_attribute(:tracker_id, tid)
226 result = write_attribute(:tracker_id, tid)
227 @custom_field_values = nil
227 @custom_field_values = nil
228 result
228 result
229 end
229 end
230
230
231 def description=(arg)
231 def description=(arg)
232 if arg.is_a?(String)
232 if arg.is_a?(String)
233 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
233 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
234 end
234 end
235 write_attribute(:description, arg)
235 write_attribute(:description, arg)
236 end
236 end
237
237
238 # Overrides attributes= so that tracker_id gets assigned first
238 # Overrides attributes= so that tracker_id gets assigned first
239 def attributes_with_tracker_first=(new_attributes, *args)
239 def attributes_with_tracker_first=(new_attributes, *args)
240 return if new_attributes.nil?
240 return if new_attributes.nil?
241 new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id]
241 new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id]
242 if new_tracker_id
242 if new_tracker_id
243 self.tracker_id = new_tracker_id
243 self.tracker_id = new_tracker_id
244 end
244 end
245 send :attributes_without_tracker_first=, new_attributes, *args
245 send :attributes_without_tracker_first=, new_attributes, *args
246 end
246 end
247 # Do not redefine alias chain on reload (see #4838)
247 # Do not redefine alias chain on reload (see #4838)
248 alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=)
248 alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=)
249
249
250 def estimated_hours=(h)
250 def estimated_hours=(h)
251 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
251 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
252 end
252 end
253
253
254 safe_attributes 'tracker_id',
254 safe_attributes 'tracker_id',
255 'status_id',
255 'status_id',
256 'parent_issue_id',
256 'parent_issue_id',
257 'category_id',
257 'category_id',
258 'assigned_to_id',
258 'assigned_to_id',
259 'priority_id',
259 'priority_id',
260 'fixed_version_id',
260 'fixed_version_id',
261 'subject',
261 'subject',
262 'description',
262 'description',
263 'start_date',
263 'start_date',
264 'due_date',
264 'due_date',
265 'done_ratio',
265 'done_ratio',
266 'estimated_hours',
266 'estimated_hours',
267 'custom_field_values',
267 'custom_field_values',
268 'custom_fields',
268 'custom_fields',
269 'lock_version',
269 'lock_version',
270 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
270 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
271
271
272 safe_attributes 'status_id',
272 safe_attributes 'status_id',
273 'assigned_to_id',
273 'assigned_to_id',
274 'fixed_version_id',
274 'fixed_version_id',
275 'done_ratio',
275 'done_ratio',
276 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
276 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
277
277
278 safe_attributes 'is_private',
278 safe_attributes 'is_private',
279 :if => lambda {|issue, user|
279 :if => lambda {|issue, user|
280 user.allowed_to?(:set_issues_private, issue.project) ||
280 user.allowed_to?(:set_issues_private, issue.project) ||
281 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
281 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
282 }
282 }
283
283
284 # Safely sets attributes
284 # Safely sets attributes
285 # Should be called from controllers instead of #attributes=
285 # Should be called from controllers instead of #attributes=
286 # attr_accessible is too rough because we still want things like
286 # attr_accessible is too rough because we still want things like
287 # Issue.new(:project => foo) to work
287 # Issue.new(:project => foo) to work
288 # TODO: move workflow/permission checks from controllers to here
288 # TODO: move workflow/permission checks from controllers to here
289 def safe_attributes=(attrs, user=User.current)
289 def safe_attributes=(attrs, user=User.current)
290 return unless attrs.is_a?(Hash)
290 return unless attrs.is_a?(Hash)
291
291
292 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
292 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
293 attrs = delete_unsafe_attributes(attrs, user)
293 attrs = delete_unsafe_attributes(attrs, user)
294 return if attrs.empty?
294 return if attrs.empty?
295
295
296 # Tracker must be set before since new_statuses_allowed_to depends on it.
296 # Tracker must be set before since new_statuses_allowed_to depends on it.
297 if t = attrs.delete('tracker_id')
297 if t = attrs.delete('tracker_id')
298 self.tracker_id = t
298 self.tracker_id = t
299 end
299 end
300
300
301 if attrs['status_id']
301 if attrs['status_id']
302 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
302 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
303 attrs.delete('status_id')
303 attrs.delete('status_id')
304 end
304 end
305 end
305 end
306
306
307 unless leaf?
307 unless leaf?
308 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
308 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
309 end
309 end
310
310
311 if attrs.has_key?('parent_issue_id')
311 if attrs.has_key?('parent_issue_id')
312 if !user.allowed_to?(:manage_subtasks, project)
312 if !user.allowed_to?(:manage_subtasks, project)
313 attrs.delete('parent_issue_id')
313 attrs.delete('parent_issue_id')
314 elsif !attrs['parent_issue_id'].blank?
314 elsif !attrs['parent_issue_id'].blank?
315 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
315 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
316 end
316 end
317 end
317 end
318
318
319 self.attributes = attrs
319 self.attributes = attrs
320 end
320 end
321
321
322 def done_ratio
322 def done_ratio
323 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
323 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
324 status.default_done_ratio
324 status.default_done_ratio
325 else
325 else
326 read_attribute(:done_ratio)
326 read_attribute(:done_ratio)
327 end
327 end
328 end
328 end
329
329
330 def self.use_status_for_done_ratio?
330 def self.use_status_for_done_ratio?
331 Setting.issue_done_ratio == 'issue_status'
331 Setting.issue_done_ratio == 'issue_status'
332 end
332 end
333
333
334 def self.use_field_for_done_ratio?
334 def self.use_field_for_done_ratio?
335 Setting.issue_done_ratio == 'issue_field'
335 Setting.issue_done_ratio == 'issue_field'
336 end
336 end
337
337
338 def validate
338 def validate
339 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
339 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
340 errors.add :due_date, :not_a_date
340 errors.add :due_date, :not_a_date
341 end
341 end
342
342
343 if self.due_date and self.start_date and self.due_date < self.start_date
343 if self.due_date and self.start_date and self.due_date < self.start_date
344 errors.add :due_date, :greater_than_start_date
344 errors.add :due_date, :greater_than_start_date
345 end
345 end
346
346
347 if start_date && soonest_start && start_date < soonest_start
347 if start_date && soonest_start && start_date < soonest_start
348 errors.add :start_date, :invalid
348 errors.add :start_date, :invalid
349 end
349 end
350
350
351 if fixed_version
351 if fixed_version
352 if !assignable_versions.include?(fixed_version)
352 if !assignable_versions.include?(fixed_version)
353 errors.add :fixed_version_id, :inclusion
353 errors.add :fixed_version_id, :inclusion
354 elsif reopened? && fixed_version.closed?
354 elsif reopened? && fixed_version.closed?
355 errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version)
355 errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version)
356 end
356 end
357 end
357 end
358
358
359 # Checks that the issue can not be added/moved to a disabled tracker
359 # Checks that the issue can not be added/moved to a disabled tracker
360 if project && (tracker_id_changed? || project_id_changed?)
360 if project && (tracker_id_changed? || project_id_changed?)
361 unless project.trackers.include?(tracker)
361 unless project.trackers.include?(tracker)
362 errors.add :tracker_id, :inclusion
362 errors.add :tracker_id, :inclusion
363 end
363 end
364 end
364 end
365
365
366 # Checks parent issue assignment
366 # Checks parent issue assignment
367 if @parent_issue
367 if @parent_issue
368 if @parent_issue.project_id != project_id
368 if @parent_issue.project_id != project_id
369 errors.add :parent_issue_id, :not_same_project
369 errors.add :parent_issue_id, :not_same_project
370 elsif !new_record?
370 elsif !new_record?
371 # moving an existing issue
371 # moving an existing issue
372 if @parent_issue.root_id != root_id
372 if @parent_issue.root_id != root_id
373 # we can always move to another tree
373 # we can always move to another tree
374 elsif move_possible?(@parent_issue)
374 elsif move_possible?(@parent_issue)
375 # move accepted inside tree
375 # move accepted inside tree
376 else
376 else
377 errors.add :parent_issue_id, :not_a_valid_parent
377 errors.add :parent_issue_id, :not_a_valid_parent
378 end
378 end
379 end
379 end
380 end
380 end
381 end
381 end
382
382
383 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
383 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
384 # even if the user turns off the setting later
384 # even if the user turns off the setting later
385 def update_done_ratio_from_issue_status
385 def update_done_ratio_from_issue_status
386 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
386 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
387 self.done_ratio = status.default_done_ratio
387 self.done_ratio = status.default_done_ratio
388 end
388 end
389 end
389 end
390
390
391 def init_journal(user, notes = "")
391 def init_journal(user, notes = "")
392 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
392 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
393 @issue_before_change = self.clone
393 @issue_before_change = self.clone
394 @issue_before_change.status = self.status
394 @issue_before_change.status = self.status
395 @custom_values_before_change = {}
395 @custom_values_before_change = {}
396 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
396 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
397 # Make sure updated_on is updated when adding a note.
397 # Make sure updated_on is updated when adding a note.
398 updated_on_will_change!
398 updated_on_will_change!
399 @current_journal
399 @current_journal
400 end
400 end
401
401
402 # Return true if the issue is closed, otherwise false
402 # Return true if the issue is closed, otherwise false
403 def closed?
403 def closed?
404 self.status.is_closed?
404 self.status.is_closed?
405 end
405 end
406
406
407 # Return true if the issue is being reopened
407 # Return true if the issue is being reopened
408 def reopened?
408 def reopened?
409 if !new_record? && status_id_changed?
409 if !new_record? && status_id_changed?
410 status_was = IssueStatus.find_by_id(status_id_was)
410 status_was = IssueStatus.find_by_id(status_id_was)
411 status_new = IssueStatus.find_by_id(status_id)
411 status_new = IssueStatus.find_by_id(status_id)
412 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
412 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
413 return true
413 return true
414 end
414 end
415 end
415 end
416 false
416 false
417 end
417 end
418
418
419 # Return true if the issue is being closed
419 # Return true if the issue is being closed
420 def closing?
420 def closing?
421 if !new_record? && status_id_changed?
421 if !new_record? && status_id_changed?
422 status_was = IssueStatus.find_by_id(status_id_was)
422 status_was = IssueStatus.find_by_id(status_id_was)
423 status_new = IssueStatus.find_by_id(status_id)
423 status_new = IssueStatus.find_by_id(status_id)
424 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
424 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
425 return true
425 return true
426 end
426 end
427 end
427 end
428 false
428 false
429 end
429 end
430
430
431 # Returns true if the issue is overdue
431 # Returns true if the issue is overdue
432 def overdue?
432 def overdue?
433 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
433 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
434 end
434 end
435
435
436 # Is the amount of work done less than it should for the due date
436 # Is the amount of work done less than it should for the due date
437 def behind_schedule?
437 def behind_schedule?
438 return false if start_date.nil? || due_date.nil?
438 return false if start_date.nil? || due_date.nil?
439 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
439 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
440 return done_date <= Date.today
440 return done_date <= Date.today
441 end
441 end
442
442
443 # Does this issue have children?
443 # Does this issue have children?
444 def children?
444 def children?
445 !leaf?
445 !leaf?
446 end
446 end
447
447
448 # Users the issue can be assigned to
448 # Users the issue can be assigned to
449 def assignable_users
449 def assignable_users
450 users = project.assignable_users
450 users = project.assignable_users
451 users << author if author
451 users << author if author
452 users.uniq.sort
452 users.uniq.sort
453 end
453 end
454
454
455 # Versions that the issue can be assigned to
455 # Versions that the issue can be assigned to
456 def assignable_versions
456 def assignable_versions
457 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
457 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
458 end
458 end
459
459
460 # Returns true if this issue is blocked by another issue that is still open
460 # Returns true if this issue is blocked by another issue that is still open
461 def blocked?
461 def blocked?
462 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
462 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
463 end
463 end
464
464
465 # Returns an array of status that user is able to apply
465 # Returns an array of status that user is able to apply
466 def new_statuses_allowed_to(user, include_default=false)
466 def new_statuses_allowed_to(user, include_default=false)
467 statuses = status.find_new_statuses_allowed_to(
467 statuses = status.find_new_statuses_allowed_to(
468 user.roles_for_project(project),
468 user.roles_for_project(project),
469 tracker,
469 tracker,
470 author == user,
470 author == user,
471 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
471 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
472 )
472 )
473 statuses << status unless statuses.empty?
473 statuses << status unless statuses.empty?
474 statuses << IssueStatus.default if include_default
474 statuses << IssueStatus.default if include_default
475 statuses = statuses.uniq.sort
475 statuses = statuses.uniq.sort
476 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
476 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
477 end
477 end
478
478
479 # Returns the mail adresses of users that should be notified
479 # Returns the mail adresses of users that should be notified
480 def recipients
480 def recipients
481 notified = project.notified_users
481 notified = project.notified_users
482 # Author and assignee are always notified unless they have been
482 # Author and assignee are always notified unless they have been
483 # locked or don't want to be notified
483 # locked or don't want to be notified
484 notified << author if author && author.active? && author.notify_about?(self)
484 notified << author if author && author.active? && author.notify_about?(self)
485 notified << assigned_to if assigned_to && assigned_to.active? && assigned_to.notify_about?(self)
485 notified << assigned_to if assigned_to && assigned_to.active? && assigned_to.notify_about?(self)
486 notified.uniq!
486 notified.uniq!
487 # Remove users that can not view the issue
487 # Remove users that can not view the issue
488 notified.reject! {|user| !visible?(user)}
488 notified.reject! {|user| !visible?(user)}
489 notified.collect(&:mail)
489 notified.collect(&:mail)
490 end
490 end
491
491
492 # Returns the total number of hours spent on this issue and its descendants
492 # Returns the total number of hours spent on this issue and its descendants
493 #
493 #
494 # Example:
494 # Example:
495 # spent_hours => 0.0
495 # spent_hours => 0.0
496 # spent_hours => 50.2
496 # spent_hours => 50.2
497 def spent_hours
497 def spent_hours
498 @spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
498 @spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
499 end
499 end
500
500
501 def relations
501 def relations
502 (relations_from + relations_to).sort
502 (relations_from + relations_to).sort
503 end
503 end
504
504
505 # Finds an issue relation given its id.
506 def find_relation(relation_id)
507 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
508 end
509
505 def all_dependent_issues(except=[])
510 def all_dependent_issues(except=[])
506 except << self
511 except << self
507 dependencies = []
512 dependencies = []
508 relations_from.each do |relation|
513 relations_from.each do |relation|
509 if relation.issue_to && !except.include?(relation.issue_to)
514 if relation.issue_to && !except.include?(relation.issue_to)
510 dependencies << relation.issue_to
515 dependencies << relation.issue_to
511 dependencies += relation.issue_to.all_dependent_issues(except)
516 dependencies += relation.issue_to.all_dependent_issues(except)
512 end
517 end
513 end
518 end
514 dependencies
519 dependencies
515 end
520 end
516
521
517 # Returns an array of issues that duplicate this one
522 # Returns an array of issues that duplicate this one
518 def duplicates
523 def duplicates
519 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
524 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
520 end
525 end
521
526
522 # Returns the due date or the target due date if any
527 # Returns the due date or the target due date if any
523 # Used on gantt chart
528 # Used on gantt chart
524 def due_before
529 def due_before
525 due_date || (fixed_version ? fixed_version.effective_date : nil)
530 due_date || (fixed_version ? fixed_version.effective_date : nil)
526 end
531 end
527
532
528 # Returns the time scheduled for this issue.
533 # Returns the time scheduled for this issue.
529 #
534 #
530 # Example:
535 # Example:
531 # Start Date: 2/26/09, End Date: 3/04/09
536 # Start Date: 2/26/09, End Date: 3/04/09
532 # duration => 6
537 # duration => 6
533 def duration
538 def duration
534 (start_date && due_date) ? due_date - start_date : 0
539 (start_date && due_date) ? due_date - start_date : 0
535 end
540 end
536
541
537 def soonest_start
542 def soonest_start
538 @soonest_start ||= (
543 @soonest_start ||= (
539 relations_to.collect{|relation| relation.successor_soonest_start} +
544 relations_to.collect{|relation| relation.successor_soonest_start} +
540 ancestors.collect(&:soonest_start)
545 ancestors.collect(&:soonest_start)
541 ).compact.max
546 ).compact.max
542 end
547 end
543
548
544 def reschedule_after(date)
549 def reschedule_after(date)
545 return if date.nil?
550 return if date.nil?
546 if leaf?
551 if leaf?
547 if start_date.nil? || start_date < date
552 if start_date.nil? || start_date < date
548 self.start_date, self.due_date = date, date + duration
553 self.start_date, self.due_date = date, date + duration
549 save
554 save
550 end
555 end
551 else
556 else
552 leaves.each do |leaf|
557 leaves.each do |leaf|
553 leaf.reschedule_after(date)
558 leaf.reschedule_after(date)
554 end
559 end
555 end
560 end
556 end
561 end
557
562
558 def <=>(issue)
563 def <=>(issue)
559 if issue.nil?
564 if issue.nil?
560 -1
565 -1
561 elsif root_id != issue.root_id
566 elsif root_id != issue.root_id
562 (root_id || 0) <=> (issue.root_id || 0)
567 (root_id || 0) <=> (issue.root_id || 0)
563 else
568 else
564 (lft || 0) <=> (issue.lft || 0)
569 (lft || 0) <=> (issue.lft || 0)
565 end
570 end
566 end
571 end
567
572
568 def to_s
573 def to_s
569 "#{tracker} ##{id}: #{subject}"
574 "#{tracker} ##{id}: #{subject}"
570 end
575 end
571
576
572 # Returns a string of css classes that apply to the issue
577 # Returns a string of css classes that apply to the issue
573 def css_classes
578 def css_classes
574 s = "issue status-#{status.position} priority-#{priority.position}"
579 s = "issue status-#{status.position} priority-#{priority.position}"
575 s << ' closed' if closed?
580 s << ' closed' if closed?
576 s << ' overdue' if overdue?
581 s << ' overdue' if overdue?
577 s << ' child' if child?
582 s << ' child' if child?
578 s << ' parent' unless leaf?
583 s << ' parent' unless leaf?
579 s << ' private' if is_private?
584 s << ' private' if is_private?
580 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
585 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
581 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
586 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
582 s
587 s
583 end
588 end
584
589
585 # Saves an issue, time_entry, attachments, and a journal from the parameters
590 # Saves an issue, time_entry, attachments, and a journal from the parameters
586 # Returns false if save fails
591 # Returns false if save fails
587 def save_issue_with_child_records(params, existing_time_entry=nil)
592 def save_issue_with_child_records(params, existing_time_entry=nil)
588 Issue.transaction do
593 Issue.transaction do
589 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
594 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
590 @time_entry = existing_time_entry || TimeEntry.new
595 @time_entry = existing_time_entry || TimeEntry.new
591 @time_entry.project = project
596 @time_entry.project = project
592 @time_entry.issue = self
597 @time_entry.issue = self
593 @time_entry.user = User.current
598 @time_entry.user = User.current
594 @time_entry.spent_on = Date.today
599 @time_entry.spent_on = Date.today
595 @time_entry.attributes = params[:time_entry]
600 @time_entry.attributes = params[:time_entry]
596 self.time_entries << @time_entry
601 self.time_entries << @time_entry
597 end
602 end
598
603
599 if valid?
604 if valid?
600 attachments = Attachment.attach_files(self, params[:attachments])
605 attachments = Attachment.attach_files(self, params[:attachments])
601
606
602 attachments[:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
607 attachments[:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
603 # TODO: Rename hook
608 # TODO: Rename hook
604 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
609 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
605 begin
610 begin
606 if save
611 if save
607 # TODO: Rename hook
612 # TODO: Rename hook
608 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
613 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
609 else
614 else
610 raise ActiveRecord::Rollback
615 raise ActiveRecord::Rollback
611 end
616 end
612 rescue ActiveRecord::StaleObjectError
617 rescue ActiveRecord::StaleObjectError
613 attachments[:files].each(&:destroy)
618 attachments[:files].each(&:destroy)
614 errors.add_to_base l(:notice_locking_conflict)
619 errors.add_to_base l(:notice_locking_conflict)
615 raise ActiveRecord::Rollback
620 raise ActiveRecord::Rollback
616 end
621 end
617 end
622 end
618 end
623 end
619 end
624 end
620
625
621 # Unassigns issues from +version+ if it's no longer shared with issue's project
626 # Unassigns issues from +version+ if it's no longer shared with issue's project
622 def self.update_versions_from_sharing_change(version)
627 def self.update_versions_from_sharing_change(version)
623 # Update issues assigned to the version
628 # Update issues assigned to the version
624 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
629 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
625 end
630 end
626
631
627 # Unassigns issues from versions that are no longer shared
632 # Unassigns issues from versions that are no longer shared
628 # after +project+ was moved
633 # after +project+ was moved
629 def self.update_versions_from_hierarchy_change(project)
634 def self.update_versions_from_hierarchy_change(project)
630 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
635 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
631 # Update issues of the moved projects and issues assigned to a version of a moved project
636 # Update issues of the moved projects and issues assigned to a version of a moved project
632 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
637 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
633 end
638 end
634
639
635 def parent_issue_id=(arg)
640 def parent_issue_id=(arg)
636 parent_issue_id = arg.blank? ? nil : arg.to_i
641 parent_issue_id = arg.blank? ? nil : arg.to_i
637 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
642 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
638 @parent_issue.id
643 @parent_issue.id
639 else
644 else
640 @parent_issue = nil
645 @parent_issue = nil
641 nil
646 nil
642 end
647 end
643 end
648 end
644
649
645 def parent_issue_id
650 def parent_issue_id
646 if instance_variable_defined? :@parent_issue
651 if instance_variable_defined? :@parent_issue
647 @parent_issue.nil? ? nil : @parent_issue.id
652 @parent_issue.nil? ? nil : @parent_issue.id
648 else
653 else
649 parent_id
654 parent_id
650 end
655 end
651 end
656 end
652
657
653 # Extracted from the ReportsController.
658 # Extracted from the ReportsController.
654 def self.by_tracker(project)
659 def self.by_tracker(project)
655 count_and_group_by(:project => project,
660 count_and_group_by(:project => project,
656 :field => 'tracker_id',
661 :field => 'tracker_id',
657 :joins => Tracker.table_name)
662 :joins => Tracker.table_name)
658 end
663 end
659
664
660 def self.by_version(project)
665 def self.by_version(project)
661 count_and_group_by(:project => project,
666 count_and_group_by(:project => project,
662 :field => 'fixed_version_id',
667 :field => 'fixed_version_id',
663 :joins => Version.table_name)
668 :joins => Version.table_name)
664 end
669 end
665
670
666 def self.by_priority(project)
671 def self.by_priority(project)
667 count_and_group_by(:project => project,
672 count_and_group_by(:project => project,
668 :field => 'priority_id',
673 :field => 'priority_id',
669 :joins => IssuePriority.table_name)
674 :joins => IssuePriority.table_name)
670 end
675 end
671
676
672 def self.by_category(project)
677 def self.by_category(project)
673 count_and_group_by(:project => project,
678 count_and_group_by(:project => project,
674 :field => 'category_id',
679 :field => 'category_id',
675 :joins => IssueCategory.table_name)
680 :joins => IssueCategory.table_name)
676 end
681 end
677
682
678 def self.by_assigned_to(project)
683 def self.by_assigned_to(project)
679 count_and_group_by(:project => project,
684 count_and_group_by(:project => project,
680 :field => 'assigned_to_id',
685 :field => 'assigned_to_id',
681 :joins => User.table_name)
686 :joins => User.table_name)
682 end
687 end
683
688
684 def self.by_author(project)
689 def self.by_author(project)
685 count_and_group_by(:project => project,
690 count_and_group_by(:project => project,
686 :field => 'author_id',
691 :field => 'author_id',
687 :joins => User.table_name)
692 :joins => User.table_name)
688 end
693 end
689
694
690 def self.by_subproject(project)
695 def self.by_subproject(project)
691 ActiveRecord::Base.connection.select_all("select s.id as status_id,
696 ActiveRecord::Base.connection.select_all("select s.id as status_id,
692 s.is_closed as closed,
697 s.is_closed as closed,
693 #{Issue.table_name}.project_id as project_id,
698 #{Issue.table_name}.project_id as project_id,
694 count(#{Issue.table_name}.id) as total
699 count(#{Issue.table_name}.id) as total
695 from
700 from
696 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
701 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
697 where
702 where
698 #{Issue.table_name}.status_id=s.id
703 #{Issue.table_name}.status_id=s.id
699 and #{Issue.table_name}.project_id = #{Project.table_name}.id
704 and #{Issue.table_name}.project_id = #{Project.table_name}.id
700 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
705 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
701 and #{Issue.table_name}.project_id <> #{project.id}
706 and #{Issue.table_name}.project_id <> #{project.id}
702 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
707 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
703 end
708 end
704 # End ReportsController extraction
709 # End ReportsController extraction
705
710
706 # Returns an array of projects that current user can move issues to
711 # Returns an array of projects that current user can move issues to
707 def self.allowed_target_projects_on_move
712 def self.allowed_target_projects_on_move
708 projects = []
713 projects = []
709 if User.current.admin?
714 if User.current.admin?
710 # admin is allowed to move issues to any active (visible) project
715 # admin is allowed to move issues to any active (visible) project
711 projects = Project.visible.all
716 projects = Project.visible.all
712 elsif User.current.logged?
717 elsif User.current.logged?
713 if Role.non_member.allowed_to?(:move_issues)
718 if Role.non_member.allowed_to?(:move_issues)
714 projects = Project.visible.all
719 projects = Project.visible.all
715 else
720 else
716 User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
721 User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
717 end
722 end
718 end
723 end
719 projects
724 projects
720 end
725 end
721
726
722 private
727 private
723
728
724 def update_nested_set_attributes
729 def update_nested_set_attributes
725 if root_id.nil?
730 if root_id.nil?
726 # issue was just created
731 # issue was just created
727 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
732 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
728 set_default_left_and_right
733 set_default_left_and_right
729 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
734 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
730 if @parent_issue
735 if @parent_issue
731 move_to_child_of(@parent_issue)
736 move_to_child_of(@parent_issue)
732 end
737 end
733 reload
738 reload
734 elsif parent_issue_id != parent_id
739 elsif parent_issue_id != parent_id
735 former_parent_id = parent_id
740 former_parent_id = parent_id
736 # moving an existing issue
741 # moving an existing issue
737 if @parent_issue && @parent_issue.root_id == root_id
742 if @parent_issue && @parent_issue.root_id == root_id
738 # inside the same tree
743 # inside the same tree
739 move_to_child_of(@parent_issue)
744 move_to_child_of(@parent_issue)
740 else
745 else
741 # to another tree
746 # to another tree
742 unless root?
747 unless root?
743 move_to_right_of(root)
748 move_to_right_of(root)
744 reload
749 reload
745 end
750 end
746 old_root_id = root_id
751 old_root_id = root_id
747 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
752 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
748 target_maxright = nested_set_scope.maximum(right_column_name) || 0
753 target_maxright = nested_set_scope.maximum(right_column_name) || 0
749 offset = target_maxright + 1 - lft
754 offset = target_maxright + 1 - lft
750 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
755 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
751 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
756 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
752 self[left_column_name] = lft + offset
757 self[left_column_name] = lft + offset
753 self[right_column_name] = rgt + offset
758 self[right_column_name] = rgt + offset
754 if @parent_issue
759 if @parent_issue
755 move_to_child_of(@parent_issue)
760 move_to_child_of(@parent_issue)
756 end
761 end
757 end
762 end
758 reload
763 reload
759 # delete invalid relations of all descendants
764 # delete invalid relations of all descendants
760 self_and_descendants.each do |issue|
765 self_and_descendants.each do |issue|
761 issue.relations.each do |relation|
766 issue.relations.each do |relation|
762 relation.destroy unless relation.valid?
767 relation.destroy unless relation.valid?
763 end
768 end
764 end
769 end
765 # update former parent
770 # update former parent
766 recalculate_attributes_for(former_parent_id) if former_parent_id
771 recalculate_attributes_for(former_parent_id) if former_parent_id
767 end
772 end
768 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
773 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
769 end
774 end
770
775
771 def update_parent_attributes
776 def update_parent_attributes
772 recalculate_attributes_for(parent_id) if parent_id
777 recalculate_attributes_for(parent_id) if parent_id
773 end
778 end
774
779
775 def recalculate_attributes_for(issue_id)
780 def recalculate_attributes_for(issue_id)
776 if issue_id && p = Issue.find_by_id(issue_id)
781 if issue_id && p = Issue.find_by_id(issue_id)
777 # priority = highest priority of children
782 # priority = highest priority of children
778 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
783 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
779 p.priority = IssuePriority.find_by_position(priority_position)
784 p.priority = IssuePriority.find_by_position(priority_position)
780 end
785 end
781
786
782 # start/due dates = lowest/highest dates of children
787 # start/due dates = lowest/highest dates of children
783 p.start_date = p.children.minimum(:start_date)
788 p.start_date = p.children.minimum(:start_date)
784 p.due_date = p.children.maximum(:due_date)
789 p.due_date = p.children.maximum(:due_date)
785 if p.start_date && p.due_date && p.due_date < p.start_date
790 if p.start_date && p.due_date && p.due_date < p.start_date
786 p.start_date, p.due_date = p.due_date, p.start_date
791 p.start_date, p.due_date = p.due_date, p.start_date
787 end
792 end
788
793
789 # done ratio = weighted average ratio of leaves
794 # done ratio = weighted average ratio of leaves
790 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
795 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
791 leaves_count = p.leaves.count
796 leaves_count = p.leaves.count
792 if leaves_count > 0
797 if leaves_count > 0
793 average = p.leaves.average(:estimated_hours).to_f
798 average = p.leaves.average(:estimated_hours).to_f
794 if average == 0
799 if average == 0
795 average = 1
800 average = 1
796 end
801 end
797 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :include => :status).to_f
802 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :include => :status).to_f
798 progress = done / (average * leaves_count)
803 progress = done / (average * leaves_count)
799 p.done_ratio = progress.round
804 p.done_ratio = progress.round
800 end
805 end
801 end
806 end
802
807
803 # estimate = sum of leaves estimates
808 # estimate = sum of leaves estimates
804 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
809 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
805 p.estimated_hours = nil if p.estimated_hours == 0.0
810 p.estimated_hours = nil if p.estimated_hours == 0.0
806
811
807 # ancestors will be recursively updated
812 # ancestors will be recursively updated
808 p.save(false)
813 p.save(false)
809 end
814 end
810 end
815 end
811
816
812 # Update issues so their versions are not pointing to a
817 # Update issues so their versions are not pointing to a
813 # fixed_version that is not shared with the issue's project
818 # fixed_version that is not shared with the issue's project
814 def self.update_versions(conditions=nil)
819 def self.update_versions(conditions=nil)
815 # Only need to update issues with a fixed_version from
820 # Only need to update issues with a fixed_version from
816 # a different project and that is not systemwide shared
821 # a different project and that is not systemwide shared
817 Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
822 Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
818 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
823 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
819 " AND #{Version.table_name}.sharing <> 'system'",
824 " AND #{Version.table_name}.sharing <> 'system'",
820 conditions),
825 conditions),
821 :include => [:project, :fixed_version]
826 :include => [:project, :fixed_version]
822 ).each do |issue|
827 ).each do |issue|
823 next if issue.project.nil? || issue.fixed_version.nil?
828 next if issue.project.nil? || issue.fixed_version.nil?
824 unless issue.project.shared_versions.include?(issue.fixed_version)
829 unless issue.project.shared_versions.include?(issue.fixed_version)
825 issue.init_journal(User.current)
830 issue.init_journal(User.current)
826 issue.fixed_version = nil
831 issue.fixed_version = nil
827 issue.save
832 issue.save
828 end
833 end
829 end
834 end
830 end
835 end
831
836
832 # Callback on attachment deletion
837 # Callback on attachment deletion
833 def attachment_removed(obj)
838 def attachment_removed(obj)
834 journal = init_journal(User.current)
839 journal = init_journal(User.current)
835 journal.details << JournalDetail.new(:property => 'attachment',
840 journal.details << JournalDetail.new(:property => 'attachment',
836 :prop_key => obj.id,
841 :prop_key => obj.id,
837 :old_value => obj.filename)
842 :old_value => obj.filename)
838 journal.save
843 journal.save
839 end
844 end
840
845
841 # Default assignment based on category
846 # Default assignment based on category
842 def default_assign
847 def default_assign
843 if assigned_to.nil? && category && category.assigned_to
848 if assigned_to.nil? && category && category.assigned_to
844 self.assigned_to = category.assigned_to
849 self.assigned_to = category.assigned_to
845 end
850 end
846 end
851 end
847
852
848 # Updates start/due dates of following issues
853 # Updates start/due dates of following issues
849 def reschedule_following_issues
854 def reschedule_following_issues
850 if start_date_changed? || due_date_changed?
855 if start_date_changed? || due_date_changed?
851 relations_from.each do |relation|
856 relations_from.each do |relation|
852 relation.set_issue_to_dates
857 relation.set_issue_to_dates
853 end
858 end
854 end
859 end
855 end
860 end
856
861
857 # Closes duplicates if the issue is being closed
862 # Closes duplicates if the issue is being closed
858 def close_duplicates
863 def close_duplicates
859 if closing?
864 if closing?
860 duplicates.each do |duplicate|
865 duplicates.each do |duplicate|
861 # Reload is need in case the duplicate was updated by a previous duplicate
866 # Reload is need in case the duplicate was updated by a previous duplicate
862 duplicate.reload
867 duplicate.reload
863 # Don't re-close it if it's already closed
868 # Don't re-close it if it's already closed
864 next if duplicate.closed?
869 next if duplicate.closed?
865 # Same user and notes
870 # Same user and notes
866 if @current_journal
871 if @current_journal
867 duplicate.init_journal(@current_journal.user, @current_journal.notes)
872 duplicate.init_journal(@current_journal.user, @current_journal.notes)
868 end
873 end
869 duplicate.update_attribute :status, self.status
874 duplicate.update_attribute :status, self.status
870 end
875 end
871 end
876 end
872 end
877 end
873
878
874 # Saves the changes in a Journal
879 # Saves the changes in a Journal
875 # Called after_save
880 # Called after_save
876 def create_journal
881 def create_journal
877 if @current_journal
882 if @current_journal
878 # attributes changes
883 # attributes changes
879 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
884 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
880 before = @issue_before_change.send(c)
885 before = @issue_before_change.send(c)
881 after = send(c)
886 after = send(c)
882 next if before == after || (before.blank? && after.blank?)
887 next if before == after || (before.blank? && after.blank?)
883 @current_journal.details << JournalDetail.new(:property => 'attr',
888 @current_journal.details << JournalDetail.new(:property => 'attr',
884 :prop_key => c,
889 :prop_key => c,
885 :old_value => @issue_before_change.send(c),
890 :old_value => @issue_before_change.send(c),
886 :value => send(c))
891 :value => send(c))
887 }
892 }
888 # custom fields changes
893 # custom fields changes
889 custom_values.each {|c|
894 custom_values.each {|c|
890 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
895 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
891 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
896 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
892 @current_journal.details << JournalDetail.new(:property => 'cf',
897 @current_journal.details << JournalDetail.new(:property => 'cf',
893 :prop_key => c.custom_field_id,
898 :prop_key => c.custom_field_id,
894 :old_value => @custom_values_before_change[c.custom_field_id],
899 :old_value => @custom_values_before_change[c.custom_field_id],
895 :value => c.value)
900 :value => c.value)
896 }
901 }
897 @current_journal.save
902 @current_journal.save
898 # reset current journal
903 # reset current journal
899 init_journal @current_journal.user, @current_journal.notes
904 init_journal @current_journal.user, @current_journal.notes
900 end
905 end
901 end
906 end
902
907
903 # Query generator for selecting groups of issue counts for a project
908 # Query generator for selecting groups of issue counts for a project
904 # based on specific criteria
909 # based on specific criteria
905 #
910 #
906 # Options
911 # Options
907 # * project - Project to search in.
912 # * project - Project to search in.
908 # * field - String. Issue field to key off of in the grouping.
913 # * field - String. Issue field to key off of in the grouping.
909 # * joins - String. The table name to join against.
914 # * joins - String. The table name to join against.
910 def self.count_and_group_by(options)
915 def self.count_and_group_by(options)
911 project = options.delete(:project)
916 project = options.delete(:project)
912 select_field = options.delete(:field)
917 select_field = options.delete(:field)
913 joins = options.delete(:joins)
918 joins = options.delete(:joins)
914
919
915 where = "#{Issue.table_name}.#{select_field}=j.id"
920 where = "#{Issue.table_name}.#{select_field}=j.id"
916
921
917 ActiveRecord::Base.connection.select_all("select s.id as status_id,
922 ActiveRecord::Base.connection.select_all("select s.id as status_id,
918 s.is_closed as closed,
923 s.is_closed as closed,
919 j.id as #{select_field},
924 j.id as #{select_field},
920 count(#{Issue.table_name}.id) as total
925 count(#{Issue.table_name}.id) as total
921 from
926 from
922 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
927 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
923 where
928 where
924 #{Issue.table_name}.status_id=s.id
929 #{Issue.table_name}.status_id=s.id
925 and #{where}
930 and #{where}
926 and #{Issue.table_name}.project_id=#{Project.table_name}.id
931 and #{Issue.table_name}.project_id=#{Project.table_name}.id
927 and #{visible_condition(User.current, :project => project)}
932 and #{visible_condition(User.current, :project => project)}
928 group by s.id, s.is_closed, j.id")
933 group by s.id, s.is_closed, j.id")
929 end
934 end
930 end
935 end
@@ -1,37 +1,37
1 <div class="contextual">
1 <div class="contextual">
2 <% if User.current.allowed_to?(:manage_issue_relations, @project) %>
2 <% if User.current.allowed_to?(:manage_issue_relations, @project) %>
3 <%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %>
3 <%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %>
4 <% end %>
4 <% end %>
5 </div>
5 </div>
6
6
7 <p><strong><%=l(:label_related_issues)%></strong></p>
7 <p><strong><%=l(:label_related_issues)%></strong></p>
8
8
9 <% if @relations.present? %>
9 <% if @relations.present? %>
10 <form>
10 <form>
11 <table class="list issues">
11 <table class="list issues">
12 <% @relations.each do |relation| %>
12 <% @relations.each do |relation| %>
13 <tr class="issue hascontextmenu">
13 <tr class="issue hascontextmenu">
14 <td class="checkbox"><%= check_box_tag("ids[]", relation.other_issue(@issue).id, false, :id => nil) %></td>
14 <td class="checkbox"><%= check_box_tag("ids[]", relation.other_issue(@issue).id, false, :id => nil) %></td>
15 <td class="subject"><%= l(relation.label_for(@issue)) %> <%= "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)})" if relation.delay && relation.delay != 0 %>
15 <td class="subject"><%= l(relation.label_for(@issue)) %> <%= "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)})" if relation.delay && relation.delay != 0 %>
16 <%= h(relation.other_issue(@issue).project) + ' - ' if Setting.cross_project_issue_relations? %>
16 <%= h(relation.other_issue(@issue).project) + ' - ' if Setting.cross_project_issue_relations? %>
17 <%= link_to_issue(relation.other_issue(@issue), :truncate => 60) %>
17 <%= link_to_issue(relation.other_issue(@issue), :truncate => 60) %>
18 </td>
18 </td>
19 <td class="status"><%= relation.other_issue(@issue).status.name %></td>
19 <td class="status"><%= relation.other_issue(@issue).status.name %></td>
20 <td class="start_date"><%= format_date(relation.other_issue(@issue).start_date) %></td>
20 <td class="start_date"><%= format_date(relation.other_issue(@issue).start_date) %></td>
21 <td class="due_date"><%= format_date(relation.other_issue(@issue).due_date) %></td>
21 <td class="due_date"><%= format_date(relation.other_issue(@issue).due_date) %></td>
22 <td class="buttons"><%= link_to_remote(image_tag('link_break.png'), { :url => {:controller => 'issue_relations', :action => 'destroy', :issue_id => @issue, :id => relation},
22 <td class="buttons"><%= link_to_remote(image_tag('link_break.png'), { :url => {:controller => 'issue_relations', :action => 'destroy', :issue_id => @issue, :id => relation},
23 :method => :post
23 :method => :delete
24 }, :title => l(:label_relation_delete)) if authorize_for('issue_relations', 'destroy') %></td>
24 }, :title => l(:label_relation_delete)) if authorize_for('issue_relations', 'destroy') %></td>
25 </tr>
25 </tr>
26 <% end %>
26 <% end %>
27 </table>
27 </table>
28 </form>
28 </form>
29 <% end %>
29 <% end %>
30
30
31 <% remote_form_for(:relation, @relation,
31 <% remote_form_for(:relation, @relation,
32 :url => {:controller => 'issue_relations', :action => 'new', :issue_id => @issue},
32 :url => {:controller => 'issue_relations', :action => 'create', :issue_id => @issue},
33 :method => :post,
33 :method => :post,
34 :complete => "Form.Element.focus('relation_issue_to_id');",
34 :complete => "Form.Element.focus('relation_issue_to_id');",
35 :html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')}) do |f| %>
35 :html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')}) do |f| %>
36 <%= render :partial => 'issue_relations/form', :locals => {:f => f}%>
36 <%= render :partial => 'issue_relations/form', :locals => {:f => f}%>
37 <% end %>
37 <% end %>
@@ -1,61 +1,61
1 api.issue do
1 api.issue do
2 api.id @issue.id
2 api.id @issue.id
3 api.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil?
3 api.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil?
4 api.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil?
4 api.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil?
5 api.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil?
5 api.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil?
6 api.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil?
6 api.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil?
7 api.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil?
7 api.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil?
8 api.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil?
8 api.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil?
9 api.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil?
9 api.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil?
10 api.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil?
10 api.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil?
11 api.parent(:id => @issue.parent_id) unless @issue.parent.nil?
11 api.parent(:id => @issue.parent_id) unless @issue.parent.nil?
12
12
13 api.subject @issue.subject
13 api.subject @issue.subject
14 api.description @issue.description
14 api.description @issue.description
15 api.start_date @issue.start_date
15 api.start_date @issue.start_date
16 api.due_date @issue.due_date
16 api.due_date @issue.due_date
17 api.done_ratio @issue.done_ratio
17 api.done_ratio @issue.done_ratio
18 api.estimated_hours @issue.estimated_hours
18 api.estimated_hours @issue.estimated_hours
19 api.spent_hours(@issue.spent_hours) if User.current.allowed_to?(:view_time_entries, @project)
19 api.spent_hours(@issue.spent_hours) if User.current.allowed_to?(:view_time_entries, @project)
20
20
21 render_api_custom_values @issue.custom_field_values, api
21 render_api_custom_values @issue.custom_field_values, api
22
22
23 api.created_on @issue.created_on
23 api.created_on @issue.created_on
24 api.updated_on @issue.updated_on
24 api.updated_on @issue.updated_on
25
25
26 render_api_issue_children(@issue, api) if include_in_api_response?('children')
26 render_api_issue_children(@issue, api) if include_in_api_response?('children')
27
27
28 api.array :relations do
28 api.array :relations do
29 @relations.each do |relation|
29 @relations.each do |relation|
30 api.relation(:id => relation.id, :issue_id => relation.other_issue(@issue).id, :relation_type => relation.relation_type_for(@issue), :delay => relation.delay)
30 api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type_for(@issue), :delay => relation.delay)
31 end
31 end
32 end if include_in_api_response?('relations') && @relations.present?
32 end if include_in_api_response?('relations') && @relations.present?
33
33
34 api.array :changesets do
34 api.array :changesets do
35 @issue.changesets.each do |changeset|
35 @issue.changesets.each do |changeset|
36 api.changeset :revision => changeset.revision do
36 api.changeset :revision => changeset.revision do
37 api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil?
37 api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil?
38 api.comments changeset.comments
38 api.comments changeset.comments
39 api.committed_on changeset.committed_on
39 api.committed_on changeset.committed_on
40 end
40 end
41 end
41 end
42 end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project)
42 end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project)
43
43
44 api.array :journals do
44 api.array :journals do
45 @issue.journals.each do |journal|
45 @issue.journals.each do |journal|
46 api.journal :id => journal.id do
46 api.journal :id => journal.id do
47 api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
47 api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
48 api.notes journal.notes
48 api.notes journal.notes
49 api.created_on journal.created_on
49 api.created_on journal.created_on
50 api.array :details do
50 api.array :details do
51 journal.details.each do |detail|
51 journal.details.each do |detail|
52 api.detail :property => detail.property, :name => detail.prop_key do
52 api.detail :property => detail.property, :name => detail.prop_key do
53 api.old_value detail.old_value
53 api.old_value detail.old_value
54 api.new_value detail.value
54 api.new_value detail.value
55 end
55 end
56 end
56 end
57 end
57 end
58 end
58 end
59 end
59 end
60 end if include_in_api_response?('journals')
60 end if include_in_api_response?('journals')
61 end
61 end
@@ -1,260 +1,255
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
80
81 # Misc issue routes. TODO: move into resources
81 # Misc issue routes. TODO: move into resources
82 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
82 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
83 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'
84 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
85 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
85 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 }
86 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 }
87 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 }
88 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
89 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
90
90
91 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
91 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
92 gantts_routes.connect '/projects/:project_id/issues/gantt'
92 gantts_routes.connect '/projects/:project_id/issues/gantt'
93 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
93 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
94 gantts_routes.connect '/issues/gantt.:format'
94 gantts_routes.connect '/issues/gantt.:format'
95 end
95 end
96
96
97 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
97 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
98 calendars_routes.connect '/projects/:project_id/issues/calendar'
98 calendars_routes.connect '/projects/:project_id/issues/calendar'
99 calendars_routes.connect '/issues/calendar'
99 calendars_routes.connect '/issues/calendar'
100 end
100 end
101
101
102 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
102 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
103 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
103 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
104 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
104 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
105 end
105 end
106
106
107 # Following two routes conflict with the resources because #index allows POST
107 # Following two routes conflict with the resources because #index allows POST
108 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
108 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
109 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
109 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
110
110
111 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
111 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
112 issues.resources :time_entries, :controller => 'timelog'
112 issues.resources :time_entries, :controller => 'timelog'
113 issues.resources :relations, :controller => 'issue_relations', :only => [:show, :create, :destroy]
113 end
114 end
114
115
115 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
116 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
116 issues.resources :time_entries, :controller => 'timelog'
117 issues.resources :time_entries, :controller => 'timelog'
117 end
118 end
118
119
119 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
120 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
121 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
122 end
123
124 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
120 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
125
121
126 map.with_options :controller => 'users' do |users|
122 map.with_options :controller => 'users' do |users|
127 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
123 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
128
124
129 users.with_options :conditions => {:method => :post} do |user_actions|
125 users.with_options :conditions => {:method => :post} do |user_actions|
130 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
126 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
131 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
127 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
132 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
128 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
133 end
129 end
134 end
130 end
135
131
136 map.resources :users, :member => {
132 map.resources :users, :member => {
137 :edit_membership => :post,
133 :edit_membership => :post,
138 :destroy_membership => :post
134 :destroy_membership => :post
139 }
135 }
140
136
141 # For nice "roadmap" in the url for the index action
137 # For nice "roadmap" in the url for the index action
142 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
138 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
143
139
144 map.all_news 'news', :controller => 'news', :action => 'index'
140 map.all_news 'news', :controller => 'news', :action => 'index'
145 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
141 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
146 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
142 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
147 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
143 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
148 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
144 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
149
145
150 map.resources :projects, :member => {
146 map.resources :projects, :member => {
151 :copy => [:get, :post],
147 :copy => [:get, :post],
152 :settings => :get,
148 :settings => :get,
153 :modules => :post,
149 :modules => :post,
154 :archive => :post,
150 :archive => :post,
155 :unarchive => :post
151 :unarchive => :post
156 } do |project|
152 } do |project|
157 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
153 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
158 project.resources :files, :only => [:index, :new, :create]
154 project.resources :files, :only => [:index, :new, :create]
159 project.resources :versions, :collection => {:close_completed => :put}, :member => {:status_by => :post}
155 project.resources :versions, :collection => {:close_completed => :put}, :member => {:status_by => :post}
160 project.resources :news, :shallow => true
156 project.resources :news, :shallow => true
161 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
157 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
162
158
163 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
159 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
164 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
160 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
165 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
161 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
166 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
162 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
167 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
163 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
168 project.resources :wiki, :except => [:new, :create], :member => {
164 project.resources :wiki, :except => [:new, :create], :member => {
169 :rename => [:get, :post],
165 :rename => [:get, :post],
170 :history => :get,
166 :history => :get,
171 :preview => :any,
167 :preview => :any,
172 :protect => :post,
168 :protect => :post,
173 :add_attachment => :post
169 :add_attachment => :post
174 }, :collection => {
170 }, :collection => {
175 :export => :get,
171 :export => :get,
176 :date_index => :get
172 :date_index => :get
177 }
173 }
178
174
179 end
175 end
180
176
181 # Destroy uses a get request to prompt the user before the actual DELETE request
177 # Destroy uses a get request to prompt the user before the actual DELETE request
182 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
178 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
183
179
184 # TODO: port to be part of the resources route(s)
180 # TODO: port to be part of the resources route(s)
185 map.with_options :controller => 'projects' do |project_mapper|
181 map.with_options :controller => 'projects' do |project_mapper|
186 project_mapper.with_options :conditions => {:method => :get} do |project_views|
182 project_mapper.with_options :conditions => {:method => :get} do |project_views|
187 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
183 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
188 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
184 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
189 end
185 end
190 end
186 end
191
187
192 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
188 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
193 activity.connect 'projects/:id/activity'
189 activity.connect 'projects/:id/activity'
194 activity.connect 'projects/:id/activity.:format'
190 activity.connect 'projects/:id/activity.:format'
195 activity.connect 'activity', :id => nil
191 activity.connect 'activity', :id => nil
196 activity.connect 'activity.:format', :id => nil
192 activity.connect 'activity.:format', :id => nil
197 end
193 end
198
194
199
195
200 map.with_options :controller => 'issue_categories' do |categories|
196 map.with_options :controller => 'issue_categories' do |categories|
201 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
197 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
202 end
198 end
203
199
204 map.with_options :controller => 'repositories' do |repositories|
200 map.with_options :controller => 'repositories' do |repositories|
205 repositories.with_options :conditions => {:method => :get} do |repository_views|
201 repositories.with_options :conditions => {:method => :get} do |repository_views|
206 repository_views.connect 'projects/:id/repository', :action => 'show'
202 repository_views.connect 'projects/:id/repository', :action => 'show'
207 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
203 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
208 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
204 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
209 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
205 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
210 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
206 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
211 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
207 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
212 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
208 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
213 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
209 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
214 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
210 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
215 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
211 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
216 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
212 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
217 # TODO: why the following route is required?
213 # TODO: why the following route is required?
218 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
214 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
219 repository_views.connect 'projects/:id/repository/:action/*path'
215 repository_views.connect 'projects/:id/repository/:action/*path'
220 end
216 end
221
217
222 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
218 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
223 end
219 end
224
220
225 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
221 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
226 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
222 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
227 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
223 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
228
224
229 map.resources :groups
225 map.resources :groups
230
226
231 #left old routes at the bottom for backwards compat
227 #left old routes at the bottom for backwards compat
232 map.connect 'projects/:project_id/queries/:action', :controller => 'queries'
228 map.connect 'projects/:project_id/queries/:action', :controller => 'queries'
233 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
229 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
234 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
230 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
235 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
231 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
236 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
232 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
237 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
233 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
238 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
239 map.connect 'projects/:project_id/news/:action', :controller => 'news'
234 map.connect 'projects/:project_id/news/:action', :controller => 'news'
240 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
235 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
241 map.with_options :controller => 'repositories' do |omap|
236 map.with_options :controller => 'repositories' do |omap|
242 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
237 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
243 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
238 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
244 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
239 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
245 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
240 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
246 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
241 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
247 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
242 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
248 end
243 end
249
244
250 map.with_options :controller => 'sys' do |sys|
245 map.with_options :controller => 'sys' do |sys|
251 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
246 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
252 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
247 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
253 end
248 end
254
249
255 # Install the default route as the lowest priority.
250 # Install the default route as the lowest priority.
256 map.connect ':controller/:action/:id'
251 map.connect ':controller/:action/:id'
257 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
252 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
258 # Used for OpenID
253 # Used for OpenID
259 map.root :controller => 'account', :action => 'login'
254 map.root :controller => 'account', :action => 'login'
260 end
255 end
@@ -1,237 +1,237
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/activity'
3 require 'redmine/activity'
4 require 'redmine/search'
4 require 'redmine/search'
5 require 'redmine/custom_field_format'
5 require 'redmine/custom_field_format'
6 require 'redmine/mime_type'
6 require 'redmine/mime_type'
7 require 'redmine/core_ext'
7 require 'redmine/core_ext'
8 require 'redmine/themes'
8 require 'redmine/themes'
9 require 'redmine/hook'
9 require 'redmine/hook'
10 require 'redmine/plugin'
10 require 'redmine/plugin'
11 require 'redmine/notifiable'
11 require 'redmine/notifiable'
12 require 'redmine/wiki_formatting'
12 require 'redmine/wiki_formatting'
13 require 'redmine/scm/base'
13 require 'redmine/scm/base'
14
14
15 begin
15 begin
16 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
16 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
17 rescue LoadError
17 rescue LoadError
18 # RMagick is not available
18 # RMagick is not available
19 end
19 end
20
20
21 if RUBY_VERSION < '1.9'
21 if RUBY_VERSION < '1.9'
22 require 'faster_csv'
22 require 'faster_csv'
23 else
23 else
24 require 'csv'
24 require 'csv'
25 FCSV = CSV
25 FCSV = CSV
26 end
26 end
27
27
28 Redmine::Scm::Base.add "Subversion"
28 Redmine::Scm::Base.add "Subversion"
29 Redmine::Scm::Base.add "Darcs"
29 Redmine::Scm::Base.add "Darcs"
30 Redmine::Scm::Base.add "Mercurial"
30 Redmine::Scm::Base.add "Mercurial"
31 Redmine::Scm::Base.add "Cvs"
31 Redmine::Scm::Base.add "Cvs"
32 Redmine::Scm::Base.add "Bazaar"
32 Redmine::Scm::Base.add "Bazaar"
33 Redmine::Scm::Base.add "Git"
33 Redmine::Scm::Base.add "Git"
34 Redmine::Scm::Base.add "Filesystem"
34 Redmine::Scm::Base.add "Filesystem"
35
35
36 Redmine::CustomFieldFormat.map do |fields|
36 Redmine::CustomFieldFormat.map do |fields|
37 fields.register Redmine::CustomFieldFormat.new('string', :label => :label_string, :order => 1)
37 fields.register Redmine::CustomFieldFormat.new('string', :label => :label_string, :order => 1)
38 fields.register Redmine::CustomFieldFormat.new('text', :label => :label_text, :order => 2)
38 fields.register Redmine::CustomFieldFormat.new('text', :label => :label_text, :order => 2)
39 fields.register Redmine::CustomFieldFormat.new('int', :label => :label_integer, :order => 3)
39 fields.register Redmine::CustomFieldFormat.new('int', :label => :label_integer, :order => 3)
40 fields.register Redmine::CustomFieldFormat.new('float', :label => :label_float, :order => 4)
40 fields.register Redmine::CustomFieldFormat.new('float', :label => :label_float, :order => 4)
41 fields.register Redmine::CustomFieldFormat.new('list', :label => :label_list, :order => 5)
41 fields.register Redmine::CustomFieldFormat.new('list', :label => :label_list, :order => 5)
42 fields.register Redmine::CustomFieldFormat.new('date', :label => :label_date, :order => 6)
42 fields.register Redmine::CustomFieldFormat.new('date', :label => :label_date, :order => 6)
43 fields.register Redmine::CustomFieldFormat.new('bool', :label => :label_boolean, :order => 7)
43 fields.register Redmine::CustomFieldFormat.new('bool', :label => :label_boolean, :order => 7)
44 fields.register Redmine::CustomFieldFormat.new('user', :label => :label_user, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 8)
44 fields.register Redmine::CustomFieldFormat.new('user', :label => :label_user, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 8)
45 fields.register Redmine::CustomFieldFormat.new('version', :label => :label_version, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 9)
45 fields.register Redmine::CustomFieldFormat.new('version', :label => :label_version, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 9)
46 end
46 end
47
47
48 # Permissions
48 # Permissions
49 Redmine::AccessControl.map do |map|
49 Redmine::AccessControl.map do |map|
50 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true
50 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true
51 map.permission :search_project, {:search => :index}, :public => true
51 map.permission :search_project, {:search => :index}, :public => true
52 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
52 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
53 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
53 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
54 map.permission :select_project_modules, {:projects => :modules}, :require => :member
54 map.permission :select_project_modules, {:projects => :modules}, :require => :member
55 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
55 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
56 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
56 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
57 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
57 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
58
58
59 map.project_module :issue_tracking do |map|
59 map.project_module :issue_tracking do |map|
60 # Issue categories
60 # Issue categories
61 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:new, :edit, :destroy]}, :require => :member
61 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:new, :edit, :destroy]}, :require => :member
62 # Issues
62 # Issues
63 map.permission :view_issues, {:issues => [:index, :show],
63 map.permission :view_issues, {:issues => [:index, :show],
64 :auto_complete => [:issues],
64 :auto_complete => [:issues],
65 :context_menus => [:issues],
65 :context_menus => [:issues],
66 :versions => [:index, :show, :status_by],
66 :versions => [:index, :show, :status_by],
67 :journals => [:index, :diff],
67 :journals => [:index, :diff],
68 :queries => :index,
68 :queries => :index,
69 :reports => [:issue_report, :issue_report_details]}
69 :reports => [:issue_report, :issue_report_details]}
70 map.permission :add_issues, {:issues => [:new, :create, :update_form]}
70 map.permission :add_issues, {:issues => [:new, :create, :update_form]}
71 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new]}
71 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new]}
72 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
72 map.permission :manage_issue_relations, {:issue_relations => [:show, :create, :destroy]}
73 map.permission :manage_subtasks, {}
73 map.permission :manage_subtasks, {}
74 map.permission :set_issues_private, {}
74 map.permission :set_issues_private, {}
75 map.permission :set_own_issues_private, {}, :require => :loggedin
75 map.permission :set_own_issues_private, {}, :require => :loggedin
76 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]}
76 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]}
77 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
77 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
78 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
78 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
79 map.permission :move_issues, {:issue_moves => [:new, :create]}, :require => :loggedin
79 map.permission :move_issues, {:issue_moves => [:new, :create]}, :require => :loggedin
80 map.permission :delete_issues, {:issues => :destroy}, :require => :member
80 map.permission :delete_issues, {:issues => :destroy}, :require => :member
81 # Queries
81 # Queries
82 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
82 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
83 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
83 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
84 # Watchers
84 # Watchers
85 map.permission :view_issue_watchers, {}
85 map.permission :view_issue_watchers, {}
86 map.permission :add_issue_watchers, {:watchers => :new}
86 map.permission :add_issue_watchers, {:watchers => :new}
87 map.permission :delete_issue_watchers, {:watchers => :destroy}
87 map.permission :delete_issue_watchers, {:watchers => :destroy}
88 end
88 end
89
89
90 map.project_module :time_tracking do |map|
90 map.project_module :time_tracking do |map|
91 map.permission :log_time, {:timelog => [:new, :create, :edit, :update, :bulk_edit, :bulk_update]}, :require => :loggedin
91 map.permission :log_time, {:timelog => [:new, :create, :edit, :update, :bulk_edit, :bulk_update]}, :require => :loggedin
92 map.permission :view_time_entries, :timelog => [:index, :show], :time_entry_reports => [:report]
92 map.permission :view_time_entries, :timelog => [:index, :show], :time_entry_reports => [:report]
93 map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
93 map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
94 map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
94 map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
95 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
95 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
96 end
96 end
97
97
98 map.project_module :news do |map|
98 map.project_module :news do |map|
99 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member
99 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member
100 map.permission :view_news, {:news => [:index, :show]}, :public => true
100 map.permission :view_news, {:news => [:index, :show]}, :public => true
101 map.permission :comment_news, {:comments => :create}
101 map.permission :comment_news, {:comments => :create}
102 end
102 end
103
103
104 map.project_module :documents do |map|
104 map.project_module :documents do |map|
105 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
105 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
106 map.permission :view_documents, :documents => [:index, :show, :download]
106 map.permission :view_documents, :documents => [:index, :show, :download]
107 end
107 end
108
108
109 map.project_module :files do |map|
109 map.project_module :files do |map|
110 map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
110 map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
111 map.permission :view_files, :files => :index, :versions => :download
111 map.permission :view_files, :files => :index, :versions => :download
112 end
112 end
113
113
114 map.project_module :wiki do |map|
114 map.project_module :wiki do |map|
115 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
115 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
116 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
116 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
117 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
117 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
118 map.permission :view_wiki_pages, :wiki => [:index, :show, :special, :date_index]
118 map.permission :view_wiki_pages, :wiki => [:index, :show, :special, :date_index]
119 map.permission :export_wiki_pages, :wiki => [:export]
119 map.permission :export_wiki_pages, :wiki => [:export]
120 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
120 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
121 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment]
121 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment]
122 map.permission :delete_wiki_pages_attachments, {}
122 map.permission :delete_wiki_pages_attachments, {}
123 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
123 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
124 end
124 end
125
125
126 map.project_module :repository do |map|
126 map.project_module :repository do |map|
127 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
127 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
128 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
128 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
129 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
129 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
130 map.permission :commit_access, {}
130 map.permission :commit_access, {}
131 end
131 end
132
132
133 map.project_module :boards do |map|
133 map.project_module :boards do |map|
134 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
134 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
135 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
135 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
136 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
136 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
137 map.permission :edit_messages, {:messages => :edit}, :require => :member
137 map.permission :edit_messages, {:messages => :edit}, :require => :member
138 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
138 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
139 map.permission :delete_messages, {:messages => :destroy}, :require => :member
139 map.permission :delete_messages, {:messages => :destroy}, :require => :member
140 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
140 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
141 end
141 end
142
142
143 map.project_module :calendar do |map|
143 map.project_module :calendar do |map|
144 map.permission :view_calendar, :calendars => [:show, :update]
144 map.permission :view_calendar, :calendars => [:show, :update]
145 end
145 end
146
146
147 map.project_module :gantt do |map|
147 map.project_module :gantt do |map|
148 map.permission :view_gantt, :gantts => [:show, :update]
148 map.permission :view_gantt, :gantts => [:show, :update]
149 end
149 end
150 end
150 end
151
151
152 Redmine::MenuManager.map :top_menu do |menu|
152 Redmine::MenuManager.map :top_menu do |menu|
153 menu.push :home, :home_path
153 menu.push :home, :home_path
154 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
154 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
155 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
155 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
156 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
156 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
157 menu.push :help, Redmine::Info.help_url, :last => true
157 menu.push :help, Redmine::Info.help_url, :last => true
158 end
158 end
159
159
160 Redmine::MenuManager.map :account_menu do |menu|
160 Redmine::MenuManager.map :account_menu do |menu|
161 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
161 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
162 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
162 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
163 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
163 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
164 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
164 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
165 end
165 end
166
166
167 Redmine::MenuManager.map :application_menu do |menu|
167 Redmine::MenuManager.map :application_menu do |menu|
168 # Empty
168 # Empty
169 end
169 end
170
170
171 Redmine::MenuManager.map :admin_menu do |menu|
171 Redmine::MenuManager.map :admin_menu do |menu|
172 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
172 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
173 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
173 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
174 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
174 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
175 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
175 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
176 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
176 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
177 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
177 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
178 :html => {:class => 'issue_statuses'}
178 :html => {:class => 'issue_statuses'}
179 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
179 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
180 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
180 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
181 :html => {:class => 'custom_fields'}
181 :html => {:class => 'custom_fields'}
182 menu.push :enumerations, {:controller => 'enumerations'}
182 menu.push :enumerations, {:controller => 'enumerations'}
183 menu.push :settings, {:controller => 'settings'}
183 menu.push :settings, {:controller => 'settings'}
184 menu.push :ldap_authentication, {:controller => 'ldap_auth_sources', :action => 'index'},
184 menu.push :ldap_authentication, {:controller => 'ldap_auth_sources', :action => 'index'},
185 :html => {:class => 'server_authentication'}
185 :html => {:class => 'server_authentication'}
186 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
186 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
187 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
187 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
188 end
188 end
189
189
190 Redmine::MenuManager.map :project_menu do |menu|
190 Redmine::MenuManager.map :project_menu do |menu|
191 menu.push :overview, { :controller => 'projects', :action => 'show' }
191 menu.push :overview, { :controller => 'projects', :action => 'show' }
192 menu.push :activity, { :controller => 'activities', :action => 'index' }
192 menu.push :activity, { :controller => 'activities', :action => 'index' }
193 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
193 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
194 :if => Proc.new { |p| p.shared_versions.any? }
194 :if => Proc.new { |p| p.shared_versions.any? }
195 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
195 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
196 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
196 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
197 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
197 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
198 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
198 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
199 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
199 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
200 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
200 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
201 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
201 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
202 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
202 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
203 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
203 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
204 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
204 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
205 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
205 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
206 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
206 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
207 menu.push :repository, { :controller => 'repositories', :action => 'show' },
207 menu.push :repository, { :controller => 'repositories', :action => 'show' },
208 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
208 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
209 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
209 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
210 end
210 end
211
211
212 Redmine::Activity.map do |activity|
212 Redmine::Activity.map do |activity|
213 activity.register :issues, :class_name => %w(Issue Journal)
213 activity.register :issues, :class_name => %w(Issue Journal)
214 activity.register :changesets
214 activity.register :changesets
215 activity.register :news
215 activity.register :news
216 activity.register :documents, :class_name => %w(Document Attachment)
216 activity.register :documents, :class_name => %w(Document Attachment)
217 activity.register :files, :class_name => 'Attachment'
217 activity.register :files, :class_name => 'Attachment'
218 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
218 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
219 activity.register :messages, :default => false
219 activity.register :messages, :default => false
220 activity.register :time_entries, :default => false
220 activity.register :time_entries, :default => false
221 end
221 end
222
222
223 Redmine::Search.map do |search|
223 Redmine::Search.map do |search|
224 search.register :issues
224 search.register :issues
225 search.register :news
225 search.register :news
226 search.register :documents
226 search.register :documents
227 search.register :changesets
227 search.register :changesets
228 search.register :wiki_pages
228 search.register :wiki_pages
229 search.register :messages
229 search.register :messages
230 search.register :projects
230 search.register :projects
231 end
231 end
232
232
233 Redmine::WikiFormatting.map do |format|
233 Redmine::WikiFormatting.map do |format|
234 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
234 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
235 end
235 end
236
236
237 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
237 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
@@ -1,102 +1,119
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
1 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
2 require 'issue_relations_controller'
19 require 'issue_relations_controller'
3
20
4 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
5 class IssueRelationsController; def rescue_action(e) raise e end; end
22 class IssueRelationsController; def rescue_action(e) raise e end; end
6
23
7
24
8 class IssueRelationsControllerTest < ActionController::TestCase
25 class IssueRelationsControllerTest < ActionController::TestCase
9 fixtures :projects,
26 fixtures :projects,
10 :users,
27 :users,
11 :roles,
28 :roles,
12 :members,
29 :members,
13 :member_roles,
30 :member_roles,
14 :issues,
31 :issues,
15 :issue_statuses,
32 :issue_statuses,
16 :issue_relations,
33 :issue_relations,
17 :enabled_modules,
34 :enabled_modules,
18 :enumerations,
35 :enumerations,
19 :trackers
36 :trackers
20
37
21 def setup
38 def setup
22 @controller = IssueRelationsController.new
39 @controller = IssueRelationsController.new
23 @request = ActionController::TestRequest.new
40 @request = ActionController::TestRequest.new
24 @response = ActionController::TestResponse.new
41 @response = ActionController::TestResponse.new
25 User.current = nil
42 User.current = nil
26 end
43 end
27
44
28 def test_new
45 def test_create
29 assert_difference 'IssueRelation.count' do
46 assert_difference 'IssueRelation.count' do
30 @request.session[:user_id] = 3
47 @request.session[:user_id] = 3
31 post :new, :issue_id => 1,
48 post :create, :issue_id => 1,
32 :relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''}
49 :relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''}
33 end
50 end
34 end
51 end
35
52
36 def test_new_xhr
53 def test_create_xhr
37 assert_difference 'IssueRelation.count' do
54 assert_difference 'IssueRelation.count' do
38 @request.session[:user_id] = 3
55 @request.session[:user_id] = 3
39 xhr :post, :new,
56 xhr :post, :create,
40 :issue_id => 3,
57 :issue_id => 3,
41 :relation => {:issue_to_id => '1', :relation_type => 'relates', :delay => ''}
58 :relation => {:issue_to_id => '1', :relation_type => 'relates', :delay => ''}
42 assert_select_rjs 'relations' do
59 assert_select_rjs 'relations' do
43 assert_select 'table', 1
60 assert_select 'table', 1
44 assert_select 'tr', 2 # relations
61 assert_select 'tr', 2 # relations
45 end
62 end
46 end
63 end
47 end
64 end
48
65
49 def test_new_should_accept_id_with_hash
66 def test_create_should_accept_id_with_hash
50 assert_difference 'IssueRelation.count' do
67 assert_difference 'IssueRelation.count' do
51 @request.session[:user_id] = 3
68 @request.session[:user_id] = 3
52 post :new, :issue_id => 1,
69 post :create, :issue_id => 1,
53 :relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''}
70 :relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''}
54 end
71 end
55 end
72 end
56
73
57 def test_new_should_not_break_with_non_numerical_id
74 def test_create_should_not_break_with_non_numerical_id
58 assert_no_difference 'IssueRelation.count' do
75 assert_no_difference 'IssueRelation.count' do
59 assert_nothing_raised do
76 assert_nothing_raised do
60 @request.session[:user_id] = 3
77 @request.session[:user_id] = 3
61 post :new, :issue_id => 1,
78 post :create, :issue_id => 1,
62 :relation => {:issue_to_id => 'foo', :relation_type => 'relates', :delay => ''}
79 :relation => {:issue_to_id => 'foo', :relation_type => 'relates', :delay => ''}
63 end
80 end
64 end
81 end
65 end
82 end
66
83
67 def test_should_create_relations_with_visible_issues_only
84 def test_should_create_relations_with_visible_issues_only
68 Setting.cross_project_issue_relations = '1'
85 Setting.cross_project_issue_relations = '1'
69 assert_nil Issue.visible(User.find(3)).find_by_id(4)
86 assert_nil Issue.visible(User.find(3)).find_by_id(4)
70
87
71 assert_no_difference 'IssueRelation.count' do
88 assert_no_difference 'IssueRelation.count' do
72 @request.session[:user_id] = 3
89 @request.session[:user_id] = 3
73 post :new, :issue_id => 1,
90 post :create, :issue_id => 1,
74 :relation => {:issue_to_id => '4', :relation_type => 'relates', :delay => ''}
91 :relation => {:issue_to_id => '4', :relation_type => 'relates', :delay => ''}
75 end
92 end
76 end
93 end
77
94
78 should "prevent relation creation when there's a circular dependency"
95 should "prevent relation creation when there's a circular dependency"
79
96
80 def test_destroy
97 def test_destroy
81 assert_difference 'IssueRelation.count', -1 do
98 assert_difference 'IssueRelation.count', -1 do
82 @request.session[:user_id] = 3
99 @request.session[:user_id] = 3
83 post :destroy, :id => '2', :issue_id => '3'
100 delete :destroy, :id => '2', :issue_id => '3'
84 end
101 end
85 end
102 end
86
103
87 def test_destroy_xhr
104 def test_destroy_xhr
88 IssueRelation.create!(:relation_type => IssueRelation::TYPE_RELATES) do |r|
105 IssueRelation.create!(:relation_type => IssueRelation::TYPE_RELATES) do |r|
89 r.issue_from_id = 3
106 r.issue_from_id = 3
90 r.issue_to_id = 1
107 r.issue_to_id = 1
91 end
108 end
92
109
93 assert_difference 'IssueRelation.count', -1 do
110 assert_difference 'IssueRelation.count', -1 do
94 @request.session[:user_id] = 3
111 @request.session[:user_id] = 3
95 xhr :post, :destroy, :id => '2', :issue_id => '3'
112 xhr :delete, :destroy, :id => '2', :issue_id => '3'
96 assert_select_rjs 'relations' do
113 assert_select_rjs 'relations' do
97 assert_select 'table', 1
114 assert_select 'table', 1
98 assert_select 'tr', 1 # relation left
115 assert_select 'tr', 1 # relation left
99 end
116 end
100 end
117 end
101 end
118 end
102 end
119 end
@@ -1,362 +1,371
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 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 :post, "/issues/1/relations", :controller => 'issue_relations', :action => 'new', :issue_id => '1'
121 should_route :post, "/issues/1/relations", :controller => 'issue_relations', :action => 'create', :issue_id => '1'
122 should_route :post, "/issues/1/relations/23/destroy", :controller => 'issue_relations', :action => 'destroy', :issue_id => '1', :id => '23'
122 should_route :post, "/issues/1/relations.xml", :controller => 'issue_relations', :action => 'create', :issue_id => '1', :format => 'xml'
123 should_route :post, "/issues/1/relations.json", :controller => 'issue_relations', :action => 'create', :issue_id => '1', :format => 'json'
124
125 should_route :get, "/issues/1/relations/23", :controller => 'issue_relations', :action => 'show', :issue_id => '1', :id => '23'
126 should_route :get, "/issues/1/relations/23.xml", :controller => 'issue_relations', :action => 'show', :issue_id => '1', :id => '23', :format => 'xml'
127 should_route :get, "/issues/1/relations/23.json", :controller => 'issue_relations', :action => 'show', :issue_id => '1', :id => '23', :format => 'json'
128
129 should_route :delete, "/issues/1/relations/23", :controller => 'issue_relations', :action => 'destroy', :issue_id => '1', :id => '23'
130 should_route :delete, "/issues/1/relations/23.xml", :controller => 'issue_relations', :action => 'destroy', :issue_id => '1', :id => '23', :format => 'xml'
131 should_route :delete, "/issues/1/relations/23.json", :controller => 'issue_relations', :action => 'destroy', :issue_id => '1', :id => '23', :format => 'json'
123 end
132 end
124
133
125 context "issue reports" do
134 context "issue reports" do
126 should_route :get, "/projects/567/issues/report", :controller => 'reports', :action => 'issue_report', :id => '567'
135 should_route :get, "/projects/567/issues/report", :controller => 'reports', :action => 'issue_report', :id => '567'
127 should_route :get, "/projects/567/issues/report/assigned_to", :controller => 'reports', :action => 'issue_report_details', :id => '567', :detail => 'assigned_to'
136 should_route :get, "/projects/567/issues/report/assigned_to", :controller => 'reports', :action => 'issue_report_details', :id => '567', :detail => 'assigned_to'
128 end
137 end
129
138
130 context "members" do
139 context "members" do
131 should_route :post, "/projects/5234/members/new", :controller => 'members', :action => 'new', :id => '5234'
140 should_route :post, "/projects/5234/members/new", :controller => 'members', :action => 'new', :id => '5234'
132 end
141 end
133
142
134 context "messages" do
143 context "messages" do
135 should_route :get, "/boards/22/topics/2", :controller => 'messages', :action => 'show', :id => '2', :board_id => '22'
144 should_route :get, "/boards/22/topics/2", :controller => 'messages', :action => 'show', :id => '2', :board_id => '22'
136 should_route :get, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
145 should_route :get, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
137 should_route :get, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
146 should_route :get, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
138
147
139 should_route :post, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
148 should_route :post, "/boards/lala/topics/new", :controller => 'messages', :action => 'new', :board_id => 'lala'
140 should_route :post, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
149 should_route :post, "/boards/lala/topics/22/edit", :controller => 'messages', :action => 'edit', :id => '22', :board_id => 'lala'
141 should_route :post, "/boards/22/topics/555/replies", :controller => 'messages', :action => 'reply', :id => '555', :board_id => '22'
150 should_route :post, "/boards/22/topics/555/replies", :controller => 'messages', :action => 'reply', :id => '555', :board_id => '22'
142 should_route :post, "/boards/22/topics/555/destroy", :controller => 'messages', :action => 'destroy', :id => '555', :board_id => '22'
151 should_route :post, "/boards/22/topics/555/destroy", :controller => 'messages', :action => 'destroy', :id => '555', :board_id => '22'
143 end
152 end
144
153
145 context "news" do
154 context "news" do
146 should_route :get, "/news", :controller => 'news', :action => 'index'
155 should_route :get, "/news", :controller => 'news', :action => 'index'
147 should_route :get, "/news.atom", :controller => 'news', :action => 'index', :format => 'atom'
156 should_route :get, "/news.atom", :controller => 'news', :action => 'index', :format => 'atom'
148 should_route :get, "/news.xml", :controller => 'news', :action => 'index', :format => 'xml'
157 should_route :get, "/news.xml", :controller => 'news', :action => 'index', :format => 'xml'
149 should_route :get, "/news.json", :controller => 'news', :action => 'index', :format => 'json'
158 should_route :get, "/news.json", :controller => 'news', :action => 'index', :format => 'json'
150 should_route :get, "/projects/567/news", :controller => 'news', :action => 'index', :project_id => '567'
159 should_route :get, "/projects/567/news", :controller => 'news', :action => 'index', :project_id => '567'
151 should_route :get, "/projects/567/news.atom", :controller => 'news', :action => 'index', :format => 'atom', :project_id => '567'
160 should_route :get, "/projects/567/news.atom", :controller => 'news', :action => 'index', :format => 'atom', :project_id => '567'
152 should_route :get, "/projects/567/news.xml", :controller => 'news', :action => 'index', :format => 'xml', :project_id => '567'
161 should_route :get, "/projects/567/news.xml", :controller => 'news', :action => 'index', :format => 'xml', :project_id => '567'
153 should_route :get, "/projects/567/news.json", :controller => 'news', :action => 'index', :format => 'json', :project_id => '567'
162 should_route :get, "/projects/567/news.json", :controller => 'news', :action => 'index', :format => 'json', :project_id => '567'
154 should_route :get, "/news/2", :controller => 'news', :action => 'show', :id => '2'
163 should_route :get, "/news/2", :controller => 'news', :action => 'show', :id => '2'
155 should_route :get, "/projects/567/news/new", :controller => 'news', :action => 'new', :project_id => '567'
164 should_route :get, "/projects/567/news/new", :controller => 'news', :action => 'new', :project_id => '567'
156 should_route :get, "/news/234", :controller => 'news', :action => 'show', :id => '234'
165 should_route :get, "/news/234", :controller => 'news', :action => 'show', :id => '234'
157 should_route :get, "/news/567/edit", :controller => 'news', :action => 'edit', :id => '567'
166 should_route :get, "/news/567/edit", :controller => 'news', :action => 'edit', :id => '567'
158 should_route :get, "/news/preview", :controller => 'previews', :action => 'news'
167 should_route :get, "/news/preview", :controller => 'previews', :action => 'news'
159
168
160 should_route :post, "/projects/567/news", :controller => 'news', :action => 'create', :project_id => '567'
169 should_route :post, "/projects/567/news", :controller => 'news', :action => 'create', :project_id => '567'
161 should_route :post, "/news/567/comments", :controller => 'comments', :action => 'create', :id => '567'
170 should_route :post, "/news/567/comments", :controller => 'comments', :action => 'create', :id => '567'
162
171
163 should_route :put, "/news/567", :controller => 'news', :action => 'update', :id => '567'
172 should_route :put, "/news/567", :controller => 'news', :action => 'update', :id => '567'
164
173
165 should_route :delete, "/news/567", :controller => 'news', :action => 'destroy', :id => '567'
174 should_route :delete, "/news/567", :controller => 'news', :action => 'destroy', :id => '567'
166 should_route :delete, "/news/567/comments/15", :controller => 'comments', :action => 'destroy', :id => '567', :comment_id => '15'
175 should_route :delete, "/news/567/comments/15", :controller => 'comments', :action => 'destroy', :id => '567', :comment_id => '15'
167 end
176 end
168
177
169 context "projects" do
178 context "projects" do
170 should_route :get, "/projects", :controller => 'projects', :action => 'index'
179 should_route :get, "/projects", :controller => 'projects', :action => 'index'
171 should_route :get, "/projects.atom", :controller => 'projects', :action => 'index', :format => 'atom'
180 should_route :get, "/projects.atom", :controller => 'projects', :action => 'index', :format => 'atom'
172 should_route :get, "/projects.xml", :controller => 'projects', :action => 'index', :format => 'xml'
181 should_route :get, "/projects.xml", :controller => 'projects', :action => 'index', :format => 'xml'
173 should_route :get, "/projects/new", :controller => 'projects', :action => 'new'
182 should_route :get, "/projects/new", :controller => 'projects', :action => 'new'
174 should_route :get, "/projects/test", :controller => 'projects', :action => 'show', :id => 'test'
183 should_route :get, "/projects/test", :controller => 'projects', :action => 'show', :id => 'test'
175 should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
184 should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
176 should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223'
185 should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223'
177 should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
186 should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
178 should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :project_id => '33'
187 should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :project_id => '33'
179 should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :project_id => '33'
188 should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :project_id => '33'
180 should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33'
189 should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33'
181 should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33'
190 should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33'
182 should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom'
191 should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom'
183
192
184 should_route :post, "/projects", :controller => 'projects', :action => 'create'
193 should_route :post, "/projects", :controller => 'projects', :action => 'create'
185 should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml'
194 should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml'
186 should_route :post, "/projects/33/files", :controller => 'files', :action => 'create', :project_id => '33'
195 should_route :post, "/projects/33/files", :controller => 'files', :action => 'create', :project_id => '33'
187 should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64'
196 should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64'
188 should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64'
197 should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64'
189
198
190 should_route :put, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'update', :project_id => '64'
199 should_route :put, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'update', :project_id => '64'
191 should_route :put, "/projects/4223", :controller => 'projects', :action => 'update', :id => '4223'
200 should_route :put, "/projects/4223", :controller => 'projects', :action => 'update', :id => '4223'
192 should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml'
201 should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml'
193
202
194 should_route :delete, "/projects/64", :controller => 'projects', :action => 'destroy', :id => '64'
203 should_route :delete, "/projects/64", :controller => 'projects', :action => 'destroy', :id => '64'
195 should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
204 should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
196 should_route :delete, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'destroy', :project_id => '64'
205 should_route :delete, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'destroy', :project_id => '64'
197 end
206 end
198
207
199 context "queries" do
208 context "queries" do
200 should_route :get, "/queries/new", :controller => 'queries', :action => 'new'
209 should_route :get, "/queries/new", :controller => 'queries', :action => 'new'
201 should_route :get, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
210 should_route :get, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
202
211
203 should_route :post, "/queries/new", :controller => 'queries', :action => 'new'
212 should_route :post, "/queries/new", :controller => 'queries', :action => 'new'
204 should_route :post, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
213 should_route :post, "/projects/redmine/queries/new", :controller => 'queries', :action => 'new', :project_id => 'redmine'
205 end
214 end
206
215
207 context "repositories" do
216 context "repositories" do
208 should_route :get, "/projects/redmine/repository", :controller => 'repositories', :action => 'show', :id => 'redmine'
217 should_route :get, "/projects/redmine/repository", :controller => 'repositories', :action => 'show', :id => 'redmine'
209 should_route :get, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
218 should_route :get, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
210 should_route :get, "/projects/redmine/repository/revisions", :controller => 'repositories', :action => 'revisions', :id => 'redmine'
219 should_route :get, "/projects/redmine/repository/revisions", :controller => 'repositories', :action => 'revisions', :id => 'redmine'
211 should_route :get, "/projects/redmine/repository/revisions.atom", :controller => 'repositories', :action => 'revisions', :id => 'redmine', :format => 'atom'
220 should_route :get, "/projects/redmine/repository/revisions.atom", :controller => 'repositories', :action => 'revisions', :id => 'redmine', :format => 'atom'
212 should_route :get, "/projects/redmine/repository/revisions/2457", :controller => 'repositories', :action => 'revision', :id => 'redmine', :rev => '2457'
221 should_route :get, "/projects/redmine/repository/revisions/2457", :controller => 'repositories', :action => 'revision', :id => 'redmine', :rev => '2457'
213 should_route :get, "/projects/redmine/repository/revisions/2457/diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457'
222 should_route :get, "/projects/redmine/repository/revisions/2457/diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457'
214 should_route :get, "/projects/redmine/repository/revisions/2457/diff.diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457', :format => 'diff'
223 should_route :get, "/projects/redmine/repository/revisions/2457/diff.diff", :controller => 'repositories', :action => 'diff', :id => 'redmine', :rev => '2457', :format => 'diff'
215 should_route :get, "/projects/redmine/repository/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c]
224 should_route :get, "/projects/redmine/repository/diff/path/to/file.c", :controller => 'repositories', :action => 'diff', :id => 'redmine', :path => %w[path to file.c]
216 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'
225 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'
217 should_route :get, "/projects/redmine/repository/browse/path/to/file.c", :controller => 'repositories', :action => 'browse', :id => 'redmine', :path => %w[path to file.c]
226 should_route :get, "/projects/redmine/repository/browse/path/to/file.c", :controller => 'repositories', :action => 'browse', :id => 'redmine', :path => %w[path to file.c]
218 should_route :get, "/projects/redmine/repository/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c]
227 should_route :get, "/projects/redmine/repository/entry/path/to/file.c", :controller => 'repositories', :action => 'entry', :id => 'redmine', :path => %w[path to file.c]
219 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'
228 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'
220 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'
229 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'
221 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'
230 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'
222 should_route :get, "/projects/redmine/repository/annotate/path/to/file.c", :controller => 'repositories', :action => 'annotate', :id => 'redmine', :path => %w[path to file.c]
231 should_route :get, "/projects/redmine/repository/annotate/path/to/file.c", :controller => 'repositories', :action => 'annotate', :id => 'redmine', :path => %w[path to file.c]
223 should_route :get, "/projects/redmine/repository/changes/path/to/file.c", :controller => 'repositories', :action => 'changes', :id => 'redmine', :path => %w[path to file.c]
232 should_route :get, "/projects/redmine/repository/changes/path/to/file.c", :controller => 'repositories', :action => 'changes', :id => 'redmine', :path => %w[path to file.c]
224 should_route :get, "/projects/redmine/repository/statistics", :controller => 'repositories', :action => 'stats', :id => 'redmine'
233 should_route :get, "/projects/redmine/repository/statistics", :controller => 'repositories', :action => 'stats', :id => 'redmine'
225
234
226
235
227 should_route :post, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
236 should_route :post, "/projects/redmine/repository/edit", :controller => 'repositories', :action => 'edit', :id => 'redmine'
228 end
237 end
229
238
230 context "timelogs (global)" do
239 context "timelogs (global)" do
231 should_route :get, "/time_entries", :controller => 'timelog', :action => 'index'
240 should_route :get, "/time_entries", :controller => 'timelog', :action => 'index'
232 should_route :get, "/time_entries.csv", :controller => 'timelog', :action => 'index', :format => 'csv'
241 should_route :get, "/time_entries.csv", :controller => 'timelog', :action => 'index', :format => 'csv'
233 should_route :get, "/time_entries.atom", :controller => 'timelog', :action => 'index', :format => 'atom'
242 should_route :get, "/time_entries.atom", :controller => 'timelog', :action => 'index', :format => 'atom'
234 should_route :get, "/time_entries/new", :controller => 'timelog', :action => 'new'
243 should_route :get, "/time_entries/new", :controller => 'timelog', :action => 'new'
235 should_route :get, "/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22'
244 should_route :get, "/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22'
236
245
237 should_route :post, "/time_entries", :controller => 'timelog', :action => 'create'
246 should_route :post, "/time_entries", :controller => 'timelog', :action => 'create'
238
247
239 should_route :put, "/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22'
248 should_route :put, "/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22'
240
249
241 should_route :delete, "/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55'
250 should_route :delete, "/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55'
242 end
251 end
243
252
244 context "timelogs (scoped under project)" do
253 context "timelogs (scoped under project)" do
245 should_route :get, "/projects/567/time_entries", :controller => 'timelog', :action => 'index', :project_id => '567'
254 should_route :get, "/projects/567/time_entries", :controller => 'timelog', :action => 'index', :project_id => '567'
246 should_route :get, "/projects/567/time_entries.csv", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'csv'
255 should_route :get, "/projects/567/time_entries.csv", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'csv'
247 should_route :get, "/projects/567/time_entries.atom", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'atom'
256 should_route :get, "/projects/567/time_entries.atom", :controller => 'timelog', :action => 'index', :project_id => '567', :format => 'atom'
248 should_route :get, "/projects/567/time_entries/new", :controller => 'timelog', :action => 'new', :project_id => '567'
257 should_route :get, "/projects/567/time_entries/new", :controller => 'timelog', :action => 'new', :project_id => '567'
249 should_route :get, "/projects/567/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :project_id => '567'
258 should_route :get, "/projects/567/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :project_id => '567'
250
259
251 should_route :post, "/projects/567/time_entries", :controller => 'timelog', :action => 'create', :project_id => '567'
260 should_route :post, "/projects/567/time_entries", :controller => 'timelog', :action => 'create', :project_id => '567'
252
261
253 should_route :put, "/projects/567/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :project_id => '567'
262 should_route :put, "/projects/567/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :project_id => '567'
254
263
255 should_route :delete, "/projects/567/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :project_id => '567'
264 should_route :delete, "/projects/567/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :project_id => '567'
256 end
265 end
257
266
258 context "timelogs (scoped under issues)" do
267 context "timelogs (scoped under issues)" do
259 should_route :get, "/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234'
268 should_route :get, "/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234'
260 should_route :get, "/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'csv'
269 should_route :get, "/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'csv'
261 should_route :get, "/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'atom'
270 should_route :get, "/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :format => 'atom'
262 should_route :get, "/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234'
271 should_route :get, "/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234'
263 should_route :get, "/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234'
272 should_route :get, "/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234'
264
273
265 should_route :post, "/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234'
274 should_route :post, "/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234'
266
275
267 should_route :put, "/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234'
276 should_route :put, "/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234'
268
277
269 should_route :delete, "/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234'
278 should_route :delete, "/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234'
270 end
279 end
271
280
272 context "timelogs (scoped under project and issues)" do
281 context "timelogs (scoped under project and issues)" do
273 should_route :get, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook'
282 should_route :get, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook'
274 should_route :get, "/projects/ecookbook/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'csv'
283 should_route :get, "/projects/ecookbook/issues/234/time_entries.csv", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'csv'
275 should_route :get, "/projects/ecookbook/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'atom'
284 should_route :get, "/projects/ecookbook/issues/234/time_entries.atom", :controller => 'timelog', :action => 'index', :issue_id => '234', :project_id => 'ecookbook', :format => 'atom'
276 should_route :get, "/projects/ecookbook/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234', :project_id => 'ecookbook'
285 should_route :get, "/projects/ecookbook/issues/234/time_entries/new", :controller => 'timelog', :action => 'new', :issue_id => '234', :project_id => 'ecookbook'
277 should_route :get, "/projects/ecookbook/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
286 should_route :get, "/projects/ecookbook/issues/234/time_entries/22/edit", :controller => 'timelog', :action => 'edit', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
278
287
279 should_route :post, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234', :project_id => 'ecookbook'
288 should_route :post, "/projects/ecookbook/issues/234/time_entries", :controller => 'timelog', :action => 'create', :issue_id => '234', :project_id => 'ecookbook'
280
289
281 should_route :put, "/projects/ecookbook/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
290 should_route :put, "/projects/ecookbook/issues/234/time_entries/22", :controller => 'timelog', :action => 'update', :id => '22', :issue_id => '234', :project_id => 'ecookbook'
282
291
283 should_route :delete, "/projects/ecookbook/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234', :project_id => 'ecookbook'
292 should_route :delete, "/projects/ecookbook/issues/234/time_entries/55", :controller => 'timelog', :action => 'destroy', :id => '55', :issue_id => '234', :project_id => 'ecookbook'
284 end
293 end
285
294
286 context "time_entry_reports" do
295 context "time_entry_reports" do
287 should_route :get, "/time_entries/report", :controller => 'time_entry_reports', :action => 'report'
296 should_route :get, "/time_entries/report", :controller => 'time_entry_reports', :action => 'report'
288 should_route :get, "/projects/567/time_entries/report", :controller => 'time_entry_reports', :action => 'report', :project_id => '567'
297 should_route :get, "/projects/567/time_entries/report", :controller => 'time_entry_reports', :action => 'report', :project_id => '567'
289 should_route :get, "/projects/567/time_entries/report.csv", :controller => 'time_entry_reports', :action => 'report', :project_id => '567', :format => 'csv'
298 should_route :get, "/projects/567/time_entries/report.csv", :controller => 'time_entry_reports', :action => 'report', :project_id => '567', :format => 'csv'
290 end
299 end
291
300
292 context "users" do
301 context "users" do
293 should_route :get, "/users", :controller => 'users', :action => 'index'
302 should_route :get, "/users", :controller => 'users', :action => 'index'
294 should_route :get, "/users.xml", :controller => 'users', :action => 'index', :format => 'xml'
303 should_route :get, "/users.xml", :controller => 'users', :action => 'index', :format => 'xml'
295 should_route :get, "/users/44", :controller => 'users', :action => 'show', :id => '44'
304 should_route :get, "/users/44", :controller => 'users', :action => 'show', :id => '44'
296 should_route :get, "/users/44.xml", :controller => 'users', :action => 'show', :id => '44', :format => 'xml'
305 should_route :get, "/users/44.xml", :controller => 'users', :action => 'show', :id => '44', :format => 'xml'
297 should_route :get, "/users/current", :controller => 'users', :action => 'show', :id => 'current'
306 should_route :get, "/users/current", :controller => 'users', :action => 'show', :id => 'current'
298 should_route :get, "/users/current.xml", :controller => 'users', :action => 'show', :id => 'current', :format => 'xml'
307 should_route :get, "/users/current.xml", :controller => 'users', :action => 'show', :id => 'current', :format => 'xml'
299 should_route :get, "/users/new", :controller => 'users', :action => 'new'
308 should_route :get, "/users/new", :controller => 'users', :action => 'new'
300 should_route :get, "/users/444/edit", :controller => 'users', :action => 'edit', :id => '444'
309 should_route :get, "/users/444/edit", :controller => 'users', :action => 'edit', :id => '444'
301 should_route :get, "/users/222/edit/membership", :controller => 'users', :action => 'edit', :id => '222', :tab => 'membership'
310 should_route :get, "/users/222/edit/membership", :controller => 'users', :action => 'edit', :id => '222', :tab => 'membership'
302
311
303 should_route :post, "/users", :controller => 'users', :action => 'create'
312 should_route :post, "/users", :controller => 'users', :action => 'create'
304 should_route :post, "/users.xml", :controller => 'users', :action => 'create', :format => 'xml'
313 should_route :post, "/users.xml", :controller => 'users', :action => 'create', :format => 'xml'
305 should_route :post, "/users/123/memberships", :controller => 'users', :action => 'edit_membership', :id => '123'
314 should_route :post, "/users/123/memberships", :controller => 'users', :action => 'edit_membership', :id => '123'
306 should_route :post, "/users/123/memberships/55", :controller => 'users', :action => 'edit_membership', :id => '123', :membership_id => '55'
315 should_route :post, "/users/123/memberships/55", :controller => 'users', :action => 'edit_membership', :id => '123', :membership_id => '55'
307 should_route :post, "/users/567/memberships/12/destroy", :controller => 'users', :action => 'destroy_membership', :id => '567', :membership_id => '12'
316 should_route :post, "/users/567/memberships/12/destroy", :controller => 'users', :action => 'destroy_membership', :id => '567', :membership_id => '12'
308
317
309 should_route :put, "/users/444", :controller => 'users', :action => 'update', :id => '444'
318 should_route :put, "/users/444", :controller => 'users', :action => 'update', :id => '444'
310 should_route :put, "/users/444.xml", :controller => 'users', :action => 'update', :id => '444', :format => 'xml'
319 should_route :put, "/users/444.xml", :controller => 'users', :action => 'update', :id => '444', :format => 'xml'
311
320
312 should_route :delete, "/users/44", :controller => 'users', :action => 'destroy', :id => '44'
321 should_route :delete, "/users/44", :controller => 'users', :action => 'destroy', :id => '44'
313 should_route :delete, "/users/44.xml", :controller => 'users', :action => 'destroy', :id => '44', :format => 'xml'
322 should_route :delete, "/users/44.xml", :controller => 'users', :action => 'destroy', :id => '44', :format => 'xml'
314 end
323 end
315
324
316 # TODO: should they all be scoped under /projects/:project_id ?
325 # TODO: should they all be scoped under /projects/:project_id ?
317 context "versions" do
326 context "versions" do
318 should_route :get, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo'
327 should_route :get, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo'
319 should_route :get, "/versions/show/1", :controller => 'versions', :action => 'show', :id => '1'
328 should_route :get, "/versions/show/1", :controller => 'versions', :action => 'show', :id => '1'
320 should_route :get, "/versions/edit/1", :controller => 'versions', :action => 'edit', :id => '1'
329 should_route :get, "/versions/edit/1", :controller => 'versions', :action => 'edit', :id => '1'
321
330
322 should_route :post, "/projects/foo/versions", :controller => 'versions', :action => 'create', :project_id => 'foo'
331 should_route :post, "/projects/foo/versions", :controller => 'versions', :action => 'create', :project_id => 'foo'
323 should_route :post, "/versions/update/1", :controller => 'versions', :action => 'update', :id => '1'
332 should_route :post, "/versions/update/1", :controller => 'versions', :action => 'update', :id => '1'
324
333
325 should_route :delete, "/versions/destroy/1", :controller => 'versions', :action => 'destroy', :id => '1'
334 should_route :delete, "/versions/destroy/1", :controller => 'versions', :action => 'destroy', :id => '1'
326 end
335 end
327
336
328 context "wiki (singular, project's pages)" do
337 context "wiki (singular, project's pages)" do
329 should_route :get, "/projects/567/wiki", :controller => 'wiki', :action => 'show', :project_id => '567'
338 should_route :get, "/projects/567/wiki", :controller => 'wiki', :action => 'show', :project_id => '567'
330 should_route :get, "/projects/567/wiki/lalala", :controller => 'wiki', :action => 'show', :project_id => '567', :id => 'lalala'
339 should_route :get, "/projects/567/wiki/lalala", :controller => 'wiki', :action => 'show', :project_id => '567', :id => 'lalala'
331 should_route :get, "/projects/567/wiki/my_page/edit", :controller => 'wiki', :action => 'edit', :project_id => '567', :id => 'my_page'
340 should_route :get, "/projects/567/wiki/my_page/edit", :controller => 'wiki', :action => 'edit', :project_id => '567', :id => 'my_page'
332 should_route :get, "/projects/1/wiki/CookBook_documentation/history", :controller => 'wiki', :action => 'history', :project_id => '1', :id => 'CookBook_documentation'
341 should_route :get, "/projects/1/wiki/CookBook_documentation/history", :controller => 'wiki', :action => 'history', :project_id => '1', :id => 'CookBook_documentation'
333 should_route :get, "/projects/1/wiki/CookBook_documentation/diff", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation'
342 should_route :get, "/projects/1/wiki/CookBook_documentation/diff", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation'
334 should_route :get, "/projects/1/wiki/CookBook_documentation/diff/2", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
343 should_route :get, "/projects/1/wiki/CookBook_documentation/diff/2", :controller => 'wiki', :action => 'diff', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
335 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'
344 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'
336 should_route :get, "/projects/1/wiki/CookBook_documentation/annotate/2", :controller => 'wiki', :action => 'annotate', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
345 should_route :get, "/projects/1/wiki/CookBook_documentation/annotate/2", :controller => 'wiki', :action => 'annotate', :project_id => '1', :id => 'CookBook_documentation', :version => '2'
337 should_route :get, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
346 should_route :get, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
338 should_route :get, "/projects/567/wiki/index", :controller => 'wiki', :action => 'index', :project_id => '567'
347 should_route :get, "/projects/567/wiki/index", :controller => 'wiki', :action => 'index', :project_id => '567'
339 should_route :get, "/projects/567/wiki/date_index", :controller => 'wiki', :action => 'date_index', :project_id => '567'
348 should_route :get, "/projects/567/wiki/date_index", :controller => 'wiki', :action => 'date_index', :project_id => '567'
340 should_route :get, "/projects/567/wiki/export", :controller => 'wiki', :action => 'export', :project_id => '567'
349 should_route :get, "/projects/567/wiki/export", :controller => 'wiki', :action => 'export', :project_id => '567'
341
350
342 should_route :post, "/projects/567/wiki/CookBook_documentation/preview", :controller => 'wiki', :action => 'preview', :project_id => '567', :id => 'CookBook_documentation'
351 should_route :post, "/projects/567/wiki/CookBook_documentation/preview", :controller => 'wiki', :action => 'preview', :project_id => '567', :id => 'CookBook_documentation'
343 should_route :post, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
352 should_route :post, "/projects/22/wiki/ladida/rename", :controller => 'wiki', :action => 'rename', :project_id => '22', :id => 'ladida'
344 should_route :post, "/projects/22/wiki/ladida/protect", :controller => 'wiki', :action => 'protect', :project_id => '22', :id => 'ladida'
353 should_route :post, "/projects/22/wiki/ladida/protect", :controller => 'wiki', :action => 'protect', :project_id => '22', :id => 'ladida'
345 should_route :post, "/projects/22/wiki/ladida/add_attachment", :controller => 'wiki', :action => 'add_attachment', :project_id => '22', :id => 'ladida'
354 should_route :post, "/projects/22/wiki/ladida/add_attachment", :controller => 'wiki', :action => 'add_attachment', :project_id => '22', :id => 'ladida'
346
355
347 should_route :put, "/projects/567/wiki/my_page", :controller => 'wiki', :action => 'update', :project_id => '567', :id => 'my_page'
356 should_route :put, "/projects/567/wiki/my_page", :controller => 'wiki', :action => 'update', :project_id => '567', :id => 'my_page'
348
357
349 should_route :delete, "/projects/22/wiki/ladida", :controller => 'wiki', :action => 'destroy', :project_id => '22', :id => 'ladida'
358 should_route :delete, "/projects/22/wiki/ladida", :controller => 'wiki', :action => 'destroy', :project_id => '22', :id => 'ladida'
350 end
359 end
351
360
352 context "wikis (plural, admin setup)" do
361 context "wikis (plural, admin setup)" do
353 should_route :get, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
362 should_route :get, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
354
363
355 should_route :post, "/projects/ladida/wiki", :controller => 'wikis', :action => 'edit', :id => 'ladida'
364 should_route :post, "/projects/ladida/wiki", :controller => 'wikis', :action => 'edit', :id => 'ladida'
356 should_route :post, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
365 should_route :post, "/projects/ladida/wiki/destroy", :controller => 'wikis', :action => 'destroy', :id => 'ladida'
357 end
366 end
358
367
359 context "administration panel" do
368 context "administration panel" do
360 should_route :get, "/admin/projects", :controller => 'admin', :action => 'projects'
369 should_route :get, "/admin/projects", :controller => 'admin', :action => 'projects'
361 end
370 end
362 end
371 end
General Comments 0
You need to be logged in to leave comments. Login now