##// END OF EJS Templates
Converts IssuesController to use the new API template system and makes xml/json responses consistent (#6136)....
Jean-Philippe Lang -
r4344:735a83c59615
parent child
Show More
@@ -0,0 +1,32
1 api.array :issues do
2 @issues.each do |issue|
3 api.issue do
4 api.id issue.id
5 api.project(:id => issue.project_id, :name => issue.project.name) unless issue.project.nil?
6 api.tracker(:id => issue.tracker_id, :name => issue.tracker.name) unless issue.tracker.nil?
7 api.status(:id => issue.status_id, :name => issue.status.name) unless issue.status.nil?
8 api.priority(:id => issue.priority_id, :name => issue.priority.name) unless issue.priority.nil?
9 api.author(:id => issue.author_id, :name => issue.author.name) unless issue.author.nil?
10 api.assigned_to(:id => issue.assigned_to_id, :name => issue.assigned_to.name) unless issue.assigned_to.nil?
11 api.category(:id => issue.category_id, :name => issue.category.name) unless issue.category.nil?
12 api.fixed_version(:id => issue.fixed_version_id, :name => issue.fixed_version.name) unless issue.fixed_version.nil?
13 api.parent(:id => issue.parent_id) unless issue.parent.nil?
14
15 api.subject issue.subject
16 api.description issue.description
17 api.start_date issue.start_date
18 api.due_date issue.due_date
19 api.done_ratio issue.done_ratio
20 api.estimated_hours issue.estimated_hours
21
22 api.array :custom_fields do
23 issue.custom_field_values.each do |custom_value|
24 api.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
25 end
26 end
27
28 api.created_on issue.created_on
29 api.updated_on issue.updated_on
30 end
31 end
32 end
@@ -0,0 +1,61
1 api.issue do
2 api.id @issue.id
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?
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?
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?
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?
11 api.parent(:id => @issue.parent_id) unless @issue.parent.nil?
12
13 api.subject @issue.subject
14 api.description @issue.description
15 api.start_date @issue.start_date
16 api.due_date @issue.due_date
17 api.done_ratio @issue.done_ratio
18 api.estimated_hours @issue.estimated_hours
19 if User.current.allowed_to?(:view_time_entries, @project)
20 api.spent_hours @issue.spent_hours
21 end
22
23 api.array :custom_fields do
24 @issue.custom_field_values.each do |custom_value|
25 api.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
26 end
27 end unless @issue.custom_field_values.empty?
28
29 api.created_on @issue.created_on
30 api.updated_on @issue.updated_on
31
32 api.array :relations do
33 @issue.relations.select {|r| r.other_issue(@issue).visible? }.each do |relation|
34 api.relation(:id => relation.id, :issue_id => relation.other_issue(@issue).id, :relation_type => relation.relation_type_for(@issue), :delay => relation.delay)
35 end
36 end
37
38 api.array :changesets do
39 @issue.changesets.each do |changeset|
40 api.changeset :revision => changeset.revision do
41 api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil?
42 api.comments changeset.comments
43 api.committed_on changeset.committed_on
44 end
45 end
46 end if User.current.allowed_to?(:view_changesets, @project) && @issue.changesets.any?
47
48 api.array :journals do
49 @issue.journals.each do |journal|
50 api.journal :id => journal.id do
51 api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
52 api.notes journal.notes
53 api.array :details do
54 journal.details.each do |detail|
55 api.detail :property => detail.property, :name => detail.prop_key, :old => detail.old_value, :new => detail.value
56 end
57 end
58 end
59 end
60 end unless @issue.journals.empty?
61 end
@@ -1,322 +1,313
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
25 before_filter :find_project, :only => [:new, :create]
25 before_filter :find_project, :only => [:new, :create]
26 before_filter :authorize, :except => [:index]
26 before_filter :authorize, :except => [:index]
27 before_filter :find_optional_project, :only => [:index]
27 before_filter :find_optional_project, :only => [:index]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
30 accept_key_auth :index, :show, :create, :update, :destroy
30 accept_key_auth :index, :show, :create, :update, :destroy
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :sort
47 helper :sort
48 include SortHelper
48 include SortHelper
49 include IssuesHelper
49 include IssuesHelper
50 helper :timelog
50 helper :timelog
51 helper :gantt
51 helper :gantt
52 include Redmine::Export::PDF
52 include Redmine::Export::PDF
53
53
54 verify :method => [:post, :delete],
54 verify :method => [:post, :delete],
55 :only => :destroy,
55 :only => :destroy,
56 :render => { :nothing => true, :status => :method_not_allowed }
56 :render => { :nothing => true, :status => :method_not_allowed }
57
57
58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
61
61
62 def index
62 def index
63 retrieve_query
63 retrieve_query
64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
65 sort_update(@query.sortable_columns)
65 sort_update(@query.sortable_columns)
66
66
67 if @query.valid?
67 if @query.valid?
68 limit = case params[:format]
68 limit = case params[:format]
69 when 'csv', 'pdf'
69 when 'csv', 'pdf'
70 Setting.issues_export_limit.to_i
70 Setting.issues_export_limit.to_i
71 when 'atom'
71 when 'atom'
72 Setting.feeds_limit.to_i
72 Setting.feeds_limit.to_i
73 else
73 else
74 per_page_option
74 per_page_option
75 end
75 end
76
76
77 @issue_count = @query.issue_count
77 @issue_count = @query.issue_count
78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
80 :order => sort_clause,
80 :order => sort_clause,
81 :offset => @issue_pages.current.offset,
81 :offset => @issue_pages.current.offset,
82 :limit => limit)
82 :limit => limit)
83 @issue_count_by_group = @query.issue_count_by_group
83 @issue_count_by_group = @query.issue_count_by_group
84
84
85 respond_to do |format|
85 respond_to do |format|
86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
87 format.xml { render :layout => false }
87 format.api { render :template => 'issues/index.apit' }
88 format.json { render :text => @issues.to_json, :layout => false }
89 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
88 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
90 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
89 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
91 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
90 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
92 end
91 end
93 else
92 else
94 # Send html if the query is not valid
93 # Send html if the query is not valid
95 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
94 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
96 end
95 end
97 rescue ActiveRecord::RecordNotFound
96 rescue ActiveRecord::RecordNotFound
98 render_404
97 render_404
99 end
98 end
100
99
101 def show
100 def show
102 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
101 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
103 @journals.each_with_index {|j,i| j.indice = i+1}
102 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
103 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105 @changesets = @issue.changesets.visible.all
104 @changesets = @issue.changesets.visible.all
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
105 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
107 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
106 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
108 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
107 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
109 @priorities = IssuePriority.all
108 @priorities = IssuePriority.all
110 @time_entry = TimeEntry.new
109 @time_entry = TimeEntry.new
111 respond_to do |format|
110 respond_to do |format|
112 format.html { render :template => 'issues/show.rhtml' }
111 format.html { render :template => 'issues/show.rhtml' }
113 format.xml { render :layout => false }
112 format.api { render :template => 'issues/show.apit' }
114 format.json { render :text => @issue.to_json, :layout => false }
115 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
113 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
116 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
114 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
117 end
115 end
118 end
116 end
119
117
120 # Add a new issue
118 # Add a new issue
121 # The new issue will be created from an existing one if copy_from parameter is given
119 # The new issue will be created from an existing one if copy_from parameter is given
122 def new
120 def new
123 respond_to do |format|
121 respond_to do |format|
124 format.html { render :action => 'new', :layout => !request.xhr? }
122 format.html { render :action => 'new', :layout => !request.xhr? }
125 format.js { render :partial => 'attributes' }
123 format.js { render :partial => 'attributes' }
126 end
124 end
127 end
125 end
128
126
129 def create
127 def create
130 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
128 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
131 if @issue.save
129 if @issue.save
132 attachments = Attachment.attach_files(@issue, params[:attachments])
130 attachments = Attachment.attach_files(@issue, params[:attachments])
133 render_attachment_warning_if_needed(@issue)
131 render_attachment_warning_if_needed(@issue)
134 flash[:notice] = l(:notice_successful_create)
132 flash[:notice] = l(:notice_successful_create)
135 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
133 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
136 respond_to do |format|
134 respond_to do |format|
137 format.html {
135 format.html {
138 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
136 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
139 { :action => 'show', :id => @issue })
137 { :action => 'show', :id => @issue })
140 }
138 }
141 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
139 format.api { render :template => 'issues/show.apit', :status => :created, :location => issue_url(@issue) }
142 format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false }
143 end
140 end
144 return
141 return
145 else
142 else
146 respond_to do |format|
143 respond_to do |format|
147 format.html { render :action => 'new' }
144 format.html { render :action => 'new' }
148 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
145 format.api { render_validation_errors(@issue) }
149 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
150 end
146 end
151 end
147 end
152 end
148 end
153
149
154 def edit
150 def edit
155 update_issue_from_params
151 update_issue_from_params
156
152
157 @journal = @issue.current_journal
153 @journal = @issue.current_journal
158
154
159 respond_to do |format|
155 respond_to do |format|
160 format.html { }
156 format.html { }
161 format.xml { }
157 format.xml { }
162 end
158 end
163 end
159 end
164
160
165 def update
161 def update
166 update_issue_from_params
162 update_issue_from_params
167
163
168 if @issue.save_issue_with_child_records(params, @time_entry)
164 if @issue.save_issue_with_child_records(params, @time_entry)
169 render_attachment_warning_if_needed(@issue)
165 render_attachment_warning_if_needed(@issue)
170 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
166 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
171
167
172 respond_to do |format|
168 respond_to do |format|
173 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
169 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
174 format.xml { head :ok }
170 format.api { head :ok }
175 format.json { head :ok }
176 end
171 end
177 else
172 else
178 render_attachment_warning_if_needed(@issue)
173 render_attachment_warning_if_needed(@issue)
179 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
174 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
180 @journal = @issue.current_journal
175 @journal = @issue.current_journal
181
176
182 respond_to do |format|
177 respond_to do |format|
183 format.html { render :action => 'edit' }
178 format.html { render :action => 'edit' }
184 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
179 format.api { render_validation_errors(@issue) }
185 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
186 end
180 end
187 end
181 end
188 end
182 end
189
183
190 # Bulk edit a set of issues
184 # Bulk edit a set of issues
191 def bulk_edit
185 def bulk_edit
192 @issues.sort!
186 @issues.sort!
193 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
187 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
194 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
188 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
195 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
189 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
196 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
190 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
197 end
191 end
198
192
199 def bulk_update
193 def bulk_update
200 @issues.sort!
194 @issues.sort!
201 attributes = parse_params_for_bulk_issue_attributes(params)
195 attributes = parse_params_for_bulk_issue_attributes(params)
202
196
203 unsaved_issue_ids = []
197 unsaved_issue_ids = []
204 @issues.each do |issue|
198 @issues.each do |issue|
205 issue.reload
199 issue.reload
206 journal = issue.init_journal(User.current, params[:notes])
200 journal = issue.init_journal(User.current, params[:notes])
207 issue.safe_attributes = attributes
201 issue.safe_attributes = attributes
208 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
202 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
209 unless issue.save
203 unless issue.save
210 # Keep unsaved issue ids to display them in flash error
204 # Keep unsaved issue ids to display them in flash error
211 unsaved_issue_ids << issue.id
205 unsaved_issue_ids << issue.id
212 end
206 end
213 end
207 end
214 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
208 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
215 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
209 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
216 end
210 end
217
211
218 def destroy
212 def destroy
219 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
213 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
220 if @hours > 0
214 if @hours > 0
221 case params[:todo]
215 case params[:todo]
222 when 'destroy'
216 when 'destroy'
223 # nothing to do
217 # nothing to do
224 when 'nullify'
218 when 'nullify'
225 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
219 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
226 when 'reassign'
220 when 'reassign'
227 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
221 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
228 if reassign_to.nil?
222 if reassign_to.nil?
229 flash.now[:error] = l(:error_issue_not_found_in_project)
223 flash.now[:error] = l(:error_issue_not_found_in_project)
230 return
224 return
231 else
225 else
232 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
226 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
233 end
227 end
234 else
228 else
235 unless params[:format] == 'xml' || params[:format] == 'json'
229 # display the destroy form if it's a user request
236 # display the destroy form if it's a user request
230 return unless api_request?
237 return
238 end
239 end
231 end
240 end
232 end
241 @issues.each(&:destroy)
233 @issues.each(&:destroy)
242 respond_to do |format|
234 respond_to do |format|
243 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
235 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
244 format.xml { head :ok }
236 format.api { head :ok }
245 format.json { head :ok }
246 end
237 end
247 end
238 end
248
239
249 private
240 private
250 def find_issue
241 def find_issue
251 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
242 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
252 @project = @issue.project
243 @project = @issue.project
253 rescue ActiveRecord::RecordNotFound
244 rescue ActiveRecord::RecordNotFound
254 render_404
245 render_404
255 end
246 end
256
247
257 def find_project
248 def find_project
258 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
249 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
259 @project = Project.find(project_id)
250 @project = Project.find(project_id)
260 rescue ActiveRecord::RecordNotFound
251 rescue ActiveRecord::RecordNotFound
261 render_404
252 render_404
262 end
253 end
263
254
264 # Used by #edit and #update to set some common instance variables
255 # Used by #edit and #update to set some common instance variables
265 # from the params
256 # from the params
266 # TODO: Refactor, not everything in here is needed by #edit
257 # TODO: Refactor, not everything in here is needed by #edit
267 def update_issue_from_params
258 def update_issue_from_params
268 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
259 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
269 @priorities = IssuePriority.all
260 @priorities = IssuePriority.all
270 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
261 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
271 @time_entry = TimeEntry.new
262 @time_entry = TimeEntry.new
272 @time_entry.attributes = params[:time_entry]
263 @time_entry.attributes = params[:time_entry]
273
264
274 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
265 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
275 @issue.init_journal(User.current, @notes)
266 @issue.init_journal(User.current, @notes)
276 @issue.safe_attributes = params[:issue]
267 @issue.safe_attributes = params[:issue]
277 end
268 end
278
269
279 # TODO: Refactor, lots of extra code in here
270 # TODO: Refactor, lots of extra code in here
280 # TODO: Changing tracker on an existing issue should not trigger this
271 # TODO: Changing tracker on an existing issue should not trigger this
281 def build_new_issue_from_params
272 def build_new_issue_from_params
282 if params[:id].blank?
273 if params[:id].blank?
283 @issue = Issue.new
274 @issue = Issue.new
284 @issue.copy_from(params[:copy_from]) if params[:copy_from]
275 @issue.copy_from(params[:copy_from]) if params[:copy_from]
285 @issue.project = @project
276 @issue.project = @project
286 else
277 else
287 @issue = @project.issues.visible.find(params[:id])
278 @issue = @project.issues.visible.find(params[:id])
288 end
279 end
289
280
290 @issue.project = @project
281 @issue.project = @project
291 # Tracker must be set before custom field values
282 # Tracker must be set before custom field values
292 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
283 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
293 if @issue.tracker.nil?
284 if @issue.tracker.nil?
294 render_error l(:error_no_tracker_in_project)
285 render_error l(:error_no_tracker_in_project)
295 return false
286 return false
296 end
287 end
297 @issue.start_date ||= Date.today
288 @issue.start_date ||= Date.today
298 if params[:issue].is_a?(Hash)
289 if params[:issue].is_a?(Hash)
299 @issue.safe_attributes = params[:issue]
290 @issue.safe_attributes = params[:issue]
300 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
291 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
301 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
292 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
302 end
293 end
303 end
294 end
304 @issue.author = User.current
295 @issue.author = User.current
305 @priorities = IssuePriority.all
296 @priorities = IssuePriority.all
306 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
297 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
307 end
298 end
308
299
309 def check_for_default_issue_status
300 def check_for_default_issue_status
310 if IssueStatus.default.nil?
301 if IssueStatus.default.nil?
311 render_error l(:error_no_default_issue_status)
302 render_error l(:error_no_default_issue_status)
312 return false
303 return false
313 end
304 end
314 end
305 end
315
306
316 def parse_params_for_bulk_issue_attributes(params)
307 def parse_params_for_bulk_issue_attributes(params)
317 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
308 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
318 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
309 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
319 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
310 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
320 attributes
311 attributes
321 end
312 end
322 end
313 end
@@ -1,68 +1,70
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 'blankslate'
18 require 'blankslate'
19
19
20 module Redmine
20 module Redmine
21 module Views
21 module Views
22 module Builders
22 module Builders
23 class Structure < BlankSlate
23 class Structure < BlankSlate
24 def initialize
24 def initialize
25 @struct = [{}]
25 @struct = [{}]
26 end
26 end
27
27
28 def array(tag, &block)
28 def array(tag, &block)
29 @struct << []
29 @struct << []
30 block.call(self)
30 block.call(self)
31 ret = @struct.pop
31 ret = @struct.pop
32 @struct.last[tag] = ret
32 @struct.last[tag] = ret
33 end
33 end
34
34
35 def method_missing(sym, *args, &block)
35 def method_missing(sym, *args, &block)
36 if args.any?
36 if args.any?
37 if args.first.is_a?(Hash)
37 if args.first.is_a?(Hash)
38 if @struct.last.is_a?(Array)
38 if @struct.last.is_a?(Array)
39 @struct.last << args.first
39 @struct.last << args.first
40 else
41 @struct.last[sym] = args.first
40 end
42 end
41 else
43 else
42 if @struct.last.is_a?(Array)
44 if @struct.last.is_a?(Array)
43 @struct.last << (args.last || {}).merge(:value => args.first)
45 @struct.last << (args.last || {}).merge(:value => args.first)
44 else
46 else
45 @struct.last[sym] = args.first
47 @struct.last[sym] = args.first
46 end
48 end
47 end
49 end
48 end
50 end
49
51
50 if block
52 if block
51 @struct << {}
53 @struct << {}
52 block.call(self)
54 block.call(self)
53 ret = @struct.pop
55 ret = @struct.pop
54 if @struct.last.is_a?(Array)
56 if @struct.last.is_a?(Array)
55 @struct.last << ret
57 @struct.last << ret
56 else
58 else
57 @struct.last[sym] = ret
59 @struct.last[sym] = ret
58 end
60 end
59 end
61 end
60 end
62 end
61
63
62 def output
64 def output
63 raise "Need to implement #{self.class.name}#output"
65 raise "Need to implement #{self.class.name}#output"
64 end
66 end
65 end
67 end
66 end
68 end
67 end
69 end
68 end
70 end
@@ -1,340 +1,340
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.dirname(__FILE__)}/../../test_helper"
18 require "#{File.dirname(__FILE__)}/../../test_helper"
19
19
20 class ApiTest::IssuesTest < ActionController::IntegrationTest
20 class ApiTest::IssuesTest < ActionController::IntegrationTest
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :versions,
28 :versions,
29 :trackers,
29 :trackers,
30 :projects_trackers,
30 :projects_trackers,
31 :issue_categories,
31 :issue_categories,
32 :enabled_modules,
32 :enabled_modules,
33 :enumerations,
33 :enumerations,
34 :attachments,
34 :attachments,
35 :workflows,
35 :workflows,
36 :custom_fields,
36 :custom_fields,
37 :custom_values,
37 :custom_values,
38 :custom_fields_projects,
38 :custom_fields_projects,
39 :custom_fields_trackers,
39 :custom_fields_trackers,
40 :time_entries,
40 :time_entries,
41 :journals,
41 :journals,
42 :journal_details,
42 :journal_details,
43 :queries
43 :queries
44
44
45 def setup
45 def setup
46 Setting.rest_api_enabled = '1'
46 Setting.rest_api_enabled = '1'
47 end
47 end
48
48
49 # Use a private project to make sure auth is really working and not just
49 # Use a private project to make sure auth is really working and not just
50 # only showing public issues.
50 # only showing public issues.
51 context "/index.xml" do
51 context "/index.xml" do
52 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
52 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
53 end
53 end
54
54
55 context "/index.json" do
55 context "/index.json" do
56 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
56 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
57 end
57 end
58
58
59 context "/index.xml with filter" do
59 context "/index.xml with filter" do
60 should_allow_api_authentication(:get, "/projects/private-child/issues.xml?status_id=5")
60 should_allow_api_authentication(:get, "/projects/private-child/issues.xml?status_id=5")
61
61
62 should "show only issues with the status_id" do
62 should "show only issues with the status_id" do
63 get '/issues.xml?status_id=5'
63 get '/issues.xml?status_id=5'
64 assert_tag :tag => 'issues',
64 assert_tag :tag => 'issues',
65 :children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
65 :children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
66 :only => { :tag => 'issue' } }
66 :only => { :tag => 'issue' } }
67 end
67 end
68 end
68 end
69
69
70 context "/index.json with filter" do
70 context "/index.json with filter" do
71 should_allow_api_authentication(:get, "/projects/private-child/issues.json?status_id=5")
71 should_allow_api_authentication(:get, "/projects/private-child/issues.json?status_id=5")
72
72
73 should "show only issues with the status_id" do
73 should "show only issues with the status_id" do
74 get '/issues.json?status_id=5'
74 get '/issues.json?status_id=5'
75
75
76 json = ActiveSupport::JSON.decode(response.body)
76 json = ActiveSupport::JSON.decode(response.body)
77 status_ids_used = json.collect {|j| j['status_id'] }
77 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
78 assert_equal 3, status_ids_used.length
78 assert_equal 3, status_ids_used.length
79 assert status_ids_used.all? {|id| id == 5 }
79 assert status_ids_used.all? {|id| id == 5 }
80 end
80 end
81
81
82 end
82 end
83
83
84 # Issue 6 is on a private project
84 # Issue 6 is on a private project
85 context "/issues/6.xml" do
85 context "/issues/6.xml" do
86 should_allow_api_authentication(:get, "/issues/6.xml")
86 should_allow_api_authentication(:get, "/issues/6.xml")
87 end
87 end
88
88
89 context "/issues/6.json" do
89 context "/issues/6.json" do
90 should_allow_api_authentication(:get, "/issues/6.json")
90 should_allow_api_authentication(:get, "/issues/6.json")
91 end
91 end
92
92
93 context "POST /issues.xml" do
93 context "POST /issues.xml" do
94 should_allow_api_authentication(:post,
94 should_allow_api_authentication(:post,
95 '/issues.xml',
95 '/issues.xml',
96 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
96 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
97 {:success_code => :created})
97 {:success_code => :created})
98
98
99 should "create an issue with the attributes" do
99 should "create an issue with the attributes" do
100 assert_difference('Issue.count') do
100 assert_difference('Issue.count') do
101 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
101 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
102 end
102 end
103
103
104 issue = Issue.first(:order => 'id DESC')
104 issue = Issue.first(:order => 'id DESC')
105 assert_equal 1, issue.project_id
105 assert_equal 1, issue.project_id
106 assert_equal 2, issue.tracker_id
106 assert_equal 2, issue.tracker_id
107 assert_equal 3, issue.status_id
107 assert_equal 3, issue.status_id
108 assert_equal 'API test', issue.subject
108 assert_equal 'API test', issue.subject
109
109
110 assert_response :created
110 assert_response :created
111 assert_equal 'application/xml', @response.content_type
111 assert_equal 'application/xml', @response.content_type
112 assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s}
112 assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s}
113 end
113 end
114 end
114 end
115
115
116 context "POST /issues.xml with failure" do
116 context "POST /issues.xml with failure" do
117 should_allow_api_authentication(:post,
117 should_allow_api_authentication(:post,
118 '/issues.xml',
118 '/issues.xml',
119 {:issue => {:project_id => 1}},
119 {:issue => {:project_id => 1}},
120 {:success_code => :unprocessable_entity})
120 {:success_code => :unprocessable_entity})
121
121
122 should "have an errors tag" do
122 should "have an errors tag" do
123 assert_no_difference('Issue.count') do
123 assert_no_difference('Issue.count') do
124 post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
124 post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
125 end
125 end
126
126
127 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
127 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
128 end
128 end
129 end
129 end
130
130
131 context "POST /issues.json" do
131 context "POST /issues.json" do
132 should_allow_api_authentication(:post,
132 should_allow_api_authentication(:post,
133 '/issues.json',
133 '/issues.json',
134 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
134 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
135 {:success_code => :created})
135 {:success_code => :created})
136
136
137 should "create an issue with the attributes" do
137 should "create an issue with the attributes" do
138 assert_difference('Issue.count') do
138 assert_difference('Issue.count') do
139 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
139 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
140 end
140 end
141
141
142 issue = Issue.first(:order => 'id DESC')
142 issue = Issue.first(:order => 'id DESC')
143 assert_equal 1, issue.project_id
143 assert_equal 1, issue.project_id
144 assert_equal 2, issue.tracker_id
144 assert_equal 2, issue.tracker_id
145 assert_equal 3, issue.status_id
145 assert_equal 3, issue.status_id
146 assert_equal 'API test', issue.subject
146 assert_equal 'API test', issue.subject
147 end
147 end
148
148
149 end
149 end
150
150
151 context "POST /issues.json with failure" do
151 context "POST /issues.json with failure" do
152 should_allow_api_authentication(:post,
152 should_allow_api_authentication(:post,
153 '/issues.json',
153 '/issues.json',
154 {:issue => {:project_id => 1}},
154 {:issue => {:project_id => 1}},
155 {:success_code => :unprocessable_entity})
155 {:success_code => :unprocessable_entity})
156
156
157 should "have an errors element" do
157 should "have an errors element" do
158 assert_no_difference('Issue.count') do
158 assert_no_difference('Issue.count') do
159 post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
159 post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
160 end
160 end
161
161
162 json = ActiveSupport::JSON.decode(response.body)
162 json = ActiveSupport::JSON.decode(response.body)
163 assert_equal "can't be blank", json.first['subject']
163 assert json['errors'].include?(['subject', "can't be blank"])
164 end
164 end
165 end
165 end
166
166
167 # Issue 6 is on a private project
167 # Issue 6 is on a private project
168 context "PUT /issues/6.xml" do
168 context "PUT /issues/6.xml" do
169 setup do
169 setup do
170 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
170 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
171 @headers = { :authorization => credentials('jsmith') }
171 @headers = { :authorization => credentials('jsmith') }
172 end
172 end
173
173
174 should_allow_api_authentication(:put,
174 should_allow_api_authentication(:put,
175 '/issues/6.xml',
175 '/issues/6.xml',
176 {:issue => {:subject => 'API update', :notes => 'A new note'}},
176 {:issue => {:subject => 'API update', :notes => 'A new note'}},
177 {:success_code => :ok})
177 {:success_code => :ok})
178
178
179 should "not create a new issue" do
179 should "not create a new issue" do
180 assert_no_difference('Issue.count') do
180 assert_no_difference('Issue.count') do
181 put '/issues/6.xml', @parameters, @headers
181 put '/issues/6.xml', @parameters, @headers
182 end
182 end
183 end
183 end
184
184
185 should "create a new journal" do
185 should "create a new journal" do
186 assert_difference('Journal.count') do
186 assert_difference('Journal.count') do
187 put '/issues/6.xml', @parameters, @headers
187 put '/issues/6.xml', @parameters, @headers
188 end
188 end
189 end
189 end
190
190
191 should "add the note to the journal" do
191 should "add the note to the journal" do
192 put '/issues/6.xml', @parameters, @headers
192 put '/issues/6.xml', @parameters, @headers
193
193
194 journal = Journal.last
194 journal = Journal.last
195 assert_equal "A new note", journal.notes
195 assert_equal "A new note", journal.notes
196 end
196 end
197
197
198 should "update the issue" do
198 should "update the issue" do
199 put '/issues/6.xml', @parameters, @headers
199 put '/issues/6.xml', @parameters, @headers
200
200
201 issue = Issue.find(6)
201 issue = Issue.find(6)
202 assert_equal "API update", issue.subject
202 assert_equal "API update", issue.subject
203 end
203 end
204
204
205 end
205 end
206
206
207 context "PUT /issues/6.xml with failed update" do
207 context "PUT /issues/6.xml with failed update" do
208 setup do
208 setup do
209 @parameters = {:issue => {:subject => ''}}
209 @parameters = {:issue => {:subject => ''}}
210 @headers = { :authorization => credentials('jsmith') }
210 @headers = { :authorization => credentials('jsmith') }
211 end
211 end
212
212
213 should_allow_api_authentication(:put,
213 should_allow_api_authentication(:put,
214 '/issues/6.xml',
214 '/issues/6.xml',
215 {:issue => {:subject => ''}}, # Missing subject should fail
215 {:issue => {:subject => ''}}, # Missing subject should fail
216 {:success_code => :unprocessable_entity})
216 {:success_code => :unprocessable_entity})
217
217
218 should "not create a new issue" do
218 should "not create a new issue" do
219 assert_no_difference('Issue.count') do
219 assert_no_difference('Issue.count') do
220 put '/issues/6.xml', @parameters, @headers
220 put '/issues/6.xml', @parameters, @headers
221 end
221 end
222 end
222 end
223
223
224 should "not create a new journal" do
224 should "not create a new journal" do
225 assert_no_difference('Journal.count') do
225 assert_no_difference('Journal.count') do
226 put '/issues/6.xml', @parameters, @headers
226 put '/issues/6.xml', @parameters, @headers
227 end
227 end
228 end
228 end
229
229
230 should "have an errors tag" do
230 should "have an errors tag" do
231 put '/issues/6.xml', @parameters, @headers
231 put '/issues/6.xml', @parameters, @headers
232
232
233 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
233 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
234 end
234 end
235 end
235 end
236
236
237 context "PUT /issues/6.json" do
237 context "PUT /issues/6.json" do
238 setup do
238 setup do
239 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
239 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
240 @headers = { :authorization => credentials('jsmith') }
240 @headers = { :authorization => credentials('jsmith') }
241 end
241 end
242
242
243 should_allow_api_authentication(:put,
243 should_allow_api_authentication(:put,
244 '/issues/6.json',
244 '/issues/6.json',
245 {:issue => {:subject => 'API update', :notes => 'A new note'}},
245 {:issue => {:subject => 'API update', :notes => 'A new note'}},
246 {:success_code => :ok})
246 {:success_code => :ok})
247
247
248 should "not create a new issue" do
248 should "not create a new issue" do
249 assert_no_difference('Issue.count') do
249 assert_no_difference('Issue.count') do
250 put '/issues/6.json', @parameters, @headers
250 put '/issues/6.json', @parameters, @headers
251 end
251 end
252 end
252 end
253
253
254 should "create a new journal" do
254 should "create a new journal" do
255 assert_difference('Journal.count') do
255 assert_difference('Journal.count') do
256 put '/issues/6.json', @parameters, @headers
256 put '/issues/6.json', @parameters, @headers
257 end
257 end
258 end
258 end
259
259
260 should "add the note to the journal" do
260 should "add the note to the journal" do
261 put '/issues/6.json', @parameters, @headers
261 put '/issues/6.json', @parameters, @headers
262
262
263 journal = Journal.last
263 journal = Journal.last
264 assert_equal "A new note", journal.notes
264 assert_equal "A new note", journal.notes
265 end
265 end
266
266
267 should "update the issue" do
267 should "update the issue" do
268 put '/issues/6.json', @parameters, @headers
268 put '/issues/6.json', @parameters, @headers
269
269
270 issue = Issue.find(6)
270 issue = Issue.find(6)
271 assert_equal "API update", issue.subject
271 assert_equal "API update", issue.subject
272 end
272 end
273
273
274 end
274 end
275
275
276 context "PUT /issues/6.json with failed update" do
276 context "PUT /issues/6.json with failed update" do
277 setup do
277 setup do
278 @parameters = {:issue => {:subject => ''}}
278 @parameters = {:issue => {:subject => ''}}
279 @headers = { :authorization => credentials('jsmith') }
279 @headers = { :authorization => credentials('jsmith') }
280 end
280 end
281
281
282 should_allow_api_authentication(:put,
282 should_allow_api_authentication(:put,
283 '/issues/6.json',
283 '/issues/6.json',
284 {:issue => {:subject => ''}}, # Missing subject should fail
284 {:issue => {:subject => ''}}, # Missing subject should fail
285 {:success_code => :unprocessable_entity})
285 {:success_code => :unprocessable_entity})
286
286
287 should "not create a new issue" do
287 should "not create a new issue" do
288 assert_no_difference('Issue.count') do
288 assert_no_difference('Issue.count') do
289 put '/issues/6.json', @parameters, @headers
289 put '/issues/6.json', @parameters, @headers
290 end
290 end
291 end
291 end
292
292
293 should "not create a new journal" do
293 should "not create a new journal" do
294 assert_no_difference('Journal.count') do
294 assert_no_difference('Journal.count') do
295 put '/issues/6.json', @parameters, @headers
295 put '/issues/6.json', @parameters, @headers
296 end
296 end
297 end
297 end
298
298
299 should "have an errors attribute" do
299 should "have an errors attribute" do
300 put '/issues/6.json', @parameters, @headers
300 put '/issues/6.json', @parameters, @headers
301
301
302 json = ActiveSupport::JSON.decode(response.body)
302 json = ActiveSupport::JSON.decode(response.body)
303 assert_equal "can't be blank", json.first['subject']
303 assert json['errors'].include?(['subject', "can't be blank"])
304 end
304 end
305 end
305 end
306
306
307 context "DELETE /issues/1.xml" do
307 context "DELETE /issues/1.xml" do
308 should_allow_api_authentication(:delete,
308 should_allow_api_authentication(:delete,
309 '/issues/6.xml',
309 '/issues/6.xml',
310 {},
310 {},
311 {:success_code => :ok})
311 {:success_code => :ok})
312
312
313 should "delete the issue" do
313 should "delete the issue" do
314 assert_difference('Issue.count',-1) do
314 assert_difference('Issue.count',-1) do
315 delete '/issues/6.xml', {}, :authorization => credentials('jsmith')
315 delete '/issues/6.xml', {}, :authorization => credentials('jsmith')
316 end
316 end
317
317
318 assert_nil Issue.find_by_id(6)
318 assert_nil Issue.find_by_id(6)
319 end
319 end
320 end
320 end
321
321
322 context "DELETE /issues/1.json" do
322 context "DELETE /issues/1.json" do
323 should_allow_api_authentication(:delete,
323 should_allow_api_authentication(:delete,
324 '/issues/6.json',
324 '/issues/6.json',
325 {},
325 {},
326 {:success_code => :ok})
326 {:success_code => :ok})
327
327
328 should "delete the issue" do
328 should "delete the issue" do
329 assert_difference('Issue.count',-1) do
329 assert_difference('Issue.count',-1) do
330 delete '/issues/6.json', {}, :authorization => credentials('jsmith')
330 delete '/issues/6.json', {}, :authorization => credentials('jsmith')
331 end
331 end
332
332
333 assert_nil Issue.find_by_id(6)
333 assert_nil Issue.find_by_id(6)
334 end
334 end
335 end
335 end
336
336
337 def credentials(user, password=nil)
337 def credentials(user, password=nil)
338 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
338 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
339 end
339 end
340 end
340 end
@@ -1,54 +1,63
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.dirname(__FILE__) + '/../../../../../test_helper'
18 require File.dirname(__FILE__) + '/../../../../../test_helper'
19
19
20 class Redmine::Views::Builders::JsonTest < HelperTestCase
20 class Redmine::Views::Builders::JsonTest < HelperTestCase
21
21
22 def test_hash
22 def test_hash
23 assert_json_output({'person' => {'name' => 'Ryan', 'age' => 32}}) do |b|
23 assert_json_output({'person' => {'name' => 'Ryan', 'age' => 32}}) do |b|
24 b.person do
24 b.person do
25 b.name 'Ryan'
25 b.name 'Ryan'
26 b.age 32
26 b.age 32
27 end
27 end
28 end
28 end
29 end
29 end
30
30
31 def test_hash_hash
32 assert_json_output({'person' => {'name' => 'Ryan', 'birth' => {'city' => 'London', 'country' => 'UK'}}}) do |b|
33 b.person do
34 b.name 'Ryan'
35 b.birth :city => 'London', :country => 'UK'
36 end
37 end
38 end
39
31 def test_array
40 def test_array
32 assert_json_output({'books' => [{'title' => 'Book 1', 'author' => 'B. Smith'}, {'title' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b|
41 assert_json_output({'books' => [{'title' => 'Book 1', 'author' => 'B. Smith'}, {'title' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b|
33 b.array :books do |b|
42 b.array :books do |b|
34 b.book :title => 'Book 1', :author => 'B. Smith'
43 b.book :title => 'Book 1', :author => 'B. Smith'
35 b.book :title => 'Book 2', :author => 'G. Cooper'
44 b.book :title => 'Book 2', :author => 'G. Cooper'
36 end
45 end
37 end
46 end
38 end
47 end
39
48
40 def test_array_with_content_tags
49 def test_array_with_content_tags
41 assert_json_output({'books' => [{'value' => 'Book 1', 'author' => 'B. Smith'}, {'value' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b|
50 assert_json_output({'books' => [{'value' => 'Book 1', 'author' => 'B. Smith'}, {'value' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b|
42 b.array :books do |b|
51 b.array :books do |b|
43 b.book 'Book 1', :author => 'B. Smith'
52 b.book 'Book 1', :author => 'B. Smith'
44 b.book 'Book 2', :author => 'G. Cooper'
53 b.book 'Book 2', :author => 'G. Cooper'
45 end
54 end
46 end
55 end
47 end
56 end
48
57
49 def assert_json_output(expected, &block)
58 def assert_json_output(expected, &block)
50 builder = Redmine::Views::Builders::Json.new
59 builder = Redmine::Views::Builders::Json.new
51 block.call(builder)
60 block.call(builder)
52 assert_equal(expected, ActiveSupport::JSON.decode(builder.output))
61 assert_equal(expected, ActiveSupport::JSON.decode(builder.output))
53 end
62 end
54 end
63 end
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now