veersion_inheritance.patch

Version inheritance patch against 0.7.2.0, first release. Please read comment 13. - Paul Rivier, 2008-06-27 13:55

Download (22.5 KB)

View differences:

app/controllers/issues_controller.rb Fri Jun 27 13:12:14 2008 +0200
20 20
  menu_item :new_issue, :only => :new
21 21
  
22 22
  before_filter :find_issue, :only => [:show, :edit, :destroy_attachment]
23
  before_filter :check_issue_perms, :only => [:show, :edit]
23 24
  before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
24 25
  before_filter :find_project, :only => [:new, :update_form, :preview]
25 26
  before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
......
28 29

  
29 30
  helper :journals
30 31
  helper :projects
31
  include ProjectsHelper   
32
  include ProjectsHelper
32 33
  helper :custom_fields
33 34
  include CustomFieldsHelper
34 35
  helper :ifpdf
......
63 64
                           :conditions => @query.statement,
64 65
                           :limit  =>  limit,
65 66
                           :offset =>  @issue_pages.current.offset
67
      @issues.map!{ |issue| issue_without_hidden_fields(issue) } # don't show forbidden fields
66 68
      respond_to do |format|
67 69
        format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 70
        format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
......
100 102
    @journals.reverse! if User.current.wants_comments_in_reverse_order?
101 103
    @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
102 104
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
105
    @issue = issue_without_hidden_fields(@issue)
106
    @journals = journals_without_hidden_entries(@journals)
103 107
    @activities = Enumeration::get_values('ACTI')
104 108
    @priorities = Enumeration::get_values('IPRI')
105 109
    @time_entry = TimeEntry.new
......
388 392
    render_404
389 393
  end
390 394
  
395
  
396

  
397
  def issue_fixed_version_allowed?(issue)
398
    ((not issue.fixed_version) ||
399
     (User.current.allowed_to?(:view_issues,
400
                               issue.fixed_version.project)))
401
  end
402

  
403
  def check_issue_perms
404
    @forbidden_fixed_version = (not issue_fixed_version_allowed?(@issue))
405
  end
406

  
407
  ## Filter journals to remove non allowed entries. This assumes there
408
  ## is only 1 parent, so it won't work with more depth. We want here
409
  ## to handle the _specific case_ of a user 'Joe' browsing 'Project
410
  ## B' issues. 'Project B' is a subproject of 'Project A' and
411
  ## inherits its versions. Joe can't see 'Project A, but issue
412
  ## fixed_versions has been set at some point to a version belonging
413
  ## to 'Project A', by an other member of 'Project B'. Joe should not
414
  ## see this sensitive information.
415
  def journals_without_hidden_entries(journals)
416
    unless (journals.empty? || (not @project.parent_id) ||
417
            (@project.parent_id && User.current.allowed_to?(:view_issues, @project.parent)))
418
      journals = journals.dup
419
      # we kill totally the journal entry when some of its elements are forbidden
420
      journals.delete_if do |j|
421
        j.details.map{ |d| (d.property == 'attr' &&
422
                            d.prop_key == 'fixed_version_id' &&
423
                            ((d.old_value &&
424
                              (@project.parent_id == Version.find(d.old_value.to_i).project_id)) ||
425
                             (d.value &&
426
                              (@project.parent_id == Version.find(d.value.to_i).project_id)))
427
                            ) }.include?(true)
428
      end
429
    end
430
    journals
431
  end
432

  
433
  ## Filter issue to remove non allowed fields. For more information,
434
  ## see documentation of journals_without_hidden_entries
435
  def issue_without_hidden_fields(issue)
436
    unless issue_fixed_version_allowed?(issue)
437
      issue = issue.dup
438
      issue.fixed_version = nil
439
    end
440
    issue
441
  end
442

  
391 443
  # Retrieve query from session or build a new query
392 444
  def retrieve_query
393 445
    if !params[:query_id].blank?
app/controllers/projects_controller.rb Fri Jun 27 13:12:14 2008 +0200
195 195

  
196 196
  def add_file
197 197
    if request.post?
198
      @version = @project.versions.find_by_id(params[:version_id])
198
      @version = @project.related_versions.find_by_id(params[:version_id])
199 199
      attachments = attach_files(@version, params[:attachments])
200 200
      Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
201 201
      redirect_to :controller => 'projects', :action => 'list_files', :id => @project
202 202
    end
203
    @versions = @project.versions.sort
203
    @versions = @project.related_versions.sort
204 204
  end
205 205
  
206 206
  def list_files
207 207
    sort_init "#{Attachment.table_name}.filename", "asc"
208 208
    sort_update
209
    @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
209
    @versions = @project.related_versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
