Patch #20384 » 0001-Implements-Workspaces-in-redmine-5.1.patch
app/controllers/admin_controller.rb | ||
---|---|---|
44 | 44 |
@entry_pages = Paginator.new @entry_count, per_page_option, params['page'] |
45 | 45 |
@projects = scope.limit(@entry_pages.per_page).offset(@entry_pages.offset).to_a |
46 | 46 | |
47 |
@workspaces = Hash[Workspace.pluck(:id, :name)] |
|
48 | ||
47 | 49 |
render :action => "projects", :layout => false if request.xhr? |
48 | 50 |
end |
49 | 51 |
app/controllers/workflows_controller.rb | ||
---|---|---|
25 | 25 |
before_action :require_admin |
26 | 26 | |
27 | 27 |
def index |
28 |
@roles = Role.sorted.select(&:consider_workflow?) |
|
29 |
@trackers = Tracker.sorted |
|
30 |
@workflow_counts = WorkflowTransition.group(:tracker_id, :role_id).count |
|
28 |
@workspaces = Workspace.sorted |
|
29 |
@workspace = Workspace.find_by_id(params[:workspace_id]) || Workspace.first |
|
30 |
@roles = Role.where(id: WorkflowTransition.where(workspace_id: @workspace.id).pluck(:role_id)).sorted.select(&:consider_workflow?) |
|
31 |
@trackers = Tracker.where(id: WorkflowTransition.where(workspace_id: @workspace.id).pluck(:tracker_id)).sorted |
|
32 |
@workflow_counts = WorkflowTransition.group(:tracker_id, :role_id, :workspace_id).count |
|
31 | 33 |
end |
32 | 34 | |
33 | 35 |
def edit |
34 |
if @trackers && @roles && @statuses.any? |
|
36 |
if @trackers && @roles && @workspaces && @statuses.any?
|
|
35 | 37 |
workflows = WorkflowTransition. |
36 |
where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id)). |
|
38 |
where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id), :workspace_id => @workspaces.map(&:id)).
|
|
37 | 39 |
preload(:old_status, :new_status) |
38 | 40 |
@workflows = {} |
39 | 41 |
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee} |
... | ... | |
43 | 45 |
end |
44 | 46 | |
45 | 47 |
def update |
46 |
if @roles && @trackers && params[:transitions] |
|
48 |
if @roles && @trackers && @workspaces && params[:transitions]
|
|
47 | 49 |
transitions = params[:transitions].deep_dup |
48 | 50 |
transitions.each do |old_status_id, transitions_by_new_status| |
49 | 51 |
transitions_by_new_status.each do |new_status_id, transition_by_rule| |
50 | 52 |
transition_by_rule.reject! {|rule, transition| transition == 'no_change'} |
51 | 53 |
end |
52 | 54 |
end |
53 |
WorkflowTransition.replace_transitions(@trackers, @roles, transitions) |
|
55 |
WorkflowTransition.replace_transitions(@trackers, @roles, transitions, @workspaces)
|
|
54 | 56 |
flash[:notice] = l(:notice_successful_update) |
55 | 57 |
end |
56 | 58 |
redirect_to_referer_or edit_workflows_path |
57 | 59 |
end |
58 | 60 | |
59 | 61 |
def permissions |
60 |
if @roles && @trackers |
|
62 |
if @roles && @trackers && @workspaces
|
|
61 | 63 |
@fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]} |
62 | 64 |
@custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort |
63 |
@permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles) |
|
65 |
@permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles, @workspaces)
|
|
64 | 66 |
@statuses.each {|status| @permissions[status.id] ||= {}} |
65 | 67 |
end |
66 | 68 |
end |
67 | 69 | |
68 | 70 |
def update_permissions |
69 |
if @roles && @trackers && params[:permissions] |
|
71 |
if @roles && @trackers && @workspaces && params[:permissions]
|
|
70 | 72 |
permissions = params[:permissions].deep_dup |
71 | 73 |
permissions.each do |field, rule_by_status_id| |
72 | 74 |
rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'} |
73 | 75 |
end |
74 |
WorkflowPermission.replace_permissions(@trackers, @roles, permissions) |
|
76 |
WorkflowPermission.replace_permissions(@trackers, @roles, permissions, @workspaces)
|
|
75 | 77 |
flash[:notice] = l(:notice_successful_update) |
76 | 78 |
end |
77 | 79 |
redirect_to_referer_or permissions_workflows_path |
... | ... | |
83 | 85 | |
84 | 86 |
def duplicate |
85 | 87 |
find_sources_and_targets |
86 |
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || |
|
87 |
(@source_tracker.nil? && @source_role.nil?) |
|
88 |
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || params[:source_workspace_id].blank? ||
|
|
89 |
(@source_tracker.nil? && @source_role.nil? && @source_workspace.nil?)
|
|
88 | 90 |
flash.now[:error] = l(:error_workflow_copy_source) |
89 | 91 |
render :copy |
90 |
elsif @target_trackers.blank? || @target_roles.blank? |
|
92 |
elsif @target_trackers.blank? || @target_roles.blank? || @target_workspaces.blank?
|
|
91 | 93 |
flash.now[:error] = l(:error_workflow_copy_target) |
92 | 94 |
render :copy |
93 | 95 |
else |
94 |
WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
|
|
96 |
WorkflowRule.copy(@source_tracker, @source_role, @source_workspace, @target_trackers, @target_roles, @target_workspaces)
|
|
95 | 97 |
flash[:notice] = l(:notice_successful_update) |
96 | 98 |
redirect_to copy_workflows_path( |
97 | 99 |
:source_tracker_id => @source_tracker, |
98 |
:source_role_id => @source_role |
|
100 |
:source_role_id => @source_role, |
|
101 |
:source_workspace_id => @source_workspace |
|
99 | 102 |
) |
100 | 103 |
end |
101 | 104 |
end |
... | ... | |
105 | 108 |
def find_sources_and_targets |
106 | 109 |
@roles = Role.sorted.select(&:consider_workflow?) |
107 | 110 |
@trackers = Tracker.sorted |
111 |
@workspaces = Workspace.sorted |
|
108 | 112 |
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' |
109 | 113 |
@source_tracker = nil |
110 | 114 |
else |
... | ... | |
115 | 119 |
else |
116 | 120 |
@source_role = Role.find_by_id(params[:source_role_id].to_i) |
117 | 121 |
end |
122 |
if params[:source_workspace_id].blank? || params[:source_workspace_id] == 'any' |
|
123 |
@source_workspace = nil |
|
124 |
else |
|
125 |
@source_workspace = Workspace.find_by_id(params[:source_workspace_id].to_i) |
|
126 |
end |
|
118 | 127 |
@target_trackers = |
119 | 128 |
if params[:target_tracker_ids].blank? |
120 | 129 |
nil |
... | ... | |
127 | 136 |
else |
128 | 137 |
Role.where(:id => params[:target_role_ids]).to_a |
129 | 138 |
end |
139 |
@target_workspaces = |
|
140 |
if params[:target_workspace_ids].blank? |
|
141 |
nil |
|
142 |
else |
|
143 |
Workspace.where(:id => params[:target_workspace_ids]).to_a |
|
144 |
end |
|
130 | 145 |
end |
131 | 146 | |
132 | 147 |
def find_trackers_roles_and_statuses_for_edit |
133 | 148 |
find_roles |
134 | 149 |
find_trackers |
150 |
find_workspaces |
|
135 | 151 |
find_statuses |
136 | 152 |
end |
137 | 153 | |
... | ... | |
155 | 171 |
@trackers = nil if @trackers.blank? |
156 | 172 |
end |
157 | 173 | |
174 |
def find_workspaces |
|
175 |
ids = Array.wrap(params[:workspace_id]) |
|
176 |
if ids == ['all'] |
|
177 |
@workspaces = Workspace.sorted.to_a |
|
178 |
elsif ids.present? |
|
179 |
@workspaces = Workspace.where(:id => ids).to_a |
|
180 |
end |
|
181 |
@workspaces = nil if @workspaces.blank? |
|
182 |
end |
|
183 | ||
158 | 184 |
def find_statuses |
159 | 185 |
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) |
160 |
if @trackers && @used_statuses_only |
|
186 |
@used_workspaces_only = (params[:used_workspaces_only] == '0' ? false : true) |
|
187 |
if @used_statuses_only || @used_workspaces_only |
|
161 | 188 |
role_ids = Role.all.select(&:consider_workflow?).map(&:id) |
162 |
status_ids = WorkflowTransition.where( |
|
163 |
:tracker_id => @trackers.map(&:id), :role_id => role_ids |
|
164 |
).where( |
|
165 |
'old_status_id <> new_status_id' |
|
166 |
).distinct.pluck(:old_status_id, :new_status_id).flatten.uniq |
|
167 |
@statuses = IssueStatus.where(:id => status_ids).sorted.to_a.presence |
|
189 |
status_ids = WorkflowRule.where(:role_id => role_ids).where('old_status_id <> new_status_id') |
|
190 |
status_ids = status_ids.where(:tracker_id => @trackers.map(&:id)) if @trackers && @used_statuses_only |
|
191 |
status_ids = status_ids.where(:workspace_id => @workspaces.map(&:id)) if @workspaces && @used_workspaces_only |
|
192 |
status_ids = status_ids.distinct.pluck(:old_status_id, :new_status_id).flatten.uniq |
|
193 |
@statuses = IssueStatus.where(:id => status_ids).sorted.to_a |
|
194 |
else |
|
195 |
@statuses = IssueStatus.sorted.to_a |
|
168 | 196 |
end |
169 |
@statuses ||= IssueStatus.sorted.to_a |
|
170 | 197 |
end |
171 | 198 |
end |
app/controllers/workspaces_controller.rb | ||
---|---|---|
1 |
# Redmine - project management software |
|
2 |
# Copyright (C) 2006-2017 Jean-Philippe Lang |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or |
|
5 |
# modify it under the terms of the GNU General Public License |
|
6 |
# as published by the Free Software Foundation; either version 2 |
|
7 |
# of the License, or (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
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 |
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
17 | ||
18 |
class WorkspacesController < ApplicationController |
|
19 |
layout 'admin' |
|
20 |
self.main_menu = false |
|
21 | ||
22 |
before_action :require_admin, :except => :index |
|
23 |
before_action :require_admin_or_api_request, :only => :index |
|
24 |
accept_api_auth :index |
|
25 | ||
26 |
def index |
|
27 |
@workspaces = Workspace.sorted.to_a |
|
28 |
respond_to do |format| |
|
29 |
format.html { render :layout => false if request.xhr? } |
|
30 |
format.api |
|
31 |
end |
|
32 |
end |
|
33 | ||
34 |
def new |
|
35 |
@workspace = Workspace.new |
|
36 |
end |
|
37 | ||
38 |
def create |
|
39 |
@workspace = Workspace.new |
|
40 |
@workspace.safe_attributes = params[:workspace] |
|
41 |
if @workspace.save |
|
42 |
flash[:notice] = l(:notice_successful_create) |
|
43 |
redirect_to workspaces_path |
|
44 |
else |
|
45 |
render :action => 'new' |
|
46 |
end |
|
47 |
end |
|
48 | ||
49 |
def edit |
|
50 |
@workspace = Workspace.find(params[:id]) |
|
51 |
end |
|
52 | ||
53 |
def update |
|
54 |
@workspace = Workspace.find(params[:id]) |
|
55 |
@workspace.safe_attributes = params[:workspace] |
|
56 |
if @workspace.save |
|
57 |
respond_to do |format| |
|
58 |
format.html { |
|
59 |
flash[:notice] = l(:notice_successful_update) |
|
60 |
redirect_to workspaces_path(:page => params[:page]) |
|
61 |
} |
|
62 |
format.js { head 200 } |
|
63 |
end |
|
64 |
else |
|
65 |
respond_to do |format| |
|
66 |
format.html { render :action => 'edit' } |
|
67 |
format.js { head 422 } |
|
68 |
end |
|
69 |
end |
|
70 |
end |
|
71 | ||
72 |
def destroy |
|
73 |
unless Project.where(:workspace_id => params[:id]).any? || params[:id] == "1" |
|
74 |
Workspace.find(params[:id]).destroy |
|
75 |
else |
|
76 |
flash[:error] = l(:error_unable_delete_workspace) |
|
77 |
end |
|
78 |
redirect_to workspaces_path |
|
79 |
end |
|
80 |
end |
app/helpers/members_helper.rb | ||
---|---|---|
48 | 48 |
s + content_tag('span', links, :class => 'pagination') |
49 | 49 |
end |
50 | 50 | |
51 |
def role_color_and_hide(role, project = nil) |
|
52 |
color_and_hide = "" |
|
53 |
if ! role.assignable |
|
54 |
color_and_hide += " style=color:darkblue" |
|
55 |
elsif ! project.nil? && ! role.workflow_rules.where(:workspace_id => project.workspace_id).any? |
|
56 |
color_and_hide += " class=unused style=color:darkred" |
|
57 |
end |
|
58 |
color_and_hide |
|
59 |
end |
|
60 | ||
51 | 61 |
# Returns inheritance information for an inherited member role |
52 | 62 |
def render_role_inheritance(member, role) |
53 | 63 |
content = member.role_inheritance(role).filter_map do |h| |
app/helpers/projects_helper.rb | ||
---|---|---|
114 | 114 |
principals_options_for_select(assignable_users, project.default_assigned_to) |
115 | 115 |
end |
116 | 116 | |
117 |
def project_workspace_options(project) |
|
118 |
grouped = Hash.new {|h,k| h[k] = []} |
|
119 |
Workspace.all.sorted.each do |workspace| |
|
120 |
grouped[workspace.name] = workspace.id |
|
121 |
end |
|
122 |
options_for_select(grouped, project.workspace_id) |
|
123 |
end |
|
124 | ||
125 |
def used_workspaces_by_tracker |
|
126 |
WorkflowRule.pluck(:tracker_id, :workspace_id).uniq.group_by(&:first).map{|k,a| [k,a.map{|x| "ws-" + x.last.to_s}.join(" ")]}.to_h |
|
127 |
end |
|
128 | ||
117 | 129 |
def project_default_issue_query_options(project) |
118 | 130 |
public_queries = IssueQuery.only_public |
119 | 131 |
grouped = { |
app/helpers/workflows_helper.rb | ||
---|---|---|
53 | 53 |
html_options = {} |
54 | 54 | |
55 | 55 |
if perm = permissions[status.id][name] |
56 |
if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size |
|
56 |
if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size * @workspaces.size
|
|
57 | 57 |
options << [l(:label_no_change_option), "no_change"] |
58 | 58 |
selected = 'no_change' |
59 | 59 |
else |
... | ... | |
80 | 80 |
if old_status == new_status |
81 | 81 |
check_box_tag(tag_name, "1", true, |
82 | 82 |
{:disabled => true, :class => "old-status-#{old_status.try(:id) || 0} new-status-#{new_status.id}"}) |
83 |
elsif w == 0 || w == @roles.size * @trackers.size |
|
83 |
elsif w == 0 || w == @roles.size * @trackers.size * @workspaces.size
|
|
84 | 84 |
hidden_field_tag(tag_name, "0", :id => nil) + |
85 | 85 |
check_box_tag(tag_name, "1", w != 0, |
86 | 86 |
:class => "old-status-#{old_status.try(:id) || 0} new-status-#{new_status.id}") |
app/models/issue.rb | ||
---|---|---|
693 | 693 |
workflow_permissions = |
694 | 694 |
WorkflowPermission.where( |
695 | 695 |
:tracker_id => tracker_id, :old_status_id => status_id, |
696 |
:role_id => roles.map(&:id) |
|
696 |
:role_id => roles.map(&:id), |
|
697 |
:workspace_id => project&.workspace_id |
|
697 | 698 |
).to_a |
698 | 699 |
if workflow_permissions.any? |
699 | 700 |
workflow_rules = workflow_permissions.inject({}) do |h, wp| |
... | ... | |
1075 | 1076 |
initial_status, |
1076 | 1077 |
roles_for_workflow(user), |
1077 | 1078 |
tracker, |
1079 |
project.workspace_id, |
|
1078 | 1080 |
author == user, |
1079 | 1081 |
assignee_transitions_allowed |
1080 |
) |
|
1082 |
) if project.present?
|
|
1081 | 1083 |
statuses << initial_status unless statuses.empty? |
1082 | 1084 |
statuses << default_status if include_default || (new_record? && statuses.empty?) |
1083 | 1085 |
app/models/issue_status.rb | ||
---|---|---|
56 | 56 |
end |
57 | 57 | |
58 | 58 |
# Returns an array of all statuses the given role can switch to |
59 |
def new_statuses_allowed_to(roles, tracker, author=false, assignee=false) |
|
60 |
self.class.new_statuses_allowed(self, roles, tracker, author, assignee) |
|
59 |
def new_statuses_allowed_to(roles, tracker, workspace_id, author=false, assignee=false)
|
|
60 |
self.class.new_statuses_allowed(self, roles, tracker, workspace_id, author, assignee)
|
|
61 | 61 |
end |
62 | 62 |
alias :find_new_statuses_allowed_to :new_statuses_allowed_to |
63 | 63 | |
64 |
def self.new_statuses_allowed(status, roles, tracker, author=false, assignee=false) |
|
65 |
if roles.present? && tracker |
|
64 |
def self.new_statuses_allowed(status, roles, tracker, workspace_id, author=false, assignee=false)
|
|
65 |
if roles.present? && tracker && workspace_id
|
|
66 | 66 |
status_id = status.try(:id) || 0 |
67 | 67 | |
68 | 68 |
scope = IssueStatus. |
69 | 69 |
joins(:workflow_transitions_as_new_status). |
70 |
where(:workflows => {:old_status_id => status_id, :role_id => roles.map(&:id), :tracker_id => tracker.id}) |
|
70 |
where(:workflows => {:old_status_id => status_id, :role_id => roles.map(&:id), :tracker_id => tracker.id, :workspace_id => workspace_id})
|
|
71 | 71 | |
72 | 72 |
unless author && assignee |
73 | 73 |
if author || assignee |
app/models/project.rb | ||
---|---|---|
41 | 41 |
has_many :versions, :dependent => :destroy |
42 | 42 |
belongs_to :default_version, :class_name => 'Version' |
43 | 43 |
belongs_to :default_assigned_to, :class_name => 'Principal' |
44 |
belongs_to :workspace |
|
44 | 45 |
has_many :time_entries, :dependent => :destroy |
45 | 46 |
# Specific overridden Activities |
46 | 47 |
has_many :time_entry_activities, :dependent => :destroy |
... | ... | |
367 | 368 |
@rolled_up_versions = nil |
368 | 369 |
@rolled_up_trackers = nil |
369 | 370 |
@rolled_up_statuses = nil |
371 |
@rolled_up_workspaces = nil |
|
370 | 372 |
@rolled_up_custom_fields = nil |
371 | 373 |
@all_issue_custom_fields = nil |
372 | 374 |
@all_time_entry_custom_fields = nil |
... | ... | |
489 | 491 |
sorted |
490 | 492 |
end |
491 | 493 | |
494 |
def rolled_up_workspaces(include_subprojects=true) |
|
495 |
if include_subprojects |
|
496 |
@rolled_up_workspaces ||= rolled_up_workspaces_base_scope. |
|
497 |
where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?", lft, rgt) |
|
498 |
else |
|
499 |
rolled_up_workspaces_base_scope. |
|
500 |
where(:projects => {:id => id}) |
|
501 |
end |
|
502 |
end |
|
503 | ||
504 |
def rolled_up_workspaces_base_scope |
|
505 |
Workspace. |
|
506 |
joins(projects: :enabled_modules). |
|
507 |
where("#{Project.table_name}.status <> ?", STATUS_ARCHIVED). |
|
508 |
where(:enabled_modules => {:name => 'issue_tracking'}). |
|
509 |
distinct. |
|
510 |
sorted |
|
511 |
end |
|
512 | ||
492 | 513 |
def rolled_up_statuses |
493 | 514 |
issue_status_ids = WorkflowTransition. |
494 | 515 |
where(:tracker_id => rolled_up_trackers.map(&:id)). |
516 |
where(:workspace_id => rolled_up_workspaces.map(&:id)). |
|
495 | 517 |
distinct. |
496 | 518 |
pluck(:old_status_id, :new_status_id). |
497 | 519 |
flatten. |
... | ... | |
835 | 857 |
'parent_id', |
836 | 858 |
'default_version_id', |
837 | 859 |
'default_issue_query_id', |
860 |
'workspace_id', |
|
838 | 861 |
'default_assigned_to_id') |
839 | 862 | |
840 | 863 |
safe_attributes( |
app/models/project_query.rb | ||
---|---|---|
36 | 36 |
QueryColumn.new(:identifier, :sortable => "#{Project.table_name}.identifier"), |
37 | 37 |
QueryColumn.new(:parent_id, :sortable => "#{Project.table_name}.lft ASC", :default_order => 'desc', :caption => :field_parent), |
38 | 38 |
QueryColumn.new(:is_public, :sortable => "#{Project.table_name}.is_public", :groupable => true), |
39 |
QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc') |
|
39 |
QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc'), |
|
40 |
QueryColumn.new(:workspace, :sortable => "#{Project.table_name}.workspace") |
|
40 | 41 |
] |
41 | 42 | |
42 | 43 |
def self.default(project: nil, user: User.current) |
... | ... | |
77 | 78 |
:values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] |
78 | 79 |
) |
79 | 80 |
add_available_filter "created_on", :type => :date_past |
81 |
add_available_filter "workspace_id", |
|
82 |
:type => :list, |
|
83 |
:values => Workspace.where.not(id: 1).order(:name).map {|w| [w.name, w.id.to_s]}. |
|
84 |
unshift([Workspace.find(1).name, Workspace.find(1).id.to_s]) |
|
80 | 85 |
add_custom_fields_filters(project_custom_fields) |
81 | 86 |
end |
82 | 87 |
app/models/role.rb | ||
---|---|---|
276 | 276 |
end |
277 | 277 | |
278 | 278 |
def copy_workflow_rules(source_role) |
279 |
WorkflowRule.copy(nil, source_role, nil, self)
|
|
279 |
WorkflowRule.copy(nil, source_role, nil, nil, self, nil)
|
|
280 | 280 |
end |
281 | 281 | |
282 | 282 |
# Find all the roles that can be given to a project member |
app/models/tracker.rb | ||
---|---|---|
143 | 143 |
end |
144 | 144 | |
145 | 145 |
def copy_workflow_rules(source_tracker) |
146 |
WorkflowRule.copy(source_tracker, nil, self, nil)
|
|
146 |
WorkflowRule.copy(source_tracker, nil, nil, self, nil, nil)
|
|
147 | 147 |
end |
148 | 148 | |
149 | 149 |
# Returns the fields that are disabled for all the given trackers |
app/models/workflow_permission.rb | ||
---|---|---|
22 | 22 |
validates_presence_of :old_status |
23 | 23 |
validate :validate_field_name |
24 | 24 | |
25 |
# Returns the workflow permissions for the given trackers and roles
|
|
25 |
# Returns the workflow permissions for the given trackers, roles and workspaces
|
|
26 | 26 |
# grouped by status_id |
27 | 27 |
# |
28 | 28 |
# Example: |
29 |
# WorkflowPermission.rules_by_status_id trackers, roles |
|
29 |
# WorkflowPermission.rules_by_status_id trackers, roles, workspaces
|
|
30 | 30 |
# # => {1 => {'start_date' => 'required', 'due_date' => 'readonly'}} |
31 |
def self.rules_by_status_id(trackers, roles) |
|
32 |
WorkflowPermission.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).inject({}) do |h, w| |
|
31 |
def self.rules_by_status_id(trackers, roles, workspaces)
|
|
32 |
WorkflowPermission.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :workspace_id => workspaces.map(&:id)).inject({}) do |h, w|
|
|
33 | 33 |
h[w.old_status_id] ||= {} |
34 | 34 |
h[w.old_status_id][w.field_name] ||= [] |
35 | 35 |
h[w.old_status_id][w.field_name] << w.rule |
... | ... | |
37 | 37 |
end |
38 | 38 |
end |
39 | 39 | |
40 |
# Replaces the workflow permissions for the given trackers and roles
|
|
40 |
# Replaces the workflow permissions for the given trackers, roles and workspaces
|
|
41 | 41 |
# |
42 | 42 |
# Example: |
43 |
# WorkflowPermission.replace_permissions trackers, roles, {'1' => {'start_date' => 'required', 'due_date' => 'readonly'}} |
|
44 |
def self.replace_permissions(trackers, roles, permissions) |
|
43 |
# WorkflowPermission.replace_permissions trackers, roles, {'1' => {'start_date' => 'required', 'due_date' => 'readonly'}}, workspaces
|
|
44 |
def self.replace_permissions(trackers, roles, permissions, workspaces)
|
|
45 | 45 |
trackers = Array.wrap trackers |
46 | 46 |
roles = Array.wrap roles |
47 |
workspaces = Array.wrap workspaces |
|
47 | 48 | |
48 | 49 |
transaction do |
49 | 50 |
permissions.each do |status_id, rule_by_field| |
50 | 51 |
rule_by_field.each do |field, rule| |
51 |
where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :old_status_id => status_id, :field_name => field).destroy_all |
|
52 |
where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :old_status_id => status_id, :field_name => field, :workspace_id => workspaces.map(&:id)).destroy_all
|
|
52 | 53 |
if rule.present? |
53 | 54 |
trackers.each do |tracker| |
54 | 55 |
roles.each do |role| |
55 |
WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule) |
|
56 |
workspaces.each do |workspace| |
|
57 |
WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule, :workspace_id => workspace.id) |
|
58 |
end |
|
56 | 59 |
end |
57 | 60 |
end |
58 | 61 |
end |
app/models/workflow_rule.rb | ||
---|---|---|
24 | 24 |
belongs_to :tracker |
25 | 25 |
belongs_to :old_status, :class_name => 'IssueStatus' |
26 | 26 |
belongs_to :new_status, :class_name => 'IssueStatus' |
27 |
belongs_to :workspace |
|
27 | 28 | |
28 |
validates_presence_of :role, :tracker |
|
29 |
validates_presence_of :role, :tracker, :workspace
|
|
29 | 30 | |
30 | 31 |
# Copies workflows from source to targets |
31 |
def self.copy(source_tracker, source_role, target_trackers, target_roles)
|
|
32 |
unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role) |
|
32 |
def self.copy(source_tracker, source_role, source_workspace, target_trackers, target_roles, target_workspaces)
|
|
33 |
unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role) || source_workspace.is_a?(Workspace)
|
|
33 | 34 |
raise ArgumentError.new( |
34 |
"source_tracker or source_role must be specified, given: " \
|
|
35 |
"#{source_tracker.class.name} and #{source_role.class.name}"
|
|
35 |
"source_tracker, source_role or source_workspace must be specified, given: " \
|
|
36 |
"#{source_tracker.class.name}, #{source_role.class.name} and #{source_workspace.class.name}"
|
|
36 | 37 |
) |
37 | 38 |
end |
38 | 39 | |
39 | 40 |
target_trackers = [target_trackers].flatten.compact |
40 | 41 |
target_roles = [target_roles].flatten.compact |
42 |
target_workspaces = [target_workspaces].flatten.compact |
|
41 | 43 | |
42 | 44 |
target_trackers = Tracker.sorted.to_a if target_trackers.empty? |
43 | 45 |
target_roles = Role.all.select(&:consider_workflow?) if target_roles.empty? |
46 |
target_workspaces = Workspace.sorted.to_a if target_workspaces.empty? |
|
44 | 47 | |
45 | 48 |
target_trackers.each do |target_tracker| |
46 | 49 |
target_roles.each do |target_role| |
47 |
copy_one(source_tracker || target_tracker, |
|
48 |
source_role || target_role, |
|
49 |
target_tracker, |
|
50 |
target_role) |
|
50 |
target_workspaces.each do |target_workspace| |
|
51 |
copy_one(source_tracker || target_tracker, |
|
52 |
source_role || target_role, |
|
53 |
source_workspace || target_workspace, |
|
54 |
target_tracker, |
|
55 |
target_role, |
|
56 |
target_workspace) |
|
57 |
end |
|
51 | 58 |
end |
52 | 59 |
end |
53 | 60 |
end |
54 | 61 | |
55 | 62 |
# Copies a single set of workflows from source to target |
56 |
def self.copy_one(source_tracker, source_role, target_tracker, target_role)
|
|
63 |
def self.copy_one(source_tracker, source_role, source_workspace, target_tracker, target_role, target_workspace)
|
|
57 | 64 |
unless source_tracker.is_a?(Tracker) && !source_tracker.new_record? && |
58 | 65 |
source_role.is_a?(Role) && !source_role.new_record? && |
66 |
source_workspace.is_a?(Workspace) && !source_workspace.new_record? && |
|
59 | 67 |
target_tracker.is_a?(Tracker) && !target_tracker.new_record? && |
60 |
target_role.is_a?(Role) && !target_role.new_record? |
|
68 |
target_role.is_a?(Role) && !target_role.new_record? && |
|
69 |
target_workspace.is_a?(Workspace) && !target_workspace.new_record? |
|
61 | 70 | |
62 | 71 |
raise ArgumentError.new("arguments can not be nil or unsaved objects") |
63 | 72 |
end |
64 | 73 | |
65 |
if source_tracker == target_tracker && source_role == target_role |
|
74 |
if source_tracker == target_tracker && source_role == target_role && source_workspace == target_workspace
|
|
66 | 75 |
false |
67 | 76 |
else |
68 | 77 |
transaction do |
69 |
where(:tracker_id => target_tracker.id, :role_id => target_role.id).delete_all |
|
78 |
where(:tracker_id => target_tracker.id, :role_id => target_role.id, :workspace_id => target_workspace.id).delete_all
|
|
70 | 79 |
connection.insert( |
71 | 80 |
"INSERT INTO #{WorkflowRule.table_name}" \ |
72 | 81 |
" (tracker_id, role_id, old_status_id, new_status_id," \ |
73 |
" author, assignee, field_name, #{connection.quote_column_name 'rule'}, type)" \ |
|
82 |
" author, assignee, field_name, #{connection.quote_column_name 'rule'}, type, workspace_id)" \
|
|
74 | 83 |
" SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id," \ |
75 |
" author, assignee, field_name, #{connection.quote_column_name 'rule'}, type" \ |
|
84 |
" author, assignee, field_name, #{connection.quote_column_name 'rule'}, type, #{target_workspace.id}" \
|
|
76 | 85 |
" FROM #{WorkflowRule.table_name}" \ |
77 |
" WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" |
|
86 |
" WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id} AND workspace_id = #{source_workspace.id}"
|
|
78 | 87 |
) |
79 | 88 |
end |
80 | 89 |
true |
app/models/workflow_transition.rb | ||
---|---|---|
20 | 20 |
class WorkflowTransition < WorkflowRule |
21 | 21 |
validates_presence_of :new_status |
22 | 22 | |
23 |
def self.replace_transitions(trackers, roles, transitions) |
|
23 |
def self.replace_transitions(trackers, roles, transitions, workspaces)
|
|
24 | 24 |
trackers = Array.wrap trackers |
25 | 25 |
roles = Array.wrap roles |
26 |
workspaces = Array.wrap workspaces |
|
26 | 27 | |
27 | 28 |
transaction do |
28 |
records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).to_a |
|
29 |
records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :workspace_id => workspaces.map(&:id)).to_a
|
|
29 | 30 | |
30 | 31 |
transitions.each do |old_status_id, transitions_by_new_status| |
31 | 32 |
transitions_by_new_status.each do |new_status_id, transition_by_rule| |
32 | 33 |
transition_by_rule.each do |rule, transition| |
33 | 34 |
trackers.each do |tracker| |
34 | 35 |
roles.each do |role| |
35 |
w = records.select do |r| |
|
36 |
r.old_status_id == old_status_id.to_i && |
|
37 |
r.new_status_id == new_status_id.to_i && |
|
38 |
r.tracker_id == tracker.id && |
|
39 |
r.role_id == role.id && |
|
40 |
!r.destroyed? |
|
41 |
end |
|
42 |
if rule == 'always' |
|
43 |
w = w.select {|r| !r.author && !r.assignee} |
|
44 |
else |
|
45 |
w = w.select {|r| r.author || r.assignee} |
|
46 |
end |
|
47 |
if w.size > 1 |
|
48 |
w[1..-1].each(&:destroy) |
|
49 |
end |
|
50 |
w = w.first |
|
51 | ||
52 |
if ["1", true].include?(transition) |
|
53 |
unless w |
|
54 |
w = WorkflowTransition. |
|
55 |
new( |
|
56 |
:old_status_id => old_status_id, |
|
57 |
:new_status_id => new_status_id, |
|
58 |
:tracker_id => tracker.id, |
|
59 |
:role_id => role.id |
|
60 |
) |
|
61 |
records << w |
|
36 |
workspaces.each do |workspace| |
|
37 |
w = records.select do |r| |
|
38 |
r.old_status_id == old_status_id.to_i && |
|
39 |
r.new_status_id == new_status_id.to_i && |
|
40 |
r.tracker_id == tracker.id && |
|
41 |
r.role_id == role.id && |
|
42 |
r.workspace_id == workspace.id && |
|
43 |
!r.destroyed? |
|
62 | 44 |
end |
63 |
w.author = true if rule == "author" |
|
64 |
w.assignee = true if rule == "assignee" |
|
65 |
w.save if w.changed? |
|
66 |
elsif w |
|
67 | 45 |
if rule == 'always' |
68 |
w.destroy |
|
69 |
elsif rule == 'author' |
|
70 |
if w.assignee |
|
71 |
w.author = false |
|
72 |
w.save if w.changed? |
|
73 |
else |
|
74 |
w.destroy |
|
46 |
w = w.select {|r| !r.author && !r.assignee} |
|
47 |
else |
|
48 |
w = w.select {|r| r.author || r.assignee} |
|
49 |
end |
|
50 |
if w.size > 1 |
|
51 |
w[1..-1].each(&:destroy) |
|
52 |
end |
|
53 |
w = w.first |
|
54 | ||
55 |
if ["1", true].include?(transition) |
|
56 |
unless w |
|
57 |
w = WorkflowTransition. |
|
58 |
new( |
|
59 |
:old_status_id => old_status_id, |
|
60 |
:new_status_id => new_status_id, |
|
61 |
:tracker_id => tracker.id, |
|
62 |
:role_id => role.id, |
|
63 |
:workspace_id => workspace.id |
|
64 |
) |
|
65 |
records << w |
|
75 | 66 |
end |
76 |
elsif rule == 'assignee'
|
|
77 |
if w.author
|
|
78 |
w.assignee = false
|
|
79 |
w.save if w.changed?
|
|
80 |
else
|
|
67 |
w.author = true if rule == "author"
|
|
68 |
w.assignee = true if rule == "assignee"
|
|
69 |
w.save if w.changed?
|
|
70 |
elsif w
|
|
71 |
if rule == 'always'
|
|
81 | 72 |
w.destroy |
73 |
elsif rule == 'author' |
|
74 |
if w.assignee |
|
75 |
w.author = false |
|
76 |
w.save if w.changed? |
|
77 |
else |
|
78 |
w.destroy |
|
79 |
end |
|
80 |
elsif rule == 'assignee' |
|
81 |
if w.author |
|
82 |
w.assignee = false |
|
83 |
w.save if w.changed? |
|
84 |
else |
|
85 |
w.destroy |
|
86 |
end |
|
82 | 87 |
end |
83 | 88 |
end |
84 | 89 |
end |
app/models/workspace.rb | ||
---|---|---|
1 |
# Redmine - project management software |
|
2 |
# Copyright (C) 2006-2017 Jean-Philippe Lang |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or |
|
5 |
# modify it under the terms of the GNU General Public License |
|
6 |
# as published by the Free Software Foundation; either version 2 |
|
7 |
# of the License, or (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
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 |
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
17 | ||
18 |
class Workspace < ActiveRecord::Base |
|
19 |
include Redmine::SafeAttributes |
|
20 | ||
21 |
before_destroy :check_integrity |
|
22 |
has_many :projects |
|
23 |
has_many :workflow_rules, :dependent => :delete_all |
|
24 |
acts_as_positioned |
|
25 | ||
26 |
validates_presence_of :name |
|
27 |
validates_uniqueness_of :name |
|
28 |
validates_length_of :name, :maximum => 30 |
|
29 | ||
30 |
scope :sorted, lambda { order(:position) } |
|
31 | ||
32 |
safe_attributes 'name', |
|
33 |
'description', |
|
34 |
'position' |
|
35 | ||
36 |
# Returns an array of IssueStatus that are used in the tracker's workflows |
|
37 |
def issue_statuses |
|
38 |
if @issue_statuses |
|
39 |
return @issue_statuses |
|
40 |
elsif new_record? |
|
41 |
return [] |
|
42 |
end |
|
43 | ||
44 |
ids = WorkflowTransition.where(workspace_id: id).map{|w| [w.old_status_id, w.new_status_id]}.flatten.uniq |
|
45 |
@issue_statuses = IssueStatus.where(:id => ids).all.sort |
|
46 |
end |
|
47 | ||
48 |
def <=>(workspace) |
|
49 |
position <=> workspace.position |
|
50 |
end |
|
51 | ||
52 |
def to_s; name end |
|
53 | ||
54 |
private |
|
55 |
def check_integrity |
|
56 |
raise Exception.new("Cannot delete workspace") if Project.where(:workspace_id => self.id).any? |
|
57 |
end |
|
58 |
end |
app/views/members/_edit.html.erb | ||
---|---|---|
3 | 3 |
:remote => request.xhr?, |
4 | 4 |
:method => :put) do |f| %> |
5 | 5 |
<p> |
6 |
<% @roles.each do |role| %> |
|
7 |
<label class="block"> |
|
6 |
<strong> |
|
7 |
<%= l(:label_role_plural) %> |
|
8 |
(<a onclick="$(this).closest('form').children('div.unused').toggle();"><%= l(:label_all) %></a>): |
|
9 |
</strong> |
|
10 |
</p> |
|
11 |
<% @roles.each do |role| %> |
|
12 |
<div<%= role_color_and_hide(role, @project) %>> |
|
8 | 13 |
<%= check_box_tag('membership[role_ids][]', |
9 | 14 |
role.id, @member.roles.to_a.include?(role), |
10 | 15 |
:id => nil, |
11 | 16 |
:disabled => !@member.role_editable?(role)) %> <%= role %> |
12 | 17 |
<%= render_role_inheritance(@member, role) %> |
13 |
</label> |
|
14 |
<% end %> |
|
15 |
</p> |
|
18 |
</div> |
|
19 |
<% end %> |
|
16 | 20 |
<%= hidden_field_tag 'membership[role_ids][]', '', :id => nil %> |
17 | 21 |
<p> |
18 | 22 |
<%= submit_tag l(:button_save) %> |
app/views/members/_new_form.html.erb | ||
---|---|---|
7 | 7 |
</div> |
8 | 8 |
</fieldset> |
9 | 9 |
<fieldset class="box"> |
10 |
<legend><%= toggle_checkboxes_link('.roles-selection input') %><%= l(:label_role_plural) %></legend>
|
|
10 |
<legend><%= toggle_checkboxes_link('.roles-selection input:visible') %><%= l(:label_role_plural) %> (<a onclick='$(".unused").toggleClass("show");'><%= l(:label_all) %></a>)</legend>
|
|
11 | 11 |
<div class="roles-selection"> |
12 | 12 |
<% User.current.managed_roles(@project).each do |role| %> |
13 |
<label><%= check_box_tag 'membership[role_ids][]', role.id, false, :id => nil %> <%= role %></label>
|
|
13 |
<label<%= role_color_and_hide(role, @project) %>><%= check_box_tag 'membership[role_ids][]', role.id, false, :id => nil %><%= role %></label>
|
|
14 | 14 |
<% end %> |
15 | 15 |
</div> |
16 | 16 |
</fieldset> |
app/views/principal_memberships/_edit.html.erb | ||
---|---|---|
3 | 3 |
:remote => request.xhr?, |
4 | 4 |
:method => :put) do %> |
5 | 5 |
<p> |
6 |
<strong> |
|
7 |
<%= l(:label_role_plural) %> |
|
8 |
(<a onclick="$(this).closest('form').children('div.unused').toggle();"><%= l(:label_all) %></a>): |
|
9 |
</strong> |
|
10 |
</p> |
|
6 | 11 |
<% @roles.each do |role| %> |
7 |
<label class="block">
|
|
12 |
<div<%= role_color_and_hide(role, @membership.project) %>>
|
|
8 | 13 |
<%= check_box_tag 'membership[role_ids][]', |
9 | 14 |
role.id, @membership.roles.to_a.include?(role), |
10 | 15 |
:id => nil, |
11 | 16 |
:disabled => !@membership.role_editable?(role) %> <%= role.name %> |
12 | 17 |
<%= render_role_inheritance(@membership, role) %> |
13 |
</label>
|
|
18 |
</div>
|
|
14 | 19 |
<% end %> |
15 |
</p> |
|
16 | 20 |
<%= hidden_field_tag 'membership[role_ids][]', '', :id => nil %> |
17 | 21 |
<p> |
18 | 22 |
<%= submit_tag l(:button_save) %> |
app/views/principal_memberships/_new_form.html.erb | ||
---|---|---|
15 | 15 |
<legend><%= toggle_checkboxes_link('.roles-selection input') %><%= l(:label_role_plural) %></legend> |
16 | 16 |
<div class="roles-selection"> |
17 | 17 |
<% @roles.each do |role| %> |
18 |
<label> |
|
18 |
<label<%= role_color_and_hide(role) %>>
|
|
19 | 19 |
<%= check_box_tag 'membership[role_ids][]', role.id, false, :id => nil %> |
20 | 20 |
<%= role %> |
21 | 21 |
</label> |
app/views/projects/_form.html.erb | ||
---|---|---|
19 | 19 |
<p><%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %></p> |
20 | 20 |
<% end %> |
21 | 21 | |
22 |
<% if @project.safe_attribute? 'workspace_id' %> |
|
23 |
<p><%= f.select :workspace_id, project_workspace_options(@project), {}, :onchange => "$('#project_trackers > label').hide(); $('#project_trackers > label.ws-' + this.value).show();" %></p> |
|
24 |
<% end %> |
|
25 | ||
22 | 26 |
<% if @project.safe_attribute? 'inherit_members' %> |
23 | 27 |
<p><%= f.check_box :inherit_members %></p> |
24 | 28 |
<% end %> |
app/views/projects/index.api.rsb | ||
---|---|---|
8 | 8 |
api.homepage project.homepage |
9 | 9 |
api.parent(:id => project.parent.id, :name => project.parent.name) if project.parent && project.parent.visible? |
10 | 10 |
api.status project.status |
11 |
api.workspace project.workspace.id |
|
11 | 12 |
api.is_public project.is_public? |
12 | 13 |
api.inherit_members project.inherit_members? |
13 | 14 |
app/views/projects/settings/_issues.html.erb | ||
---|---|---|
2 | 2 |
<%= hidden_field_tag 'tab', 'issues' %> |
3 | 3 | |
4 | 4 |
<% unless @trackers.empty? %> |
5 |
<fieldset class="box tabular" id="project_trackers"><legend><%= toggle_checkboxes_link('#project_trackers input[type=checkbox]') %><%= l(:label_tracker_plural)%></legend>
|
|
5 |
<fieldset class="box tabular" id="project_trackers"><legend><%= toggle_checkboxes_link('#project_trackers input[type=checkbox]:enabled:visible') %><%= l(:label_tracker_plural)%> (<a onclick="ws='.ws-' + $('#project_workspace_id').val(); $('#project_trackers > label' + ws).show(); $('#project_trackers > label:not(' + ws + ')').toggle();"><%= l(:label_all) %></a>)</legend>
|
|
6 | 6 |
<% if User.current.admin? %> |
7 | 7 |
<div class="contextual"><%= link_to l(:label_administration), trackers_path, :class => "icon icon-settings" %></div> |
8 | 8 |
<% end %> |
9 |
<% uwbt=used_workspaces_by_tracker %> |
|
9 | 10 |
<% @trackers.each do |tracker| %> |
10 |
<label class="floating">
|
|
11 |
<label class="floating <%= uwbt[tracker.id].to_s %>"<% unless uwbt[tracker.id].to_s.match(/\b#{@project.workspace_id}\b/) %>style="display: none;"<% end %>>
|
|
11 | 12 |
<%= check_box_tag 'project[tracker_ids][]', tracker.id, @project.trackers.to_a.include?(tracker), :id => nil %> |
12 | 13 |
<%= tracker_name_tag tracker %> |
13 | 14 |
</label> |
app/views/projects/show.api.rsb | ||
---|---|---|
5 | 5 |
api.description @project.description |
6 | 6 |
api.homepage @project.homepage |
7 | 7 |
api.parent(:id => @project.parent.id, :name => @project.parent.name) if @project.parent && @project.parent.visible? |
8 |
api.workspace(:id => @project.workspace.id, :name => @project.workspace.name) if User.current.admin? |
|
8 | 9 |
api.status @project.status |
9 | 10 |
api.is_public @project.is_public? |
10 | 11 |
api.inherit_members @project.inherit_members? |
app/views/projects/show.html.erb | ||
---|---|---|
42 | 42 |
<% render_custom_field_values(@project) do |custom_field, formatted| %> |
43 | 43 |
<li class="<%= custom_field.css_classes %>"><span class="label"><%= custom_field.name %>:</span> <%= formatted %></li> |
44 | 44 |
<% end %> |
45 |
<% if User.current.admin? %> |
|
46 |
<li><span class="label"><%=l(:field_workspace)%>:</span> <%= @project.workspace.name %></li> |
|
47 |
<% end %> |
|
45 | 48 |
</ul> |
46 | 49 |
<% end %> |
47 | 50 |
app/views/workflows/_index_table.html.erb | ||
---|---|---|
1 |
<div class="autoscroll"> |
|
2 |
<table class="list"> |
|
3 |
<thead> |
|
4 |
<tr> |
|
5 |
<th> |
|
6 |
</th> |
|
7 |
<% @roles.each do |role| %> |
|
8 |
<% next unless @workflow_counts.select{|i,v| i[1]==role.id && i[2]==@workspace.id}.present? %> |
|
9 |
<th> |
|
10 |
<%= content_tag(role.builtin? ? 'em' : 'span', role.name) %> |
|
11 |
</th> |
|
12 |
<% end %> |
|
13 |
</tr> |
|
14 |
</thead> |
|
15 |
<tbody> |
|
16 |
<% @trackers.each do |tracker| -%> |
|
17 |
<% next unless @workflow_counts.select{|i,v| i[0]==tracker.id && i[2]==@workspace.id}.present? %> |
|
18 |
<tr> |
|
19 |
<td class="name"><%= tracker.name %></td> |
|
20 |
<% @roles.each do |role| -%> |
|
21 |
<% next unless @workflow_counts.select{|i,v| i[1]==role.id && i[2]==@workspace.id}.present? %> |
|
22 |
<td> |
|
23 |
<% count = @workflow_counts[[tracker.id, role.id, @workspace.id]] || 0 %> |
|
24 |
<%= link_to((count > 0 ? count : content_tag(:span, nil, :class => 'icon-only icon-not-ok')), |
|
25 |
{:action => 'edit', :role_id => role, :tracker_id => tracker, :workspace_id => @workspace}, |
|
26 |
:title => l(:button_edit)) %> |
|
27 |
</td> |
|
28 |
<% end -%> |
|
29 |
</tr> |
|
30 |
<% end -%> |
|
31 |
</tbody> |
|
32 |
</table> |
|
33 |
</div> |
app/views/workflows/copy.html.erb | ||
---|---|---|
3 | 3 |
<%= form_tag duplicate_workflows_path, method: :post, id: 'workflow_copy_form' do %> |
4 | 4 |
<fieldset class="tabular box"> |
5 | 5 |
<legend><%= l(:label_copy_source) %></legend> |
6 |
<p> |
|
7 |
<label><%= l(:label_role) %></label> |
|
8 |
<%= select_tag('source_role_id', |
|
9 |
content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '') + |
|
10 |
content_tag('option', "--- #{ l(:label_copy_same_as_target) } ---", :value => 'any') + |
|
11 |
options_from_collection_for_select(@roles, 'id', 'name', @source_role && @source_role.id)) %> |
|
12 |
</p> |
|
6 | 13 |
<p> |
7 | 14 |
<label><%= l(:label_tracker) %></label> |
8 | 15 |
<%= select_tag('source_tracker_id', |
... | ... | |
11 | 18 |
options_from_collection_for_select(@trackers, 'id', 'name', @source_tracker && @source_tracker.id)) %> |
12 | 19 |
</p> |
13 | 20 |
<p> |
14 |
<label><%= l(:label_role) %></label>
|
|
15 |
<%= select_tag('source_role_id',
|
|
21 |
<label><%= l(:label_workspace) %></label>
|
|
22 |
<%= select_tag('source_workspace_id',
|
|
16 | 23 |
content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '') + |
17 | 24 |
content_tag('option', "--- #{ l(:label_copy_same_as_target) } ---", :value => 'any') + |
18 |
options_from_collection_for_select(@roles, 'id', 'name', @source_role && @source_role.id)) %>
|
|
25 |
options_from_collection_for_select(@workspaces, 'id', 'name', @source_workspace && @source_workspace.id)) %>
|
|
19 | 26 |
</p> |
20 | 27 |
</fieldset> |
21 | 28 | |
22 | 29 |
<fieldset class="tabular box"> |
23 | 30 |
<legend><%= l(:label_copy_target) %></legend> |
31 |
<p> |
|
32 |
<label><%= l(:label_role) %></label> |
|
33 |
<%= select_tag 'target_role_ids', |
|
34 |
content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '', :disabled => true) + |
|
35 |
options_from_collection_for_select(@roles, 'id', 'name', @target_roles && @target_roles.map(&:id)), :multiple => true %> |
|
36 |
</p> |
|
24 | 37 |
<p> |
25 | 38 |
<label><%= l(:label_tracker) %></label> |
26 | 39 |
<%= select_tag 'target_tracker_ids', |
... | ... | |
28 | 41 |
options_from_collection_for_select(@trackers, 'id', 'name', @target_trackers && @target_trackers.map(&:id)), :multiple => true %> |
29 | 42 |
</p> |
30 | 43 |
<p> |
31 |
<label><%= l(:label_role) %></label>
|
|
32 |
<%= select_tag 'target_role_ids',
|
|
44 |
<label><%= l(:label_workspace) %></label>
|
|
45 |
<%= select_tag 'target_workspace_ids',
|
|
33 | 46 |
content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '', :disabled => true) + |
34 |
options_from_collection_for_select(@roles, 'id', 'name', @target_roles && @target_roles.map(&:id)), :multiple => true %>
|
|
47 |
options_from_collection_for_select(@workspaces, 'id', 'name', @target_workspaces && @target_workspaces.map(&:id)), :multiple => true %>
|
|
35 | 48 |
</p> |
36 | 49 |
</fieldset> |
37 | 50 |
<%= submit_tag l(:button_copy) %> |
app/views/workflows/edit.html.erb | ||
---|---|---|
4 | 4 | |
5 | 5 |
<div class="tabs"> |
6 | 6 |
<ul> |
7 |
<li><%= link_to l(:label_status_transitions), edit_workflows_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li> |
|
8 |
<li><%= link_to l(:label_fields_permissions), permissions_workflows_path(:role_id => @roles, :tracker_id => @trackers) %></li> |
|
7 |
<li><%= link_to l(:label_status_transitions), edit_workflows_path(:role_id => @roles, :tracker_id => @trackers, :workspace_id => @workspaces), :class => 'selected' %></li>
|
|
8 |
<li><%= link_to l(:label_fields_permissions), permissions_workflows_path(:role_id => @roles, :tracker_id => @trackers, :workspace_id => @workspaces) %></li>
|
|
9 | 9 |
</ul> |
10 | 10 |
</div> |
11 | 11 | |
... | ... | |
23 | 23 |
<span class="toggle-multiselect icon-only"></span> |
24 | 24 |
</label> |
25 | 25 | |
26 |
<label><%=l(:label_workspace)%>: |
|
27 |
<%= options_for_workflow_select 'workspace_id[]', Workspace.sorted, @workspaces, :id => 'workspace_id', :class => 'expandable' %> |
|
28 |
<span class="toggle-multiselect icon-only"></span> |
|
29 |
</label> |
|
30 | ||
26 | 31 |
<%= submit_tag l(:button_edit), :name => nil %> |
27 | 32 | |
28 | 33 |
<%= hidden_field_tag 'used_statuses_only', '0', :id => nil %> |
29 | 34 |
<label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label> |
30 | 35 | |
36 |
<%= hidden_field_tag 'used_workspaces_only', '0', :id => nil %> |
|
37 |
<label><%= check_box_tag 'used_workspaces_only', '1', @used_workspaces_only %> <%= l(:label_display_used_workspaces_only) %></label> |
|
31 | 38 |
</p> |
32 | 39 |
<% end %> |
33 | 40 | |
34 |
<% if @trackers && @roles && @statuses.any? %> |
|
41 |
<% if @trackers && @roles && @workspaces && @statuses.any? %>
|
|
35 | 42 |
<%= form_tag workflows_path, method: :patch, id: 'workflow_form' do %> |
36 | 43 |
<%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id, :id => nil}.join.html_safe %> |
37 | 44 |
<%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id, :id => nil}.join.html_safe %> |
45 |
<%= @workspaces.map {|workspace| hidden_field_tag 'workspace_id[]', workspace.id, :id => nil}.join.html_safe %> |
|
38 | 46 |
<%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only], :id => nil %> |
47 |
<%= hidden_field_tag 'used_workspaces_only', params[:used_workspaces_only], :id => nil %> |
|
39 | 48 |
<div class="autoscroll"> |
40 | 49 |
<%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %> |
41 | 50 | |
... | ... | |
57 | 66 |
</div> |
58 | 67 |
<%= submit_tag l(:button_save) %> |
59 | 68 |
<% end %> |
60 |
<% end %> |
|
69 |
<% end %> |
app/views/workflows/index.html.erb | ||
---|---|---|
1 | 1 |
<%= title [l(:label_workflow), edit_workflows_path], l(:field_summary) %> |
2 | 2 | |
3 | 3 |
<% if @roles.empty? || @trackers.empty? %> |
4 |
<p class="nodata"><%= l(:label_no_data) %></p> |
|
4 |
<p class="nodata"><%= l(:label_no_data) %></p>
|
|
5 | 5 |
<% else %> |
6 |
<div class="autoscroll"> |
|
7 |
<table class="list"> |
|
8 |
<thead> |
|
9 |
<tr> |
|
10 |
<th></th> |
|
11 |
<% @roles.each do |role| %> |
|
12 |
<th> |
|
13 |
<%= content_tag(role.builtin? ? 'em' : 'span', role.name) %> |
|
14 |
</th> |
|
15 |
<% end %> |
|
16 |
</tr> |
|
17 |
</thead> |
|
18 |
<tbody> |
|
19 |
<% @trackers.each do |tracker| -%> |
|
20 |
<tr> |
|
21 |
<td class="name"><%= tracker.name %></td> |
|
22 |
<% @roles.each do |role| -%> |
|
23 |
<% count = @workflow_counts[[tracker.id, role.id]] || 0 %> |
|
24 |
<td> |
|
25 |
<%= link_to((count > 0 ? count : content_tag(:span, nil, :class => 'icon-only icon-not-ok')), |
|
26 |
{:action => 'edit', :role_id => role, :tracker_id => tracker}, |
|
27 |
:title => l(:button_edit)) %> |
|
28 |
</td> |
|
29 |
<% end -%> |
|
30 |
</tr> |
|
31 |
<% end -%> |
|
32 |
</tbody> |
|
33 |
</table> |
|
34 |
</div> |
|
6 |
<p><label><%=l(:label_workspace)%>: |
|
7 |
<select id="workspace_id" class="expandable" onchange="location.href=location.href.split('?')[0] + '?workspace_id=' + this.options[this.selectedIndex].value;"> |
|
8 |
<%= options_for_select(@workspaces.pluck(:name, :id), @workspace.id) %> |
|
9 |
</select> |
|
10 |
</label></p> |
|
11 |
<div class="autoscroll ws"> |
|
12 |
<%= render :partial => 'index_table' %> |
|
13 |
</div> |
|
35 | 14 |
<% end %> |
app/views/workflows/permissions.html.erb | ||
---|---|---|
4 | 4 | |
5 | 5 |
<div class="tabs"> |
6 | 6 |
<ul> |
7 |
<li><%= link_to l(:label_status_transitions), edit_workflows_path(:role_id => @roles, :tracker_id => @trackers) %></li> |
|
8 |
<li><%= link_to l(:label_fields_permissions), permissions_workflows_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li> |
|
7 |
<li><%= link_to l(:label_status_transitions), edit_workflows_path(:role_id => @roles, :tracker_id => @trackers, :workspace_id => @workspaces) %></li>
|
|
8 |
<li><%= link_to l(:label_fields_permissions), permissions_workflows_path(:role_id => @roles, :tracker_id => @trackers, :workspace_id => @workspaces), :class => 'selected' %></li>
|
|
9 | 9 |
</ul> |
10 | 10 |
</div> |
11 | 11 | |
... | ... | |
22 | 22 |
<%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %> |
23 | 23 |
<span class="toggle-multiselect icon-only"></span> |
24 | 24 |
</label> |
25 | ||
26 |
<label><%=l(:label_workspace)%>: |
|
27 |
<%= options_for_workflow_select 'workspace_id[]', Workspace.sorted, @workspaces, :id => 'workspace_id', :class => 'expandable' %> |
|
28 |
<span class="toggle-multiselect icon-only"></span> |
|
29 |
</label> |
|
25 | 30 |
<%= submit_tag l(:button_edit), :name => nil %> |
26 | 31 | |
27 | 32 |
<%= hidden_field_tag 'used_statuses_only', '0', :id => nil %> |
28 | 33 |
<label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label> |
34 | ||
35 |
<%= hidden_field_tag 'used_workspaces_only', '0', :id => nil %> |
|
36 |
<label><%= check_box_tag 'used_workspaces_only', '1', @used_workspaces_only %> <%= l(:label_display_used_workspaces_only) %></label> |
|
29 | 37 |
</p> |
30 | 38 |
<% end %> |
31 | 39 | |
32 |
<% if @trackers && @roles && @statuses.any? %> |
|
40 |
<% if @trackers && @roles && @workspaces && @statuses.any? %>
|
|
33 | 41 |
<%= form_tag update_permissions_workflows_path, method: :patch, id: 'workflow_form' do %> |
34 | 42 |
<%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id, :id => nil}.join.html_safe %> |
35 | 43 |
<%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id, :id => nil}.join.html_safe %> |
44 |
<%= @workspaces.map {|workspace| hidden_field_tag 'workspace_id[]', workspace.id, :id => nil}.join.html_safe %> |
|
36 | 45 |
<%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only], :id => nil %> |
46 |
<%= hidden_field_tag 'used_workspaces_only', params[:used_workspaces_only], :id => nil %> |
|
37 | 47 |
<div class="autoscroll"> |
38 | 48 |
<table class="list workflows fields_permissions"> |
39 | 49 |
<thead> |
app/views/workspaces/_form.html.erb | ||
---|---|---|
1 |
<%= error_messages_for 'workspace' %> |
|
2 | ||
3 |
<div class="box tabular"> |
|
4 |
<p><%= f.text_field :name, :required => true %></p> |
|
5 |
<p><%= f.text_field :description %></p> |
|
6 | ||
7 |
<%= call_hook(:view_workspaces_form, :workspace => @workspace) %> |
|
8 |
</div> |
app/views/workspaces/edit.html.erb | ||
---|---|---|
1 |
<%= title [l(:label_workspace_plural), workspaces_path], @workspace.name %> |
|
2 | ||
3 |
<%= labelled_form_for @workspace do |f| %> |
|
4 |
<%= render :partial => 'form', :locals => {:f => f} %> |
|
5 |
<%= submit_tag l(:button_save) %> |
|
6 |
<% end %> |
app/views/workspaces/index.api.rsb | ||
---|---|---|
1 |
api.array :workspaces do |
|
2 |
@workspaces.each do |workspace| |
|
3 |
api.workspace do |
|
4 |
api.id workspace.id |
|
5 |
api.name workspace.name |
|
6 |
api.description workspace.description |
|
7 |
end |
|
8 |
end |
|
9 |
end |
app/views/workspaces/index.html.erb | ||
---|---|---|
1 |
<div class="contextual"> |
|
2 |
<%= link_to l(:label_workspace_new), new_workspace_path, :class => 'icon icon-add' %> |
|
3 |
</div> |
|
4 | ||
5 |
<h2><%=l(:label_workspace_plural)%></h2> |
|
6 | ||
7 |
<table class="list workspaces"> |
|
8 |
<thead><tr> |
|
9 |
<th><%=l(:field_name)%></th> |
|
10 |
<th><%=l(:field_description)%></th> |
|
11 |
<th></th> |
|
12 |
</tr></thead> |
|
13 |
<tbody> |
|
14 |
<% for workspace in @workspaces %> |
|
15 |
<tr class="<%= workspace.id == 1 ? "builtin" : "givable" %>"> |
|
16 |
<td class="name"><%= link_to workspace.name, edit_workspace_path(workspace) %></td> |
|
17 |
<td class="description"><%= link_to workspace.description, edit_workspace_path(workspace) %></td> |
|
18 |
<td class="buttons"> |
|
19 |
<%= reorder_handle(workspace) unless workspace.id == 1 %> |
|
20 |
<%= delete_link workspace_path(workspace) unless workspace.id == 1 %> |
|
21 |
</td> |
|
22 |
</tr> |
|
23 |
<% end %> |
|
24 |
</tbody> |
|
25 |
</table> |
|
26 | ||
27 |
<% html_title(l(:label_workspace_plural)) -%> |
|
28 | ||
29 |
<%= javascript_tag do %> |
|
30 |
$(function() { $("table.workspaces tbody").positionedItems({items: ".givable"}); }); |
|
31 |
<% end %> |
app/views/workspaces/new.html.erb | ||
---|---|---|
1 |
<%= title [l(:label_workspace_plural), workspaces_path], l(:label_workspace_new) %> |
|
2 | ||
3 |
<%= labelled_form_for @workspace do |f| %> |
|
4 |
<%= render :partial => 'form', :locals => {:f => f} %> |
|
5 |
<%= submit_tag l(:button_create) %> |
|
6 |
<% end %> |
config/locales/en.yml | ||
---|---|---|
646 | 646 |
label_tracker_plural: Trackers |
647 | 647 |
label_tracker_all: All trackers |
648 | 648 |
label_tracker_new: New tracker |
649 |
label_workspace: Workspace |
|
650 |
label_workspace_plural: Workspaces |
|
651 |
label_workspace_new: New workspace |
|
652 |
error_unable_delete_workspace: Unable to delete workspace |
|
653 |
field_workspace: Workspace |
|
649 | 654 |
label_workflow: Workflow |
650 | 655 |
label_issue_status: Issue status |
651 | 656 |
label_issue_status_plural: Issue statuses |
... | ... | |
1001 | 1006 |
label_copy_target: Target |
1002 | 1007 |
label_copy_same_as_target: Same as target |
1003 | 1008 |
label_display_used_statuses_only: Only display statuses that are used by this tracker |
1009 |
label_display_used_workspaces_only: Only display statuses that are used by this workspace |
|
1004 | 1010 |
label_api_access_key: API access key |
1005 | 1011 |
label_missing_api_access_key: Missing an API access key |
1006 | 1012 |
label_api_access_key_created_on: "API access key created %{value} ago" |
config/locales/pt-BR.yml | ||
---|---|---|
378 | 378 |
label_tracker: Tipo de tarefa |
379 | 379 |
label_tracker_plural: Tipos de tarefas |
380 | 380 |
label_tracker_new: Novo tipo |
381 |
label_workspace: Espaço de trabalho |
|
382 |
label_workspace_plural: Espaços de trabalho |
|
383 |
label_workspace_new: Novo espaço de trabalho |
|
384 |
error_unable_delete_workspace: Não foi possível excluir espaço de trabalho |
|
385 |
field_workspace: Espaço de trabalho |
|
381 | 386 |
label_workflow: Fluxo de trabalho |
382 | 387 |
label_issue_status: Situação da tarefa |
383 | 388 |
label_issue_status_plural: Situação das tarefas |
... | ... | |
871 | 876 |
field_watcher: Observador |
872 | 877 |
permission_view_issues: Ver tarefas |
873 | 878 |
label_display_used_statuses_only: Somente exibir situações que são usadas por este tipo de tarefa |
879 |
label_display_used_workspaces_only: Somente situações deste espaço de trabalho |
|
874 | 880 |
label_revision_id: Revisão %{value} |
875 | 881 |
label_api_access_key: Chave de acesso à API |
876 | 882 |
button_show: Exibir |
config/routes.rb | ||
---|---|---|
383 | 383 |
end |
384 | 384 |
end |
385 | 385 | |
386 |
resources :workspaces, :except => :show |
|
387 | ||
386 | 388 |
resources :workflows, only: [:index] do |
387 | 389 |
collection do |
388 | 390 |
get 'edit' |
config/settings.yml | ||
---|---|---|
249 | 249 |
- name |
250 | 250 |
- identifier |
251 | 251 |
- short_description |
252 |
- workspace |
|
252 | 253 |
default_project_query: |
253 | 254 |
default: '' |
254 | 255 |
issue_done_ratio: |
db/migrate/20160314174310_add_workspace_to_projects.rb | ||
---|---|---|
1 |
class AddWorkspaceToProjects < ActiveRecord::Migration[4.2] |
|
2 |
def self.up |
|
3 |
add_column :projects, :workspace_id, :integer, :default => 1 |
|
4 |
end |
|
5 | ||
6 |
def self.down |
|
7 |
remove_column :projects, :workspace_id |
|
8 |
end |
|
9 |
end |
db/migrate/20160314174311_add_workspace_to_workflows.rb | ||
---|---|---|
1 |
class AddWorkspaceToWorkflows < ActiveRecord::Migration[4.2] |
|
2 |
def self.up |
|
3 |
add_column :workflows, :workspace_id, :integer, :default => 1 |
|
4 |
end |
|
5 | ||
6 |
def self.down |
|
7 |
remove_column :workflows, :workspace_id |
|
8 |
end |
|
9 |
end |
db/migrate/20160314174312_create_workspaces.rb | ||
---|---|---|
1 |
class CreateWorkspaces < ActiveRecord::Migration[4.2] |
|
2 |
def self.up |
|
3 |
create_table :workspaces do |t| |
|
4 |
t.string :name |
|
5 |
t.string :description |
|
6 |
t.integer :position, :default => nil, :null => true |
|
7 |
end |
|
8 | ||
9 |
# create default workspace |
|
10 |
unless Workspace.exists?(1) |
|
11 |
Workspace.create(:name => "Default", :description => "Default workspace", :position => 1) |
- « Previous
- 1
- …
- 3
- 4
- 5
- Next »