@@ -0,0 +1,10 | |||||
|
1 | <p> | |||
|
2 | <% if @repository.supports_cat? %> | |||
|
3 | <%= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> | | |||
|
4 | <% end %> | |||
|
5 | <% if @repository.supports_annotate? %> | |||
|
6 | <%= link_to_if action_name != 'annotate', l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> | | |||
|
7 | <% end %> | |||
|
8 | <%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %> | |||
|
9 | <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %> | |||
|
10 | </p> |
@@ -1,495 +1,498 | |||||
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 |
|
19 | menu_item :new_issue, :only => :new | |
20 |
|
20 | |||
21 | before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment] |
|
21 | before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment] | |
22 | before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] |
|
22 | before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] | |
23 | before_filter :find_project, :only => [:new, :update_form, :preview] |
|
23 | before_filter :find_project, :only => [:new, :update_form, :preview] | |
24 | before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] |
|
24 | before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] | |
25 | before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar] |
|
25 | before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar] | |
26 | accept_key_auth :index, :changes |
|
26 | accept_key_auth :index, :changes | |
27 |
|
27 | |||
28 | helper :journals |
|
28 | helper :journals | |
29 | helper :projects |
|
29 | helper :projects | |
30 | include ProjectsHelper |
|
30 | include ProjectsHelper | |
31 | helper :custom_fields |
|
31 | helper :custom_fields | |
32 | include CustomFieldsHelper |
|
32 | include CustomFieldsHelper | |
33 | helper :ifpdf |
|
33 | helper :ifpdf | |
34 | include IfpdfHelper |
|
34 | include IfpdfHelper | |
35 | helper :issue_relations |
|
35 | helper :issue_relations | |
36 | include IssueRelationsHelper |
|
36 | include IssueRelationsHelper | |
37 | helper :watchers |
|
37 | helper :watchers | |
38 | include WatchersHelper |
|
38 | include WatchersHelper | |
39 | helper :attachments |
|
39 | helper :attachments | |
40 | include AttachmentsHelper |
|
40 | include AttachmentsHelper | |
41 | helper :queries |
|
41 | helper :queries | |
42 | helper :sort |
|
42 | helper :sort | |
43 | include SortHelper |
|
43 | include SortHelper | |
44 | include IssuesHelper |
|
44 | include IssuesHelper | |
45 | helper :timelog |
|
45 | helper :timelog | |
46 |
|
46 | |||
47 | def index |
|
47 | def index | |
48 | retrieve_query |
|
48 | retrieve_query | |
49 | sort_init 'id', 'desc' |
|
49 | sort_init 'id', 'desc' | |
50 | sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h})) |
|
50 | sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h})) | |
51 |
|
51 | |||
52 | if @query.valid? |
|
52 | if @query.valid? | |
53 | limit = per_page_option |
|
53 | limit = per_page_option | |
54 | respond_to do |format| |
|
54 | respond_to do |format| | |
55 | format.html { } |
|
55 | format.html { } | |
56 | format.atom { } |
|
56 | format.atom { } | |
57 | format.csv { limit = Setting.issues_export_limit.to_i } |
|
57 | format.csv { limit = Setting.issues_export_limit.to_i } | |
58 | format.pdf { limit = Setting.issues_export_limit.to_i } |
|
58 | format.pdf { limit = Setting.issues_export_limit.to_i } | |
59 | end |
|
59 | end | |
60 | @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) |
|
60 | @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) | |
61 | @issue_pages = Paginator.new self, @issue_count, limit, params['page'] |
|
61 | @issue_pages = Paginator.new self, @issue_count, limit, params['page'] | |
62 | @issues = Issue.find :all, :order => sort_clause, |
|
62 | @issues = Issue.find :all, :order => sort_clause, | |
63 | :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ], |
|
63 | :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ], | |
64 | :conditions => @query.statement, |
|
64 | :conditions => @query.statement, | |
65 | :limit => limit, |
|
65 | :limit => limit, | |
66 | :offset => @issue_pages.current.offset |
|
66 | :offset => @issue_pages.current.offset | |
67 | respond_to do |format| |
|
67 | respond_to do |format| | |
68 | format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } |
|
68 | format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } | |
69 | format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } |
|
69 | format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } | |
70 | format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } |
|
70 | format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } | |
71 | format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') } |
|
71 | format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') } | |
72 | end |
|
72 | end | |
73 | else |
|
73 | else | |
74 | # Send html if the query is not valid |
|
74 | # Send html if the query is not valid | |
75 | render(:template => 'issues/index.rhtml', :layout => !request.xhr?) |
|
75 | render(:template => 'issues/index.rhtml', :layout => !request.xhr?) | |
76 | end |
|
76 | end | |
77 | rescue ActiveRecord::RecordNotFound |
|
77 | rescue ActiveRecord::RecordNotFound | |
78 | render_404 |
|
78 | render_404 | |
79 | end |
|
79 | end | |
80 |
|
80 | |||
81 | def changes |
|
81 | def changes | |
82 | retrieve_query |
|
82 | retrieve_query | |
83 | sort_init 'id', 'desc' |
|
83 | sort_init 'id', 'desc' | |
84 | sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h})) |
|
84 | sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h})) | |
85 |
|
85 | |||
86 | if @query.valid? |
|
86 | if @query.valid? | |
87 | @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ], |
|
87 | @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ], | |
88 | :conditions => @query.statement, |
|
88 | :conditions => @query.statement, | |
89 | :limit => 25, |
|
89 | :limit => 25, | |
90 | :order => "#{Journal.table_name}.created_on DESC" |
|
90 | :order => "#{Journal.table_name}.created_on DESC" | |
91 | end |
|
91 | end | |
92 | @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) |
|
92 | @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) | |
93 | render :layout => false, :content_type => 'application/atom+xml' |
|
93 | render :layout => false, :content_type => 'application/atom+xml' | |
94 | rescue ActiveRecord::RecordNotFound |
|
94 | rescue ActiveRecord::RecordNotFound | |
95 | render_404 |
|
95 | render_404 | |
96 | end |
|
96 | end | |
97 |
|
97 | |||
98 | def show |
|
98 | def show | |
99 | @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") |
|
99 | @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") | |
100 | @journals.each_with_index {|j,i| j.indice = i+1} |
|
100 | @journals.each_with_index {|j,i| j.indice = i+1} | |
101 | @journals.reverse! if User.current.wants_comments_in_reverse_order? |
|
101 | @journals.reverse! if User.current.wants_comments_in_reverse_order? | |
102 | @allowed_statuses = @issue.new_statuses_allowed_to(User.current) |
|
102 | @allowed_statuses = @issue.new_statuses_allowed_to(User.current) | |
103 | @edit_allowed = User.current.allowed_to?(:edit_issues, @project) |
|
103 | @edit_allowed = User.current.allowed_to?(:edit_issues, @project) | |
104 | @priorities = Enumeration::get_values('IPRI') |
|
104 | @priorities = Enumeration::get_values('IPRI') | |
105 | @time_entry = TimeEntry.new |
|
105 | @time_entry = TimeEntry.new | |
106 | respond_to do |format| |
|
106 | respond_to do |format| | |
107 | format.html { render :template => 'issues/show.rhtml' } |
|
107 | format.html { render :template => 'issues/show.rhtml' } | |
108 | format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } |
|
108 | format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } | |
109 | format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } |
|
109 | format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } | |
110 | end |
|
110 | end | |
111 | end |
|
111 | end | |
112 |
|
112 | |||
113 | # Add a new issue |
|
113 | # Add a new issue | |
114 | # The new issue will be created from an existing one if copy_from parameter is given |
|
114 | # The new issue will be created from an existing one if copy_from parameter is given | |
115 | def new |
|
115 | def new | |
116 | @issue = Issue.new |
|
116 | @issue = Issue.new | |
117 | @issue.copy_from(params[:copy_from]) if params[:copy_from] |
|
117 | @issue.copy_from(params[:copy_from]) if params[:copy_from] | |
118 | @issue.project = @project |
|
118 | @issue.project = @project | |
119 | # Tracker must be set before custom field values |
|
119 | # Tracker must be set before custom field values | |
120 | @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) |
|
120 | @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) | |
121 | if @issue.tracker.nil? |
|
121 | if @issue.tracker.nil? | |
122 | flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.' |
|
122 | flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.' | |
123 | render :nothing => true, :layout => true |
|
123 | render :nothing => true, :layout => true | |
124 | return |
|
124 | return | |
125 | end |
|
125 | end | |
126 | @issue.attributes = params[:issue] |
|
126 | if params[:issue].is_a?(Hash) | |
|
127 | @issue.attributes = params[:issue] | |||
|
128 | @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project) | |||
|
129 | end | |||
127 | @issue.author = User.current |
|
130 | @issue.author = User.current | |
128 |
|
131 | |||
129 | default_status = IssueStatus.default |
|
132 | default_status = IssueStatus.default | |
130 | unless default_status |
|
133 | unless default_status | |
131 | flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' |
|
134 | flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' | |
132 | render :nothing => true, :layout => true |
|
135 | render :nothing => true, :layout => true | |
133 | return |
|
136 | return | |
134 | end |
|
137 | end | |
135 | @issue.status = default_status |
|
138 | @issue.status = default_status | |
136 | @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq |
|
139 | @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq | |
137 |
|
140 | |||
138 | if request.get? || request.xhr? |
|
141 | if request.get? || request.xhr? | |
139 | @issue.start_date ||= Date.today |
|
142 | @issue.start_date ||= Date.today | |
140 | else |
|
143 | else | |
141 | requested_status = IssueStatus.find_by_id(params[:issue][:status_id]) |
|
144 | requested_status = IssueStatus.find_by_id(params[:issue][:status_id]) | |
142 | # Check that the user is allowed to apply the requested status |
|
145 | # Check that the user is allowed to apply the requested status | |
143 | @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status |
|
146 | @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status | |
144 | if @issue.save |
|
147 | if @issue.save | |
145 | attach_files(@issue, params[:attachments]) |
|
148 | attach_files(@issue, params[:attachments]) | |
146 | flash[:notice] = l(:notice_successful_create) |
|
149 | flash[:notice] = l(:notice_successful_create) | |
147 | Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') |
|
150 | Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') | |
148 | redirect_to :controller => 'issues', :action => 'show', :id => @issue |
|
151 | redirect_to :controller => 'issues', :action => 'show', :id => @issue | |
149 | return |
|
152 | return | |
150 | end |
|
153 | end | |
151 | end |
|
154 | end | |
152 | @priorities = Enumeration::get_values('IPRI') |
|
155 | @priorities = Enumeration::get_values('IPRI') | |
153 | render :layout => !request.xhr? |
|
156 | render :layout => !request.xhr? | |
154 | end |
|
157 | end | |
155 |
|
158 | |||
156 | # Attributes that can be updated on workflow transition (without :edit permission) |
|
159 | # Attributes that can be updated on workflow transition (without :edit permission) | |
157 | # TODO: make it configurable (at least per role) |
|
160 | # TODO: make it configurable (at least per role) | |
158 | UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION) |
|
161 | UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION) | |
159 |
|
162 | |||
160 | def edit |
|
163 | def edit | |
161 | @allowed_statuses = @issue.new_statuses_allowed_to(User.current) |
|
164 | @allowed_statuses = @issue.new_statuses_allowed_to(User.current) | |
162 | @priorities = Enumeration::get_values('IPRI') |
|
165 | @priorities = Enumeration::get_values('IPRI') | |
163 | @edit_allowed = User.current.allowed_to?(:edit_issues, @project) |
|
166 | @edit_allowed = User.current.allowed_to?(:edit_issues, @project) | |
164 | @time_entry = TimeEntry.new |
|
167 | @time_entry = TimeEntry.new | |
165 |
|
168 | |||
166 | @notes = params[:notes] |
|
169 | @notes = params[:notes] | |
167 | journal = @issue.init_journal(User.current, @notes) |
|
170 | journal = @issue.init_journal(User.current, @notes) | |
168 | # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed |
|
171 | # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed | |
169 | if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue] |
|
172 | if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue] | |
170 | attrs = params[:issue].dup |
|
173 | attrs = params[:issue].dup | |
171 | attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed |
|
174 | attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed | |
172 | attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s} |
|
175 | attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s} | |
173 | @issue.attributes = attrs |
|
176 | @issue.attributes = attrs | |
174 | end |
|
177 | end | |
175 |
|
178 | |||
176 | if request.post? |
|
179 | if request.post? | |
177 | @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today) |
|
180 | @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today) | |
178 | @time_entry.attributes = params[:time_entry] |
|
181 | @time_entry.attributes = params[:time_entry] | |
179 | attachments = attach_files(@issue, params[:attachments]) |
|
182 | attachments = attach_files(@issue, params[:attachments]) | |
180 | attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} |
|
183 | attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} | |
181 |
|
184 | |||
182 | call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal}) |
|
185 | call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal}) | |
183 |
|
186 | |||
184 | if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save |
|
187 | if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save | |
185 | # Log spend time |
|
188 | # Log spend time | |
186 | if current_role.allowed_to?(:log_time) |
|
189 | if current_role.allowed_to?(:log_time) | |
187 | @time_entry.save |
|
190 | @time_entry.save | |
188 | end |
|
191 | end | |
189 | if !journal.new_record? |
|
192 | if !journal.new_record? | |
190 | # Only send notification if something was actually changed |
|
193 | # Only send notification if something was actually changed | |
191 | flash[:notice] = l(:notice_successful_update) |
|
194 | flash[:notice] = l(:notice_successful_update) | |
192 | Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') |
|
195 | Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') | |
193 | end |
|
196 | end | |
194 | redirect_to(params[:back_to] || {:action => 'show', :id => @issue}) |
|
197 | redirect_to(params[:back_to] || {:action => 'show', :id => @issue}) | |
195 | end |
|
198 | end | |
196 | end |
|
199 | end | |
197 | rescue ActiveRecord::StaleObjectError |
|
200 | rescue ActiveRecord::StaleObjectError | |
198 | # Optimistic locking exception |
|
201 | # Optimistic locking exception | |
199 | flash.now[:error] = l(:notice_locking_conflict) |
|
202 | flash.now[:error] = l(:notice_locking_conflict) | |
200 | end |
|
203 | end | |
201 |
|
204 | |||
202 | def reply |
|
205 | def reply | |
203 | journal = Journal.find(params[:journal_id]) if params[:journal_id] |
|
206 | journal = Journal.find(params[:journal_id]) if params[:journal_id] | |
204 | if journal |
|
207 | if journal | |
205 | user = journal.user |
|
208 | user = journal.user | |
206 | text = journal.notes |
|
209 | text = journal.notes | |
207 | else |
|
210 | else | |
208 | user = @issue.author |
|
211 | user = @issue.author | |
209 | text = @issue.description |
|
212 | text = @issue.description | |
210 | end |
|
213 | end | |
211 | content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " |
|
214 | content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " | |
212 | content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" |
|
215 | content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" | |
213 | render(:update) { |page| |
|
216 | render(:update) { |page| | |
214 | page.<< "$('notes').value = \"#{content}\";" |
|
217 | page.<< "$('notes').value = \"#{content}\";" | |
215 | page.show 'update' |
|
218 | page.show 'update' | |
216 | page << "Form.Element.focus('notes');" |
|
219 | page << "Form.Element.focus('notes');" | |
217 | page << "Element.scrollTo('update');" |
|
220 | page << "Element.scrollTo('update');" | |
218 | page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" |
|
221 | page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" | |
219 | } |
|
222 | } | |
220 | end |
|
223 | end | |
221 |
|
224 | |||
222 | # Bulk edit a set of issues |
|
225 | # Bulk edit a set of issues | |
223 | def bulk_edit |
|
226 | def bulk_edit | |
224 | if request.post? |
|
227 | if request.post? | |
225 | status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id]) |
|
228 | status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id]) | |
226 | priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id]) |
|
229 | priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id]) | |
227 | assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id]) |
|
230 | assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id]) | |
228 | category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id]) |
|
231 | category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id]) | |
229 | fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id]) |
|
232 | fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id]) | |
230 |
|
233 | |||
231 | unsaved_issue_ids = [] |
|
234 | unsaved_issue_ids = [] | |
232 | @issues.each do |issue| |
|
235 | @issues.each do |issue| | |
233 | journal = issue.init_journal(User.current, params[:notes]) |
|
236 | journal = issue.init_journal(User.current, params[:notes]) | |
234 | issue.priority = priority if priority |
|
237 | issue.priority = priority if priority | |
235 | issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none' |
|
238 | issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none' | |
236 | issue.category = category if category || params[:category_id] == 'none' |
|
239 | issue.category = category if category || params[:category_id] == 'none' | |
237 | issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none' |
|
240 | issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none' | |
238 | issue.start_date = params[:start_date] unless params[:start_date].blank? |
|
241 | issue.start_date = params[:start_date] unless params[:start_date].blank? | |
239 | issue.due_date = params[:due_date] unless params[:due_date].blank? |
|
242 | issue.due_date = params[:due_date] unless params[:due_date].blank? | |
240 | issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank? |
|
243 | issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank? | |
241 | call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) |
|
244 | call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) | |
242 | # Don't save any change to the issue if the user is not authorized to apply the requested status |
|
245 | # Don't save any change to the issue if the user is not authorized to apply the requested status | |
243 | if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save |
|
246 | if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save | |
244 | # Send notification for each issue (if changed) |
|
247 | # Send notification for each issue (if changed) | |
245 | Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated') |
|
248 | Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated') | |
246 | else |
|
249 | else | |
247 | # Keep unsaved issue ids to display them in flash error |
|
250 | # Keep unsaved issue ids to display them in flash error | |
248 | unsaved_issue_ids << issue.id |
|
251 | unsaved_issue_ids << issue.id | |
249 | end |
|
252 | end | |
250 | end |
|
253 | end | |
251 | if unsaved_issue_ids.empty? |
|
254 | if unsaved_issue_ids.empty? | |
252 | flash[:notice] = l(:notice_successful_update) unless @issues.empty? |
|
255 | flash[:notice] = l(:notice_successful_update) unless @issues.empty? | |
253 | else |
|
256 | else | |
254 | flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #')) |
|
257 | flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #')) | |
255 | end |
|
258 | end | |
256 | redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project}) |
|
259 | redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project}) | |
257 | return |
|
260 | return | |
258 | end |
|
261 | end | |
259 | # Find potential statuses the user could be allowed to switch issues to |
|
262 | # Find potential statuses the user could be allowed to switch issues to | |
260 | @available_statuses = Workflow.find(:all, :include => :new_status, |
|
263 | @available_statuses = Workflow.find(:all, :include => :new_status, | |
261 | :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort |
|
264 | :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort | |
262 | end |
|
265 | end | |
263 |
|
266 | |||
264 | def move |
|
267 | def move | |
265 | @allowed_projects = [] |
|
268 | @allowed_projects = [] | |
266 | # find projects to which the user is allowed to move the issue |
|
269 | # find projects to which the user is allowed to move the issue | |
267 | if User.current.admin? |
|
270 | if User.current.admin? | |
268 | # admin is allowed to move issues to any active (visible) project |
|
271 | # admin is allowed to move issues to any active (visible) project | |
269 | @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name') |
|
272 | @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name') | |
270 | else |
|
273 | else | |
271 | User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)} |
|
274 | User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)} | |
272 | end |
|
275 | end | |
273 | @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] |
|
276 | @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] | |
274 | @target_project ||= @project |
|
277 | @target_project ||= @project | |
275 | @trackers = @target_project.trackers |
|
278 | @trackers = @target_project.trackers | |
276 | if request.post? |
|
279 | if request.post? | |
277 | new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) |
|
280 | new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) | |
278 | unsaved_issue_ids = [] |
|
281 | unsaved_issue_ids = [] | |
279 | @issues.each do |issue| |
|
282 | @issues.each do |issue| | |
280 | issue.init_journal(User.current) |
|
283 | issue.init_journal(User.current) | |
281 | unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker) |
|
284 | unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker) | |
282 | end |
|
285 | end | |
283 | if unsaved_issue_ids.empty? |
|
286 | if unsaved_issue_ids.empty? | |
284 | flash[:notice] = l(:notice_successful_update) unless @issues.empty? |
|
287 | flash[:notice] = l(:notice_successful_update) unless @issues.empty? | |
285 | else |
|
288 | else | |
286 | flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #')) |
|
289 | flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #')) | |
287 | end |
|
290 | end | |
288 | redirect_to :controller => 'issues', :action => 'index', :project_id => @project |
|
291 | redirect_to :controller => 'issues', :action => 'index', :project_id => @project | |
289 | return |
|
292 | return | |
290 | end |
|
293 | end | |
291 | render :layout => false if request.xhr? |
|
294 | render :layout => false if request.xhr? | |
292 | end |
|
295 | end | |
293 |
|
296 | |||
294 | def destroy |
|
297 | def destroy | |
295 | @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f |
|
298 | @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f | |
296 | if @hours > 0 |
|
299 | if @hours > 0 | |
297 | case params[:todo] |
|
300 | case params[:todo] | |
298 | when 'destroy' |
|
301 | when 'destroy' | |
299 | # nothing to do |
|
302 | # nothing to do | |
300 | when 'nullify' |
|
303 | when 'nullify' | |
301 | TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues]) |
|
304 | TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues]) | |
302 | when 'reassign' |
|
305 | when 'reassign' | |
303 | reassign_to = @project.issues.find_by_id(params[:reassign_to_id]) |
|
306 | reassign_to = @project.issues.find_by_id(params[:reassign_to_id]) | |
304 | if reassign_to.nil? |
|
307 | if reassign_to.nil? | |
305 | flash.now[:error] = l(:error_issue_not_found_in_project) |
|
308 | flash.now[:error] = l(:error_issue_not_found_in_project) | |
306 | return |
|
309 | return | |
307 | else |
|
310 | else | |
308 | TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues]) |
|
311 | TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues]) | |
309 | end |
|
312 | end | |
310 | else |
|
313 | else | |
311 | # display the destroy form |
|
314 | # display the destroy form | |
312 | return |
|
315 | return | |
313 | end |
|
316 | end | |
314 | end |
|
317 | end | |
315 | @issues.each(&:destroy) |
|
318 | @issues.each(&:destroy) | |
316 | redirect_to :action => 'index', :project_id => @project |
|
319 | redirect_to :action => 'index', :project_id => @project | |
317 | end |
|
320 | end | |
318 |
|
321 | |||
319 | def destroy_attachment |
|
322 | def destroy_attachment | |
320 | a = @issue.attachments.find(params[:attachment_id]) |
|
323 | a = @issue.attachments.find(params[:attachment_id]) | |
321 | a.destroy |
|
324 | a.destroy | |
322 | journal = @issue.init_journal(User.current) |
|
325 | journal = @issue.init_journal(User.current) | |
323 | journal.details << JournalDetail.new(:property => 'attachment', |
|
326 | journal.details << JournalDetail.new(:property => 'attachment', | |
324 | :prop_key => a.id, |
|
327 | :prop_key => a.id, | |
325 | :old_value => a.filename) |
|
328 | :old_value => a.filename) | |
326 | journal.save |
|
329 | journal.save | |
327 | redirect_to :action => 'show', :id => @issue |
|
330 | redirect_to :action => 'show', :id => @issue | |
328 | end |
|
331 | end | |
329 |
|
332 | |||
330 | def gantt |
|
333 | def gantt | |
331 | @gantt = Redmine::Helpers::Gantt.new(params) |
|
334 | @gantt = Redmine::Helpers::Gantt.new(params) | |
332 | retrieve_query |
|
335 | retrieve_query | |
333 | if @query.valid? |
|
336 | if @query.valid? | |
334 | events = [] |
|
337 | events = [] | |
335 | # Issues that have start and due dates |
|
338 | # Issues that have start and due dates | |
336 | events += Issue.find(:all, |
|
339 | events += Issue.find(:all, | |
337 | :order => "start_date, due_date", |
|
340 | :order => "start_date, due_date", | |
338 | :include => [:tracker, :status, :assigned_to, :priority, :project], |
|
341 | :include => [:tracker, :status, :assigned_to, :priority, :project], | |
339 | :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] |
|
342 | :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] | |
340 | ) |
|
343 | ) | |
341 | # Issues that don't have a due date but that are assigned to a version with a date |
|
344 | # Issues that don't have a due date but that are assigned to a version with a date | |
342 | events += Issue.find(:all, |
|
345 | events += Issue.find(:all, | |
343 | :order => "start_date, effective_date", |
|
346 | :order => "start_date, effective_date", | |
344 | :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version], |
|
347 | :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version], | |
345 | :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] |
|
348 | :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] | |
346 | ) |
|
349 | ) | |
347 | # Versions |
|
350 | # Versions | |
348 | events += Version.find(:all, :include => :project, |
|
351 | events += Version.find(:all, :include => :project, | |
349 | :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to]) |
|
352 | :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to]) | |
350 |
|
353 | |||
351 | @gantt.events = events |
|
354 | @gantt.events = events | |
352 | end |
|
355 | end | |
353 |
|
356 | |||
354 | respond_to do |format| |
|
357 | respond_to do |format| | |
355 | format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? } |
|
358 | format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? } | |
356 | format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image') |
|
359 | format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image') | |
357 | format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") } |
|
360 | format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") } | |
358 | end |
|
361 | end | |
359 | end |
|
362 | end | |
360 |
|
363 | |||
361 | def calendar |
|
364 | def calendar | |
362 | if params[:year] and params[:year].to_i > 1900 |
|
365 | if params[:year] and params[:year].to_i > 1900 | |
363 | @year = params[:year].to_i |
|
366 | @year = params[:year].to_i | |
364 | if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13 |
|
367 | if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13 | |
365 | @month = params[:month].to_i |
|
368 | @month = params[:month].to_i | |
366 | end |
|
369 | end | |
367 | end |
|
370 | end | |
368 | @year ||= Date.today.year |
|
371 | @year ||= Date.today.year | |
369 | @month ||= Date.today.month |
|
372 | @month ||= Date.today.month | |
370 |
|
373 | |||
371 | @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month) |
|
374 | @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month) | |
372 | retrieve_query |
|
375 | retrieve_query | |
373 | if @query.valid? |
|
376 | if @query.valid? | |
374 | events = [] |
|
377 | events = [] | |
375 | events += Issue.find(:all, |
|
378 | events += Issue.find(:all, | |
376 | :include => [:tracker, :status, :assigned_to, :priority, :project], |
|
379 | :include => [:tracker, :status, :assigned_to, :priority, :project], | |
377 | :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt] |
|
380 | :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt] | |
378 | ) |
|
381 | ) | |
379 | events += Version.find(:all, :include => :project, |
|
382 | events += Version.find(:all, :include => :project, | |
380 | :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) |
|
383 | :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) | |
381 |
|
384 | |||
382 | @calendar.events = events |
|
385 | @calendar.events = events | |
383 | end |
|
386 | end | |
384 |
|
387 | |||
385 | render :layout => false if request.xhr? |
|
388 | render :layout => false if request.xhr? | |
386 | end |
|
389 | end | |
387 |
|
390 | |||
388 | def context_menu |
|
391 | def context_menu | |
389 | @issues = Issue.find_all_by_id(params[:ids], :include => :project) |
|
392 | @issues = Issue.find_all_by_id(params[:ids], :include => :project) | |
390 | if (@issues.size == 1) |
|
393 | if (@issues.size == 1) | |
391 | @issue = @issues.first |
|
394 | @issue = @issues.first | |
392 | @allowed_statuses = @issue.new_statuses_allowed_to(User.current) |
|
395 | @allowed_statuses = @issue.new_statuses_allowed_to(User.current) | |
393 | end |
|
396 | end | |
394 | projects = @issues.collect(&:project).compact.uniq |
|
397 | projects = @issues.collect(&:project).compact.uniq | |
395 | @project = projects.first if projects.size == 1 |
|
398 | @project = projects.first if projects.size == 1 | |
396 |
|
399 | |||
397 | @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)), |
|
400 | @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)), | |
398 | :log_time => (@project && User.current.allowed_to?(:log_time, @project)), |
|
401 | :log_time => (@project && User.current.allowed_to?(:log_time, @project)), | |
399 | :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))), |
|
402 | :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))), | |
400 | :move => (@project && User.current.allowed_to?(:move_issues, @project)), |
|
403 | :move => (@project && User.current.allowed_to?(:move_issues, @project)), | |
401 | :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), |
|
404 | :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), | |
402 | :delete => (@project && User.current.allowed_to?(:delete_issues, @project)) |
|
405 | :delete => (@project && User.current.allowed_to?(:delete_issues, @project)) | |
403 | } |
|
406 | } | |
404 | if @project |
|
407 | if @project | |
405 | @assignables = @project.assignable_users |
|
408 | @assignables = @project.assignable_users | |
406 | @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to) |
|
409 | @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to) | |
407 | end |
|
410 | end | |
408 |
|
411 | |||
409 | @priorities = Enumeration.get_values('IPRI').reverse |
|
412 | @priorities = Enumeration.get_values('IPRI').reverse | |
410 | @statuses = IssueStatus.find(:all, :order => 'position') |
|
413 | @statuses = IssueStatus.find(:all, :order => 'position') | |
411 | @back = request.env['HTTP_REFERER'] |
|
414 | @back = request.env['HTTP_REFERER'] | |
412 |
|
415 | |||
413 | render :layout => false |
|
416 | render :layout => false | |
414 | end |
|
417 | end | |
415 |
|
418 | |||
416 | def update_form |
|
419 | def update_form | |
417 | @issue = Issue.new(params[:issue]) |
|
420 | @issue = Issue.new(params[:issue]) | |
418 | render :action => :new, :layout => false |
|
421 | render :action => :new, :layout => false | |
419 | end |
|
422 | end | |
420 |
|
423 | |||
421 | def preview |
|
424 | def preview | |
422 | @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? |
|
425 | @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? | |
423 | @attachements = @issue.attachments if @issue |
|
426 | @attachements = @issue.attachments if @issue | |
424 | @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil) |
|
427 | @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil) | |
425 | render :partial => 'common/preview' |
|
428 | render :partial => 'common/preview' | |
426 | end |
|
429 | end | |
427 |
|
430 | |||
428 | private |
|
431 | private | |
429 | def find_issue |
|
432 | def find_issue | |
430 | @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) |
|
433 | @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) | |
431 | @project = @issue.project |
|
434 | @project = @issue.project | |
432 | rescue ActiveRecord::RecordNotFound |
|
435 | rescue ActiveRecord::RecordNotFound | |
433 | render_404 |
|
436 | render_404 | |
434 | end |
|
437 | end | |
435 |
|
438 | |||
436 | # Filter for bulk operations |
|
439 | # Filter for bulk operations | |
437 | def find_issues |
|
440 | def find_issues | |
438 | @issues = Issue.find_all_by_id(params[:id] || params[:ids]) |
|
441 | @issues = Issue.find_all_by_id(params[:id] || params[:ids]) | |
439 | raise ActiveRecord::RecordNotFound if @issues.empty? |
|
442 | raise ActiveRecord::RecordNotFound if @issues.empty? | |
440 | projects = @issues.collect(&:project).compact.uniq |
|
443 | projects = @issues.collect(&:project).compact.uniq | |
441 | if projects.size == 1 |
|
444 | if projects.size == 1 | |
442 | @project = projects.first |
|
445 | @project = projects.first | |
443 | else |
|
446 | else | |
444 | # TODO: let users bulk edit/move/destroy issues from different projects |
|
447 | # TODO: let users bulk edit/move/destroy issues from different projects | |
445 | render_error 'Can not bulk edit/move/destroy issues from different projects' and return false |
|
448 | render_error 'Can not bulk edit/move/destroy issues from different projects' and return false | |
446 | end |
|
449 | end | |
447 | rescue ActiveRecord::RecordNotFound |
|
450 | rescue ActiveRecord::RecordNotFound | |
448 | render_404 |
|
451 | render_404 | |
449 | end |
|
452 | end | |
450 |
|
453 | |||
451 | def find_project |
|
454 | def find_project | |
452 | @project = Project.find(params[:project_id]) |
|
455 | @project = Project.find(params[:project_id]) | |
453 | rescue ActiveRecord::RecordNotFound |
|
456 | rescue ActiveRecord::RecordNotFound | |
454 | render_404 |
|
457 | render_404 | |
455 | end |
|
458 | end | |
456 |
|
459 | |||
457 | def find_optional_project |
|
460 | def find_optional_project | |
458 | @project = Project.find(params[:project_id]) unless params[:project_id].blank? |
|
461 | @project = Project.find(params[:project_id]) unless params[:project_id].blank? | |
459 | allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true) |
|
462 | allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true) | |
460 | allowed ? true : deny_access |
|
463 | allowed ? true : deny_access | |
461 | rescue ActiveRecord::RecordNotFound |
|
464 | rescue ActiveRecord::RecordNotFound | |
462 | render_404 |
|
465 | render_404 | |
463 | end |
|
466 | end | |
464 |
|
467 | |||
465 | # Retrieve query from session or build a new query |
|
468 | # Retrieve query from session or build a new query | |
466 | def retrieve_query |
|
469 | def retrieve_query | |
467 | if !params[:query_id].blank? |
|
470 | if !params[:query_id].blank? | |
468 | cond = "project_id IS NULL" |
|
471 | cond = "project_id IS NULL" | |
469 | cond << " OR project_id = #{@project.id}" if @project |
|
472 | cond << " OR project_id = #{@project.id}" if @project | |
470 | @query = Query.find(params[:query_id], :conditions => cond) |
|
473 | @query = Query.find(params[:query_id], :conditions => cond) | |
471 | @query.project = @project |
|
474 | @query.project = @project | |
472 | session[:query] = {:id => @query.id, :project_id => @query.project_id} |
|
475 | session[:query] = {:id => @query.id, :project_id => @query.project_id} | |
473 | else |
|
476 | else | |
474 | if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) |
|
477 | if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) | |
475 | # Give it a name, required to be valid |
|
478 | # Give it a name, required to be valid | |
476 | @query = Query.new(:name => "_") |
|
479 | @query = Query.new(:name => "_") | |
477 | @query.project = @project |
|
480 | @query.project = @project | |
478 | if params[:fields] and params[:fields].is_a? Array |
|
481 | if params[:fields] and params[:fields].is_a? Array | |
479 | params[:fields].each do |field| |
|
482 | params[:fields].each do |field| | |
480 | @query.add_filter(field, params[:operators][field], params[:values][field]) |
|
483 | @query.add_filter(field, params[:operators][field], params[:values][field]) | |
481 | end |
|
484 | end | |
482 | else |
|
485 | else | |
483 | @query.available_filters.keys.each do |field| |
|
486 | @query.available_filters.keys.each do |field| | |
484 | @query.add_short_filter(field, params[field]) if params[field] |
|
487 | @query.add_short_filter(field, params[field]) if params[field] | |
485 | end |
|
488 | end | |
486 | end |
|
489 | end | |
487 | session[:query] = {:project_id => @query.project_id, :filters => @query.filters} |
|
490 | session[:query] = {:project_id => @query.project_id, :filters => @query.filters} | |
488 | else |
|
491 | else | |
489 | @query = Query.find_by_id(session[:query][:id]) if session[:query][:id] |
|
492 | @query = Query.find_by_id(session[:query][:id]) if session[:query][:id] | |
490 | @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters]) |
|
493 | @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters]) | |
491 | @query.project = @project |
|
494 | @query.project = @project | |
492 | end |
|
495 | end | |
493 | end |
|
496 | end | |
494 | end |
|
497 | end | |
495 | end |
|
498 | end |
@@ -1,325 +1,328 | |||||
1 | # redMine - project management software |
|
1 | # redMine - project management software | |
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2007 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 'SVG/Graph/Bar' |
|
18 | require 'SVG/Graph/Bar' | |
19 | require 'SVG/Graph/BarHorizontal' |
|
19 | require 'SVG/Graph/BarHorizontal' | |
20 | require 'digest/sha1' |
|
20 | require 'digest/sha1' | |
21 |
|
21 | |||
22 | class ChangesetNotFound < Exception; end |
|
22 | class ChangesetNotFound < Exception; end | |
23 | class InvalidRevisionParam < Exception; end |
|
23 | class InvalidRevisionParam < Exception; end | |
24 |
|
24 | |||
25 | class RepositoriesController < ApplicationController |
|
25 | class RepositoriesController < ApplicationController | |
26 | menu_item :repository |
|
26 | menu_item :repository | |
27 | before_filter :find_repository, :except => :edit |
|
27 | before_filter :find_repository, :except => :edit | |
28 | before_filter :find_project, :only => :edit |
|
28 | before_filter :find_project, :only => :edit | |
29 | before_filter :authorize |
|
29 | before_filter :authorize | |
30 | accept_key_auth :revisions |
|
30 | accept_key_auth :revisions | |
31 |
|
31 | |||
32 | rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed |
|
32 | rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed | |
33 |
|
33 | |||
34 | def edit |
|
34 | def edit | |
35 | @repository = @project.repository |
|
35 | @repository = @project.repository | |
36 | if !@repository |
|
36 | if !@repository | |
37 | @repository = Repository.factory(params[:repository_scm]) |
|
37 | @repository = Repository.factory(params[:repository_scm]) | |
38 | @repository.project = @project if @repository |
|
38 | @repository.project = @project if @repository | |
39 | end |
|
39 | end | |
40 | if request.post? && @repository |
|
40 | if request.post? && @repository | |
41 | @repository.attributes = params[:repository] |
|
41 | @repository.attributes = params[:repository] | |
42 | @repository.save |
|
42 | @repository.save | |
43 | end |
|
43 | end | |
44 | render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'} |
|
44 | render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'} | |
45 | end |
|
45 | end | |
46 |
|
46 | |||
47 | def committers |
|
47 | def committers | |
48 | @committers = @repository.committers |
|
48 | @committers = @repository.committers | |
49 | @users = @project.users |
|
49 | @users = @project.users | |
50 | additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id) |
|
50 | additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id) | |
51 | @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty? |
|
51 | @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty? | |
52 | @users.compact! |
|
52 | @users.compact! | |
53 | @users.sort! |
|
53 | @users.sort! | |
54 | if request.post? && params[:committers].is_a?(Hash) |
|
54 | if request.post? && params[:committers].is_a?(Hash) | |
55 | # Build a hash with repository usernames as keys and corresponding user ids as values |
|
55 | # Build a hash with repository usernames as keys and corresponding user ids as values | |
56 | @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} |
|
56 | @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} | |
57 | flash[:notice] = l(:notice_successful_update) |
|
57 | flash[:notice] = l(:notice_successful_update) | |
58 | redirect_to :action => 'committers', :id => @project |
|
58 | redirect_to :action => 'committers', :id => @project | |
59 | end |
|
59 | end | |
60 | end |
|
60 | end | |
61 |
|
61 | |||
62 | def destroy |
|
62 | def destroy | |
63 | @repository.destroy |
|
63 | @repository.destroy | |
64 | redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository' |
|
64 | redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository' | |
65 | end |
|
65 | end | |
66 |
|
66 | |||
67 | def show |
|
67 | def show | |
68 | # check if new revisions have been committed in the repository |
|
68 | # check if new revisions have been committed in the repository | |
69 | @repository.fetch_changesets if Setting.autofetch_changesets? |
|
69 | @repository.fetch_changesets if Setting.autofetch_changesets? | |
70 | # root entries |
|
70 | # root entries | |
71 | @entries = @repository.entries('', @rev) |
|
71 | @entries = @repository.entries('', @rev) | |
72 | # latest changesets |
|
72 | # latest changesets | |
73 | @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC") |
|
73 | @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC") | |
74 | show_error_not_found unless @entries || @changesets.any? |
|
74 | show_error_not_found unless @entries || @changesets.any? | |
75 | end |
|
75 | end | |
76 |
|
76 | |||
77 | def browse |
|
77 | def browse | |
78 | @entries = @repository.entries(@path, @rev) |
|
78 | @entries = @repository.entries(@path, @rev) | |
79 | if request.xhr? |
|
79 | if request.xhr? | |
80 | @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) |
|
80 | @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) | |
81 | else |
|
81 | else | |
82 | show_error_not_found and return unless @entries |
|
82 | show_error_not_found and return unless @entries | |
83 | @properties = @repository.properties(@path, @rev) |
|
83 | @properties = @repository.properties(@path, @rev) | |
84 | render :action => 'browse' |
|
84 | render :action => 'browse' | |
85 | end |
|
85 | end | |
86 | end |
|
86 | end | |
87 |
|
87 | |||
88 | def changes |
|
88 | def changes | |
89 | @entry = @repository.entry(@path, @rev) |
|
89 | @entry = @repository.entry(@path, @rev) | |
90 | show_error_not_found and return unless @entry |
|
90 | show_error_not_found and return unless @entry | |
91 | @changesets = @repository.changesets_for_path(@path) |
|
91 | @changesets = @repository.changesets_for_path(@path) | |
92 | @properties = @repository.properties(@path, @rev) |
|
92 | @properties = @repository.properties(@path, @rev) | |
93 | end |
|
93 | end | |
94 |
|
94 | |||
95 | def revisions |
|
95 | def revisions | |
96 | @changeset_count = @repository.changesets.count |
|
96 | @changeset_count = @repository.changesets.count | |
97 | @changeset_pages = Paginator.new self, @changeset_count, |
|
97 | @changeset_pages = Paginator.new self, @changeset_count, | |
98 | per_page_option, |
|
98 | per_page_option, | |
99 | params['page'] |
|
99 | params['page'] | |
100 | @changesets = @repository.changesets.find(:all, |
|
100 | @changesets = @repository.changesets.find(:all, | |
101 | :limit => @changeset_pages.items_per_page, |
|
101 | :limit => @changeset_pages.items_per_page, | |
102 | :offset => @changeset_pages.current.offset, |
|
102 | :offset => @changeset_pages.current.offset, | |
103 | :include => :user) |
|
103 | :include => :user) | |
104 |
|
104 | |||
105 | respond_to do |format| |
|
105 | respond_to do |format| | |
106 | format.html { render :layout => false if request.xhr? } |
|
106 | format.html { render :layout => false if request.xhr? } | |
107 | format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } |
|
107 | format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } | |
108 | end |
|
108 | end | |
109 | end |
|
109 | end | |
110 |
|
110 | |||
111 | def entry |
|
111 | def entry | |
112 | @entry = @repository.entry(@path, @rev) |
|
112 | @entry = @repository.entry(@path, @rev) | |
113 | show_error_not_found and return unless @entry |
|
113 | show_error_not_found and return unless @entry | |
114 |
|
114 | |||
115 | # If the entry is a dir, show the browser |
|
115 | # If the entry is a dir, show the browser | |
116 | browse and return if @entry.is_dir? |
|
116 | browse and return if @entry.is_dir? | |
117 |
|
117 | |||
118 | @content = @repository.cat(@path, @rev) |
|
118 | @content = @repository.cat(@path, @rev) | |
119 | show_error_not_found and return unless @content |
|
119 | show_error_not_found and return unless @content | |
120 | if 'raw' == params[:format] || @content.is_binary_data? |
|
120 | if 'raw' == params[:format] || @content.is_binary_data? | |
121 | # Force the download if it's a binary file |
|
121 | # Force the download if it's a binary file | |
122 | send_data @content, :filename => @path.split('/').last |
|
122 | send_data @content, :filename => @path.split('/').last | |
123 | else |
|
123 | else | |
124 | # Prevent empty lines when displaying a file with Windows style eol |
|
124 | # Prevent empty lines when displaying a file with Windows style eol | |
125 | @content.gsub!("\r\n", "\n") |
|
125 | @content.gsub!("\r\n", "\n") | |
126 | end |
|
126 | end | |
127 | end |
|
127 | end | |
128 |
|
128 | |||
129 | def annotate |
|
129 | def annotate | |
|
130 | @entry = @repository.entry(@path, @rev) | |||
|
131 | show_error_not_found and return unless @entry | |||
|
132 | ||||
130 | @annotate = @repository.scm.annotate(@path, @rev) |
|
133 | @annotate = @repository.scm.annotate(@path, @rev) | |
131 | render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty? |
|
134 | render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty? | |
132 | end |
|
135 | end | |
133 |
|
136 | |||
134 | def revision |
|
137 | def revision | |
135 | @changeset = @repository.changesets.find_by_revision(@rev) |
|
138 | @changeset = @repository.changesets.find_by_revision(@rev) | |
136 | raise ChangesetNotFound unless @changeset |
|
139 | raise ChangesetNotFound unless @changeset | |
137 |
|
140 | |||
138 | respond_to do |format| |
|
141 | respond_to do |format| | |
139 | format.html |
|
142 | format.html | |
140 | format.js {render :layout => false} |
|
143 | format.js {render :layout => false} | |
141 | end |
|
144 | end | |
142 | rescue ChangesetNotFound |
|
145 | rescue ChangesetNotFound | |
143 | show_error_not_found |
|
146 | show_error_not_found | |
144 | end |
|
147 | end | |
145 |
|
148 | |||
146 | def diff |
|
149 | def diff | |
147 | if params[:format] == 'diff' |
|
150 | if params[:format] == 'diff' | |
148 | @diff = @repository.diff(@path, @rev, @rev_to) |
|
151 | @diff = @repository.diff(@path, @rev, @rev_to) | |
149 | show_error_not_found and return unless @diff |
|
152 | show_error_not_found and return unless @diff | |
150 | filename = "changeset_r#{@rev}" |
|
153 | filename = "changeset_r#{@rev}" | |
151 | filename << "_r#{@rev_to}" if @rev_to |
|
154 | filename << "_r#{@rev_to}" if @rev_to | |
152 | send_data @diff.join, :filename => "#{filename}.diff", |
|
155 | send_data @diff.join, :filename => "#{filename}.diff", | |
153 | :type => 'text/x-patch', |
|
156 | :type => 'text/x-patch', | |
154 | :disposition => 'attachment' |
|
157 | :disposition => 'attachment' | |
155 | else |
|
158 | else | |
156 | @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' |
|
159 | @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' | |
157 | @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) |
|
160 | @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) | |
158 |
|
161 | |||
159 | # Save diff type as user preference |
|
162 | # Save diff type as user preference | |
160 | if User.current.logged? && @diff_type != User.current.pref[:diff_type] |
|
163 | if User.current.logged? && @diff_type != User.current.pref[:diff_type] | |
161 | User.current.pref[:diff_type] = @diff_type |
|
164 | User.current.pref[:diff_type] = @diff_type | |
162 | User.current.preference.save |
|
165 | User.current.preference.save | |
163 | end |
|
166 | end | |
164 |
|
167 | |||
165 | @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}") |
|
168 | @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}") | |
166 | unless read_fragment(@cache_key) |
|
169 | unless read_fragment(@cache_key) | |
167 | @diff = @repository.diff(@path, @rev, @rev_to) |
|
170 | @diff = @repository.diff(@path, @rev, @rev_to) | |
168 | show_error_not_found unless @diff |
|
171 | show_error_not_found unless @diff | |
169 | end |
|
172 | end | |
170 | end |
|
173 | end | |
171 | end |
|
174 | end | |
172 |
|
175 | |||
173 | def stats |
|
176 | def stats | |
174 | end |
|
177 | end | |
175 |
|
178 | |||
176 | def graph |
|
179 | def graph | |
177 | data = nil |
|
180 | data = nil | |
178 | case params[:graph] |
|
181 | case params[:graph] | |
179 | when "commits_per_month" |
|
182 | when "commits_per_month" | |
180 | data = graph_commits_per_month(@repository) |
|
183 | data = graph_commits_per_month(@repository) | |
181 | when "commits_per_author" |
|
184 | when "commits_per_author" | |
182 | data = graph_commits_per_author(@repository) |
|
185 | data = graph_commits_per_author(@repository) | |
183 | end |
|
186 | end | |
184 | if data |
|
187 | if data | |
185 | headers["Content-Type"] = "image/svg+xml" |
|
188 | headers["Content-Type"] = "image/svg+xml" | |
186 | send_data(data, :type => "image/svg+xml", :disposition => "inline") |
|
189 | send_data(data, :type => "image/svg+xml", :disposition => "inline") | |
187 | else |
|
190 | else | |
188 | render_404 |
|
191 | render_404 | |
189 | end |
|
192 | end | |
190 | end |
|
193 | end | |
191 |
|
194 | |||
192 | private |
|
195 | private | |
193 | def find_project |
|
196 | def find_project | |
194 | @project = Project.find(params[:id]) |
|
197 | @project = Project.find(params[:id]) | |
195 | rescue ActiveRecord::RecordNotFound |
|
198 | rescue ActiveRecord::RecordNotFound | |
196 | render_404 |
|
199 | render_404 | |
197 | end |
|
200 | end | |
198 |
|
201 | |||
199 | REV_PARAM_RE = %r{^[a-f0-9]*$} |
|
202 | REV_PARAM_RE = %r{^[a-f0-9]*$} | |
200 |
|
203 | |||
201 | def find_repository |
|
204 | def find_repository | |
202 | @project = Project.find(params[:id]) |
|
205 | @project = Project.find(params[:id]) | |
203 | @repository = @project.repository |
|
206 | @repository = @project.repository | |
204 | render_404 and return false unless @repository |
|
207 | render_404 and return false unless @repository | |
205 | @path = params[:path].join('/') unless params[:path].nil? |
|
208 | @path = params[:path].join('/') unless params[:path].nil? | |
206 | @path ||= '' |
|
209 | @path ||= '' | |
207 | @rev = params[:rev] |
|
210 | @rev = params[:rev] | |
208 | @rev_to = params[:rev_to] |
|
211 | @rev_to = params[:rev_to] | |
209 | raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE) |
|
212 | raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE) | |
210 | rescue ActiveRecord::RecordNotFound |
|
213 | rescue ActiveRecord::RecordNotFound | |
211 | render_404 |
|
214 | render_404 | |
212 | rescue InvalidRevisionParam |
|
215 | rescue InvalidRevisionParam | |
213 | show_error_not_found |
|
216 | show_error_not_found | |
214 | end |
|
217 | end | |
215 |
|
218 | |||
216 | def show_error_not_found |
|
219 | def show_error_not_found | |
217 | render_error l(:error_scm_not_found) |
|
220 | render_error l(:error_scm_not_found) | |
218 | end |
|
221 | end | |
219 |
|
222 | |||
220 | # Handler for Redmine::Scm::Adapters::CommandFailed exception |
|
223 | # Handler for Redmine::Scm::Adapters::CommandFailed exception | |
221 | def show_error_command_failed(exception) |
|
224 | def show_error_command_failed(exception) | |
222 | render_error l(:error_scm_command_failed, exception.message) |
|
225 | render_error l(:error_scm_command_failed, exception.message) | |
223 | end |
|
226 | end | |
224 |
|
227 | |||
225 | def graph_commits_per_month(repository) |
|
228 | def graph_commits_per_month(repository) | |
226 | @date_to = Date.today |
|
229 | @date_to = Date.today | |
227 | @date_from = @date_to << 11 |
|
230 | @date_from = @date_to << 11 | |
228 | @date_from = Date.civil(@date_from.year, @date_from.month, 1) |
|
231 | @date_from = Date.civil(@date_from.year, @date_from.month, 1) | |
229 | commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) |
|
232 | commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) | |
230 | commits_by_month = [0] * 12 |
|
233 | commits_by_month = [0] * 12 | |
231 | commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last } |
|
234 | commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last } | |
232 |
|
235 | |||
233 | changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) |
|
236 | changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) | |
234 | changes_by_month = [0] * 12 |
|
237 | changes_by_month = [0] * 12 | |
235 | changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last } |
|
238 | changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last } | |
236 |
|
239 | |||
237 | fields = [] |
|
240 | fields = [] | |
238 | month_names = l(:actionview_datehelper_select_month_names_abbr).split(',') |
|
241 | month_names = l(:actionview_datehelper_select_month_names_abbr).split(',') | |
239 | 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]} |
|
242 | 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]} | |
240 |
|
243 | |||
241 | graph = SVG::Graph::Bar.new( |
|
244 | graph = SVG::Graph::Bar.new( | |
242 | :height => 300, |
|
245 | :height => 300, | |
243 | :width => 800, |
|
246 | :width => 800, | |
244 | :fields => fields.reverse, |
|
247 | :fields => fields.reverse, | |
245 | :stack => :side, |
|
248 | :stack => :side, | |
246 | :scale_integers => true, |
|
249 | :scale_integers => true, | |
247 | :step_x_labels => 2, |
|
250 | :step_x_labels => 2, | |
248 | :show_data_values => false, |
|
251 | :show_data_values => false, | |
249 | :graph_title => l(:label_commits_per_month), |
|
252 | :graph_title => l(:label_commits_per_month), | |
250 | :show_graph_title => true |
|
253 | :show_graph_title => true | |
251 | ) |
|
254 | ) | |
252 |
|
255 | |||
253 | graph.add_data( |
|
256 | graph.add_data( | |
254 | :data => commits_by_month[0..11].reverse, |
|
257 | :data => commits_by_month[0..11].reverse, | |
255 | :title => l(:label_revision_plural) |
|
258 | :title => l(:label_revision_plural) | |
256 | ) |
|
259 | ) | |
257 |
|
260 | |||
258 | graph.add_data( |
|
261 | graph.add_data( | |
259 | :data => changes_by_month[0..11].reverse, |
|
262 | :data => changes_by_month[0..11].reverse, | |
260 | :title => l(:label_change_plural) |
|
263 | :title => l(:label_change_plural) | |
261 | ) |
|
264 | ) | |
262 |
|
265 | |||
263 | graph.burn |
|
266 | graph.burn | |
264 | end |
|
267 | end | |
265 |
|
268 | |||
266 | def graph_commits_per_author(repository) |
|
269 | def graph_commits_per_author(repository) | |
267 | commits_by_author = repository.changesets.count(:all, :group => :committer) |
|
270 | commits_by_author = repository.changesets.count(:all, :group => :committer) | |
268 | commits_by_author.sort! {|x, y| x.last <=> y.last} |
|
271 | commits_by_author.sort! {|x, y| x.last <=> y.last} | |
269 |
|
272 | |||
270 | changes_by_author = repository.changes.count(:all, :group => :committer) |
|
273 | changes_by_author = repository.changes.count(:all, :group => :committer) | |
271 | h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} |
|
274 | h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} | |
272 |
|
275 | |||
273 | fields = commits_by_author.collect {|r| r.first} |
|
276 | fields = commits_by_author.collect {|r| r.first} | |
274 | commits_data = commits_by_author.collect {|r| r.last} |
|
277 | commits_data = commits_by_author.collect {|r| r.last} | |
275 | changes_data = commits_by_author.collect {|r| h[r.first] || 0} |
|
278 | changes_data = commits_by_author.collect {|r| h[r.first] || 0} | |
276 |
|
279 | |||
277 | fields = fields + [""]*(10 - fields.length) if fields.length<10 |
|
280 | fields = fields + [""]*(10 - fields.length) if fields.length<10 | |
278 | commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 |
|
281 | commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 | |
279 | changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10 |
|
282 | changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10 | |
280 |
|
283 | |||
281 | # Remove email adress in usernames |
|
284 | # Remove email adress in usernames | |
282 | fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } |
|
285 | fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } | |
283 |
|
286 | |||
284 | graph = SVG::Graph::BarHorizontal.new( |
|
287 | graph = SVG::Graph::BarHorizontal.new( | |
285 | :height => 400, |
|
288 | :height => 400, | |
286 | :width => 800, |
|
289 | :width => 800, | |
287 | :fields => fields, |
|
290 | :fields => fields, | |
288 | :stack => :side, |
|
291 | :stack => :side, | |
289 | :scale_integers => true, |
|
292 | :scale_integers => true, | |
290 | :show_data_values => false, |
|
293 | :show_data_values => false, | |
291 | :rotate_y_labels => false, |
|
294 | :rotate_y_labels => false, | |
292 | :graph_title => l(:label_commits_per_author), |
|
295 | :graph_title => l(:label_commits_per_author), | |
293 | :show_graph_title => true |
|
296 | :show_graph_title => true | |
294 | ) |
|
297 | ) | |
295 |
|
298 | |||
296 | graph.add_data( |
|
299 | graph.add_data( | |
297 | :data => commits_data, |
|
300 | :data => commits_data, | |
298 | :title => l(:label_revision_plural) |
|
301 | :title => l(:label_revision_plural) | |
299 | ) |
|
302 | ) | |
300 |
|
303 | |||
301 | graph.add_data( |
|
304 | graph.add_data( | |
302 | :data => changes_data, |
|
305 | :data => changes_data, | |
303 | :title => l(:label_change_plural) |
|
306 | :title => l(:label_change_plural) | |
304 | ) |
|
307 | ) | |
305 |
|
308 | |||
306 | graph.burn |
|
309 | graph.burn | |
307 | end |
|
310 | end | |
308 |
|
311 | |||
309 | end |
|
312 | end | |
310 |
|
313 | |||
311 | class Date |
|
314 | class Date | |
312 | def months_ago(date = Date.today) |
|
315 | def months_ago(date = Date.today) | |
313 | (date.year - self.year)*12 + (date.month - self.month) |
|
316 | (date.year - self.year)*12 + (date.month - self.month) | |
314 | end |
|
317 | end | |
315 |
|
318 | |||
316 | def weeks_ago(date = Date.today) |
|
319 | def weeks_ago(date = Date.today) | |
317 | (date.year - self.year)*52 + (date.cweek - self.cweek) |
|
320 | (date.year - self.year)*52 + (date.cweek - self.cweek) | |
318 | end |
|
321 | end | |
319 | end |
|
322 | end | |
320 |
|
323 | |||
321 | class String |
|
324 | class String | |
322 | def with_leading_slash |
|
325 | def with_leading_slash | |
323 | starts_with?('/') ? self : "/#{self}" |
|
326 | starts_with?('/') ? self : "/#{self}" | |
324 | end |
|
327 | end | |
325 | end |
|
328 | end |
@@ -1,247 +1,248 | |||||
1 | # redMine - project management software |
|
1 | # redMine - project management software | |
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2007 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 Mailer < ActionMailer::Base |
|
18 | class Mailer < ActionMailer::Base | |
19 | helper :application |
|
19 | helper :application | |
20 | helper :issues |
|
20 | helper :issues | |
21 | helper :custom_fields |
|
21 | helper :custom_fields | |
22 |
|
22 | |||
23 | include ActionController::UrlWriter |
|
23 | include ActionController::UrlWriter | |
24 |
|
24 | |||
25 | def issue_add(issue) |
|
25 | def issue_add(issue) | |
26 | redmine_headers 'Project' => issue.project.identifier, |
|
26 | redmine_headers 'Project' => issue.project.identifier, | |
27 | 'Issue-Id' => issue.id, |
|
27 | 'Issue-Id' => issue.id, | |
28 | 'Issue-Author' => issue.author.login |
|
28 | 'Issue-Author' => issue.author.login | |
29 | redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to |
|
29 | redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to | |
30 | recipients issue.recipients |
|
30 | recipients issue.recipients | |
|
31 | cc(issue.watcher_recipients - @recipients) | |||
31 | subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" |
|
32 | subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" | |
32 | body :issue => issue, |
|
33 | body :issue => issue, | |
33 | :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) |
|
34 | :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) | |
34 | end |
|
35 | end | |
35 |
|
36 | |||
36 | def issue_edit(journal) |
|
37 | def issue_edit(journal) | |
37 | issue = journal.journalized |
|
38 | issue = journal.journalized | |
38 | redmine_headers 'Project' => issue.project.identifier, |
|
39 | redmine_headers 'Project' => issue.project.identifier, | |
39 | 'Issue-Id' => issue.id, |
|
40 | 'Issue-Id' => issue.id, | |
40 | 'Issue-Author' => issue.author.login |
|
41 | 'Issue-Author' => issue.author.login | |
41 | redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to |
|
42 | redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to | |
42 | recipients issue.recipients |
|
43 | recipients issue.recipients | |
43 | # Watchers in cc |
|
44 | # Watchers in cc | |
44 | cc(issue.watcher_recipients - @recipients) |
|
45 | cc(issue.watcher_recipients - @recipients) | |
45 | s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " |
|
46 | s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " | |
46 | s << "(#{issue.status.name}) " if journal.new_value_for('status_id') |
|
47 | s << "(#{issue.status.name}) " if journal.new_value_for('status_id') | |
47 | s << issue.subject |
|
48 | s << issue.subject | |
48 | subject s |
|
49 | subject s | |
49 | body :issue => issue, |
|
50 | body :issue => issue, | |
50 | :journal => journal, |
|
51 | :journal => journal, | |
51 | :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) |
|
52 | :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) | |
52 | end |
|
53 | end | |
53 |
|
54 | |||
54 | def reminder(user, issues, days) |
|
55 | def reminder(user, issues, days) | |
55 | set_language_if_valid user.language |
|
56 | set_language_if_valid user.language | |
56 | recipients user.mail |
|
57 | recipients user.mail | |
57 | subject l(:mail_subject_reminder, issues.size) |
|
58 | subject l(:mail_subject_reminder, issues.size) | |
58 | body :issues => issues, |
|
59 | body :issues => issues, | |
59 | :days => days, |
|
60 | :days => days, | |
60 | :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc') |
|
61 | :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc') | |
61 | end |
|
62 | end | |
62 |
|
63 | |||
63 | def document_added(document) |
|
64 | def document_added(document) | |
64 | redmine_headers 'Project' => document.project.identifier |
|
65 | redmine_headers 'Project' => document.project.identifier | |
65 | recipients document.project.recipients |
|
66 | recipients document.project.recipients | |
66 | subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" |
|
67 | subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" | |
67 | body :document => document, |
|
68 | body :document => document, | |
68 | :document_url => url_for(:controller => 'documents', :action => 'show', :id => document) |
|
69 | :document_url => url_for(:controller => 'documents', :action => 'show', :id => document) | |
69 | end |
|
70 | end | |
70 |
|
71 | |||
71 | def attachments_added(attachments) |
|
72 | def attachments_added(attachments) | |
72 | container = attachments.first.container |
|
73 | container = attachments.first.container | |
73 | added_to = '' |
|
74 | added_to = '' | |
74 | added_to_url = '' |
|
75 | added_to_url = '' | |
75 | case container.class.name |
|
76 | case container.class.name | |
76 | when 'Version' |
|
77 | when 'Version' | |
77 | added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id) |
|
78 | added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id) | |
78 | added_to = "#{l(:label_version)}: #{container.name}" |
|
79 | added_to = "#{l(:label_version)}: #{container.name}" | |
79 | when 'Document' |
|
80 | when 'Document' | |
80 | added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) |
|
81 | added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) | |
81 | added_to = "#{l(:label_document)}: #{container.title}" |
|
82 | added_to = "#{l(:label_document)}: #{container.title}" | |
82 | end |
|
83 | end | |
83 | redmine_headers 'Project' => container.project.identifier |
|
84 | redmine_headers 'Project' => container.project.identifier | |
84 | recipients container.project.recipients |
|
85 | recipients container.project.recipients | |
85 | subject "[#{container.project.name}] #{l(:label_attachment_new)}" |
|
86 | subject "[#{container.project.name}] #{l(:label_attachment_new)}" | |
86 | body :attachments => attachments, |
|
87 | body :attachments => attachments, | |
87 | :added_to => added_to, |
|
88 | :added_to => added_to, | |
88 | :added_to_url => added_to_url |
|
89 | :added_to_url => added_to_url | |
89 | end |
|
90 | end | |
90 |
|
91 | |||
91 | def news_added(news) |
|
92 | def news_added(news) | |
92 | redmine_headers 'Project' => news.project.identifier |
|
93 | redmine_headers 'Project' => news.project.identifier | |
93 | recipients news.project.recipients |
|
94 | recipients news.project.recipients | |
94 | subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}" |
|
95 | subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}" | |
95 | body :news => news, |
|
96 | body :news => news, | |
96 | :news_url => url_for(:controller => 'news', :action => 'show', :id => news) |
|
97 | :news_url => url_for(:controller => 'news', :action => 'show', :id => news) | |
97 | end |
|
98 | end | |
98 |
|
99 | |||
99 | def message_posted(message, recipients) |
|
100 | def message_posted(message, recipients) | |
100 | redmine_headers 'Project' => message.project.identifier, |
|
101 | redmine_headers 'Project' => message.project.identifier, | |
101 | 'Topic-Id' => (message.parent_id || message.id) |
|
102 | 'Topic-Id' => (message.parent_id || message.id) | |
102 | recipients(recipients) |
|
103 | recipients(recipients) | |
103 | subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}" |
|
104 | subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}" | |
104 | body :message => message, |
|
105 | body :message => message, | |
105 | :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root) |
|
106 | :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root) | |
106 | end |
|
107 | end | |
107 |
|
108 | |||
108 | def account_information(user, password) |
|
109 | def account_information(user, password) | |
109 | set_language_if_valid user.language |
|
110 | set_language_if_valid user.language | |
110 | recipients user.mail |
|
111 | recipients user.mail | |
111 | subject l(:mail_subject_register, Setting.app_title) |
|
112 | subject l(:mail_subject_register, Setting.app_title) | |
112 | body :user => user, |
|
113 | body :user => user, | |
113 | :password => password, |
|
114 | :password => password, | |
114 | :login_url => url_for(:controller => 'account', :action => 'login') |
|
115 | :login_url => url_for(:controller => 'account', :action => 'login') | |
115 | end |
|
116 | end | |
116 |
|
117 | |||
117 | def account_activation_request(user) |
|
118 | def account_activation_request(user) | |
118 | # Send the email to all active administrators |
|
119 | # Send the email to all active administrators | |
119 | recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact |
|
120 | recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact | |
120 | subject l(:mail_subject_account_activation_request, Setting.app_title) |
|
121 | subject l(:mail_subject_account_activation_request, Setting.app_title) | |
121 | body :user => user, |
|
122 | body :user => user, | |
122 | :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc') |
|
123 | :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc') | |
123 | end |
|
124 | end | |
124 |
|
125 | |||
125 | def lost_password(token) |
|
126 | def lost_password(token) | |
126 | set_language_if_valid(token.user.language) |
|
127 | set_language_if_valid(token.user.language) | |
127 | recipients token.user.mail |
|
128 | recipients token.user.mail | |
128 | subject l(:mail_subject_lost_password, Setting.app_title) |
|
129 | subject l(:mail_subject_lost_password, Setting.app_title) | |
129 | body :token => token, |
|
130 | body :token => token, | |
130 | :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value) |
|
131 | :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value) | |
131 | end |
|
132 | end | |
132 |
|
133 | |||
133 | def register(token) |
|
134 | def register(token) | |
134 | set_language_if_valid(token.user.language) |
|
135 | set_language_if_valid(token.user.language) | |
135 | recipients token.user.mail |
|
136 | recipients token.user.mail | |
136 | subject l(:mail_subject_register, Setting.app_title) |
|
137 | subject l(:mail_subject_register, Setting.app_title) | |
137 | body :token => token, |
|
138 | body :token => token, | |
138 | :url => url_for(:controller => 'account', :action => 'activate', :token => token.value) |
|
139 | :url => url_for(:controller => 'account', :action => 'activate', :token => token.value) | |
139 | end |
|
140 | end | |
140 |
|
141 | |||
141 | def test(user) |
|
142 | def test(user) | |
142 | set_language_if_valid(user.language) |
|
143 | set_language_if_valid(user.language) | |
143 | recipients user.mail |
|
144 | recipients user.mail | |
144 | subject 'Redmine test' |
|
145 | subject 'Redmine test' | |
145 | body :url => url_for(:controller => 'welcome') |
|
146 | body :url => url_for(:controller => 'welcome') | |
146 | end |
|
147 | end | |
147 |
|
148 | |||
148 | # Overrides default deliver! method to prevent from sending an email |
|
149 | # Overrides default deliver! method to prevent from sending an email | |
149 | # with no recipient, cc or bcc |
|
150 | # with no recipient, cc or bcc | |
150 | def deliver!(mail = @mail) |
|
151 | def deliver!(mail = @mail) | |
151 | return false if (recipients.nil? || recipients.empty?) && |
|
152 | return false if (recipients.nil? || recipients.empty?) && | |
152 | (cc.nil? || cc.empty?) && |
|
153 | (cc.nil? || cc.empty?) && | |
153 | (bcc.nil? || bcc.empty?) |
|
154 | (bcc.nil? || bcc.empty?) | |
154 | super |
|
155 | super | |
155 | end |
|
156 | end | |
156 |
|
157 | |||
157 | # Sends reminders to issue assignees |
|
158 | # Sends reminders to issue assignees | |
158 | # Available options: |
|
159 | # Available options: | |
159 | # * :days => how many days in the future to remind about (defaults to 7) |
|
160 | # * :days => how many days in the future to remind about (defaults to 7) | |
160 | # * :tracker => id of tracker for filtering issues (defaults to all trackers) |
|
161 | # * :tracker => id of tracker for filtering issues (defaults to all trackers) | |
161 | # * :project => id or identifier of project to process (defaults to all projects) |
|
162 | # * :project => id or identifier of project to process (defaults to all projects) | |
162 | def self.reminders(options={}) |
|
163 | def self.reminders(options={}) | |
163 | days = options[:days] || 7 |
|
164 | days = options[:days] || 7 | |
164 | project = options[:project] ? Project.find(options[:project]) : nil |
|
165 | project = options[:project] ? Project.find(options[:project]) : nil | |
165 | tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil |
|
166 | tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil | |
166 |
|
167 | |||
167 | s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date] |
|
168 | s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date] | |
168 | s << "#{Issue.table_name}.assigned_to_id IS NOT NULL" |
|
169 | s << "#{Issue.table_name}.assigned_to_id IS NOT NULL" | |
169 | s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}" |
|
170 | s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}" | |
170 | s << "#{Issue.table_name}.project_id = #{project.id}" if project |
|
171 | s << "#{Issue.table_name}.project_id = #{project.id}" if project | |
171 | s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker |
|
172 | s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker | |
172 |
|
173 | |||
173 | issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker], |
|
174 | issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker], | |
174 | :conditions => s.conditions |
|
175 | :conditions => s.conditions | |
175 | ).group_by(&:assigned_to) |
|
176 | ).group_by(&:assigned_to) | |
176 | issues_by_assignee.each do |assignee, issues| |
|
177 | issues_by_assignee.each do |assignee, issues| | |
177 | deliver_reminder(assignee, issues, days) unless assignee.nil? |
|
178 | deliver_reminder(assignee, issues, days) unless assignee.nil? | |
178 | end |
|
179 | end | |
179 | end |
|
180 | end | |
180 |
|
181 | |||
181 | private |
|
182 | private | |
182 | def initialize_defaults(method_name) |
|
183 | def initialize_defaults(method_name) | |
183 | super |
|
184 | super | |
184 | set_language_if_valid Setting.default_language |
|
185 | set_language_if_valid Setting.default_language | |
185 | from Setting.mail_from |
|
186 | from Setting.mail_from | |
186 |
|
187 | |||
187 | # URL options |
|
188 | # URL options | |
188 | h = Setting.host_name |
|
189 | h = Setting.host_name | |
189 | h = h.to_s.gsub(%r{\/.*$}, '') unless ActionController::AbstractRequest.relative_url_root.blank? |
|
190 | h = h.to_s.gsub(%r{\/.*$}, '') unless ActionController::AbstractRequest.relative_url_root.blank? | |
190 | default_url_options[:host] = h |
|
191 | default_url_options[:host] = h | |
191 | default_url_options[:protocol] = Setting.protocol |
|
192 | default_url_options[:protocol] = Setting.protocol | |
192 |
|
193 | |||
193 | # Common headers |
|
194 | # Common headers | |
194 | headers 'X-Mailer' => 'Redmine', |
|
195 | headers 'X-Mailer' => 'Redmine', | |
195 | 'X-Redmine-Host' => Setting.host_name, |
|
196 | 'X-Redmine-Host' => Setting.host_name, | |
196 | 'X-Redmine-Site' => Setting.app_title |
|
197 | 'X-Redmine-Site' => Setting.app_title | |
197 | end |
|
198 | end | |
198 |
|
199 | |||
199 | # Appends a Redmine header field (name is prepended with 'X-Redmine-') |
|
200 | # Appends a Redmine header field (name is prepended with 'X-Redmine-') | |
200 | def redmine_headers(h) |
|
201 | def redmine_headers(h) | |
201 | h.each { |k,v| headers["X-Redmine-#{k}"] = v } |
|
202 | h.each { |k,v| headers["X-Redmine-#{k}"] = v } | |
202 | end |
|
203 | end | |
203 |
|
204 | |||
204 | # Overrides the create_mail method |
|
205 | # Overrides the create_mail method | |
205 | def create_mail |
|
206 | def create_mail | |
206 | # Removes the current user from the recipients and cc |
|
207 | # Removes the current user from the recipients and cc | |
207 | # if he doesn't want to receive notifications about what he does |
|
208 | # if he doesn't want to receive notifications about what he does | |
208 | if User.current.pref[:no_self_notified] |
|
209 | if User.current.pref[:no_self_notified] | |
209 | recipients.delete(User.current.mail) if recipients |
|
210 | recipients.delete(User.current.mail) if recipients | |
210 | cc.delete(User.current.mail) if cc |
|
211 | cc.delete(User.current.mail) if cc | |
211 | end |
|
212 | end | |
212 | # Blind carbon copy recipients |
|
213 | # Blind carbon copy recipients | |
213 | if Setting.bcc_recipients? |
|
214 | if Setting.bcc_recipients? | |
214 | bcc([recipients, cc].flatten.compact.uniq) |
|
215 | bcc([recipients, cc].flatten.compact.uniq) | |
215 | recipients [] |
|
216 | recipients [] | |
216 | cc [] |
|
217 | cc [] | |
217 | end |
|
218 | end | |
218 | super |
|
219 | super | |
219 | end |
|
220 | end | |
220 |
|
221 | |||
221 | # Renders a message with the corresponding layout |
|
222 | # Renders a message with the corresponding layout | |
222 | def render_message(method_name, body) |
|
223 | def render_message(method_name, body) | |
223 | layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml' |
|
224 | layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml' | |
224 | body[:content_for_layout] = render(:file => method_name, :body => body) |
|
225 | body[:content_for_layout] = render(:file => method_name, :body => body) | |
225 | ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true) |
|
226 | ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true) | |
226 | end |
|
227 | end | |
227 |
|
228 | |||
228 | # for the case of plain text only |
|
229 | # for the case of plain text only | |
229 | def body(*params) |
|
230 | def body(*params) | |
230 | value = super(*params) |
|
231 | value = super(*params) | |
231 | if Setting.plain_text_mail? |
|
232 | if Setting.plain_text_mail? | |
232 | templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}") |
|
233 | templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}") | |
233 | unless String === @body or templates.empty? |
|
234 | unless String === @body or templates.empty? | |
234 | template = File.basename(templates.first) |
|
235 | template = File.basename(templates.first) | |
235 | @body[:content_for_layout] = render(:file => template, :body => @body) |
|
236 | @body[:content_for_layout] = render(:file => template, :body => @body) | |
236 | @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true) |
|
237 | @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true) | |
237 | return @body |
|
238 | return @body | |
238 | end |
|
239 | end | |
239 | end |
|
240 | end | |
240 | return value |
|
241 | return value | |
241 | end |
|
242 | end | |
242 |
|
243 | |||
243 | # Makes partial rendering work with Rails 1.2 (retro-compatibility) |
|
244 | # Makes partial rendering work with Rails 1.2 (retro-compatibility) | |
244 | def self.controller_path |
|
245 | def self.controller_path | |
245 | '' |
|
246 | '' | |
246 | end unless respond_to?('controller_path') |
|
247 | end unless respond_to?('controller_path') | |
247 | end |
|
248 | end |
@@ -1,53 +1,61 | |||||
1 | <% if @issue.new_record? %> |
|
1 | <% if @issue.new_record? %> | |
2 | <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p> |
|
2 | <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p> | |
3 | <%= observe_field :issue_tracker_id, :url => { :action => :new }, |
|
3 | <%= observe_field :issue_tracker_id, :url => { :action => :new }, | |
4 | :update => :content, |
|
4 | :update => :content, | |
5 | :with => "Form.serialize('issue-form')" %> |
|
5 | :with => "Form.serialize('issue-form')" %> | |
6 | <hr /> |
|
6 | <hr /> | |
7 | <% end %> |
|
7 | <% end %> | |
8 |
|
8 | |||
9 | <div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>> |
|
9 | <div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>> | |
10 | <p><%= f.text_field :subject, :size => 80, :required => true %></p> |
|
10 | <p><%= f.text_field :subject, :size => 80, :required => true %></p> | |
11 | <p><%= f.text_area :description, :required => true, |
|
11 | <p><%= f.text_area :description, :required => true, | |
12 | :cols => 60, |
|
12 | :cols => 60, | |
13 | :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min), |
|
13 | :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min), | |
14 | :accesskey => accesskey(:edit), |
|
14 | :accesskey => accesskey(:edit), | |
15 | :class => 'wiki-edit' %></p> |
|
15 | :class => 'wiki-edit' %></p> | |
16 | </div> |
|
16 | </div> | |
17 |
|
17 | |||
18 | <div class="splitcontentleft"> |
|
18 | <div class="splitcontentleft"> | |
19 | <% if @issue.new_record? || @allowed_statuses.any? %> |
|
19 | <% if @issue.new_record? || @allowed_statuses.any? %> | |
20 | <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p> |
|
20 | <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p> | |
21 | <% else %> |
|
21 | <% else %> | |
22 | <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p> |
|
22 | <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p> | |
23 | <% end %> |
|
23 | <% end %> | |
24 |
|
24 | |||
25 | <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p> |
|
25 | <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p> | |
26 | <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p> |
|
26 | <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p> | |
27 | <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %> |
|
27 | <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %> | |
28 | <%= prompt_to_remote(l(:label_issue_category_new), |
|
28 | <%= prompt_to_remote(l(:label_issue_category_new), | |
29 | l(:label_issue_category_new), 'category[name]', |
|
29 | l(:label_issue_category_new), 'category[name]', | |
30 | {:controller => 'projects', :action => 'add_issue_category', :id => @project}, |
|
30 | {:controller => 'projects', :action => 'add_issue_category', :id => @project}, | |
31 | :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p> |
|
31 | :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p> | |
32 | <%= content_tag('p', f.select(:fixed_version_id, |
|
32 | <%= content_tag('p', f.select(:fixed_version_id, | |
33 | (@project.versions.sort.collect {|v| [v.name, v.id]}), |
|
33 | (@project.versions.sort.collect {|v| [v.name, v.id]}), | |
34 | { :include_blank => true })) unless @project.versions.empty? %> |
|
34 | { :include_blank => true })) unless @project.versions.empty? %> | |
35 | </div> |
|
35 | </div> | |
36 |
|
36 | |||
37 | <div class="splitcontentright"> |
|
37 | <div class="splitcontentright"> | |
38 | <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p> |
|
38 | <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p> | |
39 | <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p> |
|
39 | <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p> | |
40 | <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p> |
|
40 | <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p> | |
41 | <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p> |
|
41 | <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p> | |
42 | </div> |
|
42 | </div> | |
43 |
|
43 | |||
44 | <div style="clear:both;"> </div> |
|
44 | <div style="clear:both;"> </div> | |
45 | <%= render :partial => 'form_custom_fields' %> |
|
45 | <%= render :partial => 'form_custom_fields' %> | |
46 |
|
46 | |||
47 | <% if @issue.new_record? %> |
|
47 | <% if @issue.new_record? %> | |
48 | <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p> |
|
48 | <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p> | |
49 | <% end %> |
|
49 | <% end %> | |
50 |
|
50 | |||
|
51 | <% if @issue.new_record? && User.current.allowed_to?(:add_issue_watchers, @project) -%> | |||
|
52 | <p><label><%= l(:label_issue_watchers) %></label> | |||
|
53 | <% @issue.project.users.sort.each do |user| -%> | |||
|
54 | <label class="floating"><%= check_box_tag 'issue[watcher_user_ids][]', user.id, @issue.watcher_user_ids.include?(user.id) %> <%=h user %></label> | |||
|
55 | <% end -%> | |||
|
56 | </p> | |||
|
57 | <% end %> | |||
|
58 | ||||
51 | <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> |
|
59 | <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> | |
52 |
|
60 | |||
53 | <%= wikitoolbar_for 'issue_description' %> |
|
61 | <%= wikitoolbar_for 'issue_description' %> |
@@ -1,28 +1,30 | |||||
1 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> |
|
1 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> | |
2 |
|
2 | |||
|
3 | <p><%= render :partial => 'link_to_functions' %></p> | |||
|
4 | ||||
3 | <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> |
|
5 | <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> | |
4 |
|
6 | |||
5 | <div class="autoscroll"> |
|
7 | <div class="autoscroll"> | |
6 | <table class="filecontent annotate CodeRay"> |
|
8 | <table class="filecontent annotate CodeRay"> | |
7 | <tbody> |
|
9 | <tbody> | |
8 | <% line_num = 1 %> |
|
10 | <% line_num = 1 %> | |
9 | <% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %> |
|
11 | <% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %> | |
10 | <% revision = @annotate.revisions[line_num-1] %> |
|
12 | <% revision = @annotate.revisions[line_num-1] %> | |
11 | <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>"> |
|
13 | <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>"> | |
12 | <th class="line-num"><%= line_num %></th> |
|
14 | <th class="line-num"><%= line_num %></th> | |
13 | <td class="revision"> |
|
15 | <td class="revision"> | |
14 | <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td> |
|
16 | <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td> | |
15 | <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td> |
|
17 | <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td> | |
16 | <td class="line-code"><pre><%= line %></pre></td> |
|
18 | <td class="line-code"><pre><%= line %></pre></td> | |
17 | </tr> |
|
19 | </tr> | |
18 | <% line_num += 1 %> |
|
20 | <% line_num += 1 %> | |
19 | <% end %> |
|
21 | <% end %> | |
20 | </tbody> |
|
22 | </tbody> | |
21 | </table> |
|
23 | </table> | |
22 | </div> |
|
24 | </div> | |
23 |
|
25 | |||
24 | <% html_title(l(:button_annotate)) -%> |
|
26 | <% html_title(l(:button_annotate)) -%> | |
25 |
|
27 | |||
26 | <% content_for :header_tags do %> |
|
28 | <% content_for :header_tags do %> | |
27 | <%= stylesheet_link_tag 'scm' %> |
|
29 | <%= stylesheet_link_tag 'scm' %> | |
28 | <% end %> |
|
30 | <% end %> |
@@ -1,19 +1,10 | |||||
1 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2> |
|
1 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2> | |
2 |
|
2 | |||
3 | <p> |
|
3 | <p><%= render :partial => 'link_to_functions' %></p> | |
4 | <% if @repository.supports_cat? %> |
|
|||
5 | <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> | |
|
|||
6 | <% end %> |
|
|||
7 | <% if @repository.supports_annotate? %> |
|
|||
8 | <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> | |
|
|||
9 | <% end %> |
|
|||
10 | <%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %> |
|
|||
11 | <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %> |
|
|||
12 | </p> |
|
|||
13 |
|
4 | |||
14 | <%= render_properties(@properties) %> |
|
5 | <%= render_properties(@properties) %> | |
15 |
|
6 | |||
16 | <%= render(:partial => 'revisions', |
|
7 | <%= render(:partial => 'revisions', | |
17 | :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %> |
|
8 | :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %> | |
18 |
|
9 | |||
19 | <% html_title(l(:label_change_plural)) -%> |
|
10 | <% html_title(l(:label_change_plural)) -%> |
@@ -1,7 +1,9 | |||||
1 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> |
|
1 | <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> | |
2 |
|
2 | |||
|
3 | <p><%= render :partial => 'link_to_functions' %></p> | |||
|
4 | ||||
3 | <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> |
|
5 | <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> | |
4 |
|
6 | |||
5 | <% content_for :header_tags do %> |
|
7 | <% content_for :header_tags do %> | |
6 | <%= stylesheet_link_tag "scm" %> |
|
8 | <%= stylesheet_link_tag "scm" %> | |
7 | <% end %> |
|
9 | <% end %> |
@@ -1,832 +1,839 | |||||
1 | == Redmine changelog |
|
1 | == Redmine changelog | |
2 |
|
2 | |||
3 | Redmine - project management software |
|
3 | Redmine - project management software | |
4 | Copyright (C) 2006-2008 Jean-Philippe Lang |
|
4 | Copyright (C) 2006-2008 Jean-Philippe Lang | |
5 | http://www.redmine.org/ |
|
5 | http://www.redmine.org/ | |
6 |
|
6 | |||
7 |
|
7 | |||
|
8 | == v0.8.1 | |||
|
9 | ||||
|
10 | * Select watchers on new issue form | |||
|
11 | * Show view/annotate/download links on entry and annotate views | |||
|
12 | * Fixed: Deleted files are shown when using Darcs | |||
|
13 | ||||
|
14 | ||||
8 | == 2008-12-30 v0.8.0 |
|
15 | == 2008-12-30 v0.8.0 | |
9 |
|
16 | |||
10 | * Setting added in order to limit the number of diff lines that should be displayed |
|
17 | * Setting added in order to limit the number of diff lines that should be displayed | |
11 | * Makes logged-in username in topbar linking to |
|
18 | * Makes logged-in username in topbar linking to | |
12 | * Mail handler: strip tags when receiving a html-only email |
|
19 | * Mail handler: strip tags when receiving a html-only email | |
13 | * Mail handler: add watchers before sending notification |
|
20 | * Mail handler: add watchers before sending notification | |
14 | * Adds a css class (overdue) to overdue issues on issue lists and detail views |
|
21 | * Adds a css class (overdue) to overdue issues on issue lists and detail views | |
15 | * Fixed: project activity truncated after viewing user's activity |
|
22 | * Fixed: project activity truncated after viewing user's activity | |
16 | * Fixed: email address entered for password recovery shouldn't be case-sensitive |
|
23 | * Fixed: email address entered for password recovery shouldn't be case-sensitive | |
17 | * Fixed: default flag removed when editing a default enumeration |
|
24 | * Fixed: default flag removed when editing a default enumeration | |
18 | * Fixed: default category ignored when adding a document |
|
25 | * Fixed: default category ignored when adding a document | |
19 | * Fixed: error on repository user mapping when a repository username is blank |
|
26 | * Fixed: error on repository user mapping when a repository username is blank | |
20 | * Fixed: Firefox cuts off large diffs |
|
27 | * Fixed: Firefox cuts off large diffs | |
21 | * Fixed: CVS browser should not show dead revisions (deleted files) |
|
28 | * Fixed: CVS browser should not show dead revisions (deleted files) | |
22 | * Fixed: escape double-quotes in image titles |
|
29 | * Fixed: escape double-quotes in image titles | |
23 | * Fixed: escape textarea content when editing a issue note |
|
30 | * Fixed: escape textarea content when editing a issue note | |
24 | * Fixed: JS error on context menu with IE |
|
31 | * Fixed: JS error on context menu with IE | |
25 | * Fixed: bold syntax around single character in series doesn't work |
|
32 | * Fixed: bold syntax around single character in series doesn't work | |
26 | * Fixed several XSS vulnerabilities |
|
33 | * Fixed several XSS vulnerabilities | |
27 | * Fixed a SQL injection vulnerability |
|
34 | * Fixed a SQL injection vulnerability | |
28 |
|
35 | |||
29 |
|
36 | |||
30 | == 2008-12-07 v0.8.0-rc1 |
|
37 | == 2008-12-07 v0.8.0-rc1 | |
31 |
|
38 | |||
32 | * Wiki page protection |
|
39 | * Wiki page protection | |
33 | * Wiki page hierarchy. Parent page can be assigned on the Rename screen |
|
40 | * Wiki page hierarchy. Parent page can be assigned on the Rename screen | |
34 | * Adds support for issue creation via email |
|
41 | * Adds support for issue creation via email | |
35 | * Adds support for free ticket filtering and custom queries on Gantt chart and calendar |
|
42 | * Adds support for free ticket filtering and custom queries on Gantt chart and calendar | |
36 | * Cross-project search |
|
43 | * Cross-project search | |
37 | * Ability to search a project and its subprojects |
|
44 | * Ability to search a project and its subprojects | |
38 | * Ability to search the projects the user belongs to |
|
45 | * Ability to search the projects the user belongs to | |
39 | * Adds custom fields on time entries |
|
46 | * Adds custom fields on time entries | |
40 | * Adds boolean and list custom fields for time entries as criteria on time report |
|
47 | * Adds boolean and list custom fields for time entries as criteria on time report | |
41 | * Cross-project time reports |
|
48 | * Cross-project time reports | |
42 | * Display latest user's activity on account/show view |
|
49 | * Display latest user's activity on account/show view | |
43 | * Show last connexion time on user's page |
|
50 | * Show last connexion time on user's page | |
44 | * Obfuscates email address on user's account page using javascript |
|
51 | * Obfuscates email address on user's account page using javascript | |
45 | * wiki TOC rendered as an unordered list |
|
52 | * wiki TOC rendered as an unordered list | |
46 | * Adds the ability to search for a user on the administration users list |
|
53 | * Adds the ability to search for a user on the administration users list | |
47 | * Adds the ability to search for a project name or identifier on the administration projects list |
|
54 | * Adds the ability to search for a project name or identifier on the administration projects list | |
48 | * Redirect user to the previous page after logging in |
|
55 | * Redirect user to the previous page after logging in | |
49 | * Adds a permission 'view wiki edits' so that wiki history can be hidden to certain users |
|
56 | * Adds a permission 'view wiki edits' so that wiki history can be hidden to certain users | |
50 | * Adds permissions for viewing the watcher list and adding new watchers on the issue detail view |
|
57 | * Adds permissions for viewing the watcher list and adding new watchers on the issue detail view | |
51 | * Adds permissions to let users edit and/or delete their messages |
|
58 | * Adds permissions to let users edit and/or delete their messages | |
52 | * Link to activity view when displaying dates |
|
59 | * Link to activity view when displaying dates | |
53 | * Hide Redmine version in atom feeds and pdf properties |
|
60 | * Hide Redmine version in atom feeds and pdf properties | |
54 | * Maps repository users to Redmine users. Users with same username or email are automatically mapped. Mapping can be manually adjusted in repository settings. Multiple usernames can be mapped to the same Redmine user. |
|
61 | * Maps repository users to Redmine users. Users with same username or email are automatically mapped. Mapping can be manually adjusted in repository settings. Multiple usernames can be mapped to the same Redmine user. | |
55 | * Sort users by their display names so that user dropdown lists are sorted alphabetically |
|
62 | * Sort users by their display names so that user dropdown lists are sorted alphabetically | |
56 | * Adds estimated hours to issue filters |
|
63 | * Adds estimated hours to issue filters | |
57 | * Switch order of current and previous revisions in side-by-side diff |
|
64 | * Switch order of current and previous revisions in side-by-side diff | |
58 | * Render the commit changes list as a tree |
|
65 | * Render the commit changes list as a tree | |
59 | * Adds watch/unwatch functionality at forum topic level |
|
66 | * Adds watch/unwatch functionality at forum topic level | |
60 | * When moving an issue to another project, reassign it to the category with same name if any |
|
67 | * When moving an issue to another project, reassign it to the category with same name if any | |
61 | * Adds child_pages macro for wiki pages |
|
68 | * Adds child_pages macro for wiki pages | |
62 | * Use GET instead of POST on roadmap (#718), gantt and calendar forms |
|
69 | * Use GET instead of POST on roadmap (#718), gantt and calendar forms | |
63 | * Search engine: display total results count and count by result type |
|
70 | * Search engine: display total results count and count by result type | |
64 | * Email delivery configuration moved to an unversioned YAML file (config/email.yml, see the sample file) |
|
71 | * Email delivery configuration moved to an unversioned YAML file (config/email.yml, see the sample file) | |
65 | * Adds icons on search results |
|
72 | * Adds icons on search results | |
66 | * Adds 'Edit' link on account/show for admin users |
|
73 | * Adds 'Edit' link on account/show for admin users | |
67 | * Adds Lock/Unlock/Activate link on user edit screen |
|
74 | * Adds Lock/Unlock/Activate link on user edit screen | |
68 | * Adds user count in status drop down on admin user list |
|
75 | * Adds user count in status drop down on admin user list | |
69 | * Adds multi-levels blockquotes support by using > at the beginning of lines |
|
76 | * Adds multi-levels blockquotes support by using > at the beginning of lines | |
70 | * Adds a Reply link to each issue note |
|
77 | * Adds a Reply link to each issue note | |
71 | * Adds plain text only option for mail notifications |
|
78 | * Adds plain text only option for mail notifications | |
72 | * Gravatar support for issue detail, user grid, and activity stream (disabled by default) |
|
79 | * Gravatar support for issue detail, user grid, and activity stream (disabled by default) | |
73 | * Adds 'Delete wiki pages attachments' permission |
|
80 | * Adds 'Delete wiki pages attachments' permission | |
74 | * Show the most recent file when displaying an inline image |
|
81 | * Show the most recent file when displaying an inline image | |
75 | * Makes permission screens localized |
|
82 | * Makes permission screens localized | |
76 | * AuthSource list: display associated users count and disable 'Delete' buton if any |
|
83 | * AuthSource list: display associated users count and disable 'Delete' buton if any | |
77 | * Make the 'duplicates of' relation asymmetric |
|
84 | * Make the 'duplicates of' relation asymmetric | |
78 | * Adds username to the password reminder email |
|
85 | * Adds username to the password reminder email | |
79 | * Adds links to forum messages using message#id syntax |
|
86 | * Adds links to forum messages using message#id syntax | |
80 | * Allow same name for custom fields on different object types |
|
87 | * Allow same name for custom fields on different object types | |
81 | * One-click bulk edition using the issue list context menu within the same project |
|
88 | * One-click bulk edition using the issue list context menu within the same project | |
82 | * Adds support for commit logs reencoding to UTF-8 before insertion in the database. Source encoding of commit logs can be selected in Application settings -> Repositories. |
|
89 | * Adds support for commit logs reencoding to UTF-8 before insertion in the database. Source encoding of commit logs can be selected in Application settings -> Repositories. | |
83 | * Adds checkboxes toggle links on permissions report |
|
90 | * Adds checkboxes toggle links on permissions report | |
84 | * Adds Trac-Like anchors on wiki headings |
|
91 | * Adds Trac-Like anchors on wiki headings | |
85 | * Adds support for wiki links with anchor |
|
92 | * Adds support for wiki links with anchor | |
86 | * Adds category to the issue context menu |
|
93 | * Adds category to the issue context menu | |
87 | * Adds a workflow overview screen |
|
94 | * Adds a workflow overview screen | |
88 | * Appends the filename to the attachment url so that clients that ignore content-disposition http header get the real filename |
|
95 | * Appends the filename to the attachment url so that clients that ignore content-disposition http header get the real filename | |
89 | * Dots allowed in custom field name |
|
96 | * Dots allowed in custom field name | |
90 | * Adds posts quoting functionality |
|
97 | * Adds posts quoting functionality | |
91 | * Adds an option to generate sequential project identifiers |
|
98 | * Adds an option to generate sequential project identifiers | |
92 | * Adds mailto link on the user administration list |
|
99 | * Adds mailto link on the user administration list | |
93 | * Ability to remove enumerations (activities, priorities, document categories) that are in use. Associated objects can be reassigned to another value |
|
100 | * Ability to remove enumerations (activities, priorities, document categories) that are in use. Associated objects can be reassigned to another value | |
94 | * Gantt chart: display issues that don't have a due date if they are assigned to a version with a date |
|
101 | * Gantt chart: display issues that don't have a due date if they are assigned to a version with a date | |
95 | * Change projects homepage limit to 255 chars |
|
102 | * Change projects homepage limit to 255 chars | |
96 | * Improved on-the-fly account creation. If some attributes are missing (eg. not present in the LDAP) or are invalid, the registration form is displayed so that the user is able to fill or fix these attributes |
|
103 | * Improved on-the-fly account creation. If some attributes are missing (eg. not present in the LDAP) or are invalid, the registration form is displayed so that the user is able to fill or fix these attributes | |
97 | * Adds "please select" to activity select box if no activity is set as default |
|
104 | * Adds "please select" to activity select box if no activity is set as default | |
98 | * Do not silently ignore timelog validation failure on issue edit |
|
105 | * Do not silently ignore timelog validation failure on issue edit | |
99 | * Adds a rake task to send reminder emails |
|
106 | * Adds a rake task to send reminder emails | |
100 | * Allow empty cells in wiki tables |
|
107 | * Allow empty cells in wiki tables | |
101 | * Makes wiki text formatter pluggable |
|
108 | * Makes wiki text formatter pluggable | |
102 | * Adds back textile acronyms support |
|
109 | * Adds back textile acronyms support | |
103 | * Remove pre tag attributes |
|
110 | * Remove pre tag attributes | |
104 | * Plugin hooks |
|
111 | * Plugin hooks | |
105 | * Pluggable admin menu |
|
112 | * Pluggable admin menu | |
106 | * Plugins can provide activity content |
|
113 | * Plugins can provide activity content | |
107 | * Moves plugin list to its own administration menu item |
|
114 | * Moves plugin list to its own administration menu item | |
108 | * Adds url and author_url plugin attributes |
|
115 | * Adds url and author_url plugin attributes | |
109 | * Adds Plugin#requires_redmine method so that plugin compatibility can be checked against current Redmine version |
|
116 | * Adds Plugin#requires_redmine method so that plugin compatibility can be checked against current Redmine version | |
110 | * Adds atom feed on time entries details |
|
117 | * Adds atom feed on time entries details | |
111 | * Adds project name to issues feed title |
|
118 | * Adds project name to issues feed title | |
112 | * Adds a css class on menu items in order to apply item specific styles (eg. icons) |
|
119 | * Adds a css class on menu items in order to apply item specific styles (eg. icons) | |
113 | * Adds a Redmine plugin generators |
|
120 | * Adds a Redmine plugin generators | |
114 | * Adds timelog link to the issue context menu |
|
121 | * Adds timelog link to the issue context menu | |
115 | * Adds links to the user page on various views |
|
122 | * Adds links to the user page on various views | |
116 | * Turkish translation by Ismail Sezen |
|
123 | * Turkish translation by Ismail Sezen | |
117 | * Catalan translation |
|
124 | * Catalan translation | |
118 | * Vietnamese translation |
|
125 | * Vietnamese translation | |
119 | * Slovak translation |
|
126 | * Slovak translation | |
120 | * Better naming of activity feed if only one kind of event is displayed |
|
127 | * Better naming of activity feed if only one kind of event is displayed | |
121 | * Enable syntax highlight on issues, messages and news |
|
128 | * Enable syntax highlight on issues, messages and news | |
122 | * Add target version to the issue list context menu |
|
129 | * Add target version to the issue list context menu | |
123 | * Hide 'Target version' filter if no version is defined |
|
130 | * Hide 'Target version' filter if no version is defined | |
124 | * Add filters on cross-project issue list for custom fields marked as 'For all projects' |
|
131 | * Add filters on cross-project issue list for custom fields marked as 'For all projects' | |
125 | * Turn ftp urls into links |
|
132 | * Turn ftp urls into links | |
126 | * Hiding the View Differences button when a wiki page's history only has one version |
|
133 | * Hiding the View Differences button when a wiki page's history only has one version | |
127 | * Messages on a Board can now be sorted by the number of replies |
|
134 | * Messages on a Board can now be sorted by the number of replies | |
128 | * Adds a class ('me') to events of the activity view created by current user |
|
135 | * Adds a class ('me') to events of the activity view created by current user | |
129 | * Strip pre/code tags content from activity view events |
|
136 | * Strip pre/code tags content from activity view events | |
130 | * Display issue notes in the activity view |
|
137 | * Display issue notes in the activity view | |
131 | * Adds links to changesets atom feed on repository browser |
|
138 | * Adds links to changesets atom feed on repository browser | |
132 | * Track project and tracker changes in issue history |
|
139 | * Track project and tracker changes in issue history | |
133 | * Adds anchor to atom feed messages links |
|
140 | * Adds anchor to atom feed messages links | |
134 | * Adds a key in lang files to set the decimal separator (point or comma) in csv exports |
|
141 | * Adds a key in lang files to set the decimal separator (point or comma) in csv exports | |
135 | * Makes importer work with Trac 0.8.x |
|
142 | * Makes importer work with Trac 0.8.x | |
136 | * Upgraded to Prototype 1.6.0.1 |
|
143 | * Upgraded to Prototype 1.6.0.1 | |
137 | * File viewer for attached text files |
|
144 | * File viewer for attached text files | |
138 | * Menu mapper: add support for :before, :after and :last options to #push method and add #delete method |
|
145 | * Menu mapper: add support for :before, :after and :last options to #push method and add #delete method | |
139 | * Removed inconsistent revision numbers on diff view |
|
146 | * Removed inconsistent revision numbers on diff view | |
140 | * CVS: add support for modules names with spaces |
|
147 | * CVS: add support for modules names with spaces | |
141 | * Log the user in after registration if account activation is not needed |
|
148 | * Log the user in after registration if account activation is not needed | |
142 | * Mercurial adapter improvements |
|
149 | * Mercurial adapter improvements | |
143 | * Trac importer: read session_attribute table to find user's email and real name |
|
150 | * Trac importer: read session_attribute table to find user's email and real name | |
144 | * Ability to disable unused SCM adapters in application settings |
|
151 | * Ability to disable unused SCM adapters in application settings | |
145 | * Adds Filesystem adapter |
|
152 | * Adds Filesystem adapter | |
146 | * Clear changesets and changes with raw sql when deleting a repository for performance |
|
153 | * Clear changesets and changes with raw sql when deleting a repository for performance | |
147 | * Redmine.pm now uses the 'commit access' permission defined in Redmine |
|
154 | * Redmine.pm now uses the 'commit access' permission defined in Redmine | |
148 | * Reposman can create any type of scm (--scm option) |
|
155 | * Reposman can create any type of scm (--scm option) | |
149 | * Reposman creates a repository if the 'repository' module is enabled at project level only |
|
156 | * Reposman creates a repository if the 'repository' module is enabled at project level only | |
150 | * Display svn properties in the browser, svn >= 1.5.0 only |
|
157 | * Display svn properties in the browser, svn >= 1.5.0 only | |
151 | * Reduces memory usage when importing large git repositories |
|
158 | * Reduces memory usage when importing large git repositories | |
152 | * Wider SVG graphs in repository stats |
|
159 | * Wider SVG graphs in repository stats | |
153 | * SubversionAdapter#entries performance improvement |
|
160 | * SubversionAdapter#entries performance improvement | |
154 | * SCM browser: ability to download raw unified diffs |
|
161 | * SCM browser: ability to download raw unified diffs | |
155 | * More detailed error message in log when scm command fails |
|
162 | * More detailed error message in log when scm command fails | |
156 | * Adds support for file viewing with Darcs 2.0+ |
|
163 | * Adds support for file viewing with Darcs 2.0+ | |
157 | * Check that git changeset is not in the database before creating it |
|
164 | * Check that git changeset is not in the database before creating it | |
158 | * Unified diff viewer for attached files with .patch or .diff extension |
|
165 | * Unified diff viewer for attached files with .patch or .diff extension | |
159 | * File size display with Bazaar repositories |
|
166 | * File size display with Bazaar repositories | |
160 | * Git adapter: use commit time instead of author time |
|
167 | * Git adapter: use commit time instead of author time | |
161 | * Prettier url for changesets |
|
168 | * Prettier url for changesets | |
162 | * Makes changes link to entries on the revision view |
|
169 | * Makes changes link to entries on the revision view | |
163 | * Adds a field on the repository view to browse at specific revision |
|
170 | * Adds a field on the repository view to browse at specific revision | |
164 | * Adds new projects atom feed |
|
171 | * Adds new projects atom feed | |
165 | * Added rake tasks to generate rcov code coverage reports |
|
172 | * Added rake tasks to generate rcov code coverage reports | |
166 | * Add Redcloth's :block_markdown_rule to allow horizontal rules in wiki |
|
173 | * Add Redcloth's :block_markdown_rule to allow horizontal rules in wiki | |
167 | * Show the project hierarchy in the drop down list for new membership on user administration screen |
|
174 | * Show the project hierarchy in the drop down list for new membership on user administration screen | |
168 | * Split user edit screen into tabs |
|
175 | * Split user edit screen into tabs | |
169 | * Renames bundled RedCloth to RedCloth3 to avoid RedCloth 4 to be loaded instead |
|
176 | * Renames bundled RedCloth to RedCloth3 to avoid RedCloth 4 to be loaded instead | |
170 | * Fixed: Roadmap crashes when a version has a due date > 2037 |
|
177 | * Fixed: Roadmap crashes when a version has a due date > 2037 | |
171 | * Fixed: invalid effective date (eg. 99999-01-01) causes an error on version edition screen |
|
178 | * Fixed: invalid effective date (eg. 99999-01-01) causes an error on version edition screen | |
172 | * Fixed: login filter providing incorrect back_url for Redmine installed in sub-directory |
|
179 | * Fixed: login filter providing incorrect back_url for Redmine installed in sub-directory | |
173 | * Fixed: logtime entry duplicated when edited from parent project |
|
180 | * Fixed: logtime entry duplicated when edited from parent project | |
174 | * Fixed: wrong digest for text files under Windows |
|
181 | * Fixed: wrong digest for text files under Windows | |
175 | * Fixed: associated revisions are displayed in wrong order on issue view |
|
182 | * Fixed: associated revisions are displayed in wrong order on issue view | |
176 | * Fixed: Git Adapter date parsing ignores timezone |
|
183 | * Fixed: Git Adapter date parsing ignores timezone | |
177 | * Fixed: Printing long roadmap doesn't split across pages |
|
184 | * Fixed: Printing long roadmap doesn't split across pages | |
178 | * Fixes custom fields display order at several places |
|
185 | * Fixes custom fields display order at several places | |
179 | * Fixed: urls containing @ are parsed as email adress by the wiki formatter |
|
186 | * Fixed: urls containing @ are parsed as email adress by the wiki formatter | |
180 | * Fixed date filters accuracy with SQLite |
|
187 | * Fixed date filters accuracy with SQLite | |
181 | * Fixed: tokens not escaped in highlight_tokens regexp |
|
188 | * Fixed: tokens not escaped in highlight_tokens regexp | |
182 | * Fixed Bazaar shared repository browsing |
|
189 | * Fixed Bazaar shared repository browsing | |
183 | * Fixes platform determination under JRuby |
|
190 | * Fixes platform determination under JRuby | |
184 | * Fixed: Estimated time in issue's journal should be rounded to two decimals |
|
191 | * Fixed: Estimated time in issue's journal should be rounded to two decimals | |
185 | * Fixed: 'search titles only' box ignored after one search is done on titles only |
|
192 | * Fixed: 'search titles only' box ignored after one search is done on titles only | |
186 | * Fixed: non-ASCII subversion path can't be displayed |
|
193 | * Fixed: non-ASCII subversion path can't be displayed | |
187 | * Fixed: Inline images don't work if file name has upper case letters or if image is in BMP format |
|
194 | * Fixed: Inline images don't work if file name has upper case letters or if image is in BMP format | |
188 | * Fixed: document listing shows on "my page" when viewing documents is disabled for the role |
|
195 | * Fixed: document listing shows on "my page" when viewing documents is disabled for the role | |
189 | * Fixed: Latest news appear on the homepage for projects with the News module disabled |
|
196 | * Fixed: Latest news appear on the homepage for projects with the News module disabled | |
190 | * Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled |
|
197 | * Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled | |
191 | * Fixed: the default status is lost when reordering issue statuses |
|
198 | * Fixed: the default status is lost when reordering issue statuses | |
192 | * Fixes error with Postgresql and non-UTF8 commit logs |
|
199 | * Fixes error with Postgresql and non-UTF8 commit logs | |
193 | * Fixed: textile footnotes no longer work |
|
200 | * Fixed: textile footnotes no longer work | |
194 | * Fixed: http links containing parentheses fail to reder correctly |
|
201 | * Fixed: http links containing parentheses fail to reder correctly | |
195 | * Fixed: GitAdapter#get_rev should use current branch instead of hardwiring master |
|
202 | * Fixed: GitAdapter#get_rev should use current branch instead of hardwiring master | |
196 |
|
203 | |||
197 |
|
204 | |||
198 | == 2008-07-06 v0.7.3 |
|
205 | == 2008-07-06 v0.7.3 | |
199 |
|
206 | |||
200 | * Allow dot in firstnames and lastnames |
|
207 | * Allow dot in firstnames and lastnames | |
201 | * Add project name to cross-project Atom feeds |
|
208 | * Add project name to cross-project Atom feeds | |
202 | * Encoding set to utf8 in example database.yml |
|
209 | * Encoding set to utf8 in example database.yml | |
203 | * HTML titles on forums related views |
|
210 | * HTML titles on forums related views | |
204 | * Fixed: various XSS vulnerabilities |
|
211 | * Fixed: various XSS vulnerabilities | |
205 | * Fixed: Entourage (and some old client) fails to correctly render notification styles |
|
212 | * Fixed: Entourage (and some old client) fails to correctly render notification styles | |
206 | * Fixed: Fixed: timelog redirects inappropriately when :back_url is blank |
|
213 | * Fixed: Fixed: timelog redirects inappropriately when :back_url is blank | |
207 | * Fixed: wrong relative paths to images in wiki_syntax.html |
|
214 | * Fixed: wrong relative paths to images in wiki_syntax.html | |
208 |
|
215 | |||
209 |
|
216 | |||
210 | == 2008-06-15 v0.7.2 |
|
217 | == 2008-06-15 v0.7.2 | |
211 |
|
218 | |||
212 | * "New Project" link on Projects page |
|
219 | * "New Project" link on Projects page | |
213 | * Links to repository directories on the repo browser |
|
220 | * Links to repository directories on the repo browser | |
214 | * Move status to front in Activity View |
|
221 | * Move status to front in Activity View | |
215 | * Remove edit step from Status context menu |
|
222 | * Remove edit step from Status context menu | |
216 | * Fixed: No way to do textile horizontal rule |
|
223 | * Fixed: No way to do textile horizontal rule | |
217 | * Fixed: Repository: View differences doesn't work |
|
224 | * Fixed: Repository: View differences doesn't work | |
218 | * Fixed: attachement's name maybe invalid. |
|
225 | * Fixed: attachement's name maybe invalid. | |
219 | * Fixed: Error when creating a new issue |
|
226 | * Fixed: Error when creating a new issue | |
220 | * Fixed: NoMethodError on @available_filters.has_key? |
|
227 | * Fixed: NoMethodError on @available_filters.has_key? | |
221 | * Fixed: Check All / Uncheck All in Email Settings |
|
228 | * Fixed: Check All / Uncheck All in Email Settings | |
222 | * Fixed: "View differences" of one file at /repositories/revision/ fails |
|
229 | * Fixed: "View differences" of one file at /repositories/revision/ fails | |
223 | * Fixed: Column width in "my page" |
|
230 | * Fixed: Column width in "my page" | |
224 | * Fixed: private subprojects are listed on Issues view |
|
231 | * Fixed: private subprojects are listed on Issues view | |
225 | * Fixed: Textile: bold, italics, underline, etc... not working after parentheses |
|
232 | * Fixed: Textile: bold, italics, underline, etc... not working after parentheses | |
226 | * Fixed: Update issue form: comment field from log time end out of screen |
|
233 | * Fixed: Update issue form: comment field from log time end out of screen | |
227 | * Fixed: Editing role: "issue can be assigned to this role" out of box |
|
234 | * Fixed: Editing role: "issue can be assigned to this role" out of box | |
228 | * Fixed: Unable use angular braces after include word |
|
235 | * Fixed: Unable use angular braces after include word | |
229 | * Fixed: Using '*' as keyword for repository referencing keywords doesn't work |
|
236 | * Fixed: Using '*' as keyword for repository referencing keywords doesn't work | |
230 | * Fixed: Subversion repository "View differences" on each file rise ERROR |
|
237 | * Fixed: Subversion repository "View differences" on each file rise ERROR | |
231 | * Fixed: View differences for individual file of a changeset fails if the repository URL doesn't point to the repository root |
|
238 | * Fixed: View differences for individual file of a changeset fails if the repository URL doesn't point to the repository root | |
232 | * Fixed: It is possible to lock out the last admin account |
|
239 | * Fixed: It is possible to lock out the last admin account | |
233 | * Fixed: Wikis are viewable for anonymous users on public projects, despite not granting access |
|
240 | * Fixed: Wikis are viewable for anonymous users on public projects, despite not granting access | |
234 | * Fixed: Issue number display clipped on 'my issues' |
|
241 | * Fixed: Issue number display clipped on 'my issues' | |
235 | * Fixed: Roadmap version list links not carrying state |
|
242 | * Fixed: Roadmap version list links not carrying state | |
236 | * Fixed: Log Time fieldset in IssueController#edit doesn't set default Activity as default |
|
243 | * Fixed: Log Time fieldset in IssueController#edit doesn't set default Activity as default | |
237 | * Fixed: git's "get_rev" API should use repo's current branch instead of hardwiring "master" |
|
244 | * Fixed: git's "get_rev" API should use repo's current branch instead of hardwiring "master" | |
238 | * Fixed: browser's language subcodes ignored |
|
245 | * Fixed: browser's language subcodes ignored | |
239 | * Fixed: Error on project selection with numeric (only) identifier. |
|
246 | * Fixed: Error on project selection with numeric (only) identifier. | |
240 | * Fixed: Link to PDF doesn't work after creating new issue |
|
247 | * Fixed: Link to PDF doesn't work after creating new issue | |
241 | * Fixed: "Replies" should not be shown on forum threads that are locked |
|
248 | * Fixed: "Replies" should not be shown on forum threads that are locked | |
242 | * Fixed: SVN errors lead to svn username/password being displayed to end users (security issue) |
|
249 | * Fixed: SVN errors lead to svn username/password being displayed to end users (security issue) | |
243 | * Fixed: http links containing hashes don't display correct |
|
250 | * Fixed: http links containing hashes don't display correct | |
244 | * Fixed: Allow ampersands in Enumeration names |
|
251 | * Fixed: Allow ampersands in Enumeration names | |
245 | * Fixed: Atom link on saved query does not include query_id |
|
252 | * Fixed: Atom link on saved query does not include query_id | |
246 | * Fixed: Logtime info lost when there's an error updating an issue |
|
253 | * Fixed: Logtime info lost when there's an error updating an issue | |
247 | * Fixed: TOC does not parse colorization markups |
|
254 | * Fixed: TOC does not parse colorization markups | |
248 | * Fixed: CVS: add support for modules names with spaces |
|
255 | * Fixed: CVS: add support for modules names with spaces | |
249 | * Fixed: Bad rendering on projects/add |
|
256 | * Fixed: Bad rendering on projects/add | |
250 | * Fixed: exception when viewing differences on cvs |
|
257 | * Fixed: exception when viewing differences on cvs | |
251 | * Fixed: export issue to pdf will messup when use Chinese language |
|
258 | * Fixed: export issue to pdf will messup when use Chinese language | |
252 | * Fixed: Redmine::Scm::Adapters::GitAdapter#get_rev ignored GIT_BIN constant |
|
259 | * Fixed: Redmine::Scm::Adapters::GitAdapter#get_rev ignored GIT_BIN constant | |
253 | * Fixed: Adding non-ASCII new issue type in the New Issue page have encoding error using IE |
|
260 | * Fixed: Adding non-ASCII new issue type in the New Issue page have encoding error using IE | |
254 | * Fixed: Importing from trac : some wiki links are messed |
|
261 | * Fixed: Importing from trac : some wiki links are messed | |
255 | * Fixed: Incorrect weekend definition in Hebrew calendar locale |
|
262 | * Fixed: Incorrect weekend definition in Hebrew calendar locale | |
256 | * Fixed: Atom feeds don't provide author section for repository revisions |
|
263 | * Fixed: Atom feeds don't provide author section for repository revisions | |
257 | * Fixed: In Activity views, changesets titles can be multiline while they should not |
|
264 | * Fixed: In Activity views, changesets titles can be multiline while they should not | |
258 | * Fixed: Ignore unreadable subversion directories (read disabled using authz) |
|
265 | * Fixed: Ignore unreadable subversion directories (read disabled using authz) | |
259 | * Fixed: lib/SVG/Graph/Graph.rb can't externalize stylesheets |
|
266 | * Fixed: lib/SVG/Graph/Graph.rb can't externalize stylesheets | |
260 | * Fixed: Close statement handler in Redmine.pm |
|
267 | * Fixed: Close statement handler in Redmine.pm | |
261 |
|
268 | |||
262 |
|
269 | |||
263 | == 2008-05-04 v0.7.1 |
|
270 | == 2008-05-04 v0.7.1 | |
264 |
|
271 | |||
265 | * Thai translation added (Gampol Thitinilnithi) |
|
272 | * Thai translation added (Gampol Thitinilnithi) | |
266 | * Translations updates |
|
273 | * Translations updates | |
267 | * Escape HTML comment tags |
|
274 | * Escape HTML comment tags | |
268 | * Prevent "can't convert nil into String" error when :sort_order param is not present |
|
275 | * Prevent "can't convert nil into String" error when :sort_order param is not present | |
269 | * Fixed: Updating tickets add a time log with zero hours |
|
276 | * Fixed: Updating tickets add a time log with zero hours | |
270 | * Fixed: private subprojects names are revealed on the project overview |
|
277 | * Fixed: private subprojects names are revealed on the project overview | |
271 | * Fixed: Search for target version of "none" fails with postgres 8.3 |
|
278 | * Fixed: Search for target version of "none" fails with postgres 8.3 | |
272 | * Fixed: Home, Logout, Login links shouldn't be absolute links |
|
279 | * Fixed: Home, Logout, Login links shouldn't be absolute links | |
273 | * Fixed: 'Latest projects' box on the welcome screen should be hidden if there are no projects |
|
280 | * Fixed: 'Latest projects' box on the welcome screen should be hidden if there are no projects | |
274 | * Fixed: error when using upcase language name in coderay |
|
281 | * Fixed: error when using upcase language name in coderay | |
275 | * Fixed: error on Trac import when :due attribute is nil |
|
282 | * Fixed: error on Trac import when :due attribute is nil | |
276 |
|
283 | |||
277 |
|
284 | |||
278 | == 2008-04-28 v0.7.0 |
|
285 | == 2008-04-28 v0.7.0 | |
279 |
|
286 | |||
280 | * Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present |
|
287 | * Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present | |
281 | * Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list. |
|
288 | * Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list. | |
282 | * Add predefined date ranges to the time report |
|
289 | * Add predefined date ranges to the time report | |
283 | * Time report can be done at issue level |
|
290 | * Time report can be done at issue level | |
284 | * Various timelog report enhancements |
|
291 | * Various timelog report enhancements | |
285 | * Accept the following formats for "hours" field: 1h, 1 h, 1 hour, 2 hours, 30m, 30min, 1h30, 1h30m, 1:30 |
|
292 | * Accept the following formats for "hours" field: 1h, 1 h, 1 hour, 2 hours, 30m, 30min, 1h30, 1h30m, 1:30 | |
286 | * Display the context menu above and/or to the left of the click if needed |
|
293 | * Display the context menu above and/or to the left of the click if needed | |
287 | * Make the admin project files list sortable |
|
294 | * Make the admin project files list sortable | |
288 | * Mercurial: display working directory files sizes unless browsing a specific revision |
|
295 | * Mercurial: display working directory files sizes unless browsing a specific revision | |
289 | * Preserve status filter and page number when using lock/unlock/activate links on the users list |
|
296 | * Preserve status filter and page number when using lock/unlock/activate links on the users list | |
290 | * Redmine.pm support for LDAP authentication |
|
297 | * Redmine.pm support for LDAP authentication | |
291 | * Better error message and AR errors in log for failed LDAP on-the-fly user creation |
|
298 | * Better error message and AR errors in log for failed LDAP on-the-fly user creation | |
292 | * Redirected user to where he is coming from after logging hours |
|
299 | * Redirected user to where he is coming from after logging hours | |
293 | * Warn user that subprojects are also deleted when deleting a project |
|
300 | * Warn user that subprojects are also deleted when deleting a project | |
294 | * Include subprojects versions on calendar and gantt |
|
301 | * Include subprojects versions on calendar and gantt | |
295 | * Notify project members when a message is posted if they want to receive notifications |
|
302 | * Notify project members when a message is posted if they want to receive notifications | |
296 | * Fixed: Feed content limit setting has no effect |
|
303 | * Fixed: Feed content limit setting has no effect | |
297 | * Fixed: Priorities not ordered when displayed as a filter in issue list |
|
304 | * Fixed: Priorities not ordered when displayed as a filter in issue list | |
298 | * Fixed: can not display attached images inline in message replies |
|
305 | * Fixed: can not display attached images inline in message replies | |
299 | * Fixed: Boards are not deleted when project is deleted |
|
306 | * Fixed: Boards are not deleted when project is deleted | |
300 | * Fixed: trying to preview a new issue raises an exception with postgresql |
|
307 | * Fixed: trying to preview a new issue raises an exception with postgresql | |
301 | * Fixed: single file 'View difference' links do not work because of duplicate slashes in url |
|
308 | * Fixed: single file 'View difference' links do not work because of duplicate slashes in url | |
302 | * Fixed: inline image not displayed when including a wiki page |
|
309 | * Fixed: inline image not displayed when including a wiki page | |
303 | * Fixed: CVS duplicate key violation |
|
310 | * Fixed: CVS duplicate key violation | |
304 | * Fixed: ActiveRecord::StaleObjectError exception on closing a set of circular duplicate issues |
|
311 | * Fixed: ActiveRecord::StaleObjectError exception on closing a set of circular duplicate issues | |
305 | * Fixed: custom field filters behaviour |
|
312 | * Fixed: custom field filters behaviour | |
306 | * Fixed: Postgresql 8.3 compatibility |
|
313 | * Fixed: Postgresql 8.3 compatibility | |
307 | * Fixed: Links to repository directories don't work |
|
314 | * Fixed: Links to repository directories don't work | |
308 |
|
315 | |||
309 |
|
316 | |||
310 | == 2008-03-29 v0.7.0-rc1 |
|
317 | == 2008-03-29 v0.7.0-rc1 | |
311 |
|
318 | |||
312 | * Overall activity view and feed added, link is available on the project list |
|
319 | * Overall activity view and feed added, link is available on the project list | |
313 | * Git VCS support |
|
320 | * Git VCS support | |
314 | * Rails 2.0 sessions cookie store compatibility |
|
321 | * Rails 2.0 sessions cookie store compatibility | |
315 | * Use project identifiers in urls instead of ids |
|
322 | * Use project identifiers in urls instead of ids | |
316 | * Default configuration data can now be loaded from the administration screen |
|
323 | * Default configuration data can now be loaded from the administration screen | |
317 | * Administration settings screen split to tabs (email notifications options moved to 'Settings') |
|
324 | * Administration settings screen split to tabs (email notifications options moved to 'Settings') | |
318 | * Project description is now unlimited and optional |
|
325 | * Project description is now unlimited and optional | |
319 | * Wiki annotate view |
|
326 | * Wiki annotate view | |
320 | * Escape HTML tag in textile content |
|
327 | * Escape HTML tag in textile content | |
321 | * Add Redmine links to documents, versions, attachments and repository files |
|
328 | * Add Redmine links to documents, versions, attachments and repository files | |
322 | * New setting to specify how many objects should be displayed on paginated lists. There are 2 ways to select a set of issues on the issue list: |
|
329 | * New setting to specify how many objects should be displayed on paginated lists. There are 2 ways to select a set of issues on the issue list: | |
323 | * by using checkbox and/or the little pencil that will select/unselect all issues |
|
330 | * by using checkbox and/or the little pencil that will select/unselect all issues | |
324 | * by clicking on the rows (but not on the links), Ctrl and Shift keys can be used to select multiple issues |
|
331 | * by clicking on the rows (but not on the links), Ctrl and Shift keys can be used to select multiple issues | |
325 | * Context menu disabled on links so that the default context menu of the browser is displayed when right-clicking on a link (click anywhere else on the row to display the context menu) |
|
332 | * Context menu disabled on links so that the default context menu of the browser is displayed when right-clicking on a link (click anywhere else on the row to display the context menu) | |
326 | * User display format is now configurable in administration settings |
|
333 | * User display format is now configurable in administration settings | |
327 | * Issue list now supports bulk edit/move/delete (for a set of issues that belong to the same project) |
|
334 | * Issue list now supports bulk edit/move/delete (for a set of issues that belong to the same project) | |
328 | * Merged 'change status', 'edit issue' and 'add note' actions: |
|
335 | * Merged 'change status', 'edit issue' and 'add note' actions: | |
329 | * Users with 'edit issues' permission can now update any property including custom fields when adding a note or changing the status |
|
336 | * Users with 'edit issues' permission can now update any property including custom fields when adding a note or changing the status | |
330 | * 'Change issue status' permission removed. To change an issue status, a user just needs to have either 'Edit' or 'Add note' permissions and some workflow transitions allowed |
|
337 | * 'Change issue status' permission removed. To change an issue status, a user just needs to have either 'Edit' or 'Add note' permissions and some workflow transitions allowed | |
331 | * Details by assignees on issue summary view |
|
338 | * Details by assignees on issue summary view | |
332 | * 'New issue' link in the main menu (accesskey 7). The drop-down lists to add an issue on the project overview and the issue list are removed |
|
339 | * 'New issue' link in the main menu (accesskey 7). The drop-down lists to add an issue on the project overview and the issue list are removed | |
333 | * Change status select box default to current status |
|
340 | * Change status select box default to current status | |
334 | * Preview for issue notes, news and messages |
|
341 | * Preview for issue notes, news and messages | |
335 | * Optional description for attachments |
|
342 | * Optional description for attachments | |
336 | * 'Fixed version' label changed to 'Target version' |
|
343 | * 'Fixed version' label changed to 'Target version' | |
337 | * Let the user choose when deleting issues with reported hours to: |
|
344 | * Let the user choose when deleting issues with reported hours to: | |
338 | * delete the hours |
|
345 | * delete the hours | |
339 | * assign the hours to the project |
|
346 | * assign the hours to the project | |
340 | * reassign the hours to another issue |
|
347 | * reassign the hours to another issue | |
341 | * Date range filter and pagination on time entries detail view |
|
348 | * Date range filter and pagination on time entries detail view | |
342 | * Propagate time tracking to the parent project |
|
349 | * Propagate time tracking to the parent project | |
343 | * Switch added on the project activity view to include subprojects |
|
350 | * Switch added on the project activity view to include subprojects | |
344 | * Display total estimated and spent hours on the version detail view |
|
351 | * Display total estimated and spent hours on the version detail view | |
345 | * Weekly time tracking block for 'My page' |
|
352 | * Weekly time tracking block for 'My page' | |
346 | * Permissions to edit time entries |
|
353 | * Permissions to edit time entries | |
347 | * Include subprojects on the issue list, calendar, gantt and timelog by default (can be turned off is administration settings) |
|
354 | * Include subprojects on the issue list, calendar, gantt and timelog by default (can be turned off is administration settings) | |
348 | * Roadmap enhancements (separate related issues from wiki contents, leading h1 in version wiki pages is hidden, smaller wiki headings) |
|
355 | * Roadmap enhancements (separate related issues from wiki contents, leading h1 in version wiki pages is hidden, smaller wiki headings) | |
349 | * Make versions with same date sorted by name |
|
356 | * Make versions with same date sorted by name | |
350 | * Allow issue list to be sorted by target version |
|
357 | * Allow issue list to be sorted by target version | |
351 | * Related changesets messages displayed on the issue details view |
|
358 | * Related changesets messages displayed on the issue details view | |
352 | * Create a journal and send an email when an issue is closed by commit |
|
359 | * Create a journal and send an email when an issue is closed by commit | |
353 | * Add 'Author' to the available columns for the issue list |
|
360 | * Add 'Author' to the available columns for the issue list | |
354 | * More appropriate default sort order on sortable columns |
|
361 | * More appropriate default sort order on sortable columns | |
355 | * Add issue subject to the time entries view and issue subject, description and tracker to the csv export |
|
362 | * Add issue subject to the time entries view and issue subject, description and tracker to the csv export | |
356 | * Permissions to edit issue notes |
|
363 | * Permissions to edit issue notes | |
357 | * Display date/time instead of date on files list |
|
364 | * Display date/time instead of date on files list | |
358 | * Do not show Roadmap menu item if the project doesn't define any versions |
|
365 | * Do not show Roadmap menu item if the project doesn't define any versions | |
359 | * Allow longer version names (60 chars) |
|
366 | * Allow longer version names (60 chars) | |
360 | * Ability to copy an existing workflow when creating a new role |
|
367 | * Ability to copy an existing workflow when creating a new role | |
361 | * Display custom fields in two columns on the issue form |
|
368 | * Display custom fields in two columns on the issue form | |
362 | * Added 'estimated time' in the csv export of the issue list |
|
369 | * Added 'estimated time' in the csv export of the issue list | |
363 | * Display the last 30 days on the activity view rather than the current month (number of days can be configured in the application settings) |
|
370 | * Display the last 30 days on the activity view rather than the current month (number of days can be configured in the application settings) | |
364 | * Setting for whether new projects should be public by default |
|
371 | * Setting for whether new projects should be public by default | |
365 | * User preference to choose how comments/replies are displayed: in chronological or reverse chronological order |
|
372 | * User preference to choose how comments/replies are displayed: in chronological or reverse chronological order | |
366 | * Added default value for custom fields |
|
373 | * Added default value for custom fields | |
367 | * Added tabindex property on wiki toolbar buttons (to easily move from field to field using the tab key) |
|
374 | * Added tabindex property on wiki toolbar buttons (to easily move from field to field using the tab key) | |
368 | * Redirect to issue page after creating a new issue |
|
375 | * Redirect to issue page after creating a new issue | |
369 | * Wiki toolbar improvements (mainly for Firefox) |
|
376 | * Wiki toolbar improvements (mainly for Firefox) | |
370 | * Display wiki syntax quick ref link on all wiki textareas |
|
377 | * Display wiki syntax quick ref link on all wiki textareas | |
371 | * Display links to Atom feeds |
|
378 | * Display links to Atom feeds | |
372 | * Breadcrumb nav for the forums |
|
379 | * Breadcrumb nav for the forums | |
373 | * Show replies when choosing to display messages in the activity |
|
380 | * Show replies when choosing to display messages in the activity | |
374 | * Added 'include' macro to include another wiki page |
|
381 | * Added 'include' macro to include another wiki page | |
375 | * RedmineWikiFormatting page available as a static HTML file locally |
|
382 | * RedmineWikiFormatting page available as a static HTML file locally | |
376 | * Wrap diff content |
|
383 | * Wrap diff content | |
377 | * Strip out email address from authors in repository screens |
|
384 | * Strip out email address from authors in repository screens | |
378 | * Highlight the current item of the main menu |
|
385 | * Highlight the current item of the main menu | |
379 | * Added simple syntax highlighters for php and java languages |
|
386 | * Added simple syntax highlighters for php and java languages | |
380 | * Do not show empty diffs |
|
387 | * Do not show empty diffs | |
381 | * Show explicit error message when the scm command failed (eg. when svn binary is not available) |
|
388 | * Show explicit error message when the scm command failed (eg. when svn binary is not available) | |
382 | * Lithuanian translation added (Sergej Jegorov) |
|
389 | * Lithuanian translation added (Sergej Jegorov) | |
383 | * Ukrainan translation added (Natalia Konovka & Mykhaylo Sorochan) |
|
390 | * Ukrainan translation added (Natalia Konovka & Mykhaylo Sorochan) | |
384 | * Danish translation added (Mads Vestergaard) |
|
391 | * Danish translation added (Mads Vestergaard) | |
385 | * Added i18n support to the jstoolbar and various settings screen |
|
392 | * Added i18n support to the jstoolbar and various settings screen | |
386 | * RedCloth's glyphs no longer user |
|
393 | * RedCloth's glyphs no longer user | |
387 | * New icons for the wiki toolbar (from http://www.famfamfam.com/lab/icons/silk/) |
|
394 | * New icons for the wiki toolbar (from http://www.famfamfam.com/lab/icons/silk/) | |
388 | * The following menus can now be extended by plugins: top_menu, account_menu, application_menu |
|
395 | * The following menus can now be extended by plugins: top_menu, account_menu, application_menu | |
389 | * Added a simple rake task to fetch changesets from the repositories: rake redmine:fetch_changesets |
|
396 | * Added a simple rake task to fetch changesets from the repositories: rake redmine:fetch_changesets | |
390 | * Remove hardcoded "Redmine" strings in account related emails and use application title instead |
|
397 | * Remove hardcoded "Redmine" strings in account related emails and use application title instead | |
391 | * Mantis importer preserve bug ids |
|
398 | * Mantis importer preserve bug ids | |
392 | * Trac importer: Trac guide wiki pages skipped |
|
399 | * Trac importer: Trac guide wiki pages skipped | |
393 | * Trac importer: wiki attachments migration added |
|
400 | * Trac importer: wiki attachments migration added | |
394 | * Trac importer: support database schema for Trac migration |
|
401 | * Trac importer: support database schema for Trac migration | |
395 | * Trac importer: support CamelCase links |
|
402 | * Trac importer: support CamelCase links | |
396 | * Removes the Redmine version from the footer (can be viewed on admin -> info) |
|
403 | * Removes the Redmine version from the footer (can be viewed on admin -> info) | |
397 | * Rescue and display an error message when trying to delete a role that is in use |
|
404 | * Rescue and display an error message when trying to delete a role that is in use | |
398 | * Add various 'X-Redmine' headers to email notifications: X-Redmine-Host, X-Redmine-Site, X-Redmine-Project, X-Redmine-Issue-Id, -Author, -Assignee, X-Redmine-Topic-Id |
|
405 | * Add various 'X-Redmine' headers to email notifications: X-Redmine-Host, X-Redmine-Site, X-Redmine-Project, X-Redmine-Issue-Id, -Author, -Assignee, X-Redmine-Topic-Id | |
399 | * Add "--encoding utf8" option to the Mercurial "hg log" command in order to get utf8 encoded commit logs |
|
406 | * Add "--encoding utf8" option to the Mercurial "hg log" command in order to get utf8 encoded commit logs | |
400 | * Fixed: Gantt and calendar not properly refreshed (fragment caching removed) |
|
407 | * Fixed: Gantt and calendar not properly refreshed (fragment caching removed) | |
401 | * Fixed: Textile image with style attribute cause internal server error |
|
408 | * Fixed: Textile image with style attribute cause internal server error | |
402 | * Fixed: wiki TOC not rendered properly when used in an issue or document description |
|
409 | * Fixed: wiki TOC not rendered properly when used in an issue or document description | |
403 | * Fixed: 'has already been taken' error message on username and email fields if left empty |
|
410 | * Fixed: 'has already been taken' error message on username and email fields if left empty | |
404 | * Fixed: non-ascii attachement filename with IE |
|
411 | * Fixed: non-ascii attachement filename with IE | |
405 | * Fixed: wrong url for wiki syntax pop-up when Redmine urls are prefixed |
|
412 | * Fixed: wrong url for wiki syntax pop-up when Redmine urls are prefixed | |
406 | * Fixed: search for all words doesn't work |
|
413 | * Fixed: search for all words doesn't work | |
407 | * Fixed: Do not show sticky and locked checkboxes when replying to a message |
|
414 | * Fixed: Do not show sticky and locked checkboxes when replying to a message | |
408 | * Fixed: Mantis importer: do not duplicate Mantis username in firstname and lastname if realname is blank |
|
415 | * Fixed: Mantis importer: do not duplicate Mantis username in firstname and lastname if realname is blank | |
409 | * Fixed: Date custom fields not displayed as specified in application settings |
|
416 | * Fixed: Date custom fields not displayed as specified in application settings | |
410 | * Fixed: titles not escaped in the activity view |
|
417 | * Fixed: titles not escaped in the activity view | |
411 | * Fixed: issue queries can not use custom fields marked as 'for all projects' in a project context |
|
418 | * Fixed: issue queries can not use custom fields marked as 'for all projects' in a project context | |
412 | * Fixed: on calendar, gantt and in the tracker filter on the issue list, only active trackers of the project (and its sub projects) should be available |
|
419 | * Fixed: on calendar, gantt and in the tracker filter on the issue list, only active trackers of the project (and its sub projects) should be available | |
413 | * Fixed: locked users should not receive email notifications |
|
420 | * Fixed: locked users should not receive email notifications | |
414 | * Fixed: custom field selection is not saved when unchecking them all on project settings |
|
421 | * Fixed: custom field selection is not saved when unchecking them all on project settings | |
415 | * Fixed: can not lock a topic when creating it |
|
422 | * Fixed: can not lock a topic when creating it | |
416 | * Fixed: Incorrect filtering for unset values when using 'is not' filter |
|
423 | * Fixed: Incorrect filtering for unset values when using 'is not' filter | |
417 | * Fixed: PostgreSQL issues_seq_id not updated when using Trac importer |
|
424 | * Fixed: PostgreSQL issues_seq_id not updated when using Trac importer | |
418 | * Fixed: ajax pagination does not scroll up |
|
425 | * Fixed: ajax pagination does not scroll up | |
419 | * Fixed: error when uploading a file with no content-type specified by the browser |
|
426 | * Fixed: error when uploading a file with no content-type specified by the browser | |
420 | * Fixed: wiki and changeset links not displayed when previewing issue description or notes |
|
427 | * Fixed: wiki and changeset links not displayed when previewing issue description or notes | |
421 | * Fixed: 'LdapError: no bind result' error when authenticating |
|
428 | * Fixed: 'LdapError: no bind result' error when authenticating | |
422 | * Fixed: 'LdapError: invalid binding information' when no username/password are set on the LDAP account |
|
429 | * Fixed: 'LdapError: invalid binding information' when no username/password are set on the LDAP account | |
423 | * Fixed: CVS repository doesn't work if port is used in the url |
|
430 | * Fixed: CVS repository doesn't work if port is used in the url | |
424 | * Fixed: Email notifications: host name is missing in generated links |
|
431 | * Fixed: Email notifications: host name is missing in generated links | |
425 | * Fixed: Email notifications: referenced changesets, wiki pages, attachments... are not turned into links |
|
432 | * Fixed: Email notifications: referenced changesets, wiki pages, attachments... are not turned into links | |
426 | * Fixed: Do not clear issue relations when moving an issue to another project if cross-project issue relations are allowed |
|
433 | * Fixed: Do not clear issue relations when moving an issue to another project if cross-project issue relations are allowed | |
427 | * Fixed: "undefined method 'textilizable'" error on email notification when running Repository#fetch_changesets from the console |
|
434 | * Fixed: "undefined method 'textilizable'" error on email notification when running Repository#fetch_changesets from the console | |
428 | * Fixed: Do not send an email with no recipient, cc or bcc |
|
435 | * Fixed: Do not send an email with no recipient, cc or bcc | |
429 | * Fixed: fetch_changesets fails on commit comments that close 2 duplicates issues. |
|
436 | * Fixed: fetch_changesets fails on commit comments that close 2 duplicates issues. | |
430 | * Fixed: Mercurial browsing under unix-like os and for directory depth > 2 |
|
437 | * Fixed: Mercurial browsing under unix-like os and for directory depth > 2 | |
431 | * Fixed: Wiki links with pipe can not be used in wiki tables |
|
438 | * Fixed: Wiki links with pipe can not be used in wiki tables | |
432 | * Fixed: migrate_from_trac doesn't import timestamps of wiki and tickets |
|
439 | * Fixed: migrate_from_trac doesn't import timestamps of wiki and tickets | |
433 | * Fixed: when bulk editing, setting "Assigned to" to "nobody" causes an sql error with Postgresql |
|
440 | * Fixed: when bulk editing, setting "Assigned to" to "nobody" causes an sql error with Postgresql | |
434 |
|
441 | |||
435 |
|
442 | |||
436 | == 2008-03-12 v0.6.4 |
|
443 | == 2008-03-12 v0.6.4 | |
437 |
|
444 | |||
438 | * Fixed: private projects name are displayed on account/show even if the current user doesn't have access to these private projects |
|
445 | * Fixed: private projects name are displayed on account/show even if the current user doesn't have access to these private projects | |
439 | * Fixed: potential LDAP authentication security flaw |
|
446 | * Fixed: potential LDAP authentication security flaw | |
440 | * Fixed: context submenus on the issue list don't show up with IE6. |
|
447 | * Fixed: context submenus on the issue list don't show up with IE6. | |
441 | * Fixed: Themes are not applied with Rails 2.0 |
|
448 | * Fixed: Themes are not applied with Rails 2.0 | |
442 | * Fixed: crash when fetching Mercurial changesets if changeset[:files] is nil |
|
449 | * Fixed: crash when fetching Mercurial changesets if changeset[:files] is nil | |
443 | * Fixed: Mercurial repository browsing |
|
450 | * Fixed: Mercurial repository browsing | |
444 | * Fixed: undefined local variable or method 'log' in CvsAdapter when a cvs command fails |
|
451 | * Fixed: undefined local variable or method 'log' in CvsAdapter when a cvs command fails | |
445 | * Fixed: not null constraints not removed with Postgresql |
|
452 | * Fixed: not null constraints not removed with Postgresql | |
446 | * Doctype set to transitional |
|
453 | * Doctype set to transitional | |
447 |
|
454 | |||
448 |
|
455 | |||
449 | == 2007-12-18 v0.6.3 |
|
456 | == 2007-12-18 v0.6.3 | |
450 |
|
457 | |||
451 | * Fixed: upload doesn't work in 'Files' section |
|
458 | * Fixed: upload doesn't work in 'Files' section | |
452 |
|
459 | |||
453 |
|
460 | |||
454 | == 2007-12-16 v0.6.2 |
|
461 | == 2007-12-16 v0.6.2 | |
455 |
|
462 | |||
456 | * Search engine: issue custom fields can now be searched |
|
463 | * Search engine: issue custom fields can now be searched | |
457 | * News comments are now textilized |
|
464 | * News comments are now textilized | |
458 | * Updated Japanese translation (Satoru Kurashiki) |
|
465 | * Updated Japanese translation (Satoru Kurashiki) | |
459 | * Updated Chinese translation (Shortie Lo) |
|
466 | * Updated Chinese translation (Shortie Lo) | |
460 | * Fixed Rails 2.0 compatibility bugs: |
|
467 | * Fixed Rails 2.0 compatibility bugs: | |
461 | * Unable to create a wiki |
|
468 | * Unable to create a wiki | |
462 | * Gantt and calendar error |
|
469 | * Gantt and calendar error | |
463 | * Trac importer error (readonly? is defined by ActiveRecord) |
|
470 | * Trac importer error (readonly? is defined by ActiveRecord) | |
464 | * Fixed: 'assigned to me' filter broken |
|
471 | * Fixed: 'assigned to me' filter broken | |
465 | * Fixed: crash when validation fails on issue edition with no custom fields |
|
472 | * Fixed: crash when validation fails on issue edition with no custom fields | |
466 | * Fixed: reposman "can't find group" error |
|
473 | * Fixed: reposman "can't find group" error | |
467 | * Fixed: 'LDAP account password is too long' error when leaving the field empty on creation |
|
474 | * Fixed: 'LDAP account password is too long' error when leaving the field empty on creation | |
468 | * Fixed: empty lines when displaying repository files with Windows style eol |
|
475 | * Fixed: empty lines when displaying repository files with Windows style eol | |
469 | * Fixed: missing body closing tag in repository annotate and entry views |
|
476 | * Fixed: missing body closing tag in repository annotate and entry views | |
470 |
|
477 | |||
471 |
|
478 | |||
472 | == 2007-12-10 v0.6.1 |
|
479 | == 2007-12-10 v0.6.1 | |
473 |
|
480 | |||
474 | * Rails 2.0 compatibility |
|
481 | * Rails 2.0 compatibility | |
475 | * Custom fields can now be displayed as columns on the issue list |
|
482 | * Custom fields can now be displayed as columns on the issue list | |
476 | * Added version details view (accessible from the roadmap) |
|
483 | * Added version details view (accessible from the roadmap) | |
477 | * Roadmap: more accurate completion percentage calculation (done ratio of open issues is now taken into account) |
|
484 | * Roadmap: more accurate completion percentage calculation (done ratio of open issues is now taken into account) | |
478 | * Added per-project tracker selection. Trackers can be selected on project settings |
|
485 | * Added per-project tracker selection. Trackers can be selected on project settings | |
479 | * Anonymous users can now be allowed to create, edit, comment issues, comment news and post messages in the forums |
|
486 | * Anonymous users can now be allowed to create, edit, comment issues, comment news and post messages in the forums | |
480 | * Forums: messages can now be edited/deleted (explicit permissions need to be given) |
|
487 | * Forums: messages can now be edited/deleted (explicit permissions need to be given) | |
481 | * Forums: topics can be locked so that no reply can be added |
|
488 | * Forums: topics can be locked so that no reply can be added | |
482 | * Forums: topics can be marked as sticky so that they always appear at the top of the list |
|
489 | * Forums: topics can be marked as sticky so that they always appear at the top of the list | |
483 | * Forums: attachments can now be added to replies |
|
490 | * Forums: attachments can now be added to replies | |
484 | * Added time zone support |
|
491 | * Added time zone support | |
485 | * Added a setting to choose the account activation strategy (available in application settings) |
|
492 | * Added a setting to choose the account activation strategy (available in application settings) | |
486 | * Added 'Classic' theme (inspired from the v0.51 design) |
|
493 | * Added 'Classic' theme (inspired from the v0.51 design) | |
487 | * Added an alternate theme which provides issue list colorization based on issues priority |
|
494 | * Added an alternate theme which provides issue list colorization based on issues priority | |
488 | * Added Bazaar SCM adapter |
|
495 | * Added Bazaar SCM adapter | |
489 | * Added Annotate/Blame view in the repository browser (except for Darcs SCM) |
|
496 | * Added Annotate/Blame view in the repository browser (except for Darcs SCM) | |
490 | * Diff style (inline or side by side) automatically saved as a user preference |
|
497 | * Diff style (inline or side by side) automatically saved as a user preference | |
491 | * Added issues status changes on the activity view (by Cyril Mougel) |
|
498 | * Added issues status changes on the activity view (by Cyril Mougel) | |
492 | * Added forums topics on the activity view (disabled by default) |
|
499 | * Added forums topics on the activity view (disabled by default) | |
493 | * Added an option on 'My account' for users who don't want to be notified of changes that they make |
|
500 | * Added an option on 'My account' for users who don't want to be notified of changes that they make | |
494 | * Trac importer now supports mysql and postgresql databases |
|
501 | * Trac importer now supports mysql and postgresql databases | |
495 | * Trac importer improvements (by Mat Trudel) |
|
502 | * Trac importer improvements (by Mat Trudel) | |
496 | * 'fixed version' field can now be displayed on the issue list |
|
503 | * 'fixed version' field can now be displayed on the issue list | |
497 | * Added a couple of new formats for the 'date format' setting |
|
504 | * Added a couple of new formats for the 'date format' setting | |
498 | * Added Traditional Chinese translation (by Shortie Lo) |
|
505 | * Added Traditional Chinese translation (by Shortie Lo) | |
499 | * Added Russian translation (iGor kMeta) |
|
506 | * Added Russian translation (iGor kMeta) | |
500 | * Project name format limitation removed (name can now contain any character) |
|
507 | * Project name format limitation removed (name can now contain any character) | |
501 | * Project identifier maximum length changed from 12 to 20 |
|
508 | * Project identifier maximum length changed from 12 to 20 | |
502 | * Changed the maximum length of LDAP account to 255 characters |
|
509 | * Changed the maximum length of LDAP account to 255 characters | |
503 | * Removed the 12 characters limit on passwords |
|
510 | * Removed the 12 characters limit on passwords | |
504 | * Added wiki macros support |
|
511 | * Added wiki macros support | |
505 | * Performance improvement on workflow setup screen |
|
512 | * Performance improvement on workflow setup screen | |
506 | * More detailed html title on several views |
|
513 | * More detailed html title on several views | |
507 | * Custom fields can now be reordered |
|
514 | * Custom fields can now be reordered | |
508 | * Search engine: search can be restricted to an exact phrase by using quotation marks |
|
515 | * Search engine: search can be restricted to an exact phrase by using quotation marks | |
509 | * Added custom fields marked as 'For all projects' to the csv export of the cross project issue list |
|
516 | * Added custom fields marked as 'For all projects' to the csv export of the cross project issue list | |
510 | * Email notifications are now sent as Blind carbon copy by default |
|
517 | * Email notifications are now sent as Blind carbon copy by default | |
511 | * Fixed: all members (including non active) should be deleted when deleting a project |
|
518 | * Fixed: all members (including non active) should be deleted when deleting a project | |
512 | * Fixed: Error on wiki syntax link (accessible from wiki/edit) |
|
519 | * Fixed: Error on wiki syntax link (accessible from wiki/edit) | |
513 | * Fixed: 'quick jump to a revision' form on the revisions list |
|
520 | * Fixed: 'quick jump to a revision' form on the revisions list | |
514 | * Fixed: error on admin/info if there's more than 1 plugin installed |
|
521 | * Fixed: error on admin/info if there's more than 1 plugin installed | |
515 | * Fixed: svn or ldap password can be found in clear text in the html source in editing mode |
|
522 | * Fixed: svn or ldap password can be found in clear text in the html source in editing mode | |
516 | * Fixed: 'Assigned to' drop down list is not sorted |
|
523 | * Fixed: 'Assigned to' drop down list is not sorted | |
517 | * Fixed: 'View all issues' link doesn't work on issues/show |
|
524 | * Fixed: 'View all issues' link doesn't work on issues/show | |
518 | * Fixed: error on account/register when validation fails |
|
525 | * Fixed: error on account/register when validation fails | |
519 | * Fixed: Error when displaying the issue list if a float custom field is marked as 'used as filter' |
|
526 | * Fixed: Error when displaying the issue list if a float custom field is marked as 'used as filter' | |
520 | * Fixed: Mercurial adapter breaks on missing :files entry in changeset hash (James Britt) |
|
527 | * Fixed: Mercurial adapter breaks on missing :files entry in changeset hash (James Britt) | |
521 | * Fixed: Wrong feed URLs on the home page |
|
528 | * Fixed: Wrong feed URLs on the home page | |
522 | * Fixed: Update of time entry fails when the issue has been moved to an other project |
|
529 | * Fixed: Update of time entry fails when the issue has been moved to an other project | |
523 | * Fixed: Error when moving an issue without changing its tracker (Postgresql) |
|
530 | * Fixed: Error when moving an issue without changing its tracker (Postgresql) | |
524 | * Fixed: Changes not recorded when using :pserver string (CVS adapter) |
|
531 | * Fixed: Changes not recorded when using :pserver string (CVS adapter) | |
525 | * Fixed: admin should be able to move issues to any project |
|
532 | * Fixed: admin should be able to move issues to any project | |
526 | * Fixed: adding an attachment is not possible when changing the status of an issue |
|
533 | * Fixed: adding an attachment is not possible when changing the status of an issue | |
527 | * Fixed: No mime-types in documents/files downloading |
|
534 | * Fixed: No mime-types in documents/files downloading | |
528 | * Fixed: error when sorting the messages if there's only one board for the project |
|
535 | * Fixed: error when sorting the messages if there's only one board for the project | |
529 | * Fixed: 'me' doesn't appear in the drop down filters on a project issue list. |
|
536 | * Fixed: 'me' doesn't appear in the drop down filters on a project issue list. | |
530 |
|
537 | |||
531 | == 2007-11-04 v0.6.0 |
|
538 | == 2007-11-04 v0.6.0 | |
532 |
|
539 | |||
533 | * Permission model refactoring. |
|
540 | * Permission model refactoring. | |
534 | * Permissions: there are now 2 builtin roles that can be used to specify permissions given to other users than members of projects |
|
541 | * Permissions: there are now 2 builtin roles that can be used to specify permissions given to other users than members of projects | |
535 | * Permissions: some permissions (eg. browse the repository) can be removed for certain roles |
|
542 | * Permissions: some permissions (eg. browse the repository) can be removed for certain roles | |
536 | * Permissions: modules (eg. issue tracking, news, documents...) can be enabled/disabled at project level |
|
543 | * Permissions: modules (eg. issue tracking, news, documents...) can be enabled/disabled at project level | |
537 | * Added Mantis and Trac importers |
|
544 | * Added Mantis and Trac importers | |
538 | * New application layout |
|
545 | * New application layout | |
539 | * Added "Bulk edit" functionality on the issue list |
|
546 | * Added "Bulk edit" functionality on the issue list | |
540 | * More flexible mail notifications settings at user level |
|
547 | * More flexible mail notifications settings at user level | |
541 | * Added AJAX based context menu on the project issue list that provide shortcuts for editing, re-assigning, changing the status or the priority, moving or deleting an issue |
|
548 | * Added AJAX based context menu on the project issue list that provide shortcuts for editing, re-assigning, changing the status or the priority, moving or deleting an issue | |
542 | * Added the hability to copy an issue. It can be done from the "issue/show" view or from the context menu on the issue list |
|
549 | * Added the hability to copy an issue. It can be done from the "issue/show" view or from the context menu on the issue list | |
543 | * Added the ability to customize issue list columns (at application level or for each saved query) |
|
550 | * Added the ability to customize issue list columns (at application level or for each saved query) | |
544 | * Overdue versions (date reached and open issues > 0) are now always displayed on the roadmap |
|
551 | * Overdue versions (date reached and open issues > 0) are now always displayed on the roadmap | |
545 | * Added the ability to rename wiki pages (specific permission required) |
|
552 | * Added the ability to rename wiki pages (specific permission required) | |
546 | * Search engines now supports pagination. Results are sorted in reverse chronological order |
|
553 | * Search engines now supports pagination. Results are sorted in reverse chronological order | |
547 | * Added "Estimated hours" attribute on issues |
|
554 | * Added "Estimated hours" attribute on issues | |
548 | * A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category |
|
555 | * A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category | |
549 | * Forum notifications are now also sent to the authors of the thread, even if they donοΏ½t watch the board |
|
556 | * Forum notifications are now also sent to the authors of the thread, even if they donοΏ½t watch the board | |
550 | * Added an application setting to specify the application protocol (http or https) used to generate urls in emails |
|
557 | * Added an application setting to specify the application protocol (http or https) used to generate urls in emails | |
551 | * Gantt chart: now starts at the current month by default |
|
558 | * Gantt chart: now starts at the current month by default | |
552 | * Gantt chart: month count and zoom factor are automatically saved as user preferences |
|
559 | * Gantt chart: month count and zoom factor are automatically saved as user preferences | |
553 | * Wiki links can now refer to other project wikis |
|
560 | * Wiki links can now refer to other project wikis | |
554 | * Added wiki index by date |
|
561 | * Added wiki index by date | |
555 | * Added preview on add/edit issue form |
|
562 | * Added preview on add/edit issue form | |
556 | * Emails footer can now be customized from the admin interface (Admin -> Email notifications) |
|
563 | * Emails footer can now be customized from the admin interface (Admin -> Email notifications) | |
557 | * Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that theyοΏ½re properly displayed) |
|
564 | * Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that theyοΏ½re properly displayed) | |
558 | * Calendar: first day of week can now be set in lang files |
|
565 | * Calendar: first day of week can now be set in lang files | |
559 | * Automatic closing of duplicate issues |
|
566 | * Automatic closing of duplicate issues | |
560 | * Added a cross-project issue list |
|
567 | * Added a cross-project issue list | |
561 | * AJAXified the SCM browser (tree view) |
|
568 | * AJAXified the SCM browser (tree view) | |
562 | * Pretty URL for the repository browser (Cyril Mougel) |
|
569 | * Pretty URL for the repository browser (Cyril Mougel) | |
563 | * Search engine: added a checkbox to search titles only |
|
570 | * Search engine: added a checkbox to search titles only | |
564 | * Added "% done" in the filter list |
|
571 | * Added "% done" in the filter list | |
565 | * Enumerations: values can now be reordered and a default value can be specified (eg. default issue priority) |
|
572 | * Enumerations: values can now be reordered and a default value can be specified (eg. default issue priority) | |
566 | * Added some accesskeys |
|
573 | * Added some accesskeys | |
567 | * Added "Float" as a custom field format |
|
574 | * Added "Float" as a custom field format | |
568 | * Added basic Theme support |
|
575 | * Added basic Theme support | |
569 | * Added the ability to set the οΏ½done ratioοΏ½ of issues fixed by commit (Nikolay Solakov) |
|
576 | * Added the ability to set the οΏ½done ratioοΏ½ of issues fixed by commit (Nikolay Solakov) | |
570 | * Added custom fields in issue related mail notifications |
|
577 | * Added custom fields in issue related mail notifications | |
571 | * Email notifications are now sent in plain text and html |
|
578 | * Email notifications are now sent in plain text and html | |
572 | * Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed. |
|
579 | * Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed. | |
573 | * Added syntax highlightment for repository files and wiki |
|
580 | * Added syntax highlightment for repository files and wiki | |
574 | * Improved automatic Redmine links |
|
581 | * Improved automatic Redmine links | |
575 | * Added automatic table of content support on wiki pages |
|
582 | * Added automatic table of content support on wiki pages | |
576 | * Added radio buttons on the documents list to sort documents by category, date, title or author |
|
583 | * Added radio buttons on the documents list to sort documents by category, date, title or author | |
577 | * Added basic plugin support, with a sample plugin |
|
584 | * Added basic plugin support, with a sample plugin | |
578 | * Added a link to add a new category when creating or editing an issue |
|
585 | * Added a link to add a new category when creating or editing an issue | |
579 | * Added a "Assignable" boolean on the Role model. If unchecked, issues can not be assigned to users having this role. |
|
586 | * Added a "Assignable" boolean on the Role model. If unchecked, issues can not be assigned to users having this role. | |
580 | * Added an option to be able to relate issues in different projects |
|
587 | * Added an option to be able to relate issues in different projects | |
581 | * Added the ability to move issues (to another project) without changing their trackers. |
|
588 | * Added the ability to move issues (to another project) without changing their trackers. | |
582 | * Atom feeds added on project activity, news and changesets |
|
589 | * Atom feeds added on project activity, news and changesets | |
583 | * Added the ability to reset its own RSS access key |
|
590 | * Added the ability to reset its own RSS access key | |
584 | * Main project list now displays root projects with their subprojects |
|
591 | * Main project list now displays root projects with their subprojects | |
585 | * Added anchor links to issue notes |
|
592 | * Added anchor links to issue notes | |
586 | * Added reposman Ruby version. This script can now register created repositories in Redmine (Nicolas Chuche) |
|
593 | * Added reposman Ruby version. This script can now register created repositories in Redmine (Nicolas Chuche) | |
587 | * Issue notes are now included in search |
|
594 | * Issue notes are now included in search | |
588 | * Added email sending test functionality |
|
595 | * Added email sending test functionality | |
589 | * Added LDAPS support for LDAP authentication |
|
596 | * Added LDAPS support for LDAP authentication | |
590 | * Removed hard-coded URLs in mail templates |
|
597 | * Removed hard-coded URLs in mail templates | |
591 | * Subprojects are now grouped by projects in the navigation drop-down menu |
|
598 | * Subprojects are now grouped by projects in the navigation drop-down menu | |
592 | * Added a new value for date filters: this week |
|
599 | * Added a new value for date filters: this week | |
593 | * Added cache for application settings |
|
600 | * Added cache for application settings | |
594 | * Added Polish translation (Tomasz Gawryl) |
|
601 | * Added Polish translation (Tomasz Gawryl) | |
595 | * Added Czech translation (Jan Kadlecek) |
|
602 | * Added Czech translation (Jan Kadlecek) | |
596 | * Added Romanian translation (Csongor Bartus) |
|
603 | * Added Romanian translation (Csongor Bartus) | |
597 | * Added Hebrew translation (Bob Builder) |
|
604 | * Added Hebrew translation (Bob Builder) | |
598 | * Added Serbian translation (Dragan Matic) |
|
605 | * Added Serbian translation (Dragan Matic) | |
599 | * Added Korean translation (Choi Jong Yoon) |
|
606 | * Added Korean translation (Choi Jong Yoon) | |
600 | * Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations |
|
607 | * Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations | |
601 | * Performance improvement on calendar and gantt |
|
608 | * Performance improvement on calendar and gantt | |
602 | * Fixed: wiki preview doesnοΏ½t work on long entries |
|
609 | * Fixed: wiki preview doesnοΏ½t work on long entries | |
603 | * Fixed: queries with multiple custom fields return no result |
|
610 | * Fixed: queries with multiple custom fields return no result | |
604 | * Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters |
|
611 | * Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters | |
605 | * Fixed: URL with ~ broken in wiki formatting |
|
612 | * Fixed: URL with ~ broken in wiki formatting | |
606 | * Fixed: some quotation marks are rendered as strange characters in pdf |
|
613 | * Fixed: some quotation marks are rendered as strange characters in pdf | |
607 |
|
614 | |||
608 |
|
615 | |||
609 | == 2007-07-15 v0.5.1 |
|
616 | == 2007-07-15 v0.5.1 | |
610 |
|
617 | |||
611 | * per project forums added |
|
618 | * per project forums added | |
612 | * added the ability to archive projects |
|
619 | * added the ability to archive projects | |
613 | * added οΏ½WatchοΏ½ functionality on issues. It allows users to receive notifications about issue changes |
|
620 | * added οΏ½WatchοΏ½ functionality on issues. It allows users to receive notifications about issue changes | |
614 | * custom fields for issues can now be used as filters on issue list |
|
621 | * custom fields for issues can now be used as filters on issue list | |
615 | * added per user custom queries |
|
622 | * added per user custom queries | |
616 | * commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings) |
|
623 | * commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings) | |
617 | * projects list now shows the list of public projects and private projects for which the user is a member |
|
624 | * projects list now shows the list of public projects and private projects for which the user is a member | |
618 | * versions can now be created with no date |
|
625 | * versions can now be created with no date | |
619 | * added issue count details for versions on Reports view |
|
626 | * added issue count details for versions on Reports view | |
620 | * added time report, by member/activity/tracker/version and year/month/week for the selected period |
|
627 | * added time report, by member/activity/tracker/version and year/month/week for the selected period | |
621 | * each category can now be associated to a user, so that new issues in that category are automatically assigned to that user |
|
628 | * each category can now be associated to a user, so that new issues in that category are automatically assigned to that user | |
622 | * added autologin feature (disabled by default) |
|
629 | * added autologin feature (disabled by default) | |
623 | * optimistic locking added for wiki edits |
|
630 | * optimistic locking added for wiki edits | |
624 | * added wiki diff |
|
631 | * added wiki diff | |
625 | * added the ability to destroy wiki pages (requires permission) |
|
632 | * added the ability to destroy wiki pages (requires permission) | |
626 | * a wiki page can now be attached to each version, and displayed on the roadmap |
|
633 | * a wiki page can now be attached to each version, and displayed on the roadmap | |
627 | * attachments can now be added to wiki pages (original patch by Pavol Murin) and displayed online |
|
634 | * attachments can now be added to wiki pages (original patch by Pavol Murin) and displayed online | |
628 | * added an option to see all versions in the roadmap view (including completed ones) |
|
635 | * added an option to see all versions in the roadmap view (including completed ones) | |
629 | * added basic issue relations |
|
636 | * added basic issue relations | |
630 | * added the ability to log time when changing an issue status |
|
637 | * added the ability to log time when changing an issue status | |
631 | * account information can now be sent to the user when creating an account |
|
638 | * account information can now be sent to the user when creating an account | |
632 | * author and assignee of an issue always receive notifications (even if they turned of mail notifications) |
|
639 | * author and assignee of an issue always receive notifications (even if they turned of mail notifications) | |
633 | * added a quick search form in page header |
|
640 | * added a quick search form in page header | |
634 | * added 'me' value for 'assigned to' and 'author' query filters |
|
641 | * added 'me' value for 'assigned to' and 'author' query filters | |
635 | * added a link on revision screen to see the entire diff for the revision |
|
642 | * added a link on revision screen to see the entire diff for the revision | |
636 | * added last commit message for each entry in repository browser |
|
643 | * added last commit message for each entry in repository browser | |
637 | * added the ability to view a file diff with free to/from revision selection. |
|
644 | * added the ability to view a file diff with free to/from revision selection. | |
638 | * text files can now be viewed online when browsing the repository |
|
645 | * text files can now be viewed online when browsing the repository | |
639 | * added basic support for other SCM: CVS (Ralph Vater), Mercurial and Darcs |
|
646 | * added basic support for other SCM: CVS (Ralph Vater), Mercurial and Darcs | |
640 | * added fragment caching for svn diffs |
|
647 | * added fragment caching for svn diffs | |
641 | * added fragment caching for calendar and gantt views |
|
648 | * added fragment caching for calendar and gantt views | |
642 | * login field automatically focused on login form |
|
649 | * login field automatically focused on login form | |
643 | * subproject name displayed on issue list, calendar and gantt |
|
650 | * subproject name displayed on issue list, calendar and gantt | |
644 | * added an option to choose the date format: language based or ISO 8601 |
|
651 | * added an option to choose the date format: language based or ISO 8601 | |
645 | * added a simple mail handler. It lets users add notes to an existing issue by replying to the initial notification email. |
|
652 | * added a simple mail handler. It lets users add notes to an existing issue by replying to the initial notification email. | |
646 | * a 403 error page is now displayed (instead of a blank page) when trying to access a protected page |
|
653 | * a 403 error page is now displayed (instead of a blank page) when trying to access a protected page | |
647 | * added portuguese translation (Joao Carlos Clementoni) |
|
654 | * added portuguese translation (Joao Carlos Clementoni) | |
648 | * added partial online help japanese translation (Ken Date) |
|
655 | * added partial online help japanese translation (Ken Date) | |
649 | * added bulgarian translation (Nikolay Solakov) |
|
656 | * added bulgarian translation (Nikolay Solakov) | |
650 | * added dutch translation (Linda van den Brink) |
|
657 | * added dutch translation (Linda van den Brink) | |
651 | * added swedish translation (Thomas Habets) |
|
658 | * added swedish translation (Thomas Habets) | |
652 | * italian translation update (Alessio Spadaro) |
|
659 | * italian translation update (Alessio Spadaro) | |
653 | * japanese translation update (Satoru Kurashiki) |
|
660 | * japanese translation update (Satoru Kurashiki) | |
654 | * fixed: error on history atom feed when thereοΏ½s no notes on an issue change |
|
661 | * fixed: error on history atom feed when thereοΏ½s no notes on an issue change | |
655 | * fixed: error in journalizing an issue with longtext custom fields (Postgresql) |
|
662 | * fixed: error in journalizing an issue with longtext custom fields (Postgresql) | |
656 | * fixed: creation of Oracle schema |
|
663 | * fixed: creation of Oracle schema | |
657 | * fixed: last day of the month not included in project activity |
|
664 | * fixed: last day of the month not included in project activity | |
658 | * fixed: files with an apostrophe in their names can't be accessed in SVN repository |
|
665 | * fixed: files with an apostrophe in their names can't be accessed in SVN repository | |
659 | * fixed: performance issue on RepositoriesController#revisions when a changeset has a great number of changes (eg. 100,000) |
|
666 | * fixed: performance issue on RepositoriesController#revisions when a changeset has a great number of changes (eg. 100,000) | |
660 | * fixed: open/closed issue counts are always 0 on reports view (postgresql) |
|
667 | * fixed: open/closed issue counts are always 0 on reports view (postgresql) | |
661 | * fixed: date query filters (wrong results and sql error with postgresql) |
|
668 | * fixed: date query filters (wrong results and sql error with postgresql) | |
662 | * fixed: confidentiality issue on account/show (private project names displayed to anyone) |
|
669 | * fixed: confidentiality issue on account/show (private project names displayed to anyone) | |
663 | * fixed: Long text custom fields displayed without line breaks |
|
670 | * fixed: Long text custom fields displayed without line breaks | |
664 | * fixed: Error when editing the wokflow after deleting a status |
|
671 | * fixed: Error when editing the wokflow after deleting a status | |
665 | * fixed: SVN commit dates are now stored as local time |
|
672 | * fixed: SVN commit dates are now stored as local time | |
666 |
|
673 | |||
667 |
|
674 | |||
668 | == 2007-04-11 v0.5.0 |
|
675 | == 2007-04-11 v0.5.0 | |
669 |
|
676 | |||
670 | * added per project Wiki |
|
677 | * added per project Wiki | |
671 | * added rss/atom feeds at project level (custom queries can be used as feeds) |
|
678 | * added rss/atom feeds at project level (custom queries can be used as feeds) | |
672 | * added search engine (search in issues, news, commits, wiki pages, documents) |
|
679 | * added search engine (search in issues, news, commits, wiki pages, documents) | |
673 | * simple time tracking functionality added |
|
680 | * simple time tracking functionality added | |
674 | * added version due dates on calendar and gantt |
|
681 | * added version due dates on calendar and gantt | |
675 | * added subprojects issue count on project Reports page |
|
682 | * added subprojects issue count on project Reports page | |
676 | * added the ability to copy an existing workflow when creating a new tracker |
|
683 | * added the ability to copy an existing workflow when creating a new tracker | |
677 | * added the ability to include subprojects on calendar and gantt |
|
684 | * added the ability to include subprojects on calendar and gantt | |
678 | * added the ability to select trackers to display on calendar and gantt (Jeffrey Jones) |
|
685 | * added the ability to select trackers to display on calendar and gantt (Jeffrey Jones) | |
679 | * added side by side svn diff view (Cyril Mougel) |
|
686 | * added side by side svn diff view (Cyril Mougel) | |
680 | * added back subproject filter on issue list |
|
687 | * added back subproject filter on issue list | |
681 | * added permissions report in admin area |
|
688 | * added permissions report in admin area | |
682 | * added a status filter on users list |
|
689 | * added a status filter on users list | |
683 | * support for password-protected SVN repositories |
|
690 | * support for password-protected SVN repositories | |
684 | * SVN commits are now stored in the database |
|
691 | * SVN commits are now stored in the database | |
685 | * added simple svn statistics SVG graphs |
|
692 | * added simple svn statistics SVG graphs | |
686 | * progress bars for roadmap versions (Nick Read) |
|
693 | * progress bars for roadmap versions (Nick Read) | |
687 | * issue history now shows file uploads and deletions |
|
694 | * issue history now shows file uploads and deletions | |
688 | * #id patterns are turned into links to issues in descriptions and commit messages |
|
695 | * #id patterns are turned into links to issues in descriptions and commit messages | |
689 | * japanese translation added (Satoru Kurashiki) |
|
696 | * japanese translation added (Satoru Kurashiki) | |
690 | * chinese simplified translation added (Andy Wu) |
|
697 | * chinese simplified translation added (Andy Wu) | |
691 | * italian translation added (Alessio Spadaro) |
|
698 | * italian translation added (Alessio Spadaro) | |
692 | * added scripts to manage SVN repositories creation and user access control using ssh+svn (Nicolas Chuche) |
|
699 | * added scripts to manage SVN repositories creation and user access control using ssh+svn (Nicolas Chuche) | |
693 | * better calendar rendering time |
|
700 | * better calendar rendering time | |
694 | * fixed migration scripts to work with mysql 5 running in strict mode |
|
701 | * fixed migration scripts to work with mysql 5 running in strict mode | |
695 | * fixed: error when clicking "add" with no block selected on my/page_layout |
|
702 | * fixed: error when clicking "add" with no block selected on my/page_layout | |
696 | * fixed: hard coded links in navigation bar |
|
703 | * fixed: hard coded links in navigation bar | |
697 | * fixed: table_name pre/suffix support |
|
704 | * fixed: table_name pre/suffix support | |
698 |
|
705 | |||
699 |
|
706 | |||
700 | == 2007-02-18 v0.4.2 |
|
707 | == 2007-02-18 v0.4.2 | |
701 |
|
708 | |||
702 | * Rails 1.2 is now required |
|
709 | * Rails 1.2 is now required | |
703 | * settings are now stored in the database and editable through the application in: Admin -> Settings (config_custom.rb is no longer used) |
|
710 | * settings are now stored in the database and editable through the application in: Admin -> Settings (config_custom.rb is no longer used) | |
704 | * added project roadmap view |
|
711 | * added project roadmap view | |
705 | * mail notifications added when a document, a file or an attachment is added |
|
712 | * mail notifications added when a document, a file or an attachment is added | |
706 | * tooltips added on Gantt chart and calender to view the details of the issues |
|
713 | * tooltips added on Gantt chart and calender to view the details of the issues | |
707 | * ability to set the sort order for roles, trackers, issue statuses |
|
714 | * ability to set the sort order for roles, trackers, issue statuses | |
708 | * added missing fields to csv export: priority, start date, due date, done ratio |
|
715 | * added missing fields to csv export: priority, start date, due date, done ratio | |
709 | * added total number of issues per tracker on project overview |
|
716 | * added total number of issues per tracker on project overview | |
710 | * all icons replaced (new icons are based on GPL icon set: "KDE Crystal Diamond 2.5" -by paolino- and "kNeu! Alpha v0.1" -by Pablo Fabregat-) |
|
717 | * all icons replaced (new icons are based on GPL icon set: "KDE Crystal Diamond 2.5" -by paolino- and "kNeu! Alpha v0.1" -by Pablo Fabregat-) | |
711 | * added back "fixed version" field on issue screen and in filters |
|
718 | * added back "fixed version" field on issue screen and in filters | |
712 | * project settings screen split in 4 tabs |
|
719 | * project settings screen split in 4 tabs | |
713 | * custom fields screen split in 3 tabs (one for each kind of custom field) |
|
720 | * custom fields screen split in 3 tabs (one for each kind of custom field) | |
714 | * multiple issues pdf export now rendered as a table |
|
721 | * multiple issues pdf export now rendered as a table | |
715 | * added a button on users/list to manually activate an account |
|
722 | * added a button on users/list to manually activate an account | |
716 | * added a setting option to disable "password lost" functionality |
|
723 | * added a setting option to disable "password lost" functionality | |
717 | * added a setting option to set max number of issues in csv/pdf exports |
|
724 | * added a setting option to set max number of issues in csv/pdf exports | |
718 | * fixed: subprojects count is always 0 on projects list |
|
725 | * fixed: subprojects count is always 0 on projects list | |
719 | * fixed: locked users are proposed when adding a member to a project |
|
726 | * fixed: locked users are proposed when adding a member to a project | |
720 | * fixed: setting an issue status as default status leads to an sql error with SQLite |
|
727 | * fixed: setting an issue status as default status leads to an sql error with SQLite | |
721 | * fixed: unable to delete an issue status even if it's not used yet |
|
728 | * fixed: unable to delete an issue status even if it's not used yet | |
722 | * fixed: filters ignored when exporting a predefined query to csv/pdf |
|
729 | * fixed: filters ignored when exporting a predefined query to csv/pdf | |
723 | * fixed: crash when french "issue_edit" email notification is sent |
|
730 | * fixed: crash when french "issue_edit" email notification is sent | |
724 | * fixed: hide mail preference not saved (my/account) |
|
731 | * fixed: hide mail preference not saved (my/account) | |
725 | * fixed: crash when a new user try to edit its "my page" layout |
|
732 | * fixed: crash when a new user try to edit its "my page" layout | |
726 |
|
733 | |||
727 |
|
734 | |||
728 | == 2007-01-03 v0.4.1 |
|
735 | == 2007-01-03 v0.4.1 | |
729 |
|
736 | |||
730 | * fixed: emails have no recipient when one of the project members has notifications disabled |
|
737 | * fixed: emails have no recipient when one of the project members has notifications disabled | |
731 |
|
738 | |||
732 |
|
739 | |||
733 | == 2007-01-02 v0.4.0 |
|
740 | == 2007-01-02 v0.4.0 | |
734 |
|
741 | |||
735 | * simple SVN browser added (just needs svn binaries in PATH) |
|
742 | * simple SVN browser added (just needs svn binaries in PATH) | |
736 | * comments can now be added on news |
|
743 | * comments can now be added on news | |
737 | * "my page" is now customizable |
|
744 | * "my page" is now customizable | |
738 | * more powerfull and savable filters for issues lists |
|
745 | * more powerfull and savable filters for issues lists | |
739 | * improved issues change history |
|
746 | * improved issues change history | |
740 | * new functionality: move an issue to another project or tracker |
|
747 | * new functionality: move an issue to another project or tracker | |
741 | * new functionality: add a note to an issue |
|
748 | * new functionality: add a note to an issue | |
742 | * new report: project activity |
|
749 | * new report: project activity | |
743 | * "start date" and "% done" fields added on issues |
|
750 | * "start date" and "% done" fields added on issues | |
744 | * project calendar added |
|
751 | * project calendar added | |
745 | * gantt chart added (exportable to pdf) |
|
752 | * gantt chart added (exportable to pdf) | |
746 | * single/multiple issues pdf export added |
|
753 | * single/multiple issues pdf export added | |
747 | * issues reports improvements |
|
754 | * issues reports improvements | |
748 | * multiple file upload for issues, documents and files |
|
755 | * multiple file upload for issues, documents and files | |
749 | * option to set maximum size of uploaded files |
|
756 | * option to set maximum size of uploaded files | |
750 | * textile formating of issue and news descritions (RedCloth required) |
|
757 | * textile formating of issue and news descritions (RedCloth required) | |
751 | * integration of DotClear jstoolbar for textile formatting |
|
758 | * integration of DotClear jstoolbar for textile formatting | |
752 | * calendar date picker for date fields (LGPL DHTML Calendar http://sourceforge.net/projects/jscalendar) |
|
759 | * calendar date picker for date fields (LGPL DHTML Calendar http://sourceforge.net/projects/jscalendar) | |
753 | * new filter in issues list: Author |
|
760 | * new filter in issues list: Author | |
754 | * ajaxified paginators |
|
761 | * ajaxified paginators | |
755 | * news rss feed added |
|
762 | * news rss feed added | |
756 | * option to set number of results per page on issues list |
|
763 | * option to set number of results per page on issues list | |
757 | * localized csv separator (comma/semicolon) |
|
764 | * localized csv separator (comma/semicolon) | |
758 | * csv output encoded to ISO-8859-1 |
|
765 | * csv output encoded to ISO-8859-1 | |
759 | * user custom field displayed on account/show |
|
766 | * user custom field displayed on account/show | |
760 | * default configuration improved (default roles, trackers, status, permissions and workflows) |
|
767 | * default configuration improved (default roles, trackers, status, permissions and workflows) | |
761 | * language for default configuration data can now be chosen when running 'load_default_data' task |
|
768 | * language for default configuration data can now be chosen when running 'load_default_data' task | |
762 | * javascript added on custom field form to show/hide fields according to the format of custom field |
|
769 | * javascript added on custom field form to show/hide fields according to the format of custom field | |
763 | * fixed: custom fields not in csv exports |
|
770 | * fixed: custom fields not in csv exports | |
764 | * fixed: project settings now displayed according to user's permissions |
|
771 | * fixed: project settings now displayed according to user's permissions | |
765 | * fixed: application error when no version is selected on projects/add_file |
|
772 | * fixed: application error when no version is selected on projects/add_file | |
766 | * fixed: public actions not authorized for members of non public projects |
|
773 | * fixed: public actions not authorized for members of non public projects | |
767 | * fixed: non public projects were shown on welcome screen even if current user is not a member |
|
774 | * fixed: non public projects were shown on welcome screen even if current user is not a member | |
768 |
|
775 | |||
769 |
|
776 | |||
770 | == 2006-10-08 v0.3.0 |
|
777 | == 2006-10-08 v0.3.0 | |
771 |
|
778 | |||
772 | * user authentication against multiple LDAP (optional) |
|
779 | * user authentication against multiple LDAP (optional) | |
773 | * token based "lost password" functionality |
|
780 | * token based "lost password" functionality | |
774 | * user self-registration functionality (optional) |
|
781 | * user self-registration functionality (optional) | |
775 | * custom fields now available for issues, users and projects |
|
782 | * custom fields now available for issues, users and projects | |
776 | * new custom field format "text" (displayed as a textarea field) |
|
783 | * new custom field format "text" (displayed as a textarea field) | |
777 | * project & administration drop down menus in navigation bar for quicker access |
|
784 | * project & administration drop down menus in navigation bar for quicker access | |
778 | * text formatting is preserved for long text fields (issues, projects and news descriptions) |
|
785 | * text formatting is preserved for long text fields (issues, projects and news descriptions) | |
779 | * urls and emails are turned into clickable links in long text fields |
|
786 | * urls and emails are turned into clickable links in long text fields | |
780 | * "due date" field added on issues |
|
787 | * "due date" field added on issues | |
781 | * tracker selection filter added on change log |
|
788 | * tracker selection filter added on change log | |
782 | * Localization plugin replaced with GLoc 1.1.0 (iconv required) |
|
789 | * Localization plugin replaced with GLoc 1.1.0 (iconv required) | |
783 | * error messages internationalization |
|
790 | * error messages internationalization | |
784 | * german translation added (thanks to Karim Trott) |
|
791 | * german translation added (thanks to Karim Trott) | |
785 | * data locking for issues to prevent update conflicts (using ActiveRecord builtin optimistic locking) |
|
792 | * data locking for issues to prevent update conflicts (using ActiveRecord builtin optimistic locking) | |
786 | * new filter in issues list: "Fixed version" |
|
793 | * new filter in issues list: "Fixed version" | |
787 | * active filters are displayed with colored background on issues list |
|
794 | * active filters are displayed with colored background on issues list | |
788 | * custom configuration is now defined in config/config_custom.rb |
|
795 | * custom configuration is now defined in config/config_custom.rb | |
789 | * user object no more stored in session (only user_id) |
|
796 | * user object no more stored in session (only user_id) | |
790 | * news summary field is no longer required |
|
797 | * news summary field is no longer required | |
791 | * tables and forms redesign |
|
798 | * tables and forms redesign | |
792 | * Fixed: boolean custom field not working |
|
799 | * Fixed: boolean custom field not working | |
793 | * Fixed: error messages for custom fields are not displayed |
|
800 | * Fixed: error messages for custom fields are not displayed | |
794 | * Fixed: invalid custom fields should have a red border |
|
801 | * Fixed: invalid custom fields should have a red border | |
795 | * Fixed: custom fields values are not validated on issue update |
|
802 | * Fixed: custom fields values are not validated on issue update | |
796 | * Fixed: unable to choose an empty value for 'List' custom fields |
|
803 | * Fixed: unable to choose an empty value for 'List' custom fields | |
797 | * Fixed: no issue categories sorting |
|
804 | * Fixed: no issue categories sorting | |
798 | * Fixed: incorrect versions sorting |
|
805 | * Fixed: incorrect versions sorting | |
799 |
|
806 | |||
800 |
|
807 | |||
801 | == 2006-07-12 - v0.2.2 |
|
808 | == 2006-07-12 - v0.2.2 | |
802 |
|
809 | |||
803 | * Fixed: bug in "issues list" |
|
810 | * Fixed: bug in "issues list" | |
804 |
|
811 | |||
805 |
|
812 | |||
806 | == 2006-07-09 - v0.2.1 |
|
813 | == 2006-07-09 - v0.2.1 | |
807 |
|
814 | |||
808 | * new databases supported: Oracle, PostgreSQL, SQL Server |
|
815 | * new databases supported: Oracle, PostgreSQL, SQL Server | |
809 | * projects/subprojects hierarchy (1 level of subprojects only) |
|
816 | * projects/subprojects hierarchy (1 level of subprojects only) | |
810 | * environment information display in admin/info |
|
817 | * environment information display in admin/info | |
811 | * more filter options in issues list (rev6) |
|
818 | * more filter options in issues list (rev6) | |
812 | * default language based on browser settings (Accept-Language HTTP header) |
|
819 | * default language based on browser settings (Accept-Language HTTP header) | |
813 | * issues list exportable to CSV (rev6) |
|
820 | * issues list exportable to CSV (rev6) | |
814 | * simple_format and auto_link on long text fields |
|
821 | * simple_format and auto_link on long text fields | |
815 | * more data validations |
|
822 | * more data validations | |
816 | * Fixed: error when all mail notifications are unchecked in admin/mail_options |
|
823 | * Fixed: error when all mail notifications are unchecked in admin/mail_options | |
817 | * Fixed: all project news are displayed on project summary |
|
824 | * Fixed: all project news are displayed on project summary | |
818 | * Fixed: Can't change user password in users/edit |
|
825 | * Fixed: Can't change user password in users/edit | |
819 | * Fixed: Error on tables creation with PostgreSQL (rev5) |
|
826 | * Fixed: Error on tables creation with PostgreSQL (rev5) | |
820 | * Fixed: SQL error in "issue reports" view with PostgreSQL (rev5) |
|
827 | * Fixed: SQL error in "issue reports" view with PostgreSQL (rev5) | |
821 |
|
828 | |||
822 |
|
829 | |||
823 | == 2006-06-25 - v0.1.0 |
|
830 | == 2006-06-25 - v0.1.0 | |
824 |
|
831 | |||
825 | * multiple users/multiple projects |
|
832 | * multiple users/multiple projects | |
826 | * role based access control |
|
833 | * role based access control | |
827 | * issue tracking system |
|
834 | * issue tracking system | |
828 | * fully customizable workflow |
|
835 | * fully customizable workflow | |
829 | * documents/files repository |
|
836 | * documents/files repository | |
830 | * email notifications on issue creation and update |
|
837 | * email notifications on issue creation and update | |
831 | * multilanguage support (except for error messages):english, french, spanish |
|
838 | * multilanguage support (except for error messages):english, french, spanish | |
832 | * online manual in french (unfinished) |
|
839 | * online manual in french (unfinished) |
@@ -1,700 +1,700 | |||||
1 | _gloc_rule_default: '|n| n==1 ? "" : "_plural" ' |
|
1 | _gloc_rule_default: '|n| n==1 ? "" : "_plural" ' | |
2 |
|
2 | |||
3 | actionview_datehelper_select_day_prefix: |
|
3 | actionview_datehelper_select_day_prefix: | |
4 | actionview_datehelper_select_month_names: Janvier,FΓ©vrier,Mars,Avril,Mai,Juin,Juillet,AoΓ»t,Septembre,Octobre,Novembre,DΓ©cembre |
|
4 | actionview_datehelper_select_month_names: Janvier,FΓ©vrier,Mars,Avril,Mai,Juin,Juillet,AoΓ»t,Septembre,Octobre,Novembre,DΓ©cembre | |
5 | actionview_datehelper_select_month_names_abbr: Jan,FΓ©v,Mars,Avril,Mai,Juin,Juil,AoΓ»t,Sept,Oct,Nov,DΓ©c |
|
5 | actionview_datehelper_select_month_names_abbr: Jan,FΓ©v,Mars,Avril,Mai,Juin,Juil,AoΓ»t,Sept,Oct,Nov,DΓ©c | |
6 | actionview_datehelper_select_month_prefix: |
|
6 | actionview_datehelper_select_month_prefix: | |
7 | actionview_datehelper_select_year_prefix: |
|
7 | actionview_datehelper_select_year_prefix: | |
8 | actionview_datehelper_time_in_words_day: 1 jour |
|
8 | actionview_datehelper_time_in_words_day: 1 jour | |
9 | actionview_datehelper_time_in_words_day_plural: %d jours |
|
9 | actionview_datehelper_time_in_words_day_plural: %d jours | |
10 | actionview_datehelper_time_in_words_hour_about: environ une heure |
|
10 | actionview_datehelper_time_in_words_hour_about: environ une heure | |
11 | actionview_datehelper_time_in_words_hour_about_plural: environ %d heures |
|
11 | actionview_datehelper_time_in_words_hour_about_plural: environ %d heures | |
12 | actionview_datehelper_time_in_words_hour_about_single: environ une heure |
|
12 | actionview_datehelper_time_in_words_hour_about_single: environ une heure | |
13 | actionview_datehelper_time_in_words_minute: 1 minute |
|
13 | actionview_datehelper_time_in_words_minute: 1 minute | |
14 | actionview_datehelper_time_in_words_minute_half: 30 secondes |
|
14 | actionview_datehelper_time_in_words_minute_half: 30 secondes | |
15 | actionview_datehelper_time_in_words_minute_less_than: moins d'une minute |
|
15 | actionview_datehelper_time_in_words_minute_less_than: moins d'une minute | |
16 | actionview_datehelper_time_in_words_minute_plural: %d minutes |
|
16 | actionview_datehelper_time_in_words_minute_plural: %d minutes | |
17 | actionview_datehelper_time_in_words_minute_single: 1 minute |
|
17 | actionview_datehelper_time_in_words_minute_single: 1 minute | |
18 | actionview_datehelper_time_in_words_second_less_than: moins d'une seconde |
|
18 | actionview_datehelper_time_in_words_second_less_than: moins d'une seconde | |
19 | actionview_datehelper_time_in_words_second_less_than_plural: moins de %d secondes |
|
19 | actionview_datehelper_time_in_words_second_less_than_plural: moins de %d secondes | |
20 | actionview_instancetag_blank_option: Choisir |
|
20 | actionview_instancetag_blank_option: Choisir | |
21 |
|
21 | |||
22 | activerecord_error_inclusion: n'est pas inclus dans la liste |
|
22 | activerecord_error_inclusion: n'est pas inclus dans la liste | |
23 | activerecord_error_exclusion: est reservΓ© |
|
23 | activerecord_error_exclusion: est reservΓ© | |
24 | activerecord_error_invalid: est invalide |
|
24 | activerecord_error_invalid: est invalide | |
25 | activerecord_error_confirmation: ne correspond pas Γ la confirmation |
|
25 | activerecord_error_confirmation: ne correspond pas Γ la confirmation | |
26 | activerecord_error_accepted: doit Γͺtre acceptΓ© |
|
26 | activerecord_error_accepted: doit Γͺtre acceptΓ© | |
27 | activerecord_error_empty: doit Γͺtre renseignΓ© |
|
27 | activerecord_error_empty: doit Γͺtre renseignΓ© | |
28 | activerecord_error_blank: doit Γͺtre renseignΓ© |
|
28 | activerecord_error_blank: doit Γͺtre renseignΓ© | |
29 | activerecord_error_too_long: est trop long |
|
29 | activerecord_error_too_long: est trop long | |
30 | activerecord_error_too_short: est trop court |
|
30 | activerecord_error_too_short: est trop court | |
31 | activerecord_error_wrong_length: n'est pas de la bonne longueur |
|
31 | activerecord_error_wrong_length: n'est pas de la bonne longueur | |
32 | activerecord_error_taken: est dΓ©jΓ utilisΓ© |
|
32 | activerecord_error_taken: est dΓ©jΓ utilisΓ© | |
33 | activerecord_error_not_a_number: n'est pas un nombre |
|
33 | activerecord_error_not_a_number: n'est pas un nombre | |
34 | activerecord_error_not_a_date: n'est pas une date valide |
|
34 | activerecord_error_not_a_date: n'est pas une date valide | |
35 | activerecord_error_greater_than_start_date: doit Γͺtre postΓ©rieur Γ la date de dΓ©but |
|
35 | activerecord_error_greater_than_start_date: doit Γͺtre postΓ©rieur Γ la date de dΓ©but | |
36 | activerecord_error_not_same_project: n'appartient pas au mΓͺme projet |
|
36 | activerecord_error_not_same_project: n'appartient pas au mΓͺme projet | |
37 | activerecord_error_circular_dependency: Cette relation crΓ©erait une dΓ©pendance circulaire |
|
37 | activerecord_error_circular_dependency: Cette relation crΓ©erait une dΓ©pendance circulaire | |
38 |
|
38 | |||
39 | general_fmt_age: %d an |
|
39 | general_fmt_age: %d an | |
40 | general_fmt_age_plural: %d ans |
|
40 | general_fmt_age_plural: %d ans | |
41 | general_fmt_date: %%d/%%m/%%Y |
|
41 | general_fmt_date: %%d/%%m/%%Y | |
42 | general_fmt_datetime: %%d/%%m/%%Y %%H:%%M |
|
42 | general_fmt_datetime: %%d/%%m/%%Y %%H:%%M | |
43 | general_fmt_datetime_short: %%d/%%m %%H:%%M |
|
43 | general_fmt_datetime_short: %%d/%%m %%H:%%M | |
44 | general_fmt_time: %%H:%%M |
|
44 | general_fmt_time: %%H:%%M | |
45 | general_text_No: 'Non' |
|
45 | general_text_No: 'Non' | |
46 | general_text_Yes: 'Oui' |
|
46 | general_text_Yes: 'Oui' | |
47 | general_text_no: 'non' |
|
47 | general_text_no: 'non' | |
48 | general_text_yes: 'oui' |
|
48 | general_text_yes: 'oui' | |
49 | general_lang_name: 'FranΓ§ais' |
|
49 | general_lang_name: 'FranΓ§ais' | |
50 | general_csv_separator: ';' |
|
50 | general_csv_separator: ';' | |
51 | general_csv_decimal_separator: ',' |
|
51 | general_csv_decimal_separator: ',' | |
52 | general_csv_encoding: ISO-8859-1 |
|
52 | general_csv_encoding: ISO-8859-1 | |
53 | general_pdf_encoding: ISO-8859-1 |
|
53 | general_pdf_encoding: ISO-8859-1 | |
54 | general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche |
|
54 | general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche | |
55 | general_first_day_of_week: '1' |
|
55 | general_first_day_of_week: '1' | |
56 |
|
56 | |||
57 | notice_account_updated: Le compte a été mis à jour avec succès. |
|
57 | notice_account_updated: Le compte a été mis à jour avec succès. | |
58 | notice_account_invalid_creditentials: Identifiant ou mot de passe invalide. |
|
58 | notice_account_invalid_creditentials: Identifiant ou mot de passe invalide. | |
59 | notice_account_password_updated: Mot de passe mis à jour avec succès. |
|
59 | notice_account_password_updated: Mot de passe mis à jour avec succès. | |
60 | notice_account_wrong_password: Mot de passe incorrect |
|
60 | notice_account_wrong_password: Mot de passe incorrect | |
61 | notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©. |
|
61 | notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©. | |
62 | notice_account_unknown_email: Aucun compte ne correspond Γ cette adresse. |
|
62 | notice_account_unknown_email: Aucun compte ne correspond Γ cette adresse. | |
63 | notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe. |
|
63 | notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe. | |
64 | notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©. |
|
64 | notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©. | |
65 | notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ prΓ©sent vous connecter. |
|
65 | notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ prΓ©sent vous connecter. | |
66 | notice_successful_create: Création effectuée avec succès. |
|
66 | notice_successful_create: Création effectuée avec succès. | |
67 | notice_successful_update: Mise à jour effectuée avec succès. |
|
67 | notice_successful_update: Mise à jour effectuée avec succès. | |
68 | notice_successful_delete: Suppression effectuée avec succès. |
|
68 | notice_successful_delete: Suppression effectuée avec succès. | |
69 | notice_successful_connection: Connection rΓ©ussie. |
|
69 | notice_successful_connection: Connection rΓ©ussie. | |
70 | notice_file_not_found: "La page Γ laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e." |
|
70 | notice_file_not_found: "La page Γ laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e." | |
71 | notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ jour par un autre utilisateur. Mise Γ jour impossible. |
|
71 | notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ jour par un autre utilisateur. Mise Γ jour impossible. | |
72 | notice_not_authorized: "Vous n'Γͺtes pas autorisΓ©s Γ accΓ©der Γ cette page." |
|
72 | notice_not_authorized: "Vous n'Γͺtes pas autorisΓ©s Γ accΓ©der Γ cette page." | |
73 | notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ %s" |
|
73 | notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ %s" | |
74 | notice_email_error: "Erreur lors de l'envoi de l'email (%s)" |
|
74 | notice_email_error: "Erreur lors de l'envoi de l'email (%s)" | |
75 | notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée." |
|
75 | notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée." | |
76 | notice_failed_to_save_issues: "%d demande(s) sur les %d sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ jour: %s." |
|
76 | notice_failed_to_save_issues: "%d demande(s) sur les %d sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ jour: %s." | |
77 | notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ jour." |
|
77 | notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ jour." | |
78 | notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur." |
|
78 | notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur." | |
79 | notice_default_data_loaded: Paramétrage par défaut chargé avec succès. |
|
79 | notice_default_data_loaded: Paramétrage par défaut chargé avec succès. | |
80 | notice_unable_delete_version: Impossible de supprimer cette version. |
|
80 | notice_unable_delete_version: Impossible de supprimer cette version. | |
81 |
|
81 | |||
82 | error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage: %s" |
|
82 | error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage: %s" | |
83 | error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t." |
|
83 | error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t." | |
84 | error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt: %s" |
|
84 | error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt: %s" | |
85 | error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e." |
|
85 | error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e." | |
86 | error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ ce projet" |
|
86 | error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ ce projet" | |
87 |
|
87 | |||
88 | mail_subject_lost_password: Votre mot de passe %s |
|
88 | mail_subject_lost_password: Votre mot de passe %s | |
89 | mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant:' |
|
89 | mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant:' | |
90 | mail_subject_register: Activation de votre compte %s |
|
90 | mail_subject_register: Activation de votre compte %s | |
91 | mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant:' |
|
91 | mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant:' | |
92 | mail_body_account_information_external: Vous pouvez utiliser votre compte "%s" pour vous connecter. |
|
92 | mail_body_account_information_external: Vous pouvez utiliser votre compte "%s" pour vous connecter. | |
93 | mail_body_account_information: Paramètres de connexion de votre compte |
|
93 | mail_body_account_information: Paramètres de connexion de votre compte | |
94 | mail_subject_account_activation_request: "Demande d'activation d'un compte %s" |
|
94 | mail_subject_account_activation_request: "Demande d'activation d'un compte %s" | |
95 | mail_body_account_activation_request: "Un nouvel utilisateur (%s) s'est inscrit. Son compte nΓ©cessite votre approbation:" |
|
95 | mail_body_account_activation_request: "Un nouvel utilisateur (%s) s'est inscrit. Son compte nΓ©cessite votre approbation:" | |
96 | mail_subject_reminder: "%d demande(s) arrivent Γ Γ©chΓ©ance" |
|
96 | mail_subject_reminder: "%d demande(s) arrivent Γ Γ©chΓ©ance" | |
97 | mail_body_reminder: "%d demande(s) qui vous sont assignΓ©es arrivent Γ Γ©chΓ©ance dans les %d prochains jours:" |
|
97 | mail_body_reminder: "%d demande(s) qui vous sont assignΓ©es arrivent Γ Γ©chΓ©ance dans les %d prochains jours:" | |
98 |
|
98 | |||
99 | gui_validation_error: 1 erreur |
|
99 | gui_validation_error: 1 erreur | |
100 | gui_validation_error_plural: %d erreurs |
|
100 | gui_validation_error_plural: %d erreurs | |
101 |
|
101 | |||
102 | field_name: Nom |
|
102 | field_name: Nom | |
103 | field_description: Description |
|
103 | field_description: Description | |
104 | field_summary: RΓ©sumΓ© |
|
104 | field_summary: RΓ©sumΓ© | |
105 | field_is_required: Obligatoire |
|
105 | field_is_required: Obligatoire | |
106 | field_firstname: PrΓ©nom |
|
106 | field_firstname: PrΓ©nom | |
107 | field_lastname: Nom |
|
107 | field_lastname: Nom | |
108 | field_mail: Email |
|
108 | field_mail: Email | |
109 | field_filename: Fichier |
|
109 | field_filename: Fichier | |
110 | field_filesize: Taille |
|
110 | field_filesize: Taille | |
111 | field_downloads: TΓ©lΓ©chargements |
|
111 | field_downloads: TΓ©lΓ©chargements | |
112 | field_author: Auteur |
|
112 | field_author: Auteur | |
113 | field_created_on: Créé |
|
113 | field_created_on: Créé | |
114 | field_updated_on: Mis Γ jour |
|
114 | field_updated_on: Mis Γ jour | |
115 | field_field_format: Format |
|
115 | field_field_format: Format | |
116 | field_is_for_all: Pour tous les projets |
|
116 | field_is_for_all: Pour tous les projets | |
117 | field_possible_values: Valeurs possibles |
|
117 | field_possible_values: Valeurs possibles | |
118 | field_regexp: Expression régulière |
|
118 | field_regexp: Expression régulière | |
119 | field_min_length: Longueur minimum |
|
119 | field_min_length: Longueur minimum | |
120 | field_max_length: Longueur maximum |
|
120 | field_max_length: Longueur maximum | |
121 | field_value: Valeur |
|
121 | field_value: Valeur | |
122 | field_category: CatΓ©gorie |
|
122 | field_category: CatΓ©gorie | |
123 | field_title: Titre |
|
123 | field_title: Titre | |
124 | field_project: Projet |
|
124 | field_project: Projet | |
125 | field_issue: Demande |
|
125 | field_issue: Demande | |
126 | field_status: Statut |
|
126 | field_status: Statut | |
127 | field_notes: Notes |
|
127 | field_notes: Notes | |
128 | field_is_closed: Demande fermΓ©e |
|
128 | field_is_closed: Demande fermΓ©e | |
129 | field_is_default: Valeur par dΓ©faut |
|
129 | field_is_default: Valeur par dΓ©faut | |
130 | field_tracker: Tracker |
|
130 | field_tracker: Tracker | |
131 | field_subject: Sujet |
|
131 | field_subject: Sujet | |
132 | field_due_date: Date d'Γ©chΓ©ance |
|
132 | field_due_date: Date d'Γ©chΓ©ance | |
133 | field_assigned_to: AssignΓ© Γ |
|
133 | field_assigned_to: AssignΓ© Γ | |
134 | field_priority: PrioritΓ© |
|
134 | field_priority: PrioritΓ© | |
135 | field_fixed_version: Version cible |
|
135 | field_fixed_version: Version cible | |
136 | field_user: Utilisateur |
|
136 | field_user: Utilisateur | |
137 | field_role: RΓ΄le |
|
137 | field_role: RΓ΄le | |
138 | field_homepage: Site web |
|
138 | field_homepage: Site web | |
139 | field_is_public: Public |
|
139 | field_is_public: Public | |
140 | field_parent: Sous-projet de |
|
140 | field_parent: Sous-projet de | |
141 | field_is_in_chlog: Demandes affichΓ©es dans l'historique |
|
141 | field_is_in_chlog: Demandes affichΓ©es dans l'historique | |
142 | field_is_in_roadmap: Demandes affichΓ©es dans la roadmap |
|
142 | field_is_in_roadmap: Demandes affichΓ©es dans la roadmap | |
143 | field_login: Identifiant |
|
143 | field_login: Identifiant | |
144 | field_mail_notification: Notifications par mail |
|
144 | field_mail_notification: Notifications par mail | |
145 | field_admin: Administrateur |
|
145 | field_admin: Administrateur | |
146 | field_last_login_on: Dernière connexion |
|
146 | field_last_login_on: Dernière connexion | |
147 | field_language: Langue |
|
147 | field_language: Langue | |
148 | field_effective_date: Date |
|
148 | field_effective_date: Date | |
149 | field_password: Mot de passe |
|
149 | field_password: Mot de passe | |
150 | field_new_password: Nouveau mot de passe |
|
150 | field_new_password: Nouveau mot de passe | |
151 | field_password_confirmation: Confirmation |
|
151 | field_password_confirmation: Confirmation | |
152 | field_version: Version |
|
152 | field_version: Version | |
153 | field_type: Type |
|
153 | field_type: Type | |
154 | field_host: HΓ΄te |
|
154 | field_host: HΓ΄te | |
155 | field_port: Port |
|
155 | field_port: Port | |
156 | field_account: Compte |
|
156 | field_account: Compte | |
157 | field_base_dn: Base DN |
|
157 | field_base_dn: Base DN | |
158 | field_attr_login: Attribut Identifiant |
|
158 | field_attr_login: Attribut Identifiant | |
159 | field_attr_firstname: Attribut PrΓ©nom |
|
159 | field_attr_firstname: Attribut PrΓ©nom | |
160 | field_attr_lastname: Attribut Nom |
|
160 | field_attr_lastname: Attribut Nom | |
161 | field_attr_mail: Attribut Email |
|
161 | field_attr_mail: Attribut Email | |
162 | field_onthefly: CrΓ©ation des utilisateurs Γ la volΓ©e |
|
162 | field_onthefly: CrΓ©ation des utilisateurs Γ la volΓ©e | |
163 | field_start_date: DΓ©but |
|
163 | field_start_date: DΓ©but | |
164 | field_done_ratio: %% RΓ©alisΓ© |
|
164 | field_done_ratio: %% RΓ©alisΓ© | |
165 | field_auth_source: Mode d'authentification |
|
165 | field_auth_source: Mode d'authentification | |
166 | field_hide_mail: Cacher mon adresse mail |
|
166 | field_hide_mail: Cacher mon adresse mail | |
167 | field_comments: Commentaire |
|
167 | field_comments: Commentaire | |
168 | field_url: URL |
|
168 | field_url: URL | |
169 | field_start_page: Page de dΓ©marrage |
|
169 | field_start_page: Page de dΓ©marrage | |
170 | field_subproject: Sous-projet |
|
170 | field_subproject: Sous-projet | |
171 | field_hours: Heures |
|
171 | field_hours: Heures | |
172 | field_activity: ActivitΓ© |
|
172 | field_activity: ActivitΓ© | |
173 | field_spent_on: Date |
|
173 | field_spent_on: Date | |
174 | field_identifier: Identifiant |
|
174 | field_identifier: Identifiant | |
175 | field_is_filter: UtilisΓ© comme filtre |
|
175 | field_is_filter: UtilisΓ© comme filtre | |
176 | field_issue_to_id: Demande liΓ©e |
|
176 | field_issue_to_id: Demande liΓ©e | |
177 | field_delay: Retard |
|
177 | field_delay: Retard | |
178 | field_assignable: Demandes assignables Γ ce rΓ΄le |
|
178 | field_assignable: Demandes assignables Γ ce rΓ΄le | |
179 | field_redirect_existing_links: Rediriger les liens existants |
|
179 | field_redirect_existing_links: Rediriger les liens existants | |
180 | field_estimated_hours: Temps estimΓ© |
|
180 | field_estimated_hours: Temps estimΓ© | |
181 | field_column_names: Colonnes |
|
181 | field_column_names: Colonnes | |
182 | field_time_zone: Fuseau horaire |
|
182 | field_time_zone: Fuseau horaire | |
183 | field_searchable: UtilisΓ© pour les recherches |
|
183 | field_searchable: UtilisΓ© pour les recherches | |
184 | field_default_value: Valeur par dΓ©faut |
|
184 | field_default_value: Valeur par dΓ©faut | |
185 | field_comments_sorting: Afficher les commentaires |
|
185 | field_comments_sorting: Afficher les commentaires | |
186 | field_parent_title: Page parent |
|
186 | field_parent_title: Page parent | |
187 |
|
187 | |||
188 | setting_app_title: Titre de l'application |
|
188 | setting_app_title: Titre de l'application | |
189 | setting_app_subtitle: Sous-titre de l'application |
|
189 | setting_app_subtitle: Sous-titre de l'application | |
190 | setting_welcome_text: Texte d'accueil |
|
190 | setting_welcome_text: Texte d'accueil | |
191 | setting_default_language: Langue par dΓ©faut |
|
191 | setting_default_language: Langue par dΓ©faut | |
192 | setting_login_required: Authentification obligatoire |
|
192 | setting_login_required: Authentification obligatoire | |
193 | setting_self_registration: Inscription des nouveaux utilisateurs |
|
193 | setting_self_registration: Inscription des nouveaux utilisateurs | |
194 | setting_attachment_max_size: Taille max des fichiers |
|
194 | setting_attachment_max_size: Taille max des fichiers | |
195 | setting_issues_export_limit: Limite export demandes |
|
195 | setting_issues_export_limit: Limite export demandes | |
196 | setting_mail_from: Adresse d'Γ©mission |
|
196 | setting_mail_from: Adresse d'Γ©mission | |
197 | setting_bcc_recipients: Destinataires en copie cachΓ©e (cci) |
|
197 | setting_bcc_recipients: Destinataires en copie cachΓ©e (cci) | |
198 | setting_plain_text_mail: Mail texte brut (non HTML) |
|
198 | setting_plain_text_mail: Mail texte brut (non HTML) | |
199 | setting_host_name: Nom d'hΓ΄te et chemin |
|
199 | setting_host_name: Nom d'hΓ΄te et chemin | |
200 | setting_text_formatting: Formatage du texte |
|
200 | setting_text_formatting: Formatage du texte | |
201 | setting_wiki_compression: Compression historique wiki |
|
201 | setting_wiki_compression: Compression historique wiki | |
202 | setting_feeds_limit: Limite du contenu des flux RSS |
|
202 | setting_feeds_limit: Limite du contenu des flux RSS | |
203 | setting_default_projects_public: DΓ©finir les nouveaux projects comme publics par dΓ©faut |
|
203 | setting_default_projects_public: DΓ©finir les nouveaux projects comme publics par dΓ©faut | |
204 | setting_autofetch_changesets: RΓ©cupΓ©ration auto. des commits |
|
204 | setting_autofetch_changesets: RΓ©cupΓ©ration auto. des commits | |
205 | setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts |
|
205 | setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts | |
206 | setting_commit_ref_keywords: Mot-clΓ©s de rΓ©fΓ©rencement |
|
206 | setting_commit_ref_keywords: Mot-clΓ©s de rΓ©fΓ©rencement | |
207 | setting_commit_fix_keywords: Mot-clΓ©s de rΓ©solution |
|
207 | setting_commit_fix_keywords: Mot-clΓ©s de rΓ©solution | |
208 | setting_autologin: Autologin |
|
208 | setting_autologin: Autologin | |
209 | setting_date_format: Format de date |
|
209 | setting_date_format: Format de date | |
210 | setting_time_format: Format d'heure |
|
210 | setting_time_format: Format d'heure | |
211 | setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets |
|
211 | setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets | |
212 | setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes |
|
212 | setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes | |
213 | setting_repositories_encodings: Encodages des dΓ©pΓ΄ts |
|
213 | setting_repositories_encodings: Encodages des dΓ©pΓ΄ts | |
214 | setting_commit_logs_encoding: Encodage des messages de commit |
|
214 | setting_commit_logs_encoding: Encodage des messages de commit | |
215 | setting_emails_footer: Pied-de-page des emails |
|
215 | setting_emails_footer: Pied-de-page des emails | |
216 | setting_protocol: Protocole |
|
216 | setting_protocol: Protocole | |
217 | setting_per_page_options: Options d'objets affichΓ©s par page |
|
217 | setting_per_page_options: Options d'objets affichΓ©s par page | |
218 | setting_user_format: Format d'affichage des utilisateurs |
|
218 | setting_user_format: Format d'affichage des utilisateurs | |
219 | setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets |
|
219 | setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets | |
220 | setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux |
|
220 | setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux | |
221 | setting_enabled_scm: SCM activΓ©s |
|
221 | setting_enabled_scm: SCM activΓ©s | |
222 | setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails" |
|
222 | setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails" | |
223 | setting_mail_handler_api_key: ClΓ© de protection de l'API |
|
223 | setting_mail_handler_api_key: ClΓ© de protection de l'API | |
224 | setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels |
|
224 | setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels | |
225 | setting_gravatar_enabled: Afficher les Gravatar des utilisateurs |
|
225 | setting_gravatar_enabled: Afficher les Gravatar des utilisateurs | |
226 | setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es |
|
226 | setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es | |
227 |
|
227 | |||
228 | permission_edit_project: Modifier le projet |
|
228 | permission_edit_project: Modifier le projet | |
229 | permission_select_project_modules: Choisir les modules |
|
229 | permission_select_project_modules: Choisir les modules | |
230 | permission_manage_members: GΓ©rer les members |
|
230 | permission_manage_members: GΓ©rer les members | |
231 | permission_manage_versions: GΓ©rer les versions |
|
231 | permission_manage_versions: GΓ©rer les versions | |
232 | permission_manage_categories: GΓ©rer les catΓ©gories de demandes |
|
232 | permission_manage_categories: GΓ©rer les catΓ©gories de demandes | |
233 | permission_add_issues: CrΓ©er des demandes |
|
233 | permission_add_issues: CrΓ©er des demandes | |
234 | permission_edit_issues: Modifier les demandes |
|
234 | permission_edit_issues: Modifier les demandes | |
235 | permission_manage_issue_relations: GΓ©rer les relations |
|
235 | permission_manage_issue_relations: GΓ©rer les relations | |
236 | permission_add_issue_notes: Ajouter des notes |
|
236 | permission_add_issue_notes: Ajouter des notes | |
237 | permission_edit_issue_notes: Modifier les notes |
|
237 | permission_edit_issue_notes: Modifier les notes | |
238 | permission_edit_own_issue_notes: Modifier ses propres notes |
|
238 | permission_edit_own_issue_notes: Modifier ses propres notes | |
239 | permission_move_issues: DΓ©placer les demandes |
|
239 | permission_move_issues: DΓ©placer les demandes | |
240 | permission_delete_issues: Supprimer les demandes |
|
240 | permission_delete_issues: Supprimer les demandes | |
241 | permission_manage_public_queries: GΓ©rer les requΓͺtes publiques |
|
241 | permission_manage_public_queries: GΓ©rer les requΓͺtes publiques | |
242 | permission_save_queries: Sauvegarder les requΓͺtes |
|
242 | permission_save_queries: Sauvegarder les requΓͺtes | |
243 | permission_view_gantt: Voir le gantt |
|
243 | permission_view_gantt: Voir le gantt | |
244 | permission_view_calendar: Voir le calendrier |
|
244 | permission_view_calendar: Voir le calendrier | |
245 | permission_view_issue_watchers: Voir la liste des observateurs |
|
245 | permission_view_issue_watchers: Voir la liste des observateurs | |
246 | permission_add_issue_watchers: Ajouter des observateurs |
|
246 | permission_add_issue_watchers: Ajouter des observateurs | |
247 | permission_log_time: Saisir le temps passΓ© |
|
247 | permission_log_time: Saisir le temps passΓ© | |
248 | permission_view_time_entries: Voir le temps passΓ© |
|
248 | permission_view_time_entries: Voir le temps passΓ© | |
249 | permission_edit_time_entries: Modifier les temps passΓ©s |
|
249 | permission_edit_time_entries: Modifier les temps passΓ©s | |
250 | permission_edit_own_time_entries: Modifier son propre temps passΓ© |
|
250 | permission_edit_own_time_entries: Modifier son propre temps passΓ© | |
251 | permission_manage_news: GΓ©rer les annonces |
|
251 | permission_manage_news: GΓ©rer les annonces | |
252 | permission_comment_news: Commenter les annonces |
|
252 | permission_comment_news: Commenter les annonces | |
253 | permission_manage_documents: GΓ©rer les documents |
|
253 | permission_manage_documents: GΓ©rer les documents | |
254 | permission_view_documents: Voir les documents |
|
254 | permission_view_documents: Voir les documents | |
255 | permission_manage_files: GΓ©rer les fichiers |
|
255 | permission_manage_files: GΓ©rer les fichiers | |
256 | permission_view_files: Voir les fichiers |
|
256 | permission_view_files: Voir les fichiers | |
257 | permission_manage_wiki: GΓ©rer le wiki |
|
257 | permission_manage_wiki: GΓ©rer le wiki | |
258 | permission_rename_wiki_pages: Renommer les pages |
|
258 | permission_rename_wiki_pages: Renommer les pages | |
259 | permission_delete_wiki_pages: Supprimer les pages |
|
259 | permission_delete_wiki_pages: Supprimer les pages | |
260 | permission_view_wiki_pages: Voir le wiki |
|
260 | permission_view_wiki_pages: Voir le wiki | |
261 | permission_view_wiki_edits: "Voir l'historique des modifications" |
|
261 | permission_view_wiki_edits: "Voir l'historique des modifications" | |
262 | permission_edit_wiki_pages: Modifier les pages |
|
262 | permission_edit_wiki_pages: Modifier les pages | |
263 | permission_delete_wiki_pages_attachments: Supprimer les fichiers joints |
|
263 | permission_delete_wiki_pages_attachments: Supprimer les fichiers joints | |
264 | permission_protect_wiki_pages: ProtΓ©ger les pages |
|
264 | permission_protect_wiki_pages: ProtΓ©ger les pages | |
265 | permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources |
|
265 | permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources | |
266 | permission_browse_repository: Parcourir les sources |
|
266 | permission_browse_repository: Parcourir les sources | |
267 | permission_view_changesets: Voir les rΓ©visions |
|
267 | permission_view_changesets: Voir les rΓ©visions | |
268 | permission_commit_access: Droit de commit |
|
268 | permission_commit_access: Droit de commit | |
269 | permission_manage_boards: GΓ©rer les forums |
|
269 | permission_manage_boards: GΓ©rer les forums | |
270 | permission_view_messages: Voir les messages |
|
270 | permission_view_messages: Voir les messages | |
271 | permission_add_messages: Poster un message |
|
271 | permission_add_messages: Poster un message | |
272 | permission_edit_messages: Modifier les messages |
|
272 | permission_edit_messages: Modifier les messages | |
273 | permission_edit_own_messages: Modifier ses propres messages |
|
273 | permission_edit_own_messages: Modifier ses propres messages | |
274 | permission_delete_messages: Supprimer les messages |
|
274 | permission_delete_messages: Supprimer les messages | |
275 | permission_delete_own_messages: Supprimer ses propres messages |
|
275 | permission_delete_own_messages: Supprimer ses propres messages | |
276 |
|
276 | |||
277 | project_module_issue_tracking: Suivi des demandes |
|
277 | project_module_issue_tracking: Suivi des demandes | |
278 | project_module_time_tracking: Suivi du temps passΓ© |
|
278 | project_module_time_tracking: Suivi du temps passΓ© | |
279 | project_module_news: Publication d'annonces |
|
279 | project_module_news: Publication d'annonces | |
280 | project_module_documents: Publication de documents |
|
280 | project_module_documents: Publication de documents | |
281 | project_module_files: Publication de fichiers |
|
281 | project_module_files: Publication de fichiers | |
282 | project_module_wiki: Wiki |
|
282 | project_module_wiki: Wiki | |
283 | project_module_repository: DΓ©pΓ΄t de sources |
|
283 | project_module_repository: DΓ©pΓ΄t de sources | |
284 | project_module_boards: Forums de discussion |
|
284 | project_module_boards: Forums de discussion | |
285 |
|
285 | |||
286 | label_user: Utilisateur |
|
286 | label_user: Utilisateur | |
287 | label_user_plural: Utilisateurs |
|
287 | label_user_plural: Utilisateurs | |
288 | label_user_new: Nouvel utilisateur |
|
288 | label_user_new: Nouvel utilisateur | |
289 | label_project: Projet |
|
289 | label_project: Projet | |
290 | label_project_new: Nouveau projet |
|
290 | label_project_new: Nouveau projet | |
291 | label_project_plural: Projets |
|
291 | label_project_plural: Projets | |
292 | label_project_all: Tous les projets |
|
292 | label_project_all: Tous les projets | |
293 | label_project_latest: Derniers projets |
|
293 | label_project_latest: Derniers projets | |
294 | label_issue: Demande |
|
294 | label_issue: Demande | |
295 | label_issue_new: Nouvelle demande |
|
295 | label_issue_new: Nouvelle demande | |
296 | label_issue_plural: Demandes |
|
296 | label_issue_plural: Demandes | |
297 | label_issue_view_all: Voir toutes les demandes |
|
297 | label_issue_view_all: Voir toutes les demandes | |
298 | label_issue_added: Demande ajoutΓ©e |
|
298 | label_issue_added: Demande ajoutΓ©e | |
299 | label_issue_updated: Demande mise Γ jour |
|
299 | label_issue_updated: Demande mise Γ jour | |
300 | label_issues_by: Demandes par %s |
|
300 | label_issues_by: Demandes par %s | |
301 | label_document: Document |
|
301 | label_document: Document | |
302 | label_document_new: Nouveau document |
|
302 | label_document_new: Nouveau document | |
303 | label_document_plural: Documents |
|
303 | label_document_plural: Documents | |
304 | label_document_added: Document ajoutΓ© |
|
304 | label_document_added: Document ajoutΓ© | |
305 | label_role: RΓ΄le |
|
305 | label_role: RΓ΄le | |
306 | label_role_plural: RΓ΄les |
|
306 | label_role_plural: RΓ΄les | |
307 | label_role_new: Nouveau rΓ΄le |
|
307 | label_role_new: Nouveau rΓ΄le | |
308 | label_role_and_permissions: RΓ΄les et permissions |
|
308 | label_role_and_permissions: RΓ΄les et permissions | |
309 | label_member: Membre |
|
309 | label_member: Membre | |
310 | label_member_new: Nouveau membre |
|
310 | label_member_new: Nouveau membre | |
311 | label_member_plural: Membres |
|
311 | label_member_plural: Membres | |
312 | label_tracker: Tracker |
|
312 | label_tracker: Tracker | |
313 | label_tracker_plural: Trackers |
|
313 | label_tracker_plural: Trackers | |
314 | label_tracker_new: Nouveau tracker |
|
314 | label_tracker_new: Nouveau tracker | |
315 | label_workflow: Workflow |
|
315 | label_workflow: Workflow | |
316 | label_issue_status: Statut de demandes |
|
316 | label_issue_status: Statut de demandes | |
317 | label_issue_status_plural: Statuts de demandes |
|
317 | label_issue_status_plural: Statuts de demandes | |
318 | label_issue_status_new: Nouveau statut |
|
318 | label_issue_status_new: Nouveau statut | |
319 | label_issue_category: CatΓ©gorie de demandes |
|
319 | label_issue_category: CatΓ©gorie de demandes | |
320 | label_issue_category_plural: CatΓ©gories de demandes |
|
320 | label_issue_category_plural: CatΓ©gories de demandes | |
321 | label_issue_category_new: Nouvelle catΓ©gorie |
|
321 | label_issue_category_new: Nouvelle catΓ©gorie | |
322 | label_custom_field: Champ personnalisΓ© |
|
322 | label_custom_field: Champ personnalisΓ© | |
323 | label_custom_field_plural: Champs personnalisΓ©s |
|
323 | label_custom_field_plural: Champs personnalisΓ©s | |
324 | label_custom_field_new: Nouveau champ personnalisΓ© |
|
324 | label_custom_field_new: Nouveau champ personnalisΓ© | |
325 | label_enumerations: Listes de valeurs |
|
325 | label_enumerations: Listes de valeurs | |
326 | label_enumeration_new: Nouvelle valeur |
|
326 | label_enumeration_new: Nouvelle valeur | |
327 | label_information: Information |
|
327 | label_information: Information | |
328 | label_information_plural: Informations |
|
328 | label_information_plural: Informations | |
329 | label_please_login: Identification |
|
329 | label_please_login: Identification | |
330 | label_register: S'enregistrer |
|
330 | label_register: S'enregistrer | |
331 | label_password_lost: Mot de passe perdu |
|
331 | label_password_lost: Mot de passe perdu | |
332 | label_home: Accueil |
|
332 | label_home: Accueil | |
333 | label_my_page: Ma page |
|
333 | label_my_page: Ma page | |
334 | label_my_account: Mon compte |
|
334 | label_my_account: Mon compte | |
335 | label_my_projects: Mes projets |
|
335 | label_my_projects: Mes projets | |
336 | label_administration: Administration |
|
336 | label_administration: Administration | |
337 | label_login: Connexion |
|
337 | label_login: Connexion | |
338 | label_logout: DΓ©connexion |
|
338 | label_logout: DΓ©connexion | |
339 | label_help: Aide |
|
339 | label_help: Aide | |
340 | label_reported_issues: Demandes soumises |
|
340 | label_reported_issues: Demandes soumises | |
341 | label_assigned_to_me_issues: Demandes qui me sont assignΓ©es |
|
341 | label_assigned_to_me_issues: Demandes qui me sont assignΓ©es | |
342 | label_last_login: Dernière connexion |
|
342 | label_last_login: Dernière connexion | |
343 | label_last_updates: Dernière mise à jour |
|
343 | label_last_updates: Dernière mise à jour | |
344 | label_last_updates_plural: %d dernières mises à jour |
|
344 | label_last_updates_plural: %d dernières mises à jour | |
345 | label_registered_on: Inscrit le |
|
345 | label_registered_on: Inscrit le | |
346 | label_activity: ActivitΓ© |
|
346 | label_activity: ActivitΓ© | |
347 | label_overall_activity: ActivitΓ© globale |
|
347 | label_overall_activity: ActivitΓ© globale | |
348 | label_user_activity: "ActivitΓ© de %s" |
|
348 | label_user_activity: "ActivitΓ© de %s" | |
349 | label_new: Nouveau |
|
349 | label_new: Nouveau | |
350 | label_logged_as: ConnectΓ© en tant que |
|
350 | label_logged_as: ConnectΓ© en tant que | |
351 | label_environment: Environnement |
|
351 | label_environment: Environnement | |
352 | label_authentication: Authentification |
|
352 | label_authentication: Authentification | |
353 | label_auth_source: Mode d'authentification |
|
353 | label_auth_source: Mode d'authentification | |
354 | label_auth_source_new: Nouveau mode d'authentification |
|
354 | label_auth_source_new: Nouveau mode d'authentification | |
355 | label_auth_source_plural: Modes d'authentification |
|
355 | label_auth_source_plural: Modes d'authentification | |
356 | label_subproject_plural: Sous-projets |
|
356 | label_subproject_plural: Sous-projets | |
357 | label_and_its_subprojects: %s et ses sous-projets |
|
357 | label_and_its_subprojects: %s et ses sous-projets | |
358 | label_min_max_length: Longueurs mini - maxi |
|
358 | label_min_max_length: Longueurs mini - maxi | |
359 | label_list: Liste |
|
359 | label_list: Liste | |
360 | label_date: Date |
|
360 | label_date: Date | |
361 | label_integer: Entier |
|
361 | label_integer: Entier | |
362 | label_float: Nombre dΓ©cimal |
|
362 | label_float: Nombre dΓ©cimal | |
363 | label_boolean: BoolΓ©en |
|
363 | label_boolean: BoolΓ©en | |
364 | label_string: Texte |
|
364 | label_string: Texte | |
365 | label_text: Texte long |
|
365 | label_text: Texte long | |
366 | label_attribute: Attribut |
|
366 | label_attribute: Attribut | |
367 | label_attribute_plural: Attributs |
|
367 | label_attribute_plural: Attributs | |
368 | label_download: %d TΓ©lΓ©chargement |
|
368 | label_download: %d TΓ©lΓ©chargement | |
369 | label_download_plural: %d TΓ©lΓ©chargements |
|
369 | label_download_plural: %d TΓ©lΓ©chargements | |
370 | label_no_data: Aucune donnΓ©e Γ afficher |
|
370 | label_no_data: Aucune donnΓ©e Γ afficher | |
371 | label_change_status: Changer le statut |
|
371 | label_change_status: Changer le statut | |
372 | label_history: Historique |
|
372 | label_history: Historique | |
373 | label_attachment: Fichier |
|
373 | label_attachment: Fichier | |
374 | label_attachment_new: Nouveau fichier |
|
374 | label_attachment_new: Nouveau fichier | |
375 | label_attachment_delete: Supprimer le fichier |
|
375 | label_attachment_delete: Supprimer le fichier | |
376 | label_attachment_plural: Fichiers |
|
376 | label_attachment_plural: Fichiers | |
377 | label_file_added: Fichier ajoutΓ© |
|
377 | label_file_added: Fichier ajoutΓ© | |
378 | label_report: Rapport |
|
378 | label_report: Rapport | |
379 | label_report_plural: Rapports |
|
379 | label_report_plural: Rapports | |
380 | label_news: Annonce |
|
380 | label_news: Annonce | |
381 | label_news_new: Nouvelle annonce |
|
381 | label_news_new: Nouvelle annonce | |
382 | label_news_plural: Annonces |
|
382 | label_news_plural: Annonces | |
383 | label_news_latest: Dernières annonces |
|
383 | label_news_latest: Dernières annonces | |
384 | label_news_view_all: Voir toutes les annonces |
|
384 | label_news_view_all: Voir toutes les annonces | |
385 | label_news_added: Annonce ajoutΓ©e |
|
385 | label_news_added: Annonce ajoutΓ©e | |
386 | label_change_log: Historique |
|
386 | label_change_log: Historique | |
387 | label_settings: Configuration |
|
387 | label_settings: Configuration | |
388 | label_overview: AperΓ§u |
|
388 | label_overview: AperΓ§u | |
389 | label_version: Version |
|
389 | label_version: Version | |
390 | label_version_new: Nouvelle version |
|
390 | label_version_new: Nouvelle version | |
391 | label_version_plural: Versions |
|
391 | label_version_plural: Versions | |
392 | label_confirmation: Confirmation |
|
392 | label_confirmation: Confirmation | |
393 | label_export_to: 'Formats disponibles:' |
|
393 | label_export_to: 'Formats disponibles:' | |
394 | label_read: Lire... |
|
394 | label_read: Lire... | |
395 | label_public_projects: Projets publics |
|
395 | label_public_projects: Projets publics | |
396 | label_open_issues: ouvert |
|
396 | label_open_issues: ouvert | |
397 | label_open_issues_plural: ouverts |
|
397 | label_open_issues_plural: ouverts | |
398 | label_closed_issues: fermΓ© |
|
398 | label_closed_issues: fermΓ© | |
399 | label_closed_issues_plural: fermΓ©s |
|
399 | label_closed_issues_plural: fermΓ©s | |
400 | label_total: Total |
|
400 | label_total: Total | |
401 | label_permissions: Permissions |
|
401 | label_permissions: Permissions | |
402 | label_current_status: Statut actuel |
|
402 | label_current_status: Statut actuel | |
403 | label_new_statuses_allowed: Nouveaux statuts autorisΓ©s |
|
403 | label_new_statuses_allowed: Nouveaux statuts autorisΓ©s | |
404 | label_all: tous |
|
404 | label_all: tous | |
405 | label_none: aucun |
|
405 | label_none: aucun | |
406 | label_nobody: personne |
|
406 | label_nobody: personne | |
407 | label_next: Suivant |
|
407 | label_next: Suivant | |
408 | label_previous: PrΓ©cΓ©dent |
|
408 | label_previous: PrΓ©cΓ©dent | |
409 | label_used_by: UtilisΓ© par |
|
409 | label_used_by: UtilisΓ© par | |
410 | label_details: DΓ©tails |
|
410 | label_details: DΓ©tails | |
411 | label_add_note: Ajouter une note |
|
411 | label_add_note: Ajouter une note | |
412 | label_per_page: Par page |
|
412 | label_per_page: Par page | |
413 | label_calendar: Calendrier |
|
413 | label_calendar: Calendrier | |
414 | label_months_from: mois depuis |
|
414 | label_months_from: mois depuis | |
415 | label_gantt: Gantt |
|
415 | label_gantt: Gantt | |
416 | label_internal: Interne |
|
416 | label_internal: Interne | |
417 | label_last_changes: %d derniers changements |
|
417 | label_last_changes: %d derniers changements | |
418 | label_change_view_all: Voir tous les changements |
|
418 | label_change_view_all: Voir tous les changements | |
419 | label_personalize_page: Personnaliser cette page |
|
419 | label_personalize_page: Personnaliser cette page | |
420 | label_comment: Commentaire |
|
420 | label_comment: Commentaire | |
421 | label_comment_plural: Commentaires |
|
421 | label_comment_plural: Commentaires | |
422 | label_comment_add: Ajouter un commentaire |
|
422 | label_comment_add: Ajouter un commentaire | |
423 | label_comment_added: Commentaire ajoutΓ© |
|
423 | label_comment_added: Commentaire ajoutΓ© | |
424 | label_comment_delete: Supprimer les commentaires |
|
424 | label_comment_delete: Supprimer les commentaires | |
425 | label_query: Rapport personnalisΓ© |
|
425 | label_query: Rapport personnalisΓ© | |
426 | label_query_plural: Rapports personnalisΓ©s |
|
426 | label_query_plural: Rapports personnalisΓ©s | |
427 | label_query_new: Nouveau rapport |
|
427 | label_query_new: Nouveau rapport | |
428 | label_filter_add: Ajouter le filtre |
|
428 | label_filter_add: Ajouter le filtre | |
429 | label_filter_plural: Filtres |
|
429 | label_filter_plural: Filtres | |
430 | label_equals: Γ©gal |
|
430 | label_equals: Γ©gal | |
431 | label_not_equals: diffΓ©rent |
|
431 | label_not_equals: diffΓ©rent | |
432 | label_in_less_than: dans moins de |
|
432 | label_in_less_than: dans moins de | |
433 | label_in_more_than: dans plus de |
|
433 | label_in_more_than: dans plus de | |
434 | label_in: dans |
|
434 | label_in: dans | |
435 | label_today: aujourd'hui |
|
435 | label_today: aujourd'hui | |
436 | label_all_time: toute la pΓ©riode |
|
436 | label_all_time: toute la pΓ©riode | |
437 | label_yesterday: hier |
|
437 | label_yesterday: hier | |
438 | label_this_week: cette semaine |
|
438 | label_this_week: cette semaine | |
439 | label_last_week: la semaine dernière |
|
439 | label_last_week: la semaine dernière | |
440 | label_last_n_days: les %d derniers jours |
|
440 | label_last_n_days: les %d derniers jours | |
441 | label_this_month: ce mois-ci |
|
441 | label_this_month: ce mois-ci | |
442 | label_last_month: le mois dernier |
|
442 | label_last_month: le mois dernier | |
443 | label_this_year: cette annΓ©e |
|
443 | label_this_year: cette annΓ©e | |
444 | label_date_range: PΓ©riode |
|
444 | label_date_range: PΓ©riode | |
445 | label_less_than_ago: il y a moins de |
|
445 | label_less_than_ago: il y a moins de | |
446 | label_more_than_ago: il y a plus de |
|
446 | label_more_than_ago: il y a plus de | |
447 | label_ago: il y a |
|
447 | label_ago: il y a | |
448 | label_contains: contient |
|
448 | label_contains: contient | |
449 | label_not_contains: ne contient pas |
|
449 | label_not_contains: ne contient pas | |
450 | label_day_plural: jours |
|
450 | label_day_plural: jours | |
451 | label_repository: DΓ©pΓ΄t |
|
451 | label_repository: DΓ©pΓ΄t | |
452 | label_repository_plural: DΓ©pΓ΄ts |
|
452 | label_repository_plural: DΓ©pΓ΄ts | |
453 | label_browse: Parcourir |
|
453 | label_browse: Parcourir | |
454 | label_modification: %d modification |
|
454 | label_modification: %d modification | |
455 | label_modification_plural: %d modifications |
|
455 | label_modification_plural: %d modifications | |
456 | label_revision: RΓ©vision |
|
456 | label_revision: RΓ©vision | |
457 | label_revision_plural: RΓ©visions |
|
457 | label_revision_plural: RΓ©visions | |
458 | label_associated_revisions: RΓ©visions associΓ©es |
|
458 | label_associated_revisions: RΓ©visions associΓ©es | |
459 | label_added: ajoutΓ© |
|
459 | label_added: ajoutΓ© | |
460 | label_modified: modifiΓ© |
|
460 | label_modified: modifiΓ© | |
461 | label_copied: copiΓ© |
|
461 | label_copied: copiΓ© | |
462 | label_renamed: renommΓ© |
|
462 | label_renamed: renommΓ© | |
463 | label_deleted: supprimΓ© |
|
463 | label_deleted: supprimΓ© | |
464 | label_latest_revision: Dernière révision |
|
464 | label_latest_revision: Dernière révision | |
465 | label_latest_revision_plural: Dernières révisions |
|
465 | label_latest_revision_plural: Dernières révisions | |
466 | label_view_revisions: Voir les rΓ©visions |
|
466 | label_view_revisions: Voir les rΓ©visions | |
467 | label_max_size: Taille maximale |
|
467 | label_max_size: Taille maximale | |
468 | label_on: sur |
|
468 | label_on: sur | |
469 | label_sort_highest: Remonter en premier |
|
469 | label_sort_highest: Remonter en premier | |
470 | label_sort_higher: Remonter |
|
470 | label_sort_higher: Remonter | |
471 | label_sort_lower: Descendre |
|
471 | label_sort_lower: Descendre | |
472 | label_sort_lowest: Descendre en dernier |
|
472 | label_sort_lowest: Descendre en dernier | |
473 | label_roadmap: Roadmap |
|
473 | label_roadmap: Roadmap | |
474 | label_roadmap_due_in: EchΓ©ance dans %s |
|
474 | label_roadmap_due_in: EchΓ©ance dans %s | |
475 | label_roadmap_overdue: En retard de %s |
|
475 | label_roadmap_overdue: En retard de %s | |
476 | label_roadmap_no_issues: Aucune demande pour cette version |
|
476 | label_roadmap_no_issues: Aucune demande pour cette version | |
477 | label_search: Recherche |
|
477 | label_search: Recherche | |
478 | label_result_plural: RΓ©sultats |
|
478 | label_result_plural: RΓ©sultats | |
479 | label_all_words: Tous les mots |
|
479 | label_all_words: Tous les mots | |
480 | label_wiki: Wiki |
|
480 | label_wiki: Wiki | |
481 | label_wiki_edit: RΓ©vision wiki |
|
481 | label_wiki_edit: RΓ©vision wiki | |
482 | label_wiki_edit_plural: RΓ©visions wiki |
|
482 | label_wiki_edit_plural: RΓ©visions wiki | |
483 | label_wiki_page: Page wiki |
|
483 | label_wiki_page: Page wiki | |
484 | label_wiki_page_plural: Pages wiki |
|
484 | label_wiki_page_plural: Pages wiki | |
485 | label_index_by_title: Index par titre |
|
485 | label_index_by_title: Index par titre | |
486 | label_index_by_date: Index par date |
|
486 | label_index_by_date: Index par date | |
487 | label_current_version: Version actuelle |
|
487 | label_current_version: Version actuelle | |
488 | label_preview: PrΓ©visualisation |
|
488 | label_preview: PrΓ©visualisation | |
489 | label_feed_plural: Flux RSS |
|
489 | label_feed_plural: Flux RSS | |
490 | label_changes_details: DΓ©tails de tous les changements |
|
490 | label_changes_details: DΓ©tails de tous les changements | |
491 | label_issue_tracking: Suivi des demandes |
|
491 | label_issue_tracking: Suivi des demandes | |
492 | label_spent_time: Temps passΓ© |
|
492 | label_spent_time: Temps passΓ© | |
493 | label_f_hour: %.2f heure |
|
493 | label_f_hour: %.2f heure | |
494 | label_f_hour_plural: %.2f heures |
|
494 | label_f_hour_plural: %.2f heures | |
495 | label_time_tracking: Suivi du temps |
|
495 | label_time_tracking: Suivi du temps | |
496 | label_change_plural: Changements |
|
496 | label_change_plural: Changements | |
497 | label_statistics: Statistiques |
|
497 | label_statistics: Statistiques | |
498 | label_commits_per_month: Commits par mois |
|
498 | label_commits_per_month: Commits par mois | |
499 | label_commits_per_author: Commits par auteur |
|
499 | label_commits_per_author: Commits par auteur | |
500 | label_view_diff: Voir les diffΓ©rences |
|
500 | label_view_diff: Voir les diffΓ©rences | |
501 | label_diff_inline: en ligne |
|
501 | label_diff_inline: en ligne | |
502 | label_diff_side_by_side: cΓ΄te Γ cΓ΄te |
|
502 | label_diff_side_by_side: cΓ΄te Γ cΓ΄te | |
503 | label_options: Options |
|
503 | label_options: Options | |
504 | label_copy_workflow_from: Copier le workflow de |
|
504 | label_copy_workflow_from: Copier le workflow de | |
505 | label_permissions_report: Synthèse des permissions |
|
505 | label_permissions_report: Synthèse des permissions | |
506 | label_watched_issues: Demandes surveillΓ©es |
|
506 | label_watched_issues: Demandes surveillΓ©es | |
507 | label_related_issues: Demandes liΓ©es |
|
507 | label_related_issues: Demandes liΓ©es | |
508 | label_applied_status: Statut appliquΓ© |
|
508 | label_applied_status: Statut appliquΓ© | |
509 | label_loading: Chargement... |
|
509 | label_loading: Chargement... | |
510 | label_relation_new: Nouvelle relation |
|
510 | label_relation_new: Nouvelle relation | |
511 | label_relation_delete: Supprimer la relation |
|
511 | label_relation_delete: Supprimer la relation | |
512 | label_relates_to: liΓ© Γ |
|
512 | label_relates_to: liΓ© Γ | |
513 | label_duplicates: duplique |
|
513 | label_duplicates: duplique | |
514 | label_duplicated_by: dupliquΓ© par |
|
514 | label_duplicated_by: dupliquΓ© par | |
515 | label_blocks: bloque |
|
515 | label_blocks: bloque | |
516 | label_blocked_by: bloquΓ© par |
|
516 | label_blocked_by: bloquΓ© par | |
517 | label_precedes: précède |
|
517 | label_precedes: précède | |
518 | label_follows: suit |
|
518 | label_follows: suit | |
519 | label_end_to_start: fin Γ dΓ©but |
|
519 | label_end_to_start: fin Γ dΓ©but | |
520 | label_end_to_end: fin Γ fin |
|
520 | label_end_to_end: fin Γ fin | |
521 | label_start_to_start: dΓ©but Γ dΓ©but |
|
521 | label_start_to_start: dΓ©but Γ dΓ©but | |
522 | label_start_to_end: dΓ©but Γ fin |
|
522 | label_start_to_end: dΓ©but Γ fin | |
523 | label_stay_logged_in: Rester connectΓ© |
|
523 | label_stay_logged_in: Rester connectΓ© | |
524 | label_disabled: dΓ©sactivΓ© |
|
524 | label_disabled: dΓ©sactivΓ© | |
525 | label_show_completed_versions: Voir les versions passΓ©es |
|
525 | label_show_completed_versions: Voir les versions passΓ©es | |
526 | label_me: moi |
|
526 | label_me: moi | |
527 | label_board: Forum |
|
527 | label_board: Forum | |
528 | label_board_new: Nouveau forum |
|
528 | label_board_new: Nouveau forum | |
529 | label_board_plural: Forums |
|
529 | label_board_plural: Forums | |
530 | label_topic_plural: Discussions |
|
530 | label_topic_plural: Discussions | |
531 | label_message_plural: Messages |
|
531 | label_message_plural: Messages | |
532 | label_message_last: Dernier message |
|
532 | label_message_last: Dernier message | |
533 | label_message_new: Nouveau message |
|
533 | label_message_new: Nouveau message | |
534 | label_message_posted: Message ajoutΓ© |
|
534 | label_message_posted: Message ajoutΓ© | |
535 | label_reply_plural: RΓ©ponses |
|
535 | label_reply_plural: RΓ©ponses | |
536 | label_send_information: Envoyer les informations Γ l'utilisateur |
|
536 | label_send_information: Envoyer les informations Γ l'utilisateur | |
537 | label_year: AnnΓ©e |
|
537 | label_year: AnnΓ©e | |
538 | label_month: Mois |
|
538 | label_month: Mois | |
539 | label_week: Semaine |
|
539 | label_week: Semaine | |
540 | label_date_from: Du |
|
540 | label_date_from: Du | |
541 | label_date_to: Au |
|
541 | label_date_to: Au | |
542 | label_language_based: BasΓ© sur la langue de l'utilisateur |
|
542 | label_language_based: BasΓ© sur la langue de l'utilisateur | |
543 | label_sort_by: Trier par %s |
|
543 | label_sort_by: Trier par %s | |
544 | label_send_test_email: Envoyer un email de test |
|
544 | label_send_test_email: Envoyer un email de test | |
545 | label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s |
|
545 | label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s | |
546 | label_module_plural: Modules |
|
546 | label_module_plural: Modules | |
547 | label_added_time_by: AjoutΓ© par %s il y a %s |
|
547 | label_added_time_by: AjoutΓ© par %s il y a %s | |
548 | label_updated_time_by: Mis Γ jour par %s il y a %s |
|
548 | label_updated_time_by: Mis Γ jour par %s il y a %s | |
549 | label_updated_time: Mis Γ jour il y a %s |
|
549 | label_updated_time: Mis Γ jour il y a %s | |
550 | label_jump_to_a_project: Aller Γ un projet... |
|
550 | label_jump_to_a_project: Aller Γ un projet... | |
551 | label_file_plural: Fichiers |
|
551 | label_file_plural: Fichiers | |
552 | label_changeset_plural: RΓ©visions |
|
552 | label_changeset_plural: RΓ©visions | |
553 | label_default_columns: Colonnes par dΓ©faut |
|
553 | label_default_columns: Colonnes par dΓ©faut | |
554 | label_no_change_option: (Pas de changement) |
|
554 | label_no_change_option: (Pas de changement) | |
555 | label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es |
|
555 | label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es | |
556 | label_theme: Thème |
|
556 | label_theme: Thème | |
557 | label_default: DΓ©faut |
|
557 | label_default: DΓ©faut | |
558 | label_search_titles_only: Uniquement dans les titres |
|
558 | label_search_titles_only: Uniquement dans les titres | |
559 | label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets" |
|
559 | label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets" | |
560 | label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..." |
|
560 | label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..." | |
561 | label_user_mail_option_none: "Seulement pour ce que je surveille ou Γ quoi je participe" |
|
561 | label_user_mail_option_none: "Seulement pour ce que je surveille ou Γ quoi je participe" | |
562 | label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue" |
|
562 | label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue" | |
563 | label_registration_activation_by_email: activation du compte par email |
|
563 | label_registration_activation_by_email: activation du compte par email | |
564 | label_registration_manual_activation: activation manuelle du compte |
|
564 | label_registration_manual_activation: activation manuelle du compte | |
565 | label_registration_automatic_activation: activation automatique du compte |
|
565 | label_registration_automatic_activation: activation automatique du compte | |
566 | label_display_per_page: 'Par page: %s' |
|
566 | label_display_per_page: 'Par page: %s' | |
567 | label_age: Age |
|
567 | label_age: Age | |
568 | label_change_properties: Changer les propriΓ©tΓ©s |
|
568 | label_change_properties: Changer les propriΓ©tΓ©s | |
569 | label_general: GΓ©nΓ©ral |
|
569 | label_general: GΓ©nΓ©ral | |
570 | label_more: Plus |
|
570 | label_more: Plus | |
571 | label_scm: SCM |
|
571 | label_scm: SCM | |
572 | label_plugins: Plugins |
|
572 | label_plugins: Plugins | |
573 | label_ldap_authentication: Authentification LDAP |
|
573 | label_ldap_authentication: Authentification LDAP | |
574 | label_downloads_abbr: D/L |
|
574 | label_downloads_abbr: D/L | |
575 | label_optional_description: Description facultative |
|
575 | label_optional_description: Description facultative | |
576 | label_add_another_file: Ajouter un autre fichier |
|
576 | label_add_another_file: Ajouter un autre fichier | |
577 | label_preferences: PrΓ©fΓ©rences |
|
577 | label_preferences: PrΓ©fΓ©rences | |
578 | label_chronological_order: Dans l'ordre chronologique |
|
578 | label_chronological_order: Dans l'ordre chronologique | |
579 | label_reverse_chronological_order: Dans l'ordre chronologique inverse |
|
579 | label_reverse_chronological_order: Dans l'ordre chronologique inverse | |
580 | label_planning: Planning |
|
580 | label_planning: Planning | |
581 | label_incoming_emails: Emails entrants |
|
581 | label_incoming_emails: Emails entrants | |
582 | label_generate_key: GΓ©nΓ©rer une clΓ© |
|
582 | label_generate_key: GΓ©nΓ©rer une clΓ© | |
583 |
label_issue_watchers: |
|
583 | label_issue_watchers: Observateurs | |
584 | label_example: Exemple |
|
584 | label_example: Exemple | |
585 |
|
585 | |||
586 | button_login: Connexion |
|
586 | button_login: Connexion | |
587 | button_submit: Soumettre |
|
587 | button_submit: Soumettre | |
588 | button_save: Sauvegarder |
|
588 | button_save: Sauvegarder | |
589 | button_check_all: Tout cocher |
|
589 | button_check_all: Tout cocher | |
590 | button_uncheck_all: Tout dΓ©cocher |
|
590 | button_uncheck_all: Tout dΓ©cocher | |
591 | button_delete: Supprimer |
|
591 | button_delete: Supprimer | |
592 | button_create: CrΓ©er |
|
592 | button_create: CrΓ©er | |
593 | button_test: Tester |
|
593 | button_test: Tester | |
594 | button_edit: Modifier |
|
594 | button_edit: Modifier | |
595 | button_add: Ajouter |
|
595 | button_add: Ajouter | |
596 | button_change: Changer |
|
596 | button_change: Changer | |
597 | button_apply: Appliquer |
|
597 | button_apply: Appliquer | |
598 | button_clear: Effacer |
|
598 | button_clear: Effacer | |
599 | button_lock: Verrouiller |
|
599 | button_lock: Verrouiller | |
600 | button_unlock: DΓ©verrouiller |
|
600 | button_unlock: DΓ©verrouiller | |
601 | button_download: TΓ©lΓ©charger |
|
601 | button_download: TΓ©lΓ©charger | |
602 | button_list: Lister |
|
602 | button_list: Lister | |
603 | button_view: Voir |
|
603 | button_view: Voir | |
604 | button_move: DΓ©placer |
|
604 | button_move: DΓ©placer | |
605 | button_back: Retour |
|
605 | button_back: Retour | |
606 | button_cancel: Annuler |
|
606 | button_cancel: Annuler | |
607 | button_activate: Activer |
|
607 | button_activate: Activer | |
608 | button_sort: Trier |
|
608 | button_sort: Trier | |
609 | button_log_time: Saisir temps |
|
609 | button_log_time: Saisir temps | |
610 | button_rollback: Revenir Γ cette version |
|
610 | button_rollback: Revenir Γ cette version | |
611 | button_watch: Surveiller |
|
611 | button_watch: Surveiller | |
612 | button_unwatch: Ne plus surveiller |
|
612 | button_unwatch: Ne plus surveiller | |
613 | button_reply: RΓ©pondre |
|
613 | button_reply: RΓ©pondre | |
614 | button_archive: Archiver |
|
614 | button_archive: Archiver | |
615 | button_unarchive: DΓ©sarchiver |
|
615 | button_unarchive: DΓ©sarchiver | |
616 | button_reset: RΓ©initialiser |
|
616 | button_reset: RΓ©initialiser | |
617 | button_rename: Renommer |
|
617 | button_rename: Renommer | |
618 | button_change_password: Changer de mot de passe |
|
618 | button_change_password: Changer de mot de passe | |
619 | button_copy: Copier |
|
619 | button_copy: Copier | |
620 | button_annotate: Annoter |
|
620 | button_annotate: Annoter | |
621 | button_update: Mettre Γ jour |
|
621 | button_update: Mettre Γ jour | |
622 | button_configure: Configurer |
|
622 | button_configure: Configurer | |
623 | button_quote: Citer |
|
623 | button_quote: Citer | |
624 |
|
624 | |||
625 | status_active: actif |
|
625 | status_active: actif | |
626 | status_registered: enregistrΓ© |
|
626 | status_registered: enregistrΓ© | |
627 | status_locked: vΓ©rouillΓ© |
|
627 | status_locked: vΓ©rouillΓ© | |
628 |
|
628 | |||
629 | text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e |
|
629 | text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e | |
630 | text_regexp_info: ex. ^[A-Z0-9]+$ |
|
630 | text_regexp_info: ex. ^[A-Z0-9]+$ | |
631 | text_min_max_length_info: 0 pour aucune restriction |
|
631 | text_min_max_length_info: 0 pour aucune restriction | |
632 | text_project_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce projet et toutes ses donnΓ©es ? |
|
632 | text_project_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce projet et toutes ses donnΓ©es ? | |
633 | text_subprojects_destroy_warning: 'Ses sous-projets: %s seront Γ©galement supprimΓ©s.' |
|
633 | text_subprojects_destroy_warning: 'Ses sous-projets: %s seront Γ©galement supprimΓ©s.' | |
634 | text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow |
|
634 | text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow | |
635 | text_are_you_sure: Etes-vous sΓ»r ? |
|
635 | text_are_you_sure: Etes-vous sΓ»r ? | |
636 | text_journal_changed: changΓ© de %s Γ %s |
|
636 | text_journal_changed: changΓ© de %s Γ %s | |
637 | text_journal_set_to: mis Γ %s |
|
637 | text_journal_set_to: mis Γ %s | |
638 | text_journal_deleted: supprimΓ© |
|
638 | text_journal_deleted: supprimΓ© | |
639 | text_tip_task_begin_day: tΓ’che commenΓ§ant ce jour |
|
639 | text_tip_task_begin_day: tΓ’che commenΓ§ant ce jour | |
640 | text_tip_task_end_day: tΓ’che finissant ce jour |
|
640 | text_tip_task_end_day: tΓ’che finissant ce jour | |
641 | text_tip_task_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour |
|
641 | text_tip_task_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour | |
642 | text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres et tirets sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.' |
|
642 | text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres et tirets sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.' | |
643 | text_caracters_maximum: %d caractères maximum. |
|
643 | text_caracters_maximum: %d caractères maximum. | |
644 | text_caracters_minimum: %d caractères minimum. |
|
644 | text_caracters_minimum: %d caractères minimum. | |
645 | text_length_between: Longueur comprise entre %d et %d caractères. |
|
645 | text_length_between: Longueur comprise entre %d et %d caractères. | |
646 | text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker |
|
646 | text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker | |
647 | text_unallowed_characters: Caractères non autorisés |
|
647 | text_unallowed_characters: Caractères non autorisés | |
648 | text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules). |
|
648 | text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules). | |
649 | text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits |
|
649 | text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits | |
650 | text_issue_added: La demande %s a Γ©tΓ© soumise par %s. |
|
650 | text_issue_added: La demande %s a Γ©tΓ© soumise par %s. | |
651 | text_issue_updated: La demande %s a Γ©tΓ© mise Γ jour par %s. |
|
651 | text_issue_updated: La demande %s a Γ©tΓ© mise Γ jour par %s. | |
652 | text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ? |
|
652 | text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ? | |
653 | text_issue_category_destroy_question: %d demandes sont affectΓ©es Γ cette catΓ©gories. Que voulez-vous faire ? |
|
653 | text_issue_category_destroy_question: %d demandes sont affectΓ©es Γ cette catΓ©gories. Que voulez-vous faire ? | |
654 | text_issue_category_destroy_assignments: N'affecter les demandes Γ aucune autre catΓ©gorie |
|
654 | text_issue_category_destroy_assignments: N'affecter les demandes Γ aucune autre catΓ©gorie | |
655 | text_issue_category_reassign_to: RΓ©affecter les demandes Γ cette catΓ©gorie |
|
655 | text_issue_category_reassign_to: RΓ©affecter les demandes Γ cette catΓ©gorie | |
656 | text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)." |
|
656 | text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)." | |
657 | text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©." |
|
657 | text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©." | |
658 | text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut |
|
658 | text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut | |
659 | text_status_changed_by_changeset: AppliquΓ© par commit %s. |
|
659 | text_status_changed_by_changeset: AppliquΓ© par commit %s. | |
660 | text_issues_destroy_confirmation: 'Etes-vous sΓ»r de vouloir supprimer le(s) demandes(s) selectionnΓ©e(s) ?' |
|
660 | text_issues_destroy_confirmation: 'Etes-vous sΓ»r de vouloir supprimer le(s) demandes(s) selectionnΓ©e(s) ?' | |
661 | text_select_project_modules: 'Selectionner les modules Γ activer pour ce project:' |
|
661 | text_select_project_modules: 'Selectionner les modules Γ activer pour ce project:' | |
662 | text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ© |
|
662 | text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ© | |
663 | text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture |
|
663 | text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture | |
664 | text_rmagick_available: Bibliothèque RMagick présente (optionnelle) |
|
664 | text_rmagick_available: Bibliothèque RMagick présente (optionnelle) | |
665 | text_destroy_time_entries_question: %.02f heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ supprimer. Que voulez-vous faire ? |
|
665 | text_destroy_time_entries_question: %.02f heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ supprimer. Que voulez-vous faire ? | |
666 | text_destroy_time_entries: Supprimer les heures |
|
666 | text_destroy_time_entries: Supprimer les heures | |
667 | text_assign_time_entries_to_project: Reporter les heures sur le projet |
|
667 | text_assign_time_entries_to_project: Reporter les heures sur le projet | |
668 | text_reassign_time_entries: 'Reporter les heures sur cette demande:' |
|
668 | text_reassign_time_entries: 'Reporter les heures sur cette demande:' | |
669 | text_user_wrote: '%s a Γ©crit:' |
|
669 | text_user_wrote: '%s a Γ©crit:' | |
670 | text_enumeration_destroy_question: 'Cette valeur est affectΓ©e Γ %d objets.' |
|
670 | text_enumeration_destroy_question: 'Cette valeur est affectΓ©e Γ %d objets.' | |
671 | text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ cette valeur:' |
|
671 | text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ cette valeur:' | |
672 | text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/email.yml et redΓ©marrez l'application pour les activer." |
|
672 | text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/email.yml et redΓ©marrez l'application pour les activer." | |
673 | text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s." |
|
673 | text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s." | |
674 | text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.' |
|
674 | text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.' | |
675 |
|
675 | |||
676 | default_role_manager: Manager |
|
676 | default_role_manager: Manager | |
677 | default_role_developper: DΓ©veloppeur |
|
677 | default_role_developper: DΓ©veloppeur | |
678 | default_role_reporter: Rapporteur |
|
678 | default_role_reporter: Rapporteur | |
679 | default_tracker_bug: Anomalie |
|
679 | default_tracker_bug: Anomalie | |
680 | default_tracker_feature: Evolution |
|
680 | default_tracker_feature: Evolution | |
681 | default_tracker_support: Assistance |
|
681 | default_tracker_support: Assistance | |
682 | default_issue_status_new: Nouveau |
|
682 | default_issue_status_new: Nouveau | |
683 | default_issue_status_assigned: AssignΓ© |
|
683 | default_issue_status_assigned: AssignΓ© | |
684 | default_issue_status_resolved: RΓ©solu |
|
684 | default_issue_status_resolved: RΓ©solu | |
685 | default_issue_status_feedback: Commentaire |
|
685 | default_issue_status_feedback: Commentaire | |
686 | default_issue_status_closed: FermΓ© |
|
686 | default_issue_status_closed: FermΓ© | |
687 | default_issue_status_rejected: RejetΓ© |
|
687 | default_issue_status_rejected: RejetΓ© | |
688 | default_doc_category_user: Documentation utilisateur |
|
688 | default_doc_category_user: Documentation utilisateur | |
689 | default_doc_category_tech: Documentation technique |
|
689 | default_doc_category_tech: Documentation technique | |
690 | default_priority_low: Bas |
|
690 | default_priority_low: Bas | |
691 | default_priority_normal: Normal |
|
691 | default_priority_normal: Normal | |
692 | default_priority_high: Haut |
|
692 | default_priority_high: Haut | |
693 | default_priority_urgent: Urgent |
|
693 | default_priority_urgent: Urgent | |
694 | default_priority_immediate: ImmΓ©diat |
|
694 | default_priority_immediate: ImmΓ©diat | |
695 | default_activity_design: Conception |
|
695 | default_activity_design: Conception | |
696 | default_activity_development: DΓ©veloppement |
|
696 | default_activity_development: DΓ©veloppement | |
697 |
|
697 | |||
698 | enumeration_issue_priorities: PrioritΓ©s des demandes |
|
698 | enumeration_issue_priorities: PrioritΓ©s des demandes | |
699 | enumeration_doc_categories: CatΓ©gories des documents |
|
699 | enumeration_doc_categories: CatΓ©gories des documents | |
700 | enumeration_activities: ActivitΓ©s (suivi du temps) |
|
700 | enumeration_activities: ActivitΓ©s (suivi du temps) |
@@ -1,189 +1,196 | |||||
1 | # redMine - project management software |
|
1 | # redMine - project management software | |
2 | # Copyright (C) 2006-2007 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2007 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 'redmine/scm/adapters/abstract_adapter' |
|
18 | require 'redmine/scm/adapters/abstract_adapter' | |
19 | require 'rexml/document' |
|
19 | require 'rexml/document' | |
20 |
|
20 | |||
21 | module Redmine |
|
21 | module Redmine | |
22 | module Scm |
|
22 | module Scm | |
23 | module Adapters |
|
23 | module Adapters | |
24 | class DarcsAdapter < AbstractAdapter |
|
24 | class DarcsAdapter < AbstractAdapter | |
25 | # Darcs executable name |
|
25 | # Darcs executable name | |
26 | DARCS_BIN = "darcs" |
|
26 | DARCS_BIN = "darcs" | |
27 |
|
27 | |||
28 | class << self |
|
28 | class << self | |
29 | def client_version |
|
29 | def client_version | |
30 | @@client_version ||= (darcs_binary_version || []) |
|
30 | @@client_version ||= (darcs_binary_version || []) | |
31 | end |
|
31 | end | |
32 |
|
32 | |||
33 | def darcs_binary_version |
|
33 | def darcs_binary_version | |
34 | cmd = "#{DARCS_BIN} --version" |
|
34 | cmd = "#{DARCS_BIN} --version" | |
35 | version = nil |
|
35 | version = nil | |
36 | shellout(cmd) do |io| |
|
36 | shellout(cmd) do |io| | |
37 | # Read darcs version in first returned line |
|
37 | # Read darcs version in first returned line | |
38 | if m = io.gets.match(%r{((\d+\.)+\d+)}) |
|
38 | if m = io.gets.match(%r{((\d+\.)+\d+)}) | |
39 | version = m[0].scan(%r{\d+}).collect(&:to_i) |
|
39 | version = m[0].scan(%r{\d+}).collect(&:to_i) | |
40 | end |
|
40 | end | |
41 | end |
|
41 | end | |
42 | return nil if $? && $?.exitstatus != 0 |
|
42 | return nil if $? && $?.exitstatus != 0 | |
43 | version |
|
43 | version | |
44 | end |
|
44 | end | |
45 | end |
|
45 | end | |
46 |
|
46 | |||
47 | def initialize(url, root_url=nil, login=nil, password=nil) |
|
47 | def initialize(url, root_url=nil, login=nil, password=nil) | |
48 | @url = url |
|
48 | @url = url | |
49 | @root_url = url |
|
49 | @root_url = url | |
50 | end |
|
50 | end | |
51 |
|
51 | |||
52 | def supports_cat? |
|
52 | def supports_cat? | |
53 | # cat supported in darcs 2.0.0 and higher |
|
53 | # cat supported in darcs 2.0.0 and higher | |
54 | self.class.client_version_above?([2, 0, 0]) |
|
54 | self.class.client_version_above?([2, 0, 0]) | |
55 | end |
|
55 | end | |
56 |
|
56 | |||
57 | # Get info about the darcs repository |
|
57 | # Get info about the darcs repository | |
58 | def info |
|
58 | def info | |
59 | rev = revisions(nil,nil,nil,{:limit => 1}) |
|
59 | rev = revisions(nil,nil,nil,{:limit => 1}) | |
60 | rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil |
|
60 | rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil | |
61 | end |
|
61 | end | |
62 |
|
62 | |||
63 | # Returns an Entries collection |
|
63 | # Returns an Entries collection | |
64 | # or nil if the given path doesn't exist in the repository |
|
64 | # or nil if the given path doesn't exist in the repository | |
65 | def entries(path=nil, identifier=nil) |
|
65 | def entries(path=nil, identifier=nil) | |
66 | path_prefix = (path.blank? ? '' : "#{path}/") |
|
66 | path_prefix = (path.blank? ? '' : "#{path}/") | |
67 | path = '.' if path.blank? |
|
67 | path = '.' if path.blank? | |
68 | entries = Entries.new |
|
68 | entries = Entries.new | |
69 | cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output" |
|
69 | cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output" | |
70 | cmd << " --match \"hash #{identifier}\"" if identifier |
|
70 | cmd << " --match \"hash #{identifier}\"" if identifier | |
71 | cmd << " #{path}" |
|
71 | cmd << " #{path}" | |
72 | shellout(cmd) do |io| |
|
72 | shellout(cmd) do |io| | |
73 | begin |
|
73 | begin | |
74 | doc = REXML::Document.new(io) |
|
74 | doc = REXML::Document.new(io) | |
75 | if doc.root.name == 'directory' |
|
75 | if doc.root.name == 'directory' | |
76 | doc.elements.each('directory/*') do |element| |
|
76 | doc.elements.each('directory/*') do |element| | |
77 | next unless ['file', 'directory'].include? element.name |
|
77 | next unless ['file', 'directory'].include? element.name | |
78 | entries << entry_from_xml(element, path_prefix) |
|
78 | entries << entry_from_xml(element, path_prefix) | |
79 | end |
|
79 | end | |
80 | elsif doc.root.name == 'file' |
|
80 | elsif doc.root.name == 'file' | |
81 | entries << entry_from_xml(doc.root, path_prefix) |
|
81 | entries << entry_from_xml(doc.root, path_prefix) | |
82 | end |
|
82 | end | |
83 | rescue |
|
83 | rescue | |
84 | end |
|
84 | end | |
85 | end |
|
85 | end | |
86 | return nil if $? && $?.exitstatus != 0 |
|
86 | return nil if $? && $?.exitstatus != 0 | |
87 | entries.sort_by_name |
|
87 | entries.compact.sort_by_name | |
88 | end |
|
88 | end | |
89 |
|
89 | |||
90 | def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
90 | def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) | |
91 | path = '.' if path.blank? |
|
91 | path = '.' if path.blank? | |
92 | revisions = Revisions.new |
|
92 | revisions = Revisions.new | |
93 | cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output" |
|
93 | cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output" | |
94 | cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from |
|
94 | cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from | |
95 | cmd << " --last #{options[:limit].to_i}" if options[:limit] |
|
95 | cmd << " --last #{options[:limit].to_i}" if options[:limit] | |
96 | shellout(cmd) do |io| |
|
96 | shellout(cmd) do |io| | |
97 | begin |
|
97 | begin | |
98 | doc = REXML::Document.new(io) |
|
98 | doc = REXML::Document.new(io) | |
99 | doc.elements.each("changelog/patch") do |patch| |
|
99 | doc.elements.each("changelog/patch") do |patch| | |
100 | message = patch.elements['name'].text |
|
100 | message = patch.elements['name'].text | |
101 | message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment'] |
|
101 | message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment'] | |
102 | revisions << Revision.new({:identifier => nil, |
|
102 | revisions << Revision.new({:identifier => nil, | |
103 | :author => patch.attributes['author'], |
|
103 | :author => patch.attributes['author'], | |
104 | :scmid => patch.attributes['hash'], |
|
104 | :scmid => patch.attributes['hash'], | |
105 | :time => Time.parse(patch.attributes['local_date']), |
|
105 | :time => Time.parse(patch.attributes['local_date']), | |
106 | :message => message, |
|
106 | :message => message, | |
107 | :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil) |
|
107 | :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil) | |
108 | }) |
|
108 | }) | |
109 | end |
|
109 | end | |
110 | rescue |
|
110 | rescue | |
111 | end |
|
111 | end | |
112 | end |
|
112 | end | |
113 | return nil if $? && $?.exitstatus != 0 |
|
113 | return nil if $? && $?.exitstatus != 0 | |
114 | revisions |
|
114 | revisions | |
115 | end |
|
115 | end | |
116 |
|
116 | |||
117 | def diff(path, identifier_from, identifier_to=nil) |
|
117 | def diff(path, identifier_from, identifier_to=nil) | |
118 | path = '*' if path.blank? |
|
118 | path = '*' if path.blank? | |
119 | cmd = "#{DARCS_BIN} diff --repodir #{@url}" |
|
119 | cmd = "#{DARCS_BIN} diff --repodir #{@url}" | |
120 | if identifier_to.nil? |
|
120 | if identifier_to.nil? | |
121 | cmd << " --match \"hash #{identifier_from}\"" |
|
121 | cmd << " --match \"hash #{identifier_from}\"" | |
122 | else |
|
122 | else | |
123 | cmd << " --to-match \"hash #{identifier_from}\"" |
|
123 | cmd << " --to-match \"hash #{identifier_from}\"" | |
124 | cmd << " --from-match \"hash #{identifier_to}\"" |
|
124 | cmd << " --from-match \"hash #{identifier_to}\"" | |
125 | end |
|
125 | end | |
126 | cmd << " -u #{path}" |
|
126 | cmd << " -u #{path}" | |
127 | diff = [] |
|
127 | diff = [] | |
128 | shellout(cmd) do |io| |
|
128 | shellout(cmd) do |io| | |
129 | io.each_line do |line| |
|
129 | io.each_line do |line| | |
130 | diff << line |
|
130 | diff << line | |
131 | end |
|
131 | end | |
132 | end |
|
132 | end | |
133 | return nil if $? && $?.exitstatus != 0 |
|
133 | return nil if $? && $?.exitstatus != 0 | |
134 | diff |
|
134 | diff | |
135 | end |
|
135 | end | |
136 |
|
136 | |||
137 | def cat(path, identifier=nil) |
|
137 | def cat(path, identifier=nil) | |
138 | cmd = "#{DARCS_BIN} show content --repodir #{@url}" |
|
138 | cmd = "#{DARCS_BIN} show content --repodir #{@url}" | |
139 | cmd << " --match \"hash #{identifier}\"" if identifier |
|
139 | cmd << " --match \"hash #{identifier}\"" if identifier | |
140 | cmd << " #{shell_quote path}" |
|
140 | cmd << " #{shell_quote path}" | |
141 | cat = nil |
|
141 | cat = nil | |
142 | shellout(cmd) do |io| |
|
142 | shellout(cmd) do |io| | |
143 | io.binmode |
|
143 | io.binmode | |
144 | cat = io.read |
|
144 | cat = io.read | |
145 | end |
|
145 | end | |
146 | return nil if $? && $?.exitstatus != 0 |
|
146 | return nil if $? && $?.exitstatus != 0 | |
147 | cat |
|
147 | cat | |
148 | end |
|
148 | end | |
149 |
|
149 | |||
150 | private |
|
150 | private | |
151 |
|
|
151 | ||
|
152 | # Returns an Entry from the given XML element | |||
|
153 | # or nil if the entry was deleted | |||
152 | def entry_from_xml(element, path_prefix) |
|
154 | def entry_from_xml(element, path_prefix) | |
|
155 | modified_element = element.elements['modified'] | |||
|
156 | if modified_element.elements['modified_how'].text.match(/removed/) | |||
|
157 | return nil | |||
|
158 | end | |||
|
159 | ||||
153 | Entry.new({:name => element.attributes['name'], |
|
160 | Entry.new({:name => element.attributes['name'], | |
154 | :path => path_prefix + element.attributes['name'], |
|
161 | :path => path_prefix + element.attributes['name'], | |
155 | :kind => element.name == 'file' ? 'file' : 'dir', |
|
162 | :kind => element.name == 'file' ? 'file' : 'dir', | |
156 | :size => nil, |
|
163 | :size => nil, | |
157 | :lastrev => Revision.new({ |
|
164 | :lastrev => Revision.new({ | |
158 | :identifier => nil, |
|
165 | :identifier => nil, | |
159 |
:scmid => |
|
166 | :scmid => modified_element.elements['patch'].attributes['hash'] | |
160 | }) |
|
167 | }) | |
161 | }) |
|
168 | }) | |
162 | end |
|
169 | end | |
163 |
|
170 | |||
164 | # Retrieve changed paths for a single patch |
|
171 | # Retrieve changed paths for a single patch | |
165 | def get_paths_for_patch(hash) |
|
172 | def get_paths_for_patch(hash) | |
166 | cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output" |
|
173 | cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output" | |
167 | cmd << " --match \"hash #{hash}\" " |
|
174 | cmd << " --match \"hash #{hash}\" " | |
168 | paths = [] |
|
175 | paths = [] | |
169 | shellout(cmd) do |io| |
|
176 | shellout(cmd) do |io| | |
170 | begin |
|
177 | begin | |
171 | # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7) |
|
178 | # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7) | |
172 | # A root element is added so that REXML doesn't raise an error |
|
179 | # A root element is added so that REXML doesn't raise an error | |
173 | doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>") |
|
180 | doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>") | |
174 | doc.elements.each('fake_root/summary/*') do |modif| |
|
181 | doc.elements.each('fake_root/summary/*') do |modif| | |
175 | paths << {:action => modif.name[0,1].upcase, |
|
182 | paths << {:action => modif.name[0,1].upcase, | |
176 | :path => "/" + modif.text.chomp.gsub(/^\s*/, '') |
|
183 | :path => "/" + modif.text.chomp.gsub(/^\s*/, '') | |
177 | } |
|
184 | } | |
178 | end |
|
185 | end | |
179 | rescue |
|
186 | rescue | |
180 | end |
|
187 | end | |
181 | end |
|
188 | end | |
182 | paths |
|
189 | paths | |
183 | rescue CommandFailed |
|
190 | rescue CommandFailed | |
184 | paths |
|
191 | paths | |
185 | end |
|
192 | end | |
186 | end |
|
193 | end | |
187 | end |
|
194 | end | |
188 | end |
|
195 | end | |
189 | end |
|
196 | end |
@@ -1,739 +1,763 | |||||
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 | require File.dirname(__FILE__) + '/../test_helper' |
|
18 | require File.dirname(__FILE__) + '/../test_helper' | |
19 | require 'issues_controller' |
|
19 | require 'issues_controller' | |
20 |
|
20 | |||
21 | # Re-raise errors caught by the controller. |
|
21 | # Re-raise errors caught by the controller. | |
22 | class IssuesController; def rescue_action(e) raise e end; end |
|
22 | class IssuesController; def rescue_action(e) raise e end; end | |
23 |
|
23 | |||
24 | class IssuesControllerTest < Test::Unit::TestCase |
|
24 | class IssuesControllerTest < Test::Unit::TestCase | |
25 | fixtures :projects, |
|
25 | fixtures :projects, | |
26 | :users, |
|
26 | :users, | |
27 | :roles, |
|
27 | :roles, | |
28 | :members, |
|
28 | :members, | |
29 | :issues, |
|
29 | :issues, | |
30 | :issue_statuses, |
|
30 | :issue_statuses, | |
31 | :versions, |
|
31 | :versions, | |
32 | :trackers, |
|
32 | :trackers, | |
33 | :projects_trackers, |
|
33 | :projects_trackers, | |
34 | :issue_categories, |
|
34 | :issue_categories, | |
35 | :enabled_modules, |
|
35 | :enabled_modules, | |
36 | :enumerations, |
|
36 | :enumerations, | |
37 | :attachments, |
|
37 | :attachments, | |
38 | :workflows, |
|
38 | :workflows, | |
39 | :custom_fields, |
|
39 | :custom_fields, | |
40 | :custom_values, |
|
40 | :custom_values, | |
41 | :custom_fields_trackers, |
|
41 | :custom_fields_trackers, | |
42 | :time_entries, |
|
42 | :time_entries, | |
43 | :journals, |
|
43 | :journals, | |
44 | :journal_details |
|
44 | :journal_details | |
45 |
|
45 | |||
46 | def setup |
|
46 | def setup | |
47 | @controller = IssuesController.new |
|
47 | @controller = IssuesController.new | |
48 | @request = ActionController::TestRequest.new |
|
48 | @request = ActionController::TestRequest.new | |
49 | @response = ActionController::TestResponse.new |
|
49 | @response = ActionController::TestResponse.new | |
50 | User.current = nil |
|
50 | User.current = nil | |
51 | end |
|
51 | end | |
52 |
|
52 | |||
53 | def test_index |
|
53 | def test_index | |
54 | get :index |
|
54 | get :index | |
55 | assert_response :success |
|
55 | assert_response :success | |
56 | assert_template 'index.rhtml' |
|
56 | assert_template 'index.rhtml' | |
57 | assert_not_nil assigns(:issues) |
|
57 | assert_not_nil assigns(:issues) | |
58 | assert_nil assigns(:project) |
|
58 | assert_nil assigns(:project) | |
59 | assert_tag :tag => 'a', :content => /Can't print recipes/ |
|
59 | assert_tag :tag => 'a', :content => /Can't print recipes/ | |
60 | assert_tag :tag => 'a', :content => /Subproject issue/ |
|
60 | assert_tag :tag => 'a', :content => /Subproject issue/ | |
61 | # private projects hidden |
|
61 | # private projects hidden | |
62 | assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ |
|
62 | assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ | |
63 | assert_no_tag :tag => 'a', :content => /Issue on project 2/ |
|
63 | assert_no_tag :tag => 'a', :content => /Issue on project 2/ | |
64 | end |
|
64 | end | |
65 |
|
65 | |||
66 | def test_index_should_not_list_issues_when_module_disabled |
|
66 | def test_index_should_not_list_issues_when_module_disabled | |
67 | EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1") |
|
67 | EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1") | |
68 | get :index |
|
68 | get :index | |
69 | assert_response :success |
|
69 | assert_response :success | |
70 | assert_template 'index.rhtml' |
|
70 | assert_template 'index.rhtml' | |
71 | assert_not_nil assigns(:issues) |
|
71 | assert_not_nil assigns(:issues) | |
72 | assert_nil assigns(:project) |
|
72 | assert_nil assigns(:project) | |
73 | assert_no_tag :tag => 'a', :content => /Can't print recipes/ |
|
73 | assert_no_tag :tag => 'a', :content => /Can't print recipes/ | |
74 | assert_tag :tag => 'a', :content => /Subproject issue/ |
|
74 | assert_tag :tag => 'a', :content => /Subproject issue/ | |
75 | end |
|
75 | end | |
76 |
|
76 | |||
77 | def test_index_with_project |
|
77 | def test_index_with_project | |
78 | Setting.display_subprojects_issues = 0 |
|
78 | Setting.display_subprojects_issues = 0 | |
79 | get :index, :project_id => 1 |
|
79 | get :index, :project_id => 1 | |
80 | assert_response :success |
|
80 | assert_response :success | |
81 | assert_template 'index.rhtml' |
|
81 | assert_template 'index.rhtml' | |
82 | assert_not_nil assigns(:issues) |
|
82 | assert_not_nil assigns(:issues) | |
83 | assert_tag :tag => 'a', :content => /Can't print recipes/ |
|
83 | assert_tag :tag => 'a', :content => /Can't print recipes/ | |
84 | assert_no_tag :tag => 'a', :content => /Subproject issue/ |
|
84 | assert_no_tag :tag => 'a', :content => /Subproject issue/ | |
85 | end |
|
85 | end | |
86 |
|
86 | |||
87 | def test_index_with_project_and_subprojects |
|
87 | def test_index_with_project_and_subprojects | |
88 | Setting.display_subprojects_issues = 1 |
|
88 | Setting.display_subprojects_issues = 1 | |
89 | get :index, :project_id => 1 |
|
89 | get :index, :project_id => 1 | |
90 | assert_response :success |
|
90 | assert_response :success | |
91 | assert_template 'index.rhtml' |
|
91 | assert_template 'index.rhtml' | |
92 | assert_not_nil assigns(:issues) |
|
92 | assert_not_nil assigns(:issues) | |
93 | assert_tag :tag => 'a', :content => /Can't print recipes/ |
|
93 | assert_tag :tag => 'a', :content => /Can't print recipes/ | |
94 | assert_tag :tag => 'a', :content => /Subproject issue/ |
|
94 | assert_tag :tag => 'a', :content => /Subproject issue/ | |
95 | assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ |
|
95 | assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ | |
96 | end |
|
96 | end | |
97 |
|
97 | |||
98 | def test_index_with_project_and_subprojects_should_show_private_subprojects |
|
98 | def test_index_with_project_and_subprojects_should_show_private_subprojects | |
99 | @request.session[:user_id] = 2 |
|
99 | @request.session[:user_id] = 2 | |
100 | Setting.display_subprojects_issues = 1 |
|
100 | Setting.display_subprojects_issues = 1 | |
101 | get :index, :project_id => 1 |
|
101 | get :index, :project_id => 1 | |
102 | assert_response :success |
|
102 | assert_response :success | |
103 | assert_template 'index.rhtml' |
|
103 | assert_template 'index.rhtml' | |
104 | assert_not_nil assigns(:issues) |
|
104 | assert_not_nil assigns(:issues) | |
105 | assert_tag :tag => 'a', :content => /Can't print recipes/ |
|
105 | assert_tag :tag => 'a', :content => /Can't print recipes/ | |
106 | assert_tag :tag => 'a', :content => /Subproject issue/ |
|
106 | assert_tag :tag => 'a', :content => /Subproject issue/ | |
107 | assert_tag :tag => 'a', :content => /Issue of a private subproject/ |
|
107 | assert_tag :tag => 'a', :content => /Issue of a private subproject/ | |
108 | end |
|
108 | end | |
109 |
|
109 | |||
110 | def test_index_with_project_and_filter |
|
110 | def test_index_with_project_and_filter | |
111 | get :index, :project_id => 1, :set_filter => 1 |
|
111 | get :index, :project_id => 1, :set_filter => 1 | |
112 | assert_response :success |
|
112 | assert_response :success | |
113 | assert_template 'index.rhtml' |
|
113 | assert_template 'index.rhtml' | |
114 | assert_not_nil assigns(:issues) |
|
114 | assert_not_nil assigns(:issues) | |
115 | end |
|
115 | end | |
116 |
|
116 | |||
117 | def test_index_csv_with_project |
|
117 | def test_index_csv_with_project | |
118 | get :index, :format => 'csv' |
|
118 | get :index, :format => 'csv' | |
119 | assert_response :success |
|
119 | assert_response :success | |
120 | assert_not_nil assigns(:issues) |
|
120 | assert_not_nil assigns(:issues) | |
121 | assert_equal 'text/csv', @response.content_type |
|
121 | assert_equal 'text/csv', @response.content_type | |
122 |
|
122 | |||
123 | get :index, :project_id => 1, :format => 'csv' |
|
123 | get :index, :project_id => 1, :format => 'csv' | |
124 | assert_response :success |
|
124 | assert_response :success | |
125 | assert_not_nil assigns(:issues) |
|
125 | assert_not_nil assigns(:issues) | |
126 | assert_equal 'text/csv', @response.content_type |
|
126 | assert_equal 'text/csv', @response.content_type | |
127 | end |
|
127 | end | |
128 |
|
128 | |||
129 | def test_index_pdf |
|
129 | def test_index_pdf | |
130 | get :index, :format => 'pdf' |
|
130 | get :index, :format => 'pdf' | |
131 | assert_response :success |
|
131 | assert_response :success | |
132 | assert_not_nil assigns(:issues) |
|
132 | assert_not_nil assigns(:issues) | |
133 | assert_equal 'application/pdf', @response.content_type |
|
133 | assert_equal 'application/pdf', @response.content_type | |
134 |
|
134 | |||
135 | get :index, :project_id => 1, :format => 'pdf' |
|
135 | get :index, :project_id => 1, :format => 'pdf' | |
136 | assert_response :success |
|
136 | assert_response :success | |
137 | assert_not_nil assigns(:issues) |
|
137 | assert_not_nil assigns(:issues) | |
138 | assert_equal 'application/pdf', @response.content_type |
|
138 | assert_equal 'application/pdf', @response.content_type | |
139 | end |
|
139 | end | |
140 |
|
140 | |||
141 | def test_index_sort |
|
141 | def test_index_sort | |
142 | get :index, :sort_key => 'tracker' |
|
142 | get :index, :sort_key => 'tracker' | |
143 | assert_response :success |
|
143 | assert_response :success | |
144 |
|
144 | |||
145 | sort_params = @request.session['issuesindex_sort'] |
|
145 | sort_params = @request.session['issuesindex_sort'] | |
146 | assert sort_params.is_a?(Hash) |
|
146 | assert sort_params.is_a?(Hash) | |
147 | assert_equal 'tracker', sort_params[:key] |
|
147 | assert_equal 'tracker', sort_params[:key] | |
148 | assert_equal 'ASC', sort_params[:order] |
|
148 | assert_equal 'ASC', sort_params[:order] | |
149 | end |
|
149 | end | |
150 |
|
150 | |||
151 | def test_gantt |
|
151 | def test_gantt | |
152 | get :gantt, :project_id => 1 |
|
152 | get :gantt, :project_id => 1 | |
153 | assert_response :success |
|
153 | assert_response :success | |
154 | assert_template 'gantt.rhtml' |
|
154 | assert_template 'gantt.rhtml' | |
155 | assert_not_nil assigns(:gantt) |
|
155 | assert_not_nil assigns(:gantt) | |
156 | events = assigns(:gantt).events |
|
156 | events = assigns(:gantt).events | |
157 | assert_not_nil events |
|
157 | assert_not_nil events | |
158 | # Issue with start and due dates |
|
158 | # Issue with start and due dates | |
159 | i = Issue.find(1) |
|
159 | i = Issue.find(1) | |
160 | assert_not_nil i.due_date |
|
160 | assert_not_nil i.due_date | |
161 | assert events.include?(Issue.find(1)) |
|
161 | assert events.include?(Issue.find(1)) | |
162 | # Issue with without due date but targeted to a version with date |
|
162 | # Issue with without due date but targeted to a version with date | |
163 | i = Issue.find(2) |
|
163 | i = Issue.find(2) | |
164 | assert_nil i.due_date |
|
164 | assert_nil i.due_date | |
165 | assert events.include?(i) |
|
165 | assert events.include?(i) | |
166 | end |
|
166 | end | |
167 |
|
167 | |||
168 | def test_cross_project_gantt |
|
168 | def test_cross_project_gantt | |
169 | get :gantt |
|
169 | get :gantt | |
170 | assert_response :success |
|
170 | assert_response :success | |
171 | assert_template 'gantt.rhtml' |
|
171 | assert_template 'gantt.rhtml' | |
172 | assert_not_nil assigns(:gantt) |
|
172 | assert_not_nil assigns(:gantt) | |
173 | events = assigns(:gantt).events |
|
173 | events = assigns(:gantt).events | |
174 | assert_not_nil events |
|
174 | assert_not_nil events | |
175 | end |
|
175 | end | |
176 |
|
176 | |||
177 | def test_gantt_export_to_pdf |
|
177 | def test_gantt_export_to_pdf | |
178 | get :gantt, :project_id => 1, :format => 'pdf' |
|
178 | get :gantt, :project_id => 1, :format => 'pdf' | |
179 | assert_response :success |
|
179 | assert_response :success | |
180 | assert_template 'gantt.rfpdf' |
|
180 | assert_template 'gantt.rfpdf' | |
181 | assert_equal 'application/pdf', @response.content_type |
|
181 | assert_equal 'application/pdf', @response.content_type | |
182 | assert_not_nil assigns(:gantt) |
|
182 | assert_not_nil assigns(:gantt) | |
183 | end |
|
183 | end | |
184 |
|
184 | |||
185 | def test_cross_project_gantt_export_to_pdf |
|
185 | def test_cross_project_gantt_export_to_pdf | |
186 | get :gantt, :format => 'pdf' |
|
186 | get :gantt, :format => 'pdf' | |
187 | assert_response :success |
|
187 | assert_response :success | |
188 | assert_template 'gantt.rfpdf' |
|
188 | assert_template 'gantt.rfpdf' | |
189 | assert_equal 'application/pdf', @response.content_type |
|
189 | assert_equal 'application/pdf', @response.content_type | |
190 | assert_not_nil assigns(:gantt) |
|
190 | assert_not_nil assigns(:gantt) | |
191 | end |
|
191 | end | |
192 |
|
192 | |||
193 | if Object.const_defined?(:Magick) |
|
193 | if Object.const_defined?(:Magick) | |
194 | def test_gantt_image |
|
194 | def test_gantt_image | |
195 | get :gantt, :project_id => 1, :format => 'png' |
|
195 | get :gantt, :project_id => 1, :format => 'png' | |
196 | assert_response :success |
|
196 | assert_response :success | |
197 | assert_equal 'image/png', @response.content_type |
|
197 | assert_equal 'image/png', @response.content_type | |
198 | end |
|
198 | end | |
199 | else |
|
199 | else | |
200 | puts "RMagick not installed. Skipping tests !!!" |
|
200 | puts "RMagick not installed. Skipping tests !!!" | |
201 | end |
|
201 | end | |
202 |
|
202 | |||
203 | def test_calendar |
|
203 | def test_calendar | |
204 | get :calendar, :project_id => 1 |
|
204 | get :calendar, :project_id => 1 | |
205 | assert_response :success |
|
205 | assert_response :success | |
206 | assert_template 'calendar' |
|
206 | assert_template 'calendar' | |
207 | assert_not_nil assigns(:calendar) |
|
207 | assert_not_nil assigns(:calendar) | |
208 | end |
|
208 | end | |
209 |
|
209 | |||
210 | def test_cross_project_calendar |
|
210 | def test_cross_project_calendar | |
211 | get :calendar |
|
211 | get :calendar | |
212 | assert_response :success |
|
212 | assert_response :success | |
213 | assert_template 'calendar' |
|
213 | assert_template 'calendar' | |
214 | assert_not_nil assigns(:calendar) |
|
214 | assert_not_nil assigns(:calendar) | |
215 | end |
|
215 | end | |
216 |
|
216 | |||
217 | def test_changes |
|
217 | def test_changes | |
218 | get :changes, :project_id => 1 |
|
218 | get :changes, :project_id => 1 | |
219 | assert_response :success |
|
219 | assert_response :success | |
220 | assert_not_nil assigns(:journals) |
|
220 | assert_not_nil assigns(:journals) | |
221 | assert_equal 'application/atom+xml', @response.content_type |
|
221 | assert_equal 'application/atom+xml', @response.content_type | |
222 | end |
|
222 | end | |
223 |
|
223 | |||
224 | def test_show_by_anonymous |
|
224 | def test_show_by_anonymous | |
225 | get :show, :id => 1 |
|
225 | get :show, :id => 1 | |
226 | assert_response :success |
|
226 | assert_response :success | |
227 | assert_template 'show.rhtml' |
|
227 | assert_template 'show.rhtml' | |
228 | assert_not_nil assigns(:issue) |
|
228 | assert_not_nil assigns(:issue) | |
229 | assert_equal Issue.find(1), assigns(:issue) |
|
229 | assert_equal Issue.find(1), assigns(:issue) | |
230 |
|
230 | |||
231 | # anonymous role is allowed to add a note |
|
231 | # anonymous role is allowed to add a note | |
232 | assert_tag :tag => 'form', |
|
232 | assert_tag :tag => 'form', | |
233 | :descendant => { :tag => 'fieldset', |
|
233 | :descendant => { :tag => 'fieldset', | |
234 | :child => { :tag => 'legend', |
|
234 | :child => { :tag => 'legend', | |
235 | :content => /Notes/ } } |
|
235 | :content => /Notes/ } } | |
236 | end |
|
236 | end | |
237 |
|
237 | |||
238 | def test_show_by_manager |
|
238 | def test_show_by_manager | |
239 | @request.session[:user_id] = 2 |
|
239 | @request.session[:user_id] = 2 | |
240 | get :show, :id => 1 |
|
240 | get :show, :id => 1 | |
241 | assert_response :success |
|
241 | assert_response :success | |
242 |
|
242 | |||
243 | assert_tag :tag => 'form', |
|
243 | assert_tag :tag => 'form', | |
244 | :descendant => { :tag => 'fieldset', |
|
244 | :descendant => { :tag => 'fieldset', | |
245 | :child => { :tag => 'legend', |
|
245 | :child => { :tag => 'legend', | |
246 | :content => /Change properties/ } }, |
|
246 | :content => /Change properties/ } }, | |
247 | :descendant => { :tag => 'fieldset', |
|
247 | :descendant => { :tag => 'fieldset', | |
248 | :child => { :tag => 'legend', |
|
248 | :child => { :tag => 'legend', | |
249 | :content => /Log time/ } }, |
|
249 | :content => /Log time/ } }, | |
250 | :descendant => { :tag => 'fieldset', |
|
250 | :descendant => { :tag => 'fieldset', | |
251 | :child => { :tag => 'legend', |
|
251 | :child => { :tag => 'legend', | |
252 | :content => /Notes/ } } |
|
252 | :content => /Notes/ } } | |
253 | end |
|
253 | end | |
254 |
|
254 | |||
255 | def test_get_new |
|
255 | def test_get_new | |
256 | @request.session[:user_id] = 2 |
|
256 | @request.session[:user_id] = 2 | |
257 | get :new, :project_id => 1, :tracker_id => 1 |
|
257 | get :new, :project_id => 1, :tracker_id => 1 | |
258 | assert_response :success |
|
258 | assert_response :success | |
259 | assert_template 'new' |
|
259 | assert_template 'new' | |
260 |
|
260 | |||
261 | assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]', |
|
261 | assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]', | |
262 | :value => 'Default string' } |
|
262 | :value => 'Default string' } | |
263 | end |
|
263 | end | |
264 |
|
264 | |||
265 | def test_get_new_without_tracker_id |
|
265 | def test_get_new_without_tracker_id | |
266 | @request.session[:user_id] = 2 |
|
266 | @request.session[:user_id] = 2 | |
267 | get :new, :project_id => 1 |
|
267 | get :new, :project_id => 1 | |
268 | assert_response :success |
|
268 | assert_response :success | |
269 | assert_template 'new' |
|
269 | assert_template 'new' | |
270 |
|
270 | |||
271 | issue = assigns(:issue) |
|
271 | issue = assigns(:issue) | |
272 | assert_not_nil issue |
|
272 | assert_not_nil issue | |
273 | assert_equal Project.find(1).trackers.first, issue.tracker |
|
273 | assert_equal Project.find(1).trackers.first, issue.tracker | |
274 | end |
|
274 | end | |
275 |
|
275 | |||
276 | def test_update_new_form |
|
276 | def test_update_new_form | |
277 | @request.session[:user_id] = 2 |
|
277 | @request.session[:user_id] = 2 | |
278 | xhr :post, :new, :project_id => 1, |
|
278 | xhr :post, :new, :project_id => 1, | |
279 | :issue => {:tracker_id => 2, |
|
279 | :issue => {:tracker_id => 2, | |
280 | :subject => 'This is the test_new issue', |
|
280 | :subject => 'This is the test_new issue', | |
281 | :description => 'This is the description', |
|
281 | :description => 'This is the description', | |
282 | :priority_id => 5} |
|
282 | :priority_id => 5} | |
283 | assert_response :success |
|
283 | assert_response :success | |
284 | assert_template 'new' |
|
284 | assert_template 'new' | |
285 | end |
|
285 | end | |
286 |
|
286 | |||
287 | def test_post_new |
|
287 | def test_post_new | |
288 | @request.session[:user_id] = 2 |
|
288 | @request.session[:user_id] = 2 | |
289 | post :new, :project_id => 1, |
|
289 | post :new, :project_id => 1, | |
290 | :issue => {:tracker_id => 3, |
|
290 | :issue => {:tracker_id => 3, | |
291 | :subject => 'This is the test_new issue', |
|
291 | :subject => 'This is the test_new issue', | |
292 | :description => 'This is the description', |
|
292 | :description => 'This is the description', | |
293 | :priority_id => 5, |
|
293 | :priority_id => 5, | |
294 | :estimated_hours => '', |
|
294 | :estimated_hours => '', | |
295 | :custom_field_values => {'2' => 'Value for field 2'}} |
|
295 | :custom_field_values => {'2' => 'Value for field 2'}} | |
296 | assert_redirected_to 'issues/show' |
|
296 | assert_redirected_to 'issues/show' | |
297 |
|
297 | |||
298 | issue = Issue.find_by_subject('This is the test_new issue') |
|
298 | issue = Issue.find_by_subject('This is the test_new issue') | |
299 | assert_not_nil issue |
|
299 | assert_not_nil issue | |
300 | assert_equal 2, issue.author_id |
|
300 | assert_equal 2, issue.author_id | |
301 | assert_equal 3, issue.tracker_id |
|
301 | assert_equal 3, issue.tracker_id | |
302 | assert_nil issue.estimated_hours |
|
302 | assert_nil issue.estimated_hours | |
303 | v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2}) |
|
303 | v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2}) | |
304 | assert_not_nil v |
|
304 | assert_not_nil v | |
305 | assert_equal 'Value for field 2', v.value |
|
305 | assert_equal 'Value for field 2', v.value | |
306 | end |
|
306 | end | |
307 |
|
307 | |||
308 | def test_post_new_without_custom_fields_param |
|
308 | def test_post_new_without_custom_fields_param | |
309 | @request.session[:user_id] = 2 |
|
309 | @request.session[:user_id] = 2 | |
310 | post :new, :project_id => 1, |
|
310 | post :new, :project_id => 1, | |
311 | :issue => {:tracker_id => 1, |
|
311 | :issue => {:tracker_id => 1, | |
312 | :subject => 'This is the test_new issue', |
|
312 | :subject => 'This is the test_new issue', | |
313 | :description => 'This is the description', |
|
313 | :description => 'This is the description', | |
314 | :priority_id => 5} |
|
314 | :priority_id => 5} | |
315 | assert_redirected_to 'issues/show' |
|
315 | assert_redirected_to 'issues/show' | |
316 | end |
|
316 | end | |
317 |
|
317 | |||
318 | def test_post_new_with_required_custom_field_and_without_custom_fields_param |
|
318 | def test_post_new_with_required_custom_field_and_without_custom_fields_param | |
319 | field = IssueCustomField.find_by_name('Database') |
|
319 | field = IssueCustomField.find_by_name('Database') | |
320 | field.update_attribute(:is_required, true) |
|
320 | field.update_attribute(:is_required, true) | |
321 |
|
321 | |||
322 | @request.session[:user_id] = 2 |
|
322 | @request.session[:user_id] = 2 | |
323 | post :new, :project_id => 1, |
|
323 | post :new, :project_id => 1, | |
324 | :issue => {:tracker_id => 1, |
|
324 | :issue => {:tracker_id => 1, | |
325 | :subject => 'This is the test_new issue', |
|
325 | :subject => 'This is the test_new issue', | |
326 | :description => 'This is the description', |
|
326 | :description => 'This is the description', | |
327 | :priority_id => 5} |
|
327 | :priority_id => 5} | |
328 | assert_response :success |
|
328 | assert_response :success | |
329 | assert_template 'new' |
|
329 | assert_template 'new' | |
330 | issue = assigns(:issue) |
|
330 | issue = assigns(:issue) | |
331 | assert_not_nil issue |
|
331 | assert_not_nil issue | |
332 | assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values) |
|
332 | assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values) | |
333 | end |
|
333 | end | |
334 |
|
334 | |||
|
335 | def test_post_new_with_watchers | |||
|
336 | @request.session[:user_id] = 2 | |||
|
337 | ActionMailer::Base.deliveries.clear | |||
|
338 | ||||
|
339 | assert_difference 'Watcher.count', 2 do | |||
|
340 | post :new, :project_id => 1, | |||
|
341 | :issue => {:tracker_id => 1, | |||
|
342 | :subject => 'This is a new issue with watchers', | |||
|
343 | :description => 'This is the description', | |||
|
344 | :priority_id => 5, | |||
|
345 | :watcher_user_ids => ['2', '3']} | |||
|
346 | end | |||
|
347 | assert_redirected_to 'issues/show' | |||
|
348 | ||||
|
349 | issue = Issue.find_by_subject('This is a new issue with watchers') | |||
|
350 | # Watchers added | |||
|
351 | assert_equal [2, 3], issue.watcher_user_ids.sort | |||
|
352 | assert issue.watched_by?(User.find(3)) | |||
|
353 | # Watchers notified | |||
|
354 | mail = ActionMailer::Base.deliveries.last | |||
|
355 | assert_kind_of TMail::Mail, mail | |||
|
356 | assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail) | |||
|
357 | end | |||
|
358 | ||||
335 | def test_post_should_preserve_fields_values_on_validation_failure |
|
359 | def test_post_should_preserve_fields_values_on_validation_failure | |
336 | @request.session[:user_id] = 2 |
|
360 | @request.session[:user_id] = 2 | |
337 | post :new, :project_id => 1, |
|
361 | post :new, :project_id => 1, | |
338 | :issue => {:tracker_id => 1, |
|
362 | :issue => {:tracker_id => 1, | |
339 | :subject => 'This is the test_new issue', |
|
363 | :subject => 'This is the test_new issue', | |
340 | # empty description |
|
364 | # empty description | |
341 | :description => '', |
|
365 | :description => '', | |
342 | :priority_id => 6, |
|
366 | :priority_id => 6, | |
343 | :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}} |
|
367 | :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}} | |
344 | assert_response :success |
|
368 | assert_response :success | |
345 | assert_template 'new' |
|
369 | assert_template 'new' | |
346 |
|
370 | |||
347 | assert_tag :input, :attributes => { :name => 'issue[subject]', |
|
371 | assert_tag :input, :attributes => { :name => 'issue[subject]', | |
348 | :value => 'This is the test_new issue' } |
|
372 | :value => 'This is the test_new issue' } | |
349 | assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, |
|
373 | assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, | |
350 | :child => { :tag => 'option', :attributes => { :selected => 'selected', |
|
374 | :child => { :tag => 'option', :attributes => { :selected => 'selected', | |
351 | :value => '6' }, |
|
375 | :value => '6' }, | |
352 | :content => 'High' } |
|
376 | :content => 'High' } | |
353 | # Custom fields |
|
377 | # Custom fields | |
354 | assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' }, |
|
378 | assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' }, | |
355 | :child => { :tag => 'option', :attributes => { :selected => 'selected', |
|
379 | :child => { :tag => 'option', :attributes => { :selected => 'selected', | |
356 | :value => 'Oracle' }, |
|
380 | :value => 'Oracle' }, | |
357 | :content => 'Oracle' } |
|
381 | :content => 'Oracle' } | |
358 | assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]', |
|
382 | assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]', | |
359 | :value => 'Value for field 2'} |
|
383 | :value => 'Value for field 2'} | |
360 | end |
|
384 | end | |
361 |
|
385 | |||
362 | def test_copy_issue |
|
386 | def test_copy_issue | |
363 | @request.session[:user_id] = 2 |
|
387 | @request.session[:user_id] = 2 | |
364 | get :new, :project_id => 1, :copy_from => 1 |
|
388 | get :new, :project_id => 1, :copy_from => 1 | |
365 | assert_template 'new' |
|
389 | assert_template 'new' | |
366 | assert_not_nil assigns(:issue) |
|
390 | assert_not_nil assigns(:issue) | |
367 | orig = Issue.find(1) |
|
391 | orig = Issue.find(1) | |
368 | assert_equal orig.subject, assigns(:issue).subject |
|
392 | assert_equal orig.subject, assigns(:issue).subject | |
369 | end |
|
393 | end | |
370 |
|
394 | |||
371 | def test_get_edit |
|
395 | def test_get_edit | |
372 | @request.session[:user_id] = 2 |
|
396 | @request.session[:user_id] = 2 | |
373 | get :edit, :id => 1 |
|
397 | get :edit, :id => 1 | |
374 | assert_response :success |
|
398 | assert_response :success | |
375 | assert_template 'edit' |
|
399 | assert_template 'edit' | |
376 | assert_not_nil assigns(:issue) |
|
400 | assert_not_nil assigns(:issue) | |
377 | assert_equal Issue.find(1), assigns(:issue) |
|
401 | assert_equal Issue.find(1), assigns(:issue) | |
378 | end |
|
402 | end | |
379 |
|
403 | |||
380 | def test_get_edit_with_params |
|
404 | def test_get_edit_with_params | |
381 | @request.session[:user_id] = 2 |
|
405 | @request.session[:user_id] = 2 | |
382 | get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 } |
|
406 | get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 } | |
383 | assert_response :success |
|
407 | assert_response :success | |
384 | assert_template 'edit' |
|
408 | assert_template 'edit' | |
385 |
|
409 | |||
386 | issue = assigns(:issue) |
|
410 | issue = assigns(:issue) | |
387 | assert_not_nil issue |
|
411 | assert_not_nil issue | |
388 |
|
412 | |||
389 | assert_equal 5, issue.status_id |
|
413 | assert_equal 5, issue.status_id | |
390 | assert_tag :select, :attributes => { :name => 'issue[status_id]' }, |
|
414 | assert_tag :select, :attributes => { :name => 'issue[status_id]' }, | |
391 | :child => { :tag => 'option', |
|
415 | :child => { :tag => 'option', | |
392 | :content => 'Closed', |
|
416 | :content => 'Closed', | |
393 | :attributes => { :selected => 'selected' } } |
|
417 | :attributes => { :selected => 'selected' } } | |
394 |
|
418 | |||
395 | assert_equal 7, issue.priority_id |
|
419 | assert_equal 7, issue.priority_id | |
396 | assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, |
|
420 | assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, | |
397 | :child => { :tag => 'option', |
|
421 | :child => { :tag => 'option', | |
398 | :content => 'Urgent', |
|
422 | :content => 'Urgent', | |
399 | :attributes => { :selected => 'selected' } } |
|
423 | :attributes => { :selected => 'selected' } } | |
400 | end |
|
424 | end | |
401 |
|
425 | |||
402 | def test_reply_to_issue |
|
426 | def test_reply_to_issue | |
403 | @request.session[:user_id] = 2 |
|
427 | @request.session[:user_id] = 2 | |
404 | get :reply, :id => 1 |
|
428 | get :reply, :id => 1 | |
405 | assert_response :success |
|
429 | assert_response :success | |
406 | assert_select_rjs :show, "update" |
|
430 | assert_select_rjs :show, "update" | |
407 | end |
|
431 | end | |
408 |
|
432 | |||
409 | def test_reply_to_note |
|
433 | def test_reply_to_note | |
410 | @request.session[:user_id] = 2 |
|
434 | @request.session[:user_id] = 2 | |
411 | get :reply, :id => 1, :journal_id => 2 |
|
435 | get :reply, :id => 1, :journal_id => 2 | |
412 | assert_response :success |
|
436 | assert_response :success | |
413 | assert_select_rjs :show, "update" |
|
437 | assert_select_rjs :show, "update" | |
414 | end |
|
438 | end | |
415 |
|
439 | |||
416 | def test_post_edit_without_custom_fields_param |
|
440 | def test_post_edit_without_custom_fields_param | |
417 | @request.session[:user_id] = 2 |
|
441 | @request.session[:user_id] = 2 | |
418 | ActionMailer::Base.deliveries.clear |
|
442 | ActionMailer::Base.deliveries.clear | |
419 |
|
443 | |||
420 | issue = Issue.find(1) |
|
444 | issue = Issue.find(1) | |
421 | assert_equal '125', issue.custom_value_for(2).value |
|
445 | assert_equal '125', issue.custom_value_for(2).value | |
422 | old_subject = issue.subject |
|
446 | old_subject = issue.subject | |
423 | new_subject = 'Subject modified by IssuesControllerTest#test_post_edit' |
|
447 | new_subject = 'Subject modified by IssuesControllerTest#test_post_edit' | |
424 |
|
448 | |||
425 | assert_difference('Journal.count') do |
|
449 | assert_difference('Journal.count') do | |
426 | assert_difference('JournalDetail.count', 2) do |
|
450 | assert_difference('JournalDetail.count', 2) do | |
427 | post :edit, :id => 1, :issue => {:subject => new_subject, |
|
451 | post :edit, :id => 1, :issue => {:subject => new_subject, | |
428 | :priority_id => '6', |
|
452 | :priority_id => '6', | |
429 | :category_id => '1' # no change |
|
453 | :category_id => '1' # no change | |
430 | } |
|
454 | } | |
431 | end |
|
455 | end | |
432 | end |
|
456 | end | |
433 | assert_redirected_to 'issues/show/1' |
|
457 | assert_redirected_to 'issues/show/1' | |
434 | issue.reload |
|
458 | issue.reload | |
435 | assert_equal new_subject, issue.subject |
|
459 | assert_equal new_subject, issue.subject | |
436 | # Make sure custom fields were not cleared |
|
460 | # Make sure custom fields were not cleared | |
437 | assert_equal '125', issue.custom_value_for(2).value |
|
461 | assert_equal '125', issue.custom_value_for(2).value | |
438 |
|
462 | |||
439 | mail = ActionMailer::Base.deliveries.last |
|
463 | mail = ActionMailer::Base.deliveries.last | |
440 | assert_kind_of TMail::Mail, mail |
|
464 | assert_kind_of TMail::Mail, mail | |
441 | assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]") |
|
465 | assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]") | |
442 | assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}") |
|
466 | assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}") | |
443 | end |
|
467 | end | |
444 |
|
468 | |||
445 | def test_post_edit_with_custom_field_change |
|
469 | def test_post_edit_with_custom_field_change | |
446 | @request.session[:user_id] = 2 |
|
470 | @request.session[:user_id] = 2 | |
447 | issue = Issue.find(1) |
|
471 | issue = Issue.find(1) | |
448 | assert_equal '125', issue.custom_value_for(2).value |
|
472 | assert_equal '125', issue.custom_value_for(2).value | |
449 |
|
473 | |||
450 | assert_difference('Journal.count') do |
|
474 | assert_difference('Journal.count') do | |
451 | assert_difference('JournalDetail.count', 3) do |
|
475 | assert_difference('JournalDetail.count', 3) do | |
452 | post :edit, :id => 1, :issue => {:subject => 'Custom field change', |
|
476 | post :edit, :id => 1, :issue => {:subject => 'Custom field change', | |
453 | :priority_id => '6', |
|
477 | :priority_id => '6', | |
454 | :category_id => '1', # no change |
|
478 | :category_id => '1', # no change | |
455 | :custom_field_values => { '2' => 'New custom value' } |
|
479 | :custom_field_values => { '2' => 'New custom value' } | |
456 | } |
|
480 | } | |
457 | end |
|
481 | end | |
458 | end |
|
482 | end | |
459 | assert_redirected_to 'issues/show/1' |
|
483 | assert_redirected_to 'issues/show/1' | |
460 | issue.reload |
|
484 | issue.reload | |
461 | assert_equal 'New custom value', issue.custom_value_for(2).value |
|
485 | assert_equal 'New custom value', issue.custom_value_for(2).value | |
462 |
|
486 | |||
463 | mail = ActionMailer::Base.deliveries.last |
|
487 | mail = ActionMailer::Base.deliveries.last | |
464 | assert_kind_of TMail::Mail, mail |
|
488 | assert_kind_of TMail::Mail, mail | |
465 | assert mail.body.include?("Searchable field changed from 125 to New custom value") |
|
489 | assert mail.body.include?("Searchable field changed from 125 to New custom value") | |
466 | end |
|
490 | end | |
467 |
|
491 | |||
468 | def test_post_edit_with_status_and_assignee_change |
|
492 | def test_post_edit_with_status_and_assignee_change | |
469 | issue = Issue.find(1) |
|
493 | issue = Issue.find(1) | |
470 | assert_equal 1, issue.status_id |
|
494 | assert_equal 1, issue.status_id | |
471 | @request.session[:user_id] = 2 |
|
495 | @request.session[:user_id] = 2 | |
472 | assert_difference('TimeEntry.count', 0) do |
|
496 | assert_difference('TimeEntry.count', 0) do | |
473 | post :edit, |
|
497 | post :edit, | |
474 | :id => 1, |
|
498 | :id => 1, | |
475 | :issue => { :status_id => 2, :assigned_to_id => 3 }, |
|
499 | :issue => { :status_id => 2, :assigned_to_id => 3 }, | |
476 | :notes => 'Assigned to dlopper', |
|
500 | :notes => 'Assigned to dlopper', | |
477 | :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first } |
|
501 | :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first } | |
478 | end |
|
502 | end | |
479 | assert_redirected_to 'issues/show/1' |
|
503 | assert_redirected_to 'issues/show/1' | |
480 | issue.reload |
|
504 | issue.reload | |
481 | assert_equal 2, issue.status_id |
|
505 | assert_equal 2, issue.status_id | |
482 | j = issue.journals.find(:first, :order => 'id DESC') |
|
506 | j = issue.journals.find(:first, :order => 'id DESC') | |
483 | assert_equal 'Assigned to dlopper', j.notes |
|
507 | assert_equal 'Assigned to dlopper', j.notes | |
484 | assert_equal 2, j.details.size |
|
508 | assert_equal 2, j.details.size | |
485 |
|
509 | |||
486 | mail = ActionMailer::Base.deliveries.last |
|
510 | mail = ActionMailer::Base.deliveries.last | |
487 | assert mail.body.include?("Status changed from New to Assigned") |
|
511 | assert mail.body.include?("Status changed from New to Assigned") | |
488 | end |
|
512 | end | |
489 |
|
513 | |||
490 | def test_post_edit_with_note_only |
|
514 | def test_post_edit_with_note_only | |
491 | notes = 'Note added by IssuesControllerTest#test_update_with_note_only' |
|
515 | notes = 'Note added by IssuesControllerTest#test_update_with_note_only' | |
492 | # anonymous user |
|
516 | # anonymous user | |
493 | post :edit, |
|
517 | post :edit, | |
494 | :id => 1, |
|
518 | :id => 1, | |
495 | :notes => notes |
|
519 | :notes => notes | |
496 | assert_redirected_to 'issues/show/1' |
|
520 | assert_redirected_to 'issues/show/1' | |
497 | j = Issue.find(1).journals.find(:first, :order => 'id DESC') |
|
521 | j = Issue.find(1).journals.find(:first, :order => 'id DESC') | |
498 | assert_equal notes, j.notes |
|
522 | assert_equal notes, j.notes | |
499 | assert_equal 0, j.details.size |
|
523 | assert_equal 0, j.details.size | |
500 | assert_equal User.anonymous, j.user |
|
524 | assert_equal User.anonymous, j.user | |
501 |
|
525 | |||
502 | mail = ActionMailer::Base.deliveries.last |
|
526 | mail = ActionMailer::Base.deliveries.last | |
503 | assert mail.body.include?(notes) |
|
527 | assert mail.body.include?(notes) | |
504 | end |
|
528 | end | |
505 |
|
529 | |||
506 | def test_post_edit_with_note_and_spent_time |
|
530 | def test_post_edit_with_note_and_spent_time | |
507 | @request.session[:user_id] = 2 |
|
531 | @request.session[:user_id] = 2 | |
508 | spent_hours_before = Issue.find(1).spent_hours |
|
532 | spent_hours_before = Issue.find(1).spent_hours | |
509 | assert_difference('TimeEntry.count') do |
|
533 | assert_difference('TimeEntry.count') do | |
510 | post :edit, |
|
534 | post :edit, | |
511 | :id => 1, |
|
535 | :id => 1, | |
512 | :notes => '2.5 hours added', |
|
536 | :notes => '2.5 hours added', | |
513 | :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first } |
|
537 | :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first } | |
514 | end |
|
538 | end | |
515 | assert_redirected_to 'issues/show/1' |
|
539 | assert_redirected_to 'issues/show/1' | |
516 |
|
540 | |||
517 | issue = Issue.find(1) |
|
541 | issue = Issue.find(1) | |
518 |
|
542 | |||
519 | j = issue.journals.find(:first, :order => 'id DESC') |
|
543 | j = issue.journals.find(:first, :order => 'id DESC') | |
520 | assert_equal '2.5 hours added', j.notes |
|
544 | assert_equal '2.5 hours added', j.notes | |
521 | assert_equal 0, j.details.size |
|
545 | assert_equal 0, j.details.size | |
522 |
|
546 | |||
523 | t = issue.time_entries.find(:first, :order => 'id DESC') |
|
547 | t = issue.time_entries.find(:first, :order => 'id DESC') | |
524 | assert_not_nil t |
|
548 | assert_not_nil t | |
525 | assert_equal 2.5, t.hours |
|
549 | assert_equal 2.5, t.hours | |
526 | assert_equal spent_hours_before + 2.5, issue.spent_hours |
|
550 | assert_equal spent_hours_before + 2.5, issue.spent_hours | |
527 | end |
|
551 | end | |
528 |
|
552 | |||
529 | def test_post_edit_with_attachment_only |
|
553 | def test_post_edit_with_attachment_only | |
530 | set_tmp_attachments_directory |
|
554 | set_tmp_attachments_directory | |
531 |
|
555 | |||
532 | # Delete all fixtured journals, a race condition can occur causing the wrong |
|
556 | # Delete all fixtured journals, a race condition can occur causing the wrong | |
533 | # journal to get fetched in the next find. |
|
557 | # journal to get fetched in the next find. | |
534 | Journal.delete_all |
|
558 | Journal.delete_all | |
535 |
|
559 | |||
536 | # anonymous user |
|
560 | # anonymous user | |
537 | post :edit, |
|
561 | post :edit, | |
538 | :id => 1, |
|
562 | :id => 1, | |
539 | :notes => '', |
|
563 | :notes => '', | |
540 | :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}} |
|
564 | :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}} | |
541 | assert_redirected_to 'issues/show/1' |
|
565 | assert_redirected_to 'issues/show/1' | |
542 | j = Issue.find(1).journals.find(:first, :order => 'id DESC') |
|
566 | j = Issue.find(1).journals.find(:first, :order => 'id DESC') | |
543 | assert j.notes.blank? |
|
567 | assert j.notes.blank? | |
544 | assert_equal 1, j.details.size |
|
568 | assert_equal 1, j.details.size | |
545 | assert_equal 'testfile.txt', j.details.first.value |
|
569 | assert_equal 'testfile.txt', j.details.first.value | |
546 | assert_equal User.anonymous, j.user |
|
570 | assert_equal User.anonymous, j.user | |
547 |
|
571 | |||
548 | mail = ActionMailer::Base.deliveries.last |
|
572 | mail = ActionMailer::Base.deliveries.last | |
549 | assert mail.body.include?('testfile.txt') |
|
573 | assert mail.body.include?('testfile.txt') | |
550 | end |
|
574 | end | |
551 |
|
575 | |||
552 | def test_post_edit_with_no_change |
|
576 | def test_post_edit_with_no_change | |
553 | issue = Issue.find(1) |
|
577 | issue = Issue.find(1) | |
554 | issue.journals.clear |
|
578 | issue.journals.clear | |
555 | ActionMailer::Base.deliveries.clear |
|
579 | ActionMailer::Base.deliveries.clear | |
556 |
|
580 | |||
557 | post :edit, |
|
581 | post :edit, | |
558 | :id => 1, |
|
582 | :id => 1, | |
559 | :notes => '' |
|
583 | :notes => '' | |
560 | assert_redirected_to 'issues/show/1' |
|
584 | assert_redirected_to 'issues/show/1' | |
561 |
|
585 | |||
562 | issue.reload |
|
586 | issue.reload | |
563 | assert issue.journals.empty? |
|
587 | assert issue.journals.empty? | |
564 | # No email should be sent |
|
588 | # No email should be sent | |
565 | assert ActionMailer::Base.deliveries.empty? |
|
589 | assert ActionMailer::Base.deliveries.empty? | |
566 | end |
|
590 | end | |
567 |
|
591 | |||
568 | def test_bulk_edit |
|
592 | def test_bulk_edit | |
569 | @request.session[:user_id] = 2 |
|
593 | @request.session[:user_id] = 2 | |
570 | # update issues priority |
|
594 | # update issues priority | |
571 | post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => '' |
|
595 | post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => '' | |
572 | assert_response 302 |
|
596 | assert_response 302 | |
573 | # check that the issues were updated |
|
597 | # check that the issues were updated | |
574 | assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id} |
|
598 | assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id} | |
575 | assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes |
|
599 | assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes | |
576 | end |
|
600 | end | |
577 |
|
601 | |||
578 | def test_bulk_unassign |
|
602 | def test_bulk_unassign | |
579 | assert_not_nil Issue.find(2).assigned_to |
|
603 | assert_not_nil Issue.find(2).assigned_to | |
580 | @request.session[:user_id] = 2 |
|
604 | @request.session[:user_id] = 2 | |
581 | # unassign issues |
|
605 | # unassign issues | |
582 | post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none' |
|
606 | post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none' | |
583 | assert_response 302 |
|
607 | assert_response 302 | |
584 | # check that the issues were updated |
|
608 | # check that the issues were updated | |
585 | assert_nil Issue.find(2).assigned_to |
|
609 | assert_nil Issue.find(2).assigned_to | |
586 | end |
|
610 | end | |
587 |
|
611 | |||
588 | def test_move_one_issue_to_another_project |
|
612 | def test_move_one_issue_to_another_project | |
589 | @request.session[:user_id] = 1 |
|
613 | @request.session[:user_id] = 1 | |
590 | post :move, :id => 1, :new_project_id => 2 |
|
614 | post :move, :id => 1, :new_project_id => 2 | |
591 | assert_redirected_to 'projects/ecookbook/issues' |
|
615 | assert_redirected_to 'projects/ecookbook/issues' | |
592 | assert_equal 2, Issue.find(1).project_id |
|
616 | assert_equal 2, Issue.find(1).project_id | |
593 | end |
|
617 | end | |
594 |
|
618 | |||
595 | def test_bulk_move_to_another_project |
|
619 | def test_bulk_move_to_another_project | |
596 | @request.session[:user_id] = 1 |
|
620 | @request.session[:user_id] = 1 | |
597 | post :move, :ids => [1, 2], :new_project_id => 2 |
|
621 | post :move, :ids => [1, 2], :new_project_id => 2 | |
598 | assert_redirected_to 'projects/ecookbook/issues' |
|
622 | assert_redirected_to 'projects/ecookbook/issues' | |
599 | # Issues moved to project 2 |
|
623 | # Issues moved to project 2 | |
600 | assert_equal 2, Issue.find(1).project_id |
|
624 | assert_equal 2, Issue.find(1).project_id | |
601 | assert_equal 2, Issue.find(2).project_id |
|
625 | assert_equal 2, Issue.find(2).project_id | |
602 | # No tracker change |
|
626 | # No tracker change | |
603 | assert_equal 1, Issue.find(1).tracker_id |
|
627 | assert_equal 1, Issue.find(1).tracker_id | |
604 | assert_equal 2, Issue.find(2).tracker_id |
|
628 | assert_equal 2, Issue.find(2).tracker_id | |
605 | end |
|
629 | end | |
606 |
|
630 | |||
607 | def test_bulk_move_to_another_tracker |
|
631 | def test_bulk_move_to_another_tracker | |
608 | @request.session[:user_id] = 1 |
|
632 | @request.session[:user_id] = 1 | |
609 | post :move, :ids => [1, 2], :new_tracker_id => 2 |
|
633 | post :move, :ids => [1, 2], :new_tracker_id => 2 | |
610 | assert_redirected_to 'projects/ecookbook/issues' |
|
634 | assert_redirected_to 'projects/ecookbook/issues' | |
611 | assert_equal 2, Issue.find(1).tracker_id |
|
635 | assert_equal 2, Issue.find(1).tracker_id | |
612 | assert_equal 2, Issue.find(2).tracker_id |
|
636 | assert_equal 2, Issue.find(2).tracker_id | |
613 | end |
|
637 | end | |
614 |
|
638 | |||
615 | def test_context_menu_one_issue |
|
639 | def test_context_menu_one_issue | |
616 | @request.session[:user_id] = 2 |
|
640 | @request.session[:user_id] = 2 | |
617 | get :context_menu, :ids => [1] |
|
641 | get :context_menu, :ids => [1] | |
618 | assert_response :success |
|
642 | assert_response :success | |
619 | assert_template 'context_menu' |
|
643 | assert_template 'context_menu' | |
620 | assert_tag :tag => 'a', :content => 'Edit', |
|
644 | assert_tag :tag => 'a', :content => 'Edit', | |
621 | :attributes => { :href => '/issues/edit/1', |
|
645 | :attributes => { :href => '/issues/edit/1', | |
622 | :class => 'icon-edit' } |
|
646 | :class => 'icon-edit' } | |
623 | assert_tag :tag => 'a', :content => 'Closed', |
|
647 | assert_tag :tag => 'a', :content => 'Closed', | |
624 | :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5', |
|
648 | :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5', | |
625 | :class => '' } |
|
649 | :class => '' } | |
626 | assert_tag :tag => 'a', :content => 'Immediate', |
|
650 | assert_tag :tag => 'a', :content => 'Immediate', | |
627 | :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&priority_id=8', |
|
651 | :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&priority_id=8', | |
628 | :class => '' } |
|
652 | :class => '' } | |
629 | assert_tag :tag => 'a', :content => 'Dave Lopper', |
|
653 | assert_tag :tag => 'a', :content => 'Dave Lopper', | |
630 | :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&ids%5B%5D=1', |
|
654 | :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&ids%5B%5D=1', | |
631 | :class => '' } |
|
655 | :class => '' } | |
632 | assert_tag :tag => 'a', :content => 'Copy', |
|
656 | assert_tag :tag => 'a', :content => 'Copy', | |
633 | :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1', |
|
657 | :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1', | |
634 | :class => 'icon-copy' } |
|
658 | :class => 'icon-copy' } | |
635 | assert_tag :tag => 'a', :content => 'Move', |
|
659 | assert_tag :tag => 'a', :content => 'Move', | |
636 | :attributes => { :href => '/issues/move?ids%5B%5D=1', |
|
660 | :attributes => { :href => '/issues/move?ids%5B%5D=1', | |
637 | :class => 'icon-move' } |
|
661 | :class => 'icon-move' } | |
638 | assert_tag :tag => 'a', :content => 'Delete', |
|
662 | assert_tag :tag => 'a', :content => 'Delete', | |
639 | :attributes => { :href => '/issues/destroy?ids%5B%5D=1', |
|
663 | :attributes => { :href => '/issues/destroy?ids%5B%5D=1', | |
640 | :class => 'icon-del' } |
|
664 | :class => 'icon-del' } | |
641 | end |
|
665 | end | |
642 |
|
666 | |||
643 | def test_context_menu_one_issue_by_anonymous |
|
667 | def test_context_menu_one_issue_by_anonymous | |
644 | get :context_menu, :ids => [1] |
|
668 | get :context_menu, :ids => [1] | |
645 | assert_response :success |
|
669 | assert_response :success | |
646 | assert_template 'context_menu' |
|
670 | assert_template 'context_menu' | |
647 | assert_tag :tag => 'a', :content => 'Delete', |
|
671 | assert_tag :tag => 'a', :content => 'Delete', | |
648 | :attributes => { :href => '#', |
|
672 | :attributes => { :href => '#', | |
649 | :class => 'icon-del disabled' } |
|
673 | :class => 'icon-del disabled' } | |
650 | end |
|
674 | end | |
651 |
|
675 | |||
652 | def test_context_menu_multiple_issues_of_same_project |
|
676 | def test_context_menu_multiple_issues_of_same_project | |
653 | @request.session[:user_id] = 2 |
|
677 | @request.session[:user_id] = 2 | |
654 | get :context_menu, :ids => [1, 2] |
|
678 | get :context_menu, :ids => [1, 2] | |
655 | assert_response :success |
|
679 | assert_response :success | |
656 | assert_template 'context_menu' |
|
680 | assert_template 'context_menu' | |
657 | assert_tag :tag => 'a', :content => 'Edit', |
|
681 | assert_tag :tag => 'a', :content => 'Edit', | |
658 | :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2', |
|
682 | :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2', | |
659 | :class => 'icon-edit' } |
|
683 | :class => 'icon-edit' } | |
660 | assert_tag :tag => 'a', :content => 'Immediate', |
|
684 | assert_tag :tag => 'a', :content => 'Immediate', | |
661 | :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2&priority_id=8', |
|
685 | :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2&priority_id=8', | |
662 | :class => '' } |
|
686 | :class => '' } | |
663 | assert_tag :tag => 'a', :content => 'Dave Lopper', |
|
687 | assert_tag :tag => 'a', :content => 'Dave Lopper', | |
664 | :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&ids%5B%5D=1&ids%5B%5D=2', |
|
688 | :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&ids%5B%5D=1&ids%5B%5D=2', | |
665 | :class => '' } |
|
689 | :class => '' } | |
666 | assert_tag :tag => 'a', :content => 'Move', |
|
690 | assert_tag :tag => 'a', :content => 'Move', | |
667 | :attributes => { :href => '/issues/move?ids%5B%5D=1&ids%5B%5D=2', |
|
691 | :attributes => { :href => '/issues/move?ids%5B%5D=1&ids%5B%5D=2', | |
668 | :class => 'icon-move' } |
|
692 | :class => 'icon-move' } | |
669 | assert_tag :tag => 'a', :content => 'Delete', |
|
693 | assert_tag :tag => 'a', :content => 'Delete', | |
670 | :attributes => { :href => '/issues/destroy?ids%5B%5D=1&ids%5B%5D=2', |
|
694 | :attributes => { :href => '/issues/destroy?ids%5B%5D=1&ids%5B%5D=2', | |
671 | :class => 'icon-del' } |
|
695 | :class => 'icon-del' } | |
672 | end |
|
696 | end | |
673 |
|
697 | |||
674 | def test_context_menu_multiple_issues_of_different_project |
|
698 | def test_context_menu_multiple_issues_of_different_project | |
675 | @request.session[:user_id] = 2 |
|
699 | @request.session[:user_id] = 2 | |
676 | get :context_menu, :ids => [1, 2, 4] |
|
700 | get :context_menu, :ids => [1, 2, 4] | |
677 | assert_response :success |
|
701 | assert_response :success | |
678 | assert_template 'context_menu' |
|
702 | assert_template 'context_menu' | |
679 | assert_tag :tag => 'a', :content => 'Delete', |
|
703 | assert_tag :tag => 'a', :content => 'Delete', | |
680 | :attributes => { :href => '#', |
|
704 | :attributes => { :href => '#', | |
681 | :class => 'icon-del disabled' } |
|
705 | :class => 'icon-del disabled' } | |
682 | end |
|
706 | end | |
683 |
|
707 | |||
684 | def test_destroy_issue_with_no_time_entries |
|
708 | def test_destroy_issue_with_no_time_entries | |
685 | assert_nil TimeEntry.find_by_issue_id(2) |
|
709 | assert_nil TimeEntry.find_by_issue_id(2) | |
686 | @request.session[:user_id] = 2 |
|
710 | @request.session[:user_id] = 2 | |
687 | post :destroy, :id => 2 |
|
711 | post :destroy, :id => 2 | |
688 | assert_redirected_to 'projects/ecookbook/issues' |
|
712 | assert_redirected_to 'projects/ecookbook/issues' | |
689 | assert_nil Issue.find_by_id(2) |
|
713 | assert_nil Issue.find_by_id(2) | |
690 | end |
|
714 | end | |
691 |
|
715 | |||
692 | def test_destroy_issues_with_time_entries |
|
716 | def test_destroy_issues_with_time_entries | |
693 | @request.session[:user_id] = 2 |
|
717 | @request.session[:user_id] = 2 | |
694 | post :destroy, :ids => [1, 3] |
|
718 | post :destroy, :ids => [1, 3] | |
695 | assert_response :success |
|
719 | assert_response :success | |
696 | assert_template 'destroy' |
|
720 | assert_template 'destroy' | |
697 | assert_not_nil assigns(:hours) |
|
721 | assert_not_nil assigns(:hours) | |
698 | assert Issue.find_by_id(1) && Issue.find_by_id(3) |
|
722 | assert Issue.find_by_id(1) && Issue.find_by_id(3) | |
699 | end |
|
723 | end | |
700 |
|
724 | |||
701 | def test_destroy_issues_and_destroy_time_entries |
|
725 | def test_destroy_issues_and_destroy_time_entries | |
702 | @request.session[:user_id] = 2 |
|
726 | @request.session[:user_id] = 2 | |
703 | post :destroy, :ids => [1, 3], :todo => 'destroy' |
|
727 | post :destroy, :ids => [1, 3], :todo => 'destroy' | |
704 | assert_redirected_to 'projects/ecookbook/issues' |
|
728 | assert_redirected_to 'projects/ecookbook/issues' | |
705 | assert !(Issue.find_by_id(1) || Issue.find_by_id(3)) |
|
729 | assert !(Issue.find_by_id(1) || Issue.find_by_id(3)) | |
706 | assert_nil TimeEntry.find_by_id([1, 2]) |
|
730 | assert_nil TimeEntry.find_by_id([1, 2]) | |
707 | end |
|
731 | end | |
708 |
|
732 | |||
709 | def test_destroy_issues_and_assign_time_entries_to_project |
|
733 | def test_destroy_issues_and_assign_time_entries_to_project | |
710 | @request.session[:user_id] = 2 |
|
734 | @request.session[:user_id] = 2 | |
711 | post :destroy, :ids => [1, 3], :todo => 'nullify' |
|
735 | post :destroy, :ids => [1, 3], :todo => 'nullify' | |
712 | assert_redirected_to 'projects/ecookbook/issues' |
|
736 | assert_redirected_to 'projects/ecookbook/issues' | |
713 | assert !(Issue.find_by_id(1) || Issue.find_by_id(3)) |
|
737 | assert !(Issue.find_by_id(1) || Issue.find_by_id(3)) | |
714 | assert_nil TimeEntry.find(1).issue_id |
|
738 | assert_nil TimeEntry.find(1).issue_id | |
715 | assert_nil TimeEntry.find(2).issue_id |
|
739 | assert_nil TimeEntry.find(2).issue_id | |
716 | end |
|
740 | end | |
717 |
|
741 | |||
718 | def test_destroy_issues_and_reassign_time_entries_to_another_issue |
|
742 | def test_destroy_issues_and_reassign_time_entries_to_another_issue | |
719 | @request.session[:user_id] = 2 |
|
743 | @request.session[:user_id] = 2 | |
720 | post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2 |
|
744 | post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2 | |
721 | assert_redirected_to 'projects/ecookbook/issues' |
|
745 | assert_redirected_to 'projects/ecookbook/issues' | |
722 | assert !(Issue.find_by_id(1) || Issue.find_by_id(3)) |
|
746 | assert !(Issue.find_by_id(1) || Issue.find_by_id(3)) | |
723 | assert_equal 2, TimeEntry.find(1).issue_id |
|
747 | assert_equal 2, TimeEntry.find(1).issue_id | |
724 | assert_equal 2, TimeEntry.find(2).issue_id |
|
748 | assert_equal 2, TimeEntry.find(2).issue_id | |
725 | end |
|
749 | end | |
726 |
|
750 | |||
727 | def test_destroy_attachment |
|
751 | def test_destroy_attachment | |
728 | issue = Issue.find(3) |
|
752 | issue = Issue.find(3) | |
729 | a = issue.attachments.size |
|
753 | a = issue.attachments.size | |
730 | @request.session[:user_id] = 2 |
|
754 | @request.session[:user_id] = 2 | |
731 | post :destroy_attachment, :id => 3, :attachment_id => 1 |
|
755 | post :destroy_attachment, :id => 3, :attachment_id => 1 | |
732 | assert_redirected_to 'issues/show/3' |
|
756 | assert_redirected_to 'issues/show/3' | |
733 | assert_nil Attachment.find_by_id(1) |
|
757 | assert_nil Attachment.find_by_id(1) | |
734 | issue.reload |
|
758 | issue.reload | |
735 | assert_equal((a-1), issue.attachments.size) |
|
759 | assert_equal((a-1), issue.attachments.size) | |
736 | j = issue.journals.find(:first, :order => 'created_on DESC') |
|
760 | j = issue.journals.find(:first, :order => 'created_on DESC') | |
737 | assert_equal 'attachment', j.details.first.property |
|
761 | assert_equal 'attachment', j.details.first.property | |
738 | end |
|
762 | end | |
739 | end |
|
763 | end |
@@ -1,62 +1,68 | |||||
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 | require File.dirname(__FILE__) + '/../test_helper' |
|
18 | require File.dirname(__FILE__) + '/../test_helper' | |
19 |
|
19 | |||
20 | class RepositoryDarcsTest < Test::Unit::TestCase |
|
20 | class RepositoryDarcsTest < Test::Unit::TestCase | |
21 | fixtures :projects |
|
21 | fixtures :projects | |
22 |
|
22 | |||
23 | # No '..' in the repository path |
|
23 | # No '..' in the repository path | |
24 | REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository' |
|
24 | REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository' | |
25 |
|
25 | |||
26 | def setup |
|
26 | def setup | |
27 | @project = Project.find(1) |
|
27 | @project = Project.find(1) | |
28 | assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH) |
|
28 | assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH) | |
29 | end |
|
29 | end | |
30 |
|
30 | |||
31 | if File.directory?(REPOSITORY_PATH) |
|
31 | if File.directory?(REPOSITORY_PATH) | |
32 | def test_fetch_changesets_from_scratch |
|
32 | def test_fetch_changesets_from_scratch | |
33 | @repository.fetch_changesets |
|
33 | @repository.fetch_changesets | |
34 | @repository.reload |
|
34 | @repository.reload | |
35 |
|
35 | |||
36 | assert_equal 6, @repository.changesets.count |
|
36 | assert_equal 6, @repository.changesets.count | |
37 | assert_equal 13, @repository.changes.count |
|
37 | assert_equal 13, @repository.changes.count | |
38 | assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments |
|
38 | assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments | |
39 | end |
|
39 | end | |
40 |
|
40 | |||
41 | def test_fetch_changesets_incremental |
|
41 | def test_fetch_changesets_incremental | |
42 | @repository.fetch_changesets |
|
42 | @repository.fetch_changesets | |
43 | # Remove changesets with revision > 3 |
|
43 | # Remove changesets with revision > 3 | |
44 | @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3} |
|
44 | @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3} | |
45 | @repository.reload |
|
45 | @repository.reload | |
46 | assert_equal 3, @repository.changesets.count |
|
46 | assert_equal 3, @repository.changesets.count | |
47 |
|
47 | |||
48 | @repository.fetch_changesets |
|
48 | @repository.fetch_changesets | |
49 | assert_equal 6, @repository.changesets.count |
|
49 | assert_equal 6, @repository.changesets.count | |
50 | end |
|
50 | end | |
51 |
|
51 | |||
|
52 | def test_deleted_files_should_not_be_listed | |||
|
53 | entries = @repository.entries('sources') | |||
|
54 | assert entries.detect {|e| e.name == 'watchers_controller.rb'} | |||
|
55 | assert_nil entries.detect {|e| e.name == 'welcome_controller.rb'} | |||
|
56 | end | |||
|
57 | ||||
52 | def test_cat |
|
58 | def test_cat | |
53 | @repository.fetch_changesets |
|
59 | @repository.fetch_changesets | |
54 | cat = @repository.cat("sources/welcome_controller.rb", 2) |
|
60 | cat = @repository.cat("sources/welcome_controller.rb", 2) | |
55 | assert_not_nil cat |
|
61 | assert_not_nil cat | |
56 | assert cat.include?('class WelcomeController < ApplicationController') |
|
62 | assert cat.include?('class WelcomeController < ApplicationController') | |
57 | end |
|
63 | end | |
58 | else |
|
64 | else | |
59 | puts "Darcs test repository NOT FOUND. Skipping unit tests !!!" |
|
65 | puts "Darcs test repository NOT FOUND. Skipping unit tests !!!" | |
60 | def test_fake; assert true end |
|
66 | def test_fake; assert true end | |
61 | end |
|
67 | end | |
62 | end |
|
68 | end |
@@ -1,69 +1,71 | |||||
1 | # ActsAsWatchable |
|
1 | # ActsAsWatchable | |
2 | module Redmine |
|
2 | module Redmine | |
3 | module Acts |
|
3 | module Acts | |
4 | module Watchable |
|
4 | module Watchable | |
5 | def self.included(base) |
|
5 | def self.included(base) | |
6 | base.extend ClassMethods |
|
6 | base.extend ClassMethods | |
7 | end |
|
7 | end | |
8 |
|
8 | |||
9 | module ClassMethods |
|
9 | module ClassMethods | |
10 | def acts_as_watchable(options = {}) |
|
10 | def acts_as_watchable(options = {}) | |
11 | return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods) |
|
11 | return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods) | |
12 | send :include, Redmine::Acts::Watchable::InstanceMethods |
|
12 | send :include, Redmine::Acts::Watchable::InstanceMethods | |
13 |
|
13 | |||
14 | class_eval do |
|
14 | class_eval do | |
15 | has_many :watchers, :as => :watchable, :dependent => :delete_all |
|
15 | has_many :watchers, :as => :watchable, :dependent => :delete_all | |
16 | has_many :watcher_users, :through => :watchers, :source => :user |
|
16 | has_many :watcher_users, :through => :watchers, :source => :user | |
|
17 | ||||
|
18 | attr_protected :watcher_ids, :watcher_user_ids | |||
17 | end |
|
19 | end | |
18 | end |
|
20 | end | |
19 | end |
|
21 | end | |
20 |
|
22 | |||
21 | module InstanceMethods |
|
23 | module InstanceMethods | |
22 | def self.included(base) |
|
24 | def self.included(base) | |
23 | base.extend ClassMethods |
|
25 | base.extend ClassMethods | |
24 | end |
|
26 | end | |
25 |
|
27 | |||
26 | # Returns an array of users that are proposed as watchers |
|
28 | # Returns an array of users that are proposed as watchers | |
27 | def addable_watcher_users |
|
29 | def addable_watcher_users | |
28 | self.project.users.sort - self.watcher_users |
|
30 | self.project.users.sort - self.watcher_users | |
29 | end |
|
31 | end | |
30 |
|
32 | |||
31 | # Adds user as a watcher |
|
33 | # Adds user as a watcher | |
32 | def add_watcher(user) |
|
34 | def add_watcher(user) | |
33 | self.watchers << Watcher.new(:user => user) |
|
35 | self.watchers << Watcher.new(:user => user) | |
34 | end |
|
36 | end | |
35 |
|
37 | |||
36 | # Removes user from the watchers list |
|
38 | # Removes user from the watchers list | |
37 | def remove_watcher(user) |
|
39 | def remove_watcher(user) | |
38 | return nil unless user && user.is_a?(User) |
|
40 | return nil unless user && user.is_a?(User) | |
39 | Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}" |
|
41 | Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}" | |
40 | end |
|
42 | end | |
41 |
|
43 | |||
42 | # Adds/removes watcher |
|
44 | # Adds/removes watcher | |
43 | def set_watcher(user, watching=true) |
|
45 | def set_watcher(user, watching=true) | |
44 | watching ? add_watcher(user) : remove_watcher(user) |
|
46 | watching ? add_watcher(user) : remove_watcher(user) | |
45 | end |
|
47 | end | |
46 |
|
48 | |||
47 | # Returns if object is watched by user |
|
49 | # Returns if object is watched by user | |
48 | def watched_by?(user) |
|
50 | def watched_by?(user) | |
49 | !self.watchers.find(:first, |
|
51 | !self.watchers.find(:first, | |
50 | :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]).nil? |
|
52 | :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]).nil? | |
51 | end |
|
53 | end | |
52 |
|
54 | |||
53 | # Returns an array of watchers' email addresses |
|
55 | # Returns an array of watchers' email addresses | |
54 | def watcher_recipients |
|
56 | def watcher_recipients | |
55 | self.watchers.collect { |w| w.user.mail if w.user.active? }.compact |
|
57 | self.watchers.collect { |w| w.user.mail if w.user.active? }.compact | |
56 | end |
|
58 | end | |
57 |
|
59 | |||
58 | module ClassMethods |
|
60 | module ClassMethods | |
59 | # Returns the objects that are watched by user |
|
61 | # Returns the objects that are watched by user | |
60 | def watched_by(user) |
|
62 | def watched_by(user) | |
61 | find(:all, |
|
63 | find(:all, | |
62 | :include => :watchers, |
|
64 | :include => :watchers, | |
63 | :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]) |
|
65 | :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]) | |
64 | end |
|
66 | end | |
65 | end |
|
67 | end | |
66 | end |
|
68 | end | |
67 | end |
|
69 | end | |
68 | end |
|
70 | end | |
69 | end |
|
71 | end |
General Comments 0
You need to be logged in to leave comments.
Login now