210 210
    render :layout => !request.xhr?
211 211
  end
212 212
  
......
214 214
  def changelog
215 215
    @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
216 216
    retrieve_selected_tracker_ids(@trackers)    
217
    @versions = @project.versions.sort
217
    @versions = @project.related_versions.sort
218 218
  end
219 219

  
220 220
  def roadmap
221 221
    @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
222 222
    retrieve_selected_tracker_ids(@trackers)
223
    @versions = @project.versions.sort
224
    @versions = @versions.select {|v| !v.completed? } unless params[:completed]
223
    # retrieve_related_versions could be integrated right here.
224
    @versions = retrieve_related_versions
225 225
  end
226 226
  
227 227
  def activity
......
342 342
                           :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
343 343
                           ) unless @selected_tracker_ids.empty?
344 344
      events += Version.find(:all, :include => :project,
345
                                   :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
345
                             :conditions => { :effective_date => @calendar.startdt..@calendar.enddt})
346 346
    end
347
    # add parent versions if relevant
348
    events += @project.related_versions.select{ |v| ((v.project_id == @project.parent_id) &&
349
                                                     v.effective_date) }
350
    
347 351
    @calendar.events = events
348 352
    
349 353
    render :layout => false if request.xhr?
......
390 394
      @events += Version.find(:all, :include => :project,
391 395
                                    :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
392 396
    end
397
    
398
    # add parent versions if relevant
399
    @events += @project.related_versions.select{ |v| ((v.project_id == @project.parent_id) &&
400
                                                      v.effective_date) }
401
        
393 402
    @events.sort! {|x,y| x.start_date <=> y.start_date }
394 403
    
395 404
    if params[:format]=='pdf'
......
423 432
    render_404
424 433
  end
425 434

  
435
  def retrieve_related_versions
436
    related_versions=@project.related_versions
437
    # should this test be moved to projects_helper.rb : version_visible_issues ?
438
    params[:completed] ? related_versions : related_versions.select {|v| !v.completed? }
439
  end
440

  
426 441
  def retrieve_selected_tracker_ids(selectable_trackers)
427 442
    if ids = params[:tracker_ids]
428 443
      @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
app/controllers/reports_controller.rb Fri Jun 27 13:12:14 2008 +0200
32 32
      render :template => "reports/issue_report_details"
33 33
    when "version"
34 34
      @field = "fixed_version_id"
35
      @rows = @project.versions.sort
35
      @rows = @project.related_versions.sort
36 36
      @data = issues_by_version
37 37
      @report_title = l(:field_version)
38 38
      render :template => "reports/issue_report_details"
......
68 68
      render :template => "reports/issue_report_details"  
69 69
    else
70 70
      @trackers = @project.trackers
71
      @versions = @project.versions.sort
71
      @versions = @project.related_versions.sort
72 72
      @priorities = Enumeration::get_values('IPRI')
73 73
      @categories = @project.issue_categories
74 74
      @assignees = @project.members.collect { |m| m.user }
app/controllers/versions_controller.rb Fri Jun 27 13:12:14 2008 +0200
20 20
  menu_item :roadmap
21 21
  before_filter :find_project, :authorize
22 22

  
23
  helper :projects
24
  include ProjectsHelper   
25

  
23 26
  def show
27
    @issues = version_visible_issues(@version,@project,@project.tracker_ids)
24 28
  end
25 29
  
26 30
  def edit
app/helpers/projects_helper.rb Fri Jun 27 13:12:14 2008 +0200
46 46
    tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}     
47 47
  end
48 48
  
49
  ## return an array of the issues attached to version that a user should see
50
  ## As a convenience, allow limitation by trackers with Array of tracker_ids
51
  def version_visible_issues(version,project,tracker_ids=[],user=User.current)
52
    # Returns void when user can not see issues or if tracker_ids is void
53
    return [] if ((not user.allowed_to?(:view_issues,
54
                                        version.project)) or
55
                  tracker_ids.empty?)
56
    # user can see project issues, carry on, retrieve them ...
57
    issues = version.
