##// END OF EJS Templates
Displays the full form when creating a version from the issue form so that required custom fields can be filled (#7398)....
Jean-Philippe Lang -
r8725:60b9e59d1580
parent child
Show More
@@ -0,0 +1,9
1 <h3 class="title"><%=l(:label_version_new)%></h3>
2
3 <% labelled_remote_form_for @version, :url => project_versions_path(@project) do |f| %>
4 <%= render :partial => 'versions/form', :locals => { :f => f } %>
5 <p class="buttons">
6 <%= submit_tag l(:button_create), :name => nil %>
7 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
8 </p>
9 <% end %>
@@ -1,189 +1,205
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class VersionsController < ApplicationController
19 19 menu_item :roadmap
20 20 model_object Version
21 21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
22 22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
23 23 before_filter :find_project, :only => [:index, :new, :create, :close_completed]
24 24 before_filter :authorize
25 25
26 26 accept_api_auth :index, :create, :update, :destroy
27 27
28 28 helper :custom_fields
29 29 helper :projects
30 30
31 31 def index
32 32 respond_to do |format|
33 33 format.html {
34 34 @trackers = @project.trackers.find(:all, :order => 'position')
35 35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
36 36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
37 37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
38 38
39 39 @versions = @project.shared_versions || []
40 40 @versions += @project.rolled_up_versions.visible if @with_subprojects
41 41 @versions = @versions.uniq.sort
42 42 unless params[:completed]
43 43 @completed_versions = @versions.select {|version| version.closed? || version.completed? }
44 44 @versions -= @completed_versions
45 45 end
46 46
47 47 @issues_by_version = {}
48 48 unless @selected_tracker_ids.empty?
49 49 @versions.each do |version|
50 50 issues = version.fixed_issues.visible.find(:all,
51 51 :include => [:project, :status, :tracker, :priority],
52 52 :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
53 53 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
54 54 @issues_by_version[version] = issues
55 55 end
56 56 end
57 57 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
58 58 }
59 59 format.api {
60 60 @versions = @project.shared_versions.all
61 61 }
62 62 end
63 63 end
64 64
65 65 def show
66 66 respond_to do |format|
67 67 format.html {
68 68 @issues = @version.fixed_issues.visible.find(:all,
69 69 :include => [:status, :tracker, :priority],
70 70 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
71 71 }
72 72 format.api
73 73 end
74 74 end
75 75
76 76 def new
77 77 @version = @project.versions.build(params[:version])
78
79 respond_to do |format|
80 format.html
81 format.js do
82 render :update do |page|
83 page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
84 page << "showModal('ajax-modal', '600px');"
85 page << "Form.Element.focus('version_name');"
86 end
87 end
88 end
78 89 end
79 90
80 91 def create
81 92 @version = @project.versions.build
82 93 if params[:version]
83 94 attributes = params[:version].dup
84 95 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
85 96 @version.attributes = attributes
86 97 end
87 98
88 99 if request.post?
89 100 if @version.save
90 101 respond_to do |format|
91 102 format.html do
92 103 flash[:notice] = l(:notice_successful_create)
93 104 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
94 105 end
95 106 format.js do
107 render(:update) {|page|
108 page << 'hideModal();'
96 109 # IE doesn't support the replace_html rjs method for select box options
97 render(:update) {|page| page.replace "issue_fixed_version_id",
110 page.replace "issue_fixed_version_id",
98 111 content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
99 112 }
100 113 end
101 114 format.api do
102 115 render :action => 'show', :status => :created, :location => version_url(@version)
103 116 end
104 117 end
105 118 else
106 119 respond_to do |format|
107 120 format.html { render :action => 'new' }
108 121 format.js do
109 render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
122 render :update do |page|
123 page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
124 page << "Form.Element.focus('version_name');"
125 end
110 126 end
111 127 format.api { render_validation_errors(@version) }
112 128 end
113 129 end
114 130 end
115 131 end
116 132
117 133 def edit
118 134 end
119 135
120 136 def update
121 137 if request.put? && params[:version]
122 138 attributes = params[:version].dup
123 139 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
124 140 if @version.update_attributes(attributes)
125 141 respond_to do |format|
126 142 format.html {
127 143 flash[:notice] = l(:notice_successful_update)
128 144 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
129 145 }
130 146 format.api { head :ok }
131 147 end
132 148 else
133 149 respond_to do |format|
134 150 format.html { render :action => 'edit' }
135 151 format.api { render_validation_errors(@version) }
136 152 end
137 153 end
138 154 end
139 155 end
140 156
141 157 def close_completed
142 158 if request.put?
143 159 @project.close_completed_versions
144 160 end
145 161 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
146 162 end
147 163
148 164 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
149 165 def destroy
150 166 if @version.fixed_issues.empty?
151 167 @version.destroy
152 168 respond_to do |format|
153 169 format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
154 170 format.api { head :ok }
155 171 end
156 172 else
157 173 respond_to do |format|
158 174 format.html {
159 175 flash[:error] = l(:notice_unable_delete_version)
160 176 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
161 177 }
162 178 format.api { head :unprocessable_entity }
163 179 end
164 180 end
165 181 end
166 182
167 183 def status_by
168 184 respond_to do |format|
169 185 format.html { render :action => 'show' }
170 186 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
171 187 end
172 188 end
173 189
174 190 private
175 191 def find_project
176 192 @project = Project.find(params[:project_id])
177 193 rescue ActiveRecord::RecordNotFound
178 194 render_404
179 195 end
180 196
181 197 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
182 198 if ids = params[:tracker_ids]
183 199 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
184 200 else
185 201 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
186 202 end
187 203 end
188 204
189 205 end
@@ -1,69 +1,67
1 1 <% labelled_fields_for :issue, @issue do |f| %>
2 2
3 3 <div class="splitcontentleft">
4 4 <% if @issue.safe_attribute? 'status_id' %>
5 5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
6 6 <% else %>
7 7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
8 8 <% end %>
9 9
10 10 <% if @issue.safe_attribute? 'priority_id' %>
11 11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
12 12 <% end %>
13 13
14 14 <% if @issue.safe_attribute? 'assigned_to_id' %>
15 15 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
16 16 <% end %>
17 17
18 18 <% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
19 19 <p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
20 20 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
21 21 l(:label_issue_category_new),
22 22 'issue_category[name]',
23 23 {:controller => 'issue_categories', :action => 'create', :project_id => @issue.project},
24 24 :title => l(:label_issue_category_new),
25 25 :tabindex => 199) if User.current.allowed_to?(:manage_categories, @issue.project) %></p>
26 26 <% end %>
27 27
28 28 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
29 29 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
30 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
31 l(:label_version_new),
32 'version[name]',
33 {:controller => 'versions', :action => 'create', :project_id => @issue.project},
30 <%= link_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
31 {:url => new_project_version_path(@issue.project), :method => 'get'},
34 32 :title => l(:label_version_new),
35 33 :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %>
36 34 </p>
37 35 <% end %>
38 36 </div>
39 37
40 38 <div class="splitcontentright">
41 39 <% if @issue.safe_attribute? 'parent_issue_id' %>
42 40 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
43 41 <div id="parent_issue_candidates" class="autocomplete"></div>
44 42 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @issue.project) }')" %>
45 43 <% end %>
46 44
47 45 <% if @issue.safe_attribute? 'start_date' %>
48 46 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
49 47 <% end %>
50 48
51 49 <% if @issue.safe_attribute? 'due_date' %>
52 50 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
53 51 <% end %>
54 52
55 53 <% if @issue.safe_attribute? 'estimated_hours' %>
56 54 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
57 55 <% end %>
58 56
59 57 <% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
60 58 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
61 59 <% end %>
62 60 </div>
63 61
64 62 <div style="clear:both;"> </div>
65 63 <% if @issue.safe_attribute? 'custom_field_values' %>
66 64 <%= render :partial => 'issues/form_custom_fields' %>
67 65 <% end %>
68 66
69 67 <% end %>
@@ -1,517 +1,522
1 1 /* Redmine - project management software
2 2 Copyright (C) 2006-2012 Jean-Philippe Lang */
3 3
4 4 function checkAll (id, checked) {
5 5 var els = Element.descendants(id);
6 6 for (var i = 0; i < els.length; i++) {
7 7 if (els[i].disabled==false) {
8 8 els[i].checked = checked;
9 9 }
10 10 }
11 11 }
12 12
13 13 function toggleCheckboxesBySelector(selector) {
14 14 boxes = $$(selector);
15 15 var all_checked = true;
16 16 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
17 17 for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
18 18 }
19 19
20 20 function setCheckboxesBySelector(checked, selector) {
21 21 var boxes = $$(selector);
22 22 boxes.each(function(ele) {
23 23 ele.checked = checked;
24 24 });
25 25 }
26 26
27 27 function showAndScrollTo(id, focus) {
28 28 Element.show(id);
29 29 if (focus!=null) { Form.Element.focus(focus); }
30 30 Element.scrollTo(id);
31 31 }
32 32
33 33 function toggleRowGroup(el) {
34 34 var tr = Element.up(el, 'tr');
35 35 var n = Element.next(tr);
36 36 tr.toggleClassName('open');
37 37 while (n != undefined && !n.hasClassName('group')) {
38 38 Element.toggle(n);
39 39 n = Element.next(n);
40 40 }
41 41 }
42 42
43 43 function collapseAllRowGroups(el) {
44 44 var tbody = Element.up(el, 'tbody');
45 45 tbody.childElements('tr').each(function(tr) {
46 46 if (tr.hasClassName('group')) {
47 47 tr.removeClassName('open');
48 48 } else {
49 49 tr.hide();
50 50 }
51 51 })
52 52 }
53 53
54 54 function expandAllRowGroups(el) {
55 55 var tbody = Element.up(el, 'tbody');
56 56 tbody.childElements('tr').each(function(tr) {
57 57 if (tr.hasClassName('group')) {
58 58 tr.addClassName('open');
59 59 } else {
60 60 tr.show();
61 61 }
62 62 })
63 63 }
64 64
65 65 function toggleAllRowGroups(el) {
66 66 var tr = Element.up(el, 'tr');
67 67 if (tr.hasClassName('open')) {
68 68 collapseAllRowGroups(el);
69 69 } else {
70 70 expandAllRowGroups(el);
71 71 }
72 72 }
73 73
74 74 function toggleFieldset(el) {
75 75 var fieldset = Element.up(el, 'fieldset');
76 76 fieldset.toggleClassName('collapsed');
77 77 Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2});
78 78 }
79 79
80 80 function hideFieldset(el) {
81 81 var fieldset = Element.up(el, 'fieldset');
82 82 fieldset.toggleClassName('collapsed');
83 83 fieldset.down('div').hide();
84 84 }
85 85
86 86 function add_filter() {
87 87 select = $('add_filter_select');
88 88 field = select.value
89 89 Element.show('tr_' + field);
90 90 check_box = $('cb_' + field);
91 91 check_box.checked = true;
92 92 toggle_filter(field);
93 93 select.selectedIndex = 0;
94 94
95 95 for (i=0; i<select.options.length; i++) {
96 96 if (select.options[i].value == field) {
97 97 select.options[i].disabled = true;
98 98 }
99 99 }
100 100 }
101 101
102 102 function toggle_filter(field) {
103 103 check_box = $('cb_' + field);
104 104 if (check_box.checked) {
105 105 Element.show("operators_" + field);
106 106 Form.Element.enable("operators_" + field);
107 107 toggle_operator(field);
108 108 } else {
109 109 Element.hide("operators_" + field);
110 110 Form.Element.disable("operators_" + field);
111 111 enableValues(field, []);
112 112 }
113 113 }
114 114
115 115 function enableValues(field, indexes) {
116 116 var f = $$(".values_" + field);
117 117 for(var i=0;i<f.length;i++) {
118 118 if (indexes.include(i)) {
119 119 Form.Element.enable(f[i]);
120 120 f[i].up('span').show();
121 121 } else {
122 122 f[i].value = '';
123 123 Form.Element.disable(f[i]);
124 124 f[i].up('span').hide();
125 125 }
126 126 }
127 127 if (indexes.length > 0) {
128 128 Element.show("div_values_" + field);
129 129 } else {
130 130 Element.hide("div_values_" + field);
131 131 }
132 132 }
133 133
134 134 function toggle_operator(field) {
135 135 operator = $("operators_" + field);
136 136 switch (operator.value) {
137 137 case "!*":
138 138 case "*":
139 139 case "t":
140 140 case "w":
141 141 case "o":
142 142 case "c":
143 143 enableValues(field, []);
144 144 break;
145 145 case "><":
146 146 enableValues(field, [0,1]);
147 147 break;
148 148 case "<t+":
149 149 case ">t+":
150 150 case "t+":
151 151 case ">t-":
152 152 case "<t-":
153 153 case "t-":
154 154 enableValues(field, [2]);
155 155 break;
156 156 default:
157 157 enableValues(field, [0]);
158 158 break;
159 159 }
160 160 }
161 161
162 162 function toggle_multi_select(el) {
163 163 var select = $(el);
164 164 if (select.multiple == true) {
165 165 select.multiple = false;
166 166 } else {
167 167 select.multiple = true;
168 168 }
169 169 }
170 170
171 171 function submit_query_form(id) {
172 172 selectAllOptions("selected_columns");
173 173 $(id).submit();
174 174 }
175 175
176 176 function apply_filters_observer() {
177 177 $$("#query_form input[type=text]").invoke("observe", "keypress", function(e){
178 178 if(e.keyCode == Event.KEY_RETURN) {
179 179 submit_query_form("query_form");
180 180 }
181 181 });
182 182 }
183 183
184 184 var fileFieldCount = 1;
185 185
186 186 function addFileField() {
187 187 var fields = $('attachments_fields');
188 188 if (fields.childElements().length >= 10) return false;
189 189 fileFieldCount++;
190 190 var s = new Element('span');
191 191 s.update(fields.down('span').innerHTML);
192 192 s.down('input.file').name = "attachments[" + fileFieldCount + "][file]";
193 193 s.down('input.description').name = "attachments[" + fileFieldCount + "][description]";
194 194 fields.appendChild(s);
195 195 }
196 196
197 197 function removeFileField(el) {
198 198 var fields = $('attachments_fields');
199 199 var s = Element.up(el, 'span');
200 200 if (fields.childElements().length > 1) {
201 201 s.remove();
202 202 } else {
203 203 s.update(s.innerHTML);
204 204 }
205 205 }
206 206
207 207 function checkFileSize(el, maxSize, message) {
208 208 var files = el.files;
209 209 if (files) {
210 210 for (var i=0; i<files.length; i++) {
211 211 if (files[i].size > maxSize) {
212 212 alert(message);
213 213 el.value = "";
214 214 }
215 215 }
216 216 }
217 217 }
218 218
219 219 function showTab(name) {
220 220 var f = $$('div#content .tab-content');
221 221 for(var i=0; i<f.length; i++){
222 222 Element.hide(f[i]);
223 223 }
224 224 var f = $$('div.tabs a');
225 225 for(var i=0; i<f.length; i++){
226 226 Element.removeClassName(f[i], "selected");
227 227 }
228 228 Element.show('tab-content-' + name);
229 229 Element.addClassName('tab-' + name, "selected");
230 230 return false;
231 231 }
232 232
233 233 function moveTabRight(el) {
234 234 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
235 235 var tabsWidth = 0;
236 236 var i;
237 237 for (i=0; i<lis.length; i++) {
238 238 if (lis[i].visible()) {
239 239 tabsWidth += lis[i].getWidth() + 6;
240 240 }
241 241 }
242 242 if (tabsWidth < Element.up(el, 'div.tabs').getWidth() - 60) {
243 243 return;
244 244 }
245 245 i=0;
246 246 while (i<lis.length && !lis[i].visible()) {
247 247 i++;
248 248 }
249 249 lis[i].hide();
250 250 }
251 251
252 252 function moveTabLeft(el) {
253 253 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
254 254 var i = 0;
255 255 while (i<lis.length && !lis[i].visible()) {
256 256 i++;
257 257 }
258 258 if (i>0) {
259 259 lis[i-1].show();
260 260 }
261 261 }
262 262
263 263 function displayTabsButtons() {
264 264 var lis;
265 265 var tabsWidth = 0;
266 266 var i;
267 267 $$('div.tabs').each(function(el) {
268 268 lis = el.down('ul').childElements();
269 269 for (i=0; i<lis.length; i++) {
270 270 if (lis[i].visible()) {
271 271 tabsWidth += lis[i].getWidth() + 6;
272 272 }
273 273 }
274 274 if ((tabsWidth < el.getWidth() - 60) && (lis[0].visible())) {
275 275 el.down('div.tabs-buttons').hide();
276 276 } else {
277 277 el.down('div.tabs-buttons').show();
278 278 }
279 279 });
280 280 }
281 281
282 282 function setPredecessorFieldsVisibility() {
283 283 relationType = $('relation_relation_type');
284 284 if (relationType && (relationType.value == "precedes" || relationType.value == "follows")) {
285 285 Element.show('predecessor_fields');
286 286 } else {
287 287 Element.hide('predecessor_fields');
288 288 }
289 289 }
290 290
291 291 function promptToRemote(text, param, url) {
292 292 value = prompt(text + ':');
293 293 if (value) {
294 294 new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true});
295 295 return false;
296 296 }
297 297 }
298 298
299 299 function showModal(id, width) {
300 300 el = $(id);
301 301 if (el == undefined || el.visible()) {return;}
302 302 var h = $$('body')[0].getHeight();
303 303 var d = document.createElement("div");
304 304 d.id = 'modalbg';
305 305 $('main').appendChild(d);
306 306 $('modalbg').setStyle({ width: '100%', height: h + 'px' });
307 307 $('modalbg').show();
308 308
309 309 var pageWidth = document.viewport.getWidth();
310 310 if (width) {
311 311 el.setStyle({'width': width});
312 312 }
313 313 el.setStyle({'left': (((pageWidth - el.getWidth())/2 *100) / pageWidth) + '%'});
314 314 el.addClassName('modal');
315 315 el.show();
316 316
317 317 var submit = el.down("input[type=submit]");
318 318 if (submit) {
319 319 submit.focus();
320 320 }
321 321 }
322 322
323 323 function hideModal(el) {
324 var modal = Element.up(el, 'div.modal');
324 var modal;
325 if (el) {
326 modal = Element.up(el, 'div.modal');
327 } else {
328 modal = $('ajax-modal');
329 }
325 330 if (modal) {
326 331 modal.hide();
327 332 }
328 333 var bg = $('modalbg');
329 334 if (bg) {
330 335 bg.remove();
331 336 }
332 337 }
333 338
334 339 function collapseScmEntry(id) {
335 340 var els = document.getElementsByClassName(id, 'browser');
336 341 for (var i = 0; i < els.length; i++) {
337 342 if (els[i].hasClassName('open')) {
338 343 collapseScmEntry(els[i].id);
339 344 }
340 345 Element.hide(els[i]);
341 346 }
342 347 $(id).removeClassName('open');
343 348 }
344 349
345 350 function expandScmEntry(id) {
346 351 var els = document.getElementsByClassName(id, 'browser');
347 352 for (var i = 0; i < els.length; i++) {
348 353 Element.show(els[i]);
349 354 if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
350 355 expandScmEntry(els[i].id);
351 356 }
352 357 }
353 358 $(id).addClassName('open');
354 359 }
355 360
356 361 function scmEntryClick(id) {
357 362 el = $(id);
358 363 if (el.hasClassName('open')) {
359 364 collapseScmEntry(id);
360 365 el.addClassName('collapsed');
361 366 return false;
362 367 } else if (el.hasClassName('loaded')) {
363 368 expandScmEntry(id);
364 369 el.removeClassName('collapsed');
365 370 return false;
366 371 }
367 372 if (el.hasClassName('loading')) {
368 373 return false;
369 374 }
370 375 el.addClassName('loading');
371 376 return true;
372 377 }
373 378
374 379 function scmEntryLoaded(id) {
375 380 Element.addClassName(id, 'open');
376 381 Element.addClassName(id, 'loaded');
377 382 Element.removeClassName(id, 'loading');
378 383 }
379 384
380 385 function randomKey(size) {
381 386 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
382 387 var key = '';
383 388 for (i = 0; i < size; i++) {
384 389 key += chars[Math.floor(Math.random() * chars.length)];
385 390 }
386 391 return key;
387 392 }
388 393
389 394 function observeParentIssueField(url) {
390 395 new Ajax.Autocompleter('issue_parent_issue_id',
391 396 'parent_issue_candidates',
392 397 url,
393 398 { minChars: 3,
394 399 frequency: 0.5,
395 400 paramName: 'q',
396 401 method: 'get',
397 402 updateElement: function(value) {
398 403 document.getElementById('issue_parent_issue_id').value = value.id;
399 404 }});
400 405 }
401 406
402 407 function observeRelatedIssueField(url) {
403 408 new Ajax.Autocompleter('relation_issue_to_id',
404 409 'related_issue_candidates',
405 410 url,
406 411 { minChars: 3,
407 412 frequency: 0.5,
408 413 paramName: 'q',
409 414 method: 'get',
410 415 updateElement: function(value) {
411 416 document.getElementById('relation_issue_to_id').value = value.id;
412 417 },
413 418 parameters: 'scope=all'
414 419 });
415 420 }
416 421
417 422 function setVisible(id, visible) {
418 423 var el = $(id);
419 424 if (el) {if (visible) {el.show();} else {el.hide();}}
420 425 }
421 426
422 427 function observeProjectModules() {
423 428 var f = function() {
424 429 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
425 430 var c = ($('project_enabled_module_names_issue_tracking').checked == true);
426 431 setVisible('project_trackers', c);
427 432 setVisible('project_issue_custom_fields', c);
428 433 };
429 434
430 435 Event.observe(window, 'load', f);
431 436 Event.observe('project_enabled_module_names_issue_tracking', 'change', f);
432 437 }
433 438
434 439 /*
435 440 * Class used to warn user when leaving a page with unsaved textarea
436 441 * Author: mathias.fischer@berlinonline.de
437 442 */
438 443
439 444 var WarnLeavingUnsaved = Class.create({
440 445 observedForms: false,
441 446 observedElements: false,
442 447 changedForms: false,
443 448 message: null,
444 449
445 450 initialize: function(message){
446 451 this.observedForms = $$('form');
447 452 this.observedElements = $$('textarea');
448 453 this.message = message;
449 454
450 455 this.observedElements.each(this.observeChange.bind(this));
451 456 this.observedForms.each(this.submitAction.bind(this));
452 457
453 458 window.onbeforeunload = this.unload.bind(this);
454 459 },
455 460
456 461 unload: function(){
457 462 this.observedElements.each(function(el) {el.blur();})
458 463 if(this.changedForms)
459 464 return this.message;
460 465 },
461 466
462 467 setChanged: function(){
463 468 this.changedForms = true;
464 469 },
465 470
466 471 setUnchanged: function(){
467 472 this.changedForms = false;
468 473 },
469 474
470 475 observeChange: function(element){
471 476 element.observe('change',this.setChanged.bindAsEventListener(this));
472 477 },
473 478
474 479 submitAction: function(element){
475 480 element.observe('submit',this.setUnchanged.bindAsEventListener(this));
476 481 }
477 482 });
478 483
479 484 /*
480 485 * 1 - registers a callback which copies the csrf token into the
481 486 * X-CSRF-Token header with each ajax request. Necessary to
482 487 * work with rails applications which have fixed
483 488 * CVE-2011-0447
484 489 * 2 - shows and hides ajax indicator
485 490 */
486 491 Ajax.Responders.register({
487 492 onCreate: function(request){
488 493 var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
489 494
490 495 if (csrf_meta_tag) {
491 496 var header = 'X-CSRF-Token',
492 497 token = csrf_meta_tag.readAttribute('content');
493 498
494 499 if (!request.options.requestHeaders) {
495 500 request.options.requestHeaders = {};
496 501 }
497 502 request.options.requestHeaders[header] = token;
498 503 }
499 504
500 505 if ($('ajax-indicator') && Ajax.activeRequestCount > 0) {
501 506 Element.show('ajax-indicator');
502 507 }
503 508 },
504 509 onComplete: function(){
505 510 if ($('ajax-indicator') && Ajax.activeRequestCount == 0) {
506 511 Element.hide('ajax-indicator');
507 512 }
508 513 }
509 514 });
510 515
511 516 function hideOnLoad() {
512 517 $$('.hol').each(function(el) {
513 518 el.hide();
514 519 });
515 520 }
516 521
517 522 Event.observe(window, 'load', hideOnLoad);
@@ -1,187 +1,211
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19 require 'versions_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class VersionsController; def rescue_action(e) raise e end; end
23 23
24 24 class VersionsControllerTest < ActionController::TestCase
25 25 fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules, :issue_statuses
26 26
27 27 def setup
28 28 @controller = VersionsController.new
29 29 @request = ActionController::TestRequest.new
30 30 @response = ActionController::TestResponse.new
31 31 User.current = nil
32 32 end
33 33
34 34 def test_index
35 35 get :index, :project_id => 1
36 36 assert_response :success
37 37 assert_template 'index'
38 38 assert_not_nil assigns(:versions)
39 39 # Version with no date set appears
40 40 assert assigns(:versions).include?(Version.find(3))
41 41 # Completed version doesn't appear
42 42 assert !assigns(:versions).include?(Version.find(1))
43 43 # Context menu on issues
44 44 assert_select "script", :text => Regexp.new(Regexp.escape("new ContextMenu('/issues/context_menu')"))
45 45 # Links to versions anchors
46 46 assert_tag 'a', :attributes => {:href => '#2.0'},
47 47 :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}}
48 48 # Links to completed versions in the sidebar
49 49 assert_tag 'a', :attributes => {:href => '/versions/1'},
50 50 :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}}
51 51 end
52 52
53 53 def test_index_with_completed_versions
54 54 get :index, :project_id => 1, :completed => 1
55 55 assert_response :success
56 56 assert_template 'index'
57 57 assert_not_nil assigns(:versions)
58 58 # Version with no date set appears
59 59 assert assigns(:versions).include?(Version.find(3))
60 60 # Completed version appears
61 61 assert assigns(:versions).include?(Version.find(1))
62 62 end
63 63
64 64 def test_index_with_tracker_ids
65 65 get :index, :project_id => 1, :tracker_ids => [1, 3]
66 66 assert_response :success
67 67 assert_template 'index'
68 68 assert_not_nil assigns(:issues_by_version)
69 69 assert_nil assigns(:issues_by_version).values.flatten.detect {|issue| issue.tracker_id == 2}
70 70 end
71 71
72 72 def test_index_showing_subprojects_versions
73 73 @subproject_version = Version.generate!(:project => Project.find(3))
74 74 get :index, :project_id => 1, :with_subprojects => 1
75 75 assert_response :success
76 76 assert_template 'index'
77 77 assert_not_nil assigns(:versions)
78 78
79 79 assert assigns(:versions).include?(Version.find(4)), "Shared version not found"
80 80 assert assigns(:versions).include?(@subproject_version), "Subproject version not found"
81 81 end
82 82
83 83 def test_show
84 84 get :show, :id => 2
85 85 assert_response :success
86 86 assert_template 'show'
87 87 assert_not_nil assigns(:version)
88 88
89 89 assert_tag :tag => 'h2', :content => /1.0/
90 90 end
91 91
92 92 def test_new
93 93 @request.session[:user_id] = 2
94 94 get :new, :project_id => '1'
95 95 assert_response :success
96 96 assert_template 'new'
97 97 end
98 98
99 def test_new_from_issue_form
100 @request.session[:user_id] = 2
101 xhr :get, :new, :project_id => '1'
102 assert_response :success
103 assert_select_rjs :replace_html, "ajax-modal" do
104 assert_select "form[action=/projects/ecookbook/versions]"
105 assert_select "input#version_name"
106 end
107 end
108
99 109 def test_create
100 110 @request.session[:user_id] = 2 # manager
101 111 assert_difference 'Version.count' do
102 112 post :create, :project_id => '1', :version => {:name => 'test_add_version'}
103 113 end
104 114 assert_redirected_to '/projects/ecookbook/settings/versions'
105 115 version = Version.find_by_name('test_add_version')
106 116 assert_not_nil version
107 117 assert_equal 1, version.project_id
108 118 end
109 119
110 120 def test_create_from_issue_form
111 @request.session[:user_id] = 2 # manager
121 @request.session[:user_id] = 2
112 122 assert_difference 'Version.count' do
113 123 xhr :post, :create, :project_id => '1', :version => {:name => 'test_add_version_from_issue_form'}
114 124 end
115 assert_response :success
116 assert_select_rjs :replace, 'issue_fixed_version_id'
117 125 version = Version.find_by_name('test_add_version_from_issue_form')
118 126 assert_not_nil version
119 127 assert_equal 1, version.project_id
128
129 assert_response :success
130 assert_select_rjs :replace, 'issue_fixed_version_id' do
131 assert_select "option[value=#{version.id}][selected=selected]"
132 end
133 end
134
135 def test_create_from_issue_form_with_failure
136 @request.session[:user_id] = 2
137 assert_no_difference 'Version.count' do
138 xhr :post, :create, :project_id => '1', :version => {:name => ''}
139 end
140 assert_response :success
141 assert_select_rjs :replace_html, "ajax-modal" do
142 assert_select "div#errorExplanation"
143 end
120 144 end
121 145
122 146 def test_get_edit
123 147 @request.session[:user_id] = 2
124 148 get :edit, :id => 2
125 149 assert_response :success
126 150 assert_template 'edit'
127 151 end
128 152
129 153 def test_close_completed
130 154 Version.update_all("status = 'open'")
131 155 @request.session[:user_id] = 2
132 156 put :close_completed, :project_id => 'ecookbook'
133 157 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
134 158 assert_not_nil Version.find_by_status('closed')
135 159 end
136 160
137 161 def test_post_update
138 162 @request.session[:user_id] = 2
139 163 put :update, :id => 2,
140 164 :version => { :name => 'New version name',
141 165 :effective_date => Date.today.strftime("%Y-%m-%d")}
142 166 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
143 167 version = Version.find(2)
144 168 assert_equal 'New version name', version.name
145 169 assert_equal Date.today, version.effective_date
146 170 end
147 171
148 172 def test_post_update_with_validation_failure
149 173 @request.session[:user_id] = 2
150 174 put :update, :id => 2,
151 175 :version => { :name => '',
152 176 :effective_date => Date.today.strftime("%Y-%m-%d")}
153 177 assert_response :success
154 178 assert_template 'edit'
155 179 end
156 180
157 181 def test_destroy
158 182 @request.session[:user_id] = 2
159 183 assert_difference 'Version.count', -1 do
160 184 delete :destroy, :id => 3
161 185 end
162 186 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
163 187 assert_nil Version.find_by_id(3)
164 188 end
165 189
166 190 def test_destroy_version_in_use_should_fail
167 191 @request.session[:user_id] = 2
168 192 assert_no_difference 'Version.count' do
169 193 delete :destroy, :id => 2
170 194 end
171 195 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
172 196 assert flash[:error].match(/Unable to delete version/)
173 197 assert Version.find_by_id(2)
174 198 end
175 199
176 200 def test_issue_status_by
177 201 xhr :get, :status_by, :id => 2
178 202 assert_response :success
179 203 assert_template '_issue_counts'
180 204 end
181 205
182 206 def test_issue_status_by_status
183 207 xhr :get, :status_by, :id => 2, :status_by => 'status'
184 208 assert_response :success
185 209 assert_template '_issue_counts'
186 210 end
187 211 end
General Comments 0
You need to be logged in to leave comments. Login now