Feature #5358 » shared_categories_redmine_1.3.1_v3.diff
| app/controllers/issue_categories_controller.rb Wed Feb 22 14:09:19 2012 -0800 → app/controllers/issue_categories_controller.rb Wed Feb 29 18:06:47 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 29 18:06:47 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.shared_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 29 18:06:47 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 |
l("label_version_sharing_#{sharing}") + project_name
|
|
| 115 |
end |
|
| 116 |
|
|
| 117 |
def format_category_sharing_simple(sharing) |
|
| 118 |
sharing_label = IssueCategory::SHARINGS.include?(sharing) ? sharing : 'none' |
|
| 119 |
l("label_version_sharing_#{sharing_label}")
|
|
| 120 |
end |
|
| 110 | 121 |
end |
| app/models/issue.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/issue.rb Wed Feb 29 18:06:47 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(["#{IssueCategory.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 29 18:06:47 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 29 18:06:47 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 29 18:06:47 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 29 18:06:47 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 29 18:06:47 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 29 18:06:47 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_simple(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 29 18:06:47 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 29 18:06:47 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 29 18:06:47 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 |
|