58
      fixed_issues.find(:all,
59
                        :include => [:status, :tracker],
60
                        :conditions => ["tracker_id in (#{tracker_ids.join(',')})"],
61
                        :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
62
    # Only keep issues from self, parent or children,
63
    related_projects=[project.id].concat(project.child_ids) # project and children
64
    related_projects.push project.parent.id if project.parent # parent if relevant
65
    related_projects = related_projects.select{|x| user.allowed_to?(:view_issues, Project.find(x))}
66
    # now only keep related issues. (issues from brother projects are discarded)
67
    issues.select {|x| related_projects.include?(x.project.id) }
68
  end
69

  
49 70
  # Generates a gantt image
50 71
  # Only defined if RMagick is avalaible
51 72
  def gantt_image(events, date_from, months, zoom)
app/models/project.rb Fri Jun 27 13:12:14 2008 +0200
237 237
    end
238 238
  end
239 239

  
240
  ## Return versions attached to self, and to parent if relevant.
241
  ## Sorting is done in the SQL request to take advantage of cache
242
  def related_versions(user=User.current)
243
    Version.find(:all,
244
                 :conditions => {:project_id => if self.parent &&
245
                                                    user.allowed_to?(:view_issues,
246
                                                                     self.parent) then
247
                                                  [self.id, self.parent_id]
248
                                                else [self.id]; end},
249
                 :order => "effective_date ASC, name ASC ")
250
  end
251

  
240 252
protected
241 253
  def validate
242 254
    errors.add(parent_id, " must be a root project") if parent and parent.parent
app/models/query.rb Fri Jun 27 13:12:14 2008 +0200
168 168
    if project
169 169
      # project specific filters      
170 170
      @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
171
      @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
171
      @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.related_versions.sort.collect{|s| [s.name, s.id.to_s] } }
172 172
      unless @project.active_children.empty?
173 173
        @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
174 174
      end
app/views/issues/_form.rhtml Fri Jun 27 13:12:14 2008 +0200
29 29
                     l(:label_issue_category_new), 'category[name]', 
30 30
                     {:controller => 'projects', :action => 'add_issue_category', :id => @project},
31 31
                     :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
32
<%= content_tag('p', f.select(:fixed_version_id, 
33
                              (@project.versions.sort.collect {|v| [v.name, v.id]}),
34
                              { :include_blank => true })) unless @project.versions.empty? %>
32
<%= unless (@project.related_versions.empty? || @forbidden_fixed_version) then
33
      content_tag('p', f.select(:fixed_version_id,
34
				(@project.related_versions.sort.collect {|v| [v.name, v.id]}),
35
				{ :include_blank => true }))
36
    end %>
35 37
</div>
36 38

  
37 39
<div class="splitcontentright">
app/views/issues/_form_update.rhtml Fri Jun 27 13:12:14 2008 +0200
4 4
</div>
5 5
<div class="splitcontentright">
6 6
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
7
<%= content_tag('p', f.select(:fixed_version_id, 
8
                          (@project.versions.sort.collect {|v| [v.name, v.id]}),
9
                          { :include_blank => true })) unless @project.versions.empty? %>
7
<%= unless (@issue.fixed_version &&
8
            (not User.current.allowed_to?(:view_issues,
9
					  @issue.fixed_version.project)) ||
10
	    @project.related_versions.empty?) then
11
      content_tag('p', f.select(:fixed_version_id, 
12
				(@project.related_versions.sort.collect {|v| [v.name, v.id]}),
13
				{ :include_blank => true }))
14
    end %>
10 15
</div>
app/views/issues/_pdf.rfpdf Fri Jun 27 13:12:14 2008 +0200
86 86
	pdf.SetFontStyle('B',9)
87 87
    pdf.Cell(190,5, l(:label_history), "B")
88 88
    pdf.Ln	
89
	for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
89
	for journal in @journals
90 90
		pdf.SetFontStyle('B',8)
91 91
	    pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
92 92
	    pdf.Ln
app/views/issues/bulk_edit.rhtml Fri Jun 27 13:12:14 2008 +0200
27 27
<label><%= l(:field_fixed_version) %>: 
28 28
<%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
29 29
                                   content_tag('option', l(:label_none), :value => 'none') +
30
                                   options_from_collection_for_select(@project.versions, :id, :name)) %></label>
30
                                   options_from_collection_for_select(@project.related_versions, :id, :name)) %></label>
31 31
</p>
32 32

  
33 33
<p>
app/views/projects/roadmap.rhtml Fri Jun 27 13:12:14 2008 +0200
1 1
<h2><%=l(:label_roadmap)%></h2>
2 2

  
3 3
<% if @versions.empty? %>
4
<p class="nodata"><%= l(:label_no_data) %></p>
4
  <p class="nodata"><%= l(:label_no_data) %></p>
5 5
<% else %>
6
<div id="roadmap">
7
<% @versions.each do |version| %>   
8
    <%= tag 'a', :name => version.name %>
9
    <h3 class="icon22 icon22-package"><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></h3>
10
    <%= render :partial => 'versions/overview', :locals => {:version => version} %>
11
    <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
