Feature #5358 » shared_categories_redmine_13.patch
app/controllers/issue_categories_controller.rb Wed Feb 22 14:09:19 2012 -0800 → app/controllers/issue_categories_controller.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
43 | 43 |
end |
44 | 44 | |
45 | 45 |
verify :method => :post, :only => :create |
46 | ||
47 |
helper :projects |
|
48 | ||
46 | 49 |
def create |
47 | 50 |
@category = @project.issue_categories.build(params[:issue_category]) |
48 | 51 |
if @category.save |
app/controllers/reports_controller.rb Wed Feb 22 14:09:19 2012 -0800 → app/controllers/reports_controller.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
23 | 23 |
@trackers = @project.trackers |
24 | 24 |
@versions = @project.shared_versions.sort |
25 | 25 |
@priorities = IssuePriority.all |
26 |
@categories = @project.issue_categories
|
|
26 |
@categories = @project.share_categories.sort
|
|
27 | 27 |
@assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort |
28 | 28 |
@authors = @project.users.sort |
29 | 29 |
@subprojects = @project.descendants.visible |
... | ... | |
58 | 58 |
@report_title = l(:field_priority) |
59 | 59 |
when "category" |
60 | 60 |
@field = "category_id" |
61 |
@rows = @project.issue_categories
|
|
61 |
@rows = @project.shared_categories.sort
|
|
62 | 62 |
@data = Issue.by_category(@project) |
63 | 63 |
@report_title = l(:field_category) |
64 | 64 |
when "assigned_to" |
app/helpers/projects_helper.rb Wed Feb 22 14:09:19 2012 -0800 → app/helpers/projects_helper.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
107 | 107 |
sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing) |
108 | 108 |
l("label_version_sharing_#{sharing}") |
109 | 109 |
end |
110 | ||
111 |
def format_category_sharing(category, project) |
|
112 |
sharing = IssueCategory::SHARINGS.include?(category.sharing) ? category.sharing : 'none' |
|
113 |
project_name = category.project != project ? ' (' + category.project.name + ')' : '' |
|
114 |
# sharing = category.sharing |
|
115 |
# sharing = 'none' unless IssueCategory::SHARINGS.include?(category.sharing) |
|
116 |
# if category.project != project |
|
117 |
# project_name = ' (' + category.project.name + ')' |
|
118 |
# else |
|
119 |
# project_name = '' |
|
120 |
# end |
|
121 |
l("label_version_sharing_#{sharing}") + project_name |
|
122 |
end |
|
110 | 123 |
end |
app/models/issue.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/issue.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
161 | 161 |
issue.relations_to.clear |
162 | 162 |
end |
163 | 163 |
# issue is moved to another project |
164 |
# reassign to the category with same name if any |
|
165 |
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) |
|
166 |
issue.category = new_category |
|
167 | 164 |
# Keep the fixed_version if it's still valid in the new_project |
168 | 165 |
unless new_project.shared_versions.include?(issue.fixed_version) |
169 | 166 |
issue.fixed_version = nil |
170 | 167 |
end |
168 |
# Keep the category if it's still valid in the new_project |
|
169 |
unless new_project.shared_categories.include?(issue.category) |
|
170 |
# try to reassign to the category with same name if any |
|
171 |
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) |
|
172 |
issue.category = new_category |
|
173 |
end |
|
171 | 174 |
issue.project = new_project |
172 | 175 |
if issue.parent && issue.parent.project_id != issue.project_id |
173 | 176 |
issue.parent_issue_id = nil |
... | ... | |
359 | 362 |
end |
360 | 363 |
end |
361 | 364 | |
365 |
if category |
|
366 |
if !assignable_categories.include?(category) |
|
367 |
errors.add :category_id, :inclusion |
|
368 |
end |
|
369 |
end |
|
370 | ||
362 | 371 |
# Checks that the issue can not be added/moved to a disabled tracker |
363 | 372 |
if project && (tracker_id_changed? || project_id_changed?) |
364 | 373 |
unless project.trackers.include?(tracker) |
... | ... | |
461 | 470 |
@assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort |
462 | 471 |
end |
463 | 472 | |
473 |
# Categories that the issue can be assigned to |
|
474 |
def assignable_categories |
|
475 |
@assignable_categories ||= (project.shared_categories + [IssueCategory.find_by_id(category_id_was)]).compact.uniq.sort |
|
476 |
end |
|
477 | ||
464 | 478 |
# Returns true if this issue is blocked by another issue that is still open |
465 | 479 |
def blocked? |
466 | 480 |
!relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil? |
... | ... | |
647 | 661 |
update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id]) |
648 | 662 |
end |
649 | 663 | |
664 |
# Unassigns issues from +category+ if it's no longer shared with issue's project |
|
665 |
def self.update_categories_from_sharing_change(category) |
|
666 |
# Update issues assigned to the category |
|
667 |
update_categories(["#{Issue.table_name}.category_id = ?", category.id]) |
|
668 |
end |
|
669 | ||
650 | 670 |
# Unassigns issues from versions that are no longer shared |
651 | 671 |
# after +project+ was moved |
652 | 672 |
def self.update_versions_from_hierarchy_change(project) |
... | ... | |
655 | 675 |
Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids]) |
656 | 676 |
end |
657 | 677 | |
678 |
# Unassigns issues from categories that are no longer shared |
|
679 |
# after +project+ was moved |
|
680 |
def self.update_categories_from_hierarchy_change(project) |
|
681 |
moved_project_ids = project.self_and_descendants.reload.collect(&:id) |
|
682 |
# Update issues of the moved projects and issues assigned to a category of a moved project |
|
683 |
Issue.update_categories(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids]) |
|
684 |
end |
|
685 | ||
658 | 686 |
def parent_issue_id=(arg) |
659 | 687 |
parent_issue_id = arg.blank? ? nil : arg.to_i |
660 | 688 |
if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id) |
... | ... | |
852 | 880 |
end |
853 | 881 |
end |
854 | 882 | |
883 |
# Update issues so their categories are not pointing to a |
|
884 |
# fixed_version that is not shared with the issue's project |
|
885 |
def self.update_categories(conditions=nil) |
|
886 |
# Only need to update issues with a fixed_version from |
|
887 |
# a different project and that is not systemwide shared |
|
888 |
Issue.all(:conditions => merge_conditions("#{Issue.table_name}.category_id IS NOT NULL" + |
|
889 |
" AND #{Issue.table_name}.project_id <> #{IssueCategory.table_name}.project_id" + |
|
890 |
" AND #{IssueCategory.table_name}.sharing <> 'system'", |
|
891 |
conditions), |
|
892 |
:include => [:project, :category] |
|
893 |
).each do |issue| |
|
894 |
next if issue.project.nil? || issue.category.nil? |
|
895 |
unless issue.project.shared_categories.include?(issue.category) |
|
896 |
issue.init_journal(User.current) |
|
897 |
issue.category = nil |
|
898 |
issue.save |
|
899 |
end |
|
900 |
end |
|
901 |
end |
|
902 | ||
855 | 903 |
# Callback on attachment deletion |
856 | 904 |
def attachment_added(obj) |
857 | 905 |
if @current_journal && !obj.new_record? |
app/models/issue_category.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/issue_category.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 | |
18 | 18 |
class IssueCategory < ActiveRecord::Base |
19 |
after_update :update_issues_from_sharing_change |
|
19 | 20 |
belongs_to :project |
20 | 21 |
belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id' |
21 | 22 |
has_many :issues, :foreign_key => 'category_id', :dependent => :nullify |
22 | 23 | |
24 |
SHARINGS = %w(none descendants hierarchy tree system) |
|
25 | ||
23 | 26 |
validates_presence_of :name |
24 | 27 |
validates_uniqueness_of :name, :scope => [:project_id] |
25 | 28 |
validates_length_of :name, :maximum => 30 |
26 |
|
|
29 |
validates_inclusion_of :sharing, :in => SHARINGS |
|
30 | ||
27 | 31 |
attr_protected :project_id |
28 | 32 | |
29 | 33 |
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}} |
... | ... | |
44 | 48 |
end |
45 | 49 | |
46 | 50 |
def to_s; name end |
51 |
# Returns the sharings that +user+ can set the category to |
|
52 |
def allowed_sharings(user = User.current) |
|
53 |
SHARINGS.select do |s| |
|
54 |
if sharing == s |
|
55 |
true |
|
56 |
else |
|
57 |
case s |
|
58 |
when 'system' |
|
59 |
# Only admin users can set a systemwide sharing |
|
60 |
user.admin? |
|
61 |
when 'hierarchy', 'tree' |
|
62 |
# Only users allowed to manage categories of the root project can |
|
63 |
# set sharing to hierarchy or tree |
|
64 |
project.nil? || user.allowed_to?(:manage_categories, project.root) |
|
65 |
else |
|
66 |
true |
|
67 |
end |
|
68 |
end |
|
69 |
end |
|
70 |
end |
|
71 | ||
72 |
# Update the issue's fixed categories. Used if a category's sharing changes. |
|
73 |
def update_issues_from_sharing_change |
|
74 |
if sharing_changed? |
|
75 |
if SHARINGS.index(sharing_was).nil? || |
|
76 |
SHARINGS.index(sharing).nil? || |
|
77 |
SHARINGS.index(sharing_was) > SHARINGS.index(sharing) |
|
78 |
Issue.update_categories_from_sharing_change self |
|
79 |
end |
|
80 |
end |
|
81 |
end |
|
47 | 82 |
end |
app/models/mail_handler.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/mail_handler.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
267 | 267 |
'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), |
268 | 268 |
'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), |
269 | 269 |
'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), |
270 |
'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
|
|
270 |
'category_id' => (k = get_keyword(:category)) && issue.project.shared_categories.named(k).first.try(:id),
|
|
271 | 271 |
'assigned_to_id' => assigned_to.try(:id), |
272 | 272 |
'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.named(k).first.try(:id), |
273 | 273 |
'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), |
app/models/project.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/project.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
354 | 354 |
move_to_child_of(p) |
355 | 355 |
end |
356 | 356 |
Issue.update_versions_from_hierarchy_change(self) |
357 |
Issue.update_categories_from_hierarchy_change(self) |
|
357 | 358 |
true |
358 | 359 |
else |
359 | 360 |
# Can not move to the given target |
... | ... | |
402 | 403 |
"))") |
403 | 404 |
end |
404 | 405 |
end |
406 |
# Returns a scope of the categories used by the project |
|
407 |
def shared_categories |
|
408 |
@shared_categories ||= |
|
409 |
IssueCategory.find(:all, :include => :project, |
|
410 |
:conditions => "#{Project.table_name}.id = #{id}" + |
|
411 |
" OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" + |
|
412 |
" #{IssueCategory.table_name}.sharing = 'system'" + |
|
413 |
" OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{IssueCategory.table_name}.sharing = 'tree')" + |
|
414 |
" OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{IssueCategory.table_name}.sharing IN ('hierarchy', 'descendants'))" + |
|
415 |
" OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{IssueCategory.table_name}.sharing = 'hierarchy')" + |
|
416 |
"))") |
|
417 |
end |
|
405 | 418 | |
406 | 419 |
# Returns a hash of project users grouped by role |
407 | 420 |
def users_by_role |
app/models/query.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/query.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
270 | 270 | |
271 | 271 |
if project |
272 | 272 |
# project specific filters |
273 |
categories = project.issue_categories.all
|
|
273 |
categories = project.shared_categories
|
|
274 | 274 |
unless categories.empty? |
275 |
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
|
|
275 |
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
|
|
276 | 276 |
end |
277 | 277 |
versions = project.shared_versions.all |
278 | 278 |
unless versions.empty? |
app/views/context_menus/issues.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/context_menus/issues.html.erb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
70 | 70 |
</ul> |
71 | 71 |
</li> |
72 | 72 |
<% end %> |
73 |
<% unless @project.nil? || @project.issue_categories.empty? -%>
|
|
73 |
<% unless @project.nil? || @project.shared_categories.empty? -%>
|
|
74 | 74 |
<li class="folder"> |
75 | 75 |
<a href="#" class="submenu"><%= l(:field_category) %></a> |
76 | 76 |
<ul> |
77 |
<% @project.issue_categories.each do |u| -%>
|
|
77 |
<% @project.shared_categories.each do |u| -%>
|
|
78 | 78 |
<li><%= context_menu_link h(u.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post, |
79 | 79 |
:selected => (@issue && u == @issue.category), :disabled => !@can[:update] %></li> |
80 | 80 |
<% end -%> |
app/views/issue_categories/_form.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/issue_categories/_form.html.erb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
3 | 3 |
<div class="box"> |
4 | 4 |
<p><%= f.text_field :name, :size => 30, :required => true %></p> |
5 | 5 |
<p><%= f.select :assigned_to_id, principals_options_for_select(@project.assignable_users, @category.assigned_to), :include_blank => true %></p> |
6 |
<p><%= f.select :sharing, @category.allowed_sharings.collect{|c| [format_category_sharing(c), c]} %></p> |
|
6 | 7 |
</div> |
app/views/issues/_attributes.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/issues/_attributes.html.erb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
9 | 9 | |
10 | 10 |
<p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p> |
11 | 11 |
<p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p> |
12 |
<% unless @project.issue_categories.empty? %>
|
|
13 |
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
|
|
12 |
<% unless @issue.assignable_categories.empty? %>
|
|
13 |
<p><%= f.select :category_id, (@issue.assignable_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
|
|
14 | 14 |
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'), |
15 | 15 |
l(:label_issue_category_new), |
16 | 16 |
'issue_category[name]', |
app/views/projects/settings/_issue_categories.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/projects/settings/_issue_categories.html.erb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
1 |
<% if @project.issue_categories.any? %>
|
|
1 |
<% if @project.shared_categories.any? %>
|
|
2 | 2 |
<table class="list"> |
3 | 3 |
<thead><tr> |
4 | 4 |
<th><%= l(:label_issue_category) %></th> |
5 | 5 |
<th><%= l(:field_assigned_to) %></th> |
6 |
<th><%= l(:field_sharing) %></th> |
|
6 | 7 |
<th></th> |
7 | 8 |
</tr></thead> |
8 | 9 |
<tbody> |
9 |
<% for category in @project.issue_categories %>
|
|
10 |
<% for category in @project.shared_categories %>
|
|
10 | 11 |
<% unless category.new_record? %> |
11 |
<tr class="<%= cycle 'odd', 'even' %>"> |
|
12 |
<tr class="<%= cycle 'odd', 'even' %> <%= 'shared' if category.project != @project %>">
|
|
12 | 13 |
<td><%=h(category.name) %></td> |
13 | 14 |
<td><%=h(category.assigned_to.name) if category.assigned_to %></td> |
15 |
<td class="sharing"><%=h format_category_sharing(category, @project) %></td> |
|
14 | 16 |
<td class="buttons"> |
15 | 17 |
<% if User.current.allowed_to?(:manage_categories, @project) %> |
16 | 18 |
<%= link_to l(:button_edit), edit_issue_category_path(category), :class => 'icon icon-edit' %> |
/dev/null Thu Jan 01 00:00:00 1970 +0000 → db/migrate/20120222000000_add_issue_category_sharing.rb Wed Feb 22 17:26:32 2012 -0800 | ||
---|---|---|
1 |
class AddIssueCategorySharing < ActiveRecord::Migration |
|
2 |
def self.up |
|
3 |
add_column :issue_categories, :sharing, :string, :default => 'none', :null => false |
|
4 |
add_index :issue_categories, :sharing |
|
5 |
end |
|
6 | ||
7 |
def self.down |
|
8 |
remove_column :issue_categories, :sharing |
|
9 |
end |
|
10 |
end |