12

  
13
    <% issues = version.fixed_issues.find(:all,
14
                                          :include => [:status, :tracker],
15
                                          :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
16
                                          :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") unless @selected_tracker_ids.empty?
17
       issues ||= []
18
    %>
19
    <% if issues.size > 0 %>
20
    <fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
21
    <ul>
22
    <%- issues.each do |issue| -%>
23
        <li class="issue <%= 'closed' if issue.closed? %>"><%= link_to_issue(issue) %>: <%=h issue.subject %></li>
24
    <%- end -%>
25
    </ul>
26
    </fieldset>
6
  <div id="roadmap">
7
    <% @versions.each do |version| %>
8
      <% issues = version_visible_issues(version,@project,@selected_tracker_ids) %>
9
      <% next if (version.project.id != @project.id && issues.empty?) %>
10
      <%= tag 'a', :name => version.name %>
11
      <h3 class="icon22 icon22-package"><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></h3>
12
      <%= render :partial => 'versions/overview', :locals => {:version => version} %>
13
      <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
14
      
15
      <% if issues.size > 0 %>
16
	<fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
17
	  <ul>
18
	    <%- issues.each do |issue| -%>
19
              <li class="issue <%= 'closed' if issue.closed? %>">
20
		<%= link_to_issue(issue) %>:
21
		<%=h issue.subject %>
22
		<%= (" <em>(" + issue.project.name + ")</em>") unless issue.project.id == @project.id %>
23
</li>
24
	      <%- end -%>
25
	  </ul>
26
	</fieldset>
27
	<% end %>
28
      <% end %>
29
  </div>
30
  <% end %>
31
  
32
  <% content_for :sidebar do %>
33
    <% form_tag do %>
34
      <h3><%= l(:label_roadmap) %></h3>
35
      <% @trackers.each do |tracker| %>
36
	<label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s), :id => nil %>
37
	  <%= tracker.name %></label><br />
38
      <% end %>
39
      <br />
40
      <label for="completed"><%= check_box_tag "completed", 1, params[:completed] %> <%= l(:label_show_completed_versions) %></label>
41
      <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
27 42
    <% end %>
28
<% end %>
29
</div>
30
<% end %>
31

  
32
<% content_for :sidebar do %>
33
<% form_tag do %>
34
<h3><%= l(:label_roadmap) %></h3>
35
<% @trackers.each do |tracker| %>
36
  <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s), :id => nil %>
37
  <%= tracker.name %></label><br />
38
<% end %>
39
<br />
40
<label for="completed"><%= check_box_tag "completed", 1, params[:completed] %> <%= l(:label_show_completed_versions) %></label>
41
<p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
42
<% end %>
43

  
44
<h3><%= l(:label_version_plural) %></h3>
45
<% @versions.each do |version| %>
46
<%= link_to version.name, "##{version.name}" %><br />
47
<% end %>
48
<% end %>
49

  
50
<% html_title(l(:label_roadmap)) %>
43
    
44
    <h3><%= l(:label_version_plural) %></h3>
45
    <% @versions.each do |version| %>
46
      <%= link_to version.name, "##{version.name}" %><br />
47
    <% end %>
48
  <% end %>
49
  
50
  <% html_title(l(:label_roadmap)) %>
app/views/versions/show.rhtml Fri Jun 27 13:12:14 2008 +0200
31 31
<%= render :partial => 'versions/overview', :locals => {:version => @version} %>
32 32
<%= render(:partial => "wiki/content", :locals => {:content => @version.wiki_page.content}) if @version.wiki_page %>
33 33

  
34
<% issues = @version.fixed_issues.find(:all,
35
                                       :include => [:status, :tracker],
36
                                       :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") %>
37
<% if issues.size > 0 %>
34
<% if @issues.size > 0 %>
38 35
<fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
39 36
<ul>
40
<% issues.each do |issue| -%>
41
    <li class="issue <%= 'closed' if issue.closed? %>"><%= link_to_issue(issue) %>: <%=h issue.subject %></li>
37
<% @issues.each do |issue| -%>
38
    <li class="issue <%= 'closed' if issue.closed? %>">
39
      <%= link_to_issue(issue) %>: <%=h issue.subject %>
40
      <%= (" <em>(" + issue.project.name + ")</em>") unless issue.project.id == @project.id %>
41
    </li>
42 42
<% end -%>
43 43
</ul>
44 44
</fieldset>
lib/redmine.rb Fri Jun 27 13:12:14 2008 +0200
116 116
  menu.push :overview, { :controller => 'projects', :action => 'show' }
117 117
  menu.push :activity, { :controller => 'projects', :action => 'activity' }
118 118
  menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' }, 
119
              :if => Proc.new { |p| p.versions.any? }
119
              :if => Proc.new { |p| p.related_versions.any? }
120 120
  menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
121 121
  menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
122 122
              :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }