##// END OF EJS Templates
Bulk edit workflows for multiple trackers/roles (#16164)....
Jean-Philippe Lang -
r12649:b6c794d16b47
parent child
Show More
@@ -0,0 +1,93
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 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 require File.expand_path('../../test_helper', __FILE__)
19
20 class WorkflowTransitionTest < ActiveSupport::TestCase
21 fixtures :roles, :trackers, :issue_statuses
22
23 def setup
24 WorkflowTransition.delete_all
25 end
26
27 def test_replace_transitions_should_create_enabled_transitions
28 w = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
29
30 transitions = {'1' => {
31 '2' => {'always' => '1'},
32 '3' => {'always' => '1'}
33 }}
34 assert_difference 'WorkflowTransition.count' do
35 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
36 end
37 assert WorkflowTransition.where(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3).exists?
38 end
39
40 def test_replace_transitions_should_delete_disabled_transitions
41 w1 = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
42 w2 = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
43
44 transitions = {'1' => {
45 '2' => {'always' => '0'},
46 '3' => {'always' => '1'}
47 }}
48 assert_difference 'WorkflowTransition.count', -1 do
49 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
50 end
51 assert !WorkflowTransition.exists?(w1.id)
52 end
53
54 def test_replace_transitions_should_create_enabled_additional_transitions
55 transitions = {'1' => {
56 '2' => {'always' => '0', 'assignee' => '0', 'author' => '1'}
57 }}
58 assert_difference 'WorkflowTransition.count' do
59 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
60 end
61 w = WorkflowTransition.where(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2).first
62 assert w
63 assert_equal false, w.assignee
64 assert_equal true, w.author
65 end
66
67 def test_replace_transitions_should_delete_disabled_additional_transitions
68 w = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :assignee => true)
69
70 transitions = {'1' => {
71 '2' => {'always' => '0', 'assignee' => '0', 'author' => '0'}
72 }}
73 assert_difference 'WorkflowTransition.count', -1 do
74 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
75 end
76 assert !WorkflowTransition.exists?(w.id)
77 end
78
79 def test_replace_transitions_should_update_additional_transitions
80 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :assignee => true)
81
82 transitions = {'1' => {
83 '2' => {'always' => '0', 'assignee' => '0', 'author' => '1'}
84 }}
85 assert_no_difference 'WorkflowTransition.count' do
86 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
87 end
88 w = WorkflowTransition.where(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2).first
89 assert w
90 assert_equal false, w.assignee
91 assert_equal true, w.author
92 end
93 end
@@ -1,129 +1,140
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class WorkflowsController < ApplicationController
18 class WorkflowsController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin, :find_roles, :find_trackers
21 before_filter :require_admin
22
22
23 def index
23 def index
24 @workflow_counts = WorkflowTransition.count_by_tracker_and_role
24 @workflow_counts = WorkflowTransition.count_by_tracker_and_role
25 end
25 end
26
26
27 def edit
27 def edit
28 @role = Role.find_by_id(params[:role_id]) if params[:role_id]
28 find_trackers_roles_and_statuses_for_edit
29 @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
30
29
31 if request.post?
30 if request.post? && @roles && @trackers && params[:transitions]
32 WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
31 transitions = params[:transitions].deep_dup
33 (params[:issue_status] || []).each { |status_id, transitions|
32 transitions.each do |old_status_id, transitions_by_new_status|
34 transitions.each { |new_status_id, options|
33 transitions_by_new_status.each do |new_status_id, transition_by_rule|
35 author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
34 transition_by_rule.reject! {|rule, transition| transition == 'no_change'}
36 assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
35 end
37 WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
38 }
39 }
40 if @role.save
41 flash[:notice] = l(:notice_successful_update)
42 redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
43 return
44 end
36 end
37 WorkflowTransition.replace_transitions(@trackers, @roles, transitions)
38 flash[:notice] = l(:notice_successful_update)
39 redirect_to_referer_or workflows_edit_path
40 return
45 end
41 end
46
42
47 @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
43 if @trackers && @roles && @statuses.any?
48 if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
44 workflows = WorkflowTransition.where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id)).all
49 @statuses = @tracker.issue_statuses
50 end
51 @statuses ||= IssueStatus.sorted.all
52
53 if @tracker && @role && @statuses.any?
54 workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all
55 @workflows = {}
45 @workflows = {}
56 @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
46 @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
57 @workflows['author'] = workflows.select {|w| w.author}
47 @workflows['author'] = workflows.select {|w| w.author}
58 @workflows['assignee'] = workflows.select {|w| w.assignee}
48 @workflows['assignee'] = workflows.select {|w| w.assignee}
59 end
49 end
60 end
50 end
61
51
62 def permissions
52 def permissions
63 @role = Role.find_by_id(params[:role_id]) if params[:role_id]
53 find_trackers_roles_and_statuses_for_edit
64 @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
65
54
66 if request.post? && @role && @tracker
55 if request.post? && @roles && @trackers && params[:permissions]
67 WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
56 permissions = params[:permissions].deep_dup
57 permissions.each { |field, rule_by_status_id|
58 rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'}
59 }
60 WorkflowPermission.replace_permissions(@trackers, @roles, permissions)
68 flash[:notice] = l(:notice_successful_update)
61 flash[:notice] = l(:notice_successful_update)
69 redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
62 redirect_to_referer_or workflows_permissions_path
70 return
63 return
71 end
64 end
72
65
73 @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
66 if @roles && @trackers
74 if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
67 @fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
75 @statuses = @tracker.issue_statuses
68 @custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort
76 end
69 @permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles)
77 @statuses ||= IssueStatus.sorted.all
78
79 if @role && @tracker
80 @fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
81 @custom_fields = @tracker.custom_fields
82 @permissions = WorkflowPermission.
83 where(:tracker_id => @tracker.id, :role_id => @role.id).inject({}) do |h, w|
84 h[w.old_status_id] ||= {}
85 h[w.old_status_id][w.field_name] = w.rule
86 h
87 end
88 @statuses.each {|status| @permissions[status.id] ||= {}}
70 @statuses.each {|status| @permissions[status.id] ||= {}}
89 end
71 end
90 end
72 end
91
73
92 def copy
74 def copy
75 @roles = Role.sorted
76 @trackers = Tracker.sorted
77
93 if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
78 if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
94 @source_tracker = nil
79 @source_tracker = nil
95 else
80 else
96 @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i)
81 @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i)
97 end
82 end
98 if params[:source_role_id].blank? || params[:source_role_id] == 'any'
83 if params[:source_role_id].blank? || params[:source_role_id] == 'any'
99 @source_role = nil
84 @source_role = nil
100 else
85 else
101 @source_role = Role.find_by_id(params[:source_role_id].to_i)
86 @source_role = Role.find_by_id(params[:source_role_id].to_i)
102 end
87 end
103 @target_trackers = params[:target_tracker_ids].blank? ?
88 @target_trackers = params[:target_tracker_ids].blank? ?
104 nil : Tracker.where(:id => params[:target_tracker_ids]).all
89 nil : Tracker.where(:id => params[:target_tracker_ids]).all
105 @target_roles = params[:target_role_ids].blank? ?
90 @target_roles = params[:target_role_ids].blank? ?
106 nil : Role.where(:id => params[:target_role_ids]).all
91 nil : Role.where(:id => params[:target_role_ids]).all
107 if request.post?
92 if request.post?
108 if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
93 if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
109 flash.now[:error] = l(:error_workflow_copy_source)
94 flash.now[:error] = l(:error_workflow_copy_source)
110 elsif @target_trackers.blank? || @target_roles.blank?
95 elsif @target_trackers.blank? || @target_roles.blank?
111 flash.now[:error] = l(:error_workflow_copy_target)
96 flash.now[:error] = l(:error_workflow_copy_target)
112 else
97 else
113 WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
98 WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
114 flash[:notice] = l(:notice_successful_update)
99 flash[:notice] = l(:notice_successful_update)
115 redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role)
100 redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role)
116 end
101 end
117 end
102 end
118 end
103 end
119
104
120 private
105 private
121
106
107 def find_trackers_roles_and_statuses_for_edit
108 find_roles
109 find_trackers
110 find_statuses
111 end
112
122 def find_roles
113 def find_roles
123 @roles = Role.sorted.all
114 ids = Array.wrap(params[:role_id])
115 if ids == ['all']
116 @roles = Role.sorted.all
117 elsif ids.present?
118 @roles = Role.where(:id => ids).all
119 end
120 @roles = nil if @roles.blank?
124 end
121 end
125
122
126 def find_trackers
123 def find_trackers
127 @trackers = Tracker.sorted.all
124 ids = Array.wrap(params[:tracker_id])
125 if ids == ['all']
126 @trackers = Tracker.sorted.all
127 elsif ids.present?
128 @trackers = Tracker.where(:id => ids).all
129 end
130 @trackers = nil if @trackers.blank?
131 end
132
133 def find_statuses
134 @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
135 if @trackers && @used_statuses_only
136 @statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence
137 end
138 @statuses ||= IssueStatus.sorted.all
128 end
139 end
129 end
140 end
@@ -1,41 +1,91
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module WorkflowsHelper
20 module WorkflowsHelper
21 def options_for_workflow_select(name, objects, selected, options={})
22 option_tags = ''.html_safe
23 multiple = false
24 if selected
25 if selected.size == objects.size
26 selected = 'all'
27 else
28 selected = selected.map(&:id)
29 if selected.size > 1
30 multiple = true
31 end
32 end
33 else
34 selected = objects.first.try(:id)
35 end
36 all_tag_options = {:value => 'all', :selected => (selected == 'all')}
37 if multiple
38 all_tag_options.merge!(:style => "display:none;")
39 end
40 option_tags << content_tag('option', 'All', all_tag_options)
41 option_tags << options_from_collection_for_select(objects, "id", "name", selected)
42 select_tag name, option_tags, {:multiple => multiple}.merge(options)
43 end
44
21 def field_required?(field)
45 def field_required?(field)
22 field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
46 field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
23 end
47 end
24
48
25 def field_permission_tag(permissions, status, field, role)
49 def field_permission_tag(permissions, status, field, roles)
26 name = field.is_a?(CustomField) ? field.id.to_s : field
50 name = field.is_a?(CustomField) ? field.id.to_s : field
27 options = [["", ""], [l(:label_readonly), "readonly"]]
51 options = [["", ""], [l(:label_readonly), "readonly"]]
28 options << [l(:label_required), "required"] unless field_required?(field)
52 options << [l(:label_required), "required"] unless field_required?(field)
29 html_options = {}
53 html_options = {}
30 selected = permissions[status.id][name]
54
55 if perm = permissions[status.id][name]
56 if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size
57 options << [l(:label_no_change_option), "no_change"]
58 selected = 'no_change'
59 else
60 selected = perm.first
61 end
62 end
63
64 hidden = field.is_a?(CustomField) &&
65 !field.visible? &&
66 !roles.detect {|role| role.custom_fields.to_a.include?(field)}
31
67
32 hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field)
33 if hidden
68 if hidden
34 options[0][0] = l(:label_hidden)
69 options[0][0] = l(:label_hidden)
35 selected = ''
70 selected = ''
36 html_options[:disabled] = true
71 html_options[:disabled] = true
37 end
72 end
38
73
39 select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options)
74 select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options)
75 end
76
77 def transition_tag(workflows, old_status, new_status, name)
78 w = workflows.select {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id}.size
79
80 tag_name = "transitions[#{ old_status.id }][#{new_status.id}][#{name}]"
81 if w == 0 || w == @roles.size * @trackers.size
82
83 hidden_field_tag(tag_name, "0") +
84 check_box_tag(tag_name, "1", w != 0,
85 :class => "old-status-#{old_status.id} new-status-#{new_status.id}")
86 else
87 select_tag tag_name,
88 options_for_select([["Oui", "1"], ["Non", "0"], [l(:label_no_change_option), "no_change"]], "no_change")
89 end
40 end
90 end
41 end
91 end
@@ -1,45 +1,68
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class WorkflowPermission < WorkflowRule
18 class WorkflowPermission < WorkflowRule
19 validates_inclusion_of :rule, :in => %w(readonly required)
19 validates_inclusion_of :rule, :in => %w(readonly required)
20 validate :validate_field_name
20 validate :validate_field_name
21
21
22 # Replaces the workflow permissions for the given tracker and role
22 # Returns the workflow permissions for the given trackers and roles
23 # grouped by status_id
23 #
24 #
24 # Example:
25 # Example:
25 # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}}
26 # WorkflowPermission.rules_by_status_id trackers, roles
26 def self.replace_permissions(tracker, role, permissions)
27 # # => {1 => {'start_date' => 'required', 'due_date' => 'readonly'}}
27 destroy_all(:tracker_id => tracker.id, :role_id => role.id)
28 def self.rules_by_status_id(trackers, roles)
29 WorkflowPermission.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).inject({}) do |h, w|
30 h[w.old_status_id] ||= {}
31 h[w.old_status_id][w.field_name] ||= []
32 h[w.old_status_id][w.field_name] << w.rule
33 h
34 end
35 end
28
36
29 permissions.each { |field, rule_by_status_id|
37 # Replaces the workflow permissions for the given trackers and roles
30 rule_by_status_id.each { |status_id, rule|
38 #
31 if rule.present?
39 # Example:
32 WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
40 # WorkflowPermission.replace_permissions trackers, roles, {'1' => {'start_date' => 'required', 'due_date' => 'readonly'}}
33 end
41 def self.replace_permissions(trackers, roles, permissions)
42 trackers = Array.wrap trackers
43 roles = Array.wrap roles
44
45 transaction do
46 permissions.each { |status_id, rule_by_field|
47 rule_by_field.each { |field, rule|
48 destroy_all(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :old_status_id => status_id, :field_name => field)
49 if rule.present?
50 trackers.each do |tracker|
51 roles.each do |role|
52 WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
53 end
54 end
55 end
56 }
34 }
57 }
35 }
58 end
36 end
59 end
37
60
38 protected
61 protected
39
62
40 def validate_field_name
63 def validate_field_name
41 unless Tracker::CORE_FIELDS_ALL.include?(field_name) || field_name.to_s.match(/^\d+$/)
64 unless Tracker::CORE_FIELDS_ALL.include?(field_name) || field_name.to_s.match(/^\d+$/)
42 errors.add :field_name, :invalid
65 errors.add :field_name, :invalid
43 end
66 end
44 end
67 end
45 end
68 end
@@ -1,39 +1,104
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class WorkflowTransition < WorkflowRule
18 class WorkflowTransition < WorkflowRule
19 validates_presence_of :new_status
19 validates_presence_of :new_status
20
20
21 # Returns workflow transitions count by tracker and role
21 # Returns workflow transitions count by tracker and role
22 def self.count_by_tracker_and_role
22 def self.count_by_tracker_and_role
23 counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id")
23 counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id")
24 roles = Role.sorted.all
24 roles = Role.sorted.all
25 trackers = Tracker.sorted.all
25 trackers = Tracker.sorted.all
26
26
27 result = []
27 result = []
28 trackers.each do |tracker|
28 trackers.each do |tracker|
29 t = []
29 t = []
30 roles.each do |role|
30 roles.each do |role|
31 row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s}
31 row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s}
32 t << [role, (row.nil? ? 0 : row['c'].to_i)]
32 t << [role, (row.nil? ? 0 : row['c'].to_i)]
33 end
33 end
34 result << [tracker, t]
34 result << [tracker, t]
35 end
35 end
36
36
37 result
37 result
38 end
38 end
39
40 def self.replace_transitions(trackers, roles, transitions)
41 trackers = Array.wrap trackers
42 roles = Array.wrap roles
43
44 transaction do
45 records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).all
46
47 transitions.each do |old_status_id, transitions_by_new_status|
48 transitions_by_new_status.each do |new_status_id, transition_by_rule|
49 transition_by_rule.each do |rule, transition|
50 trackers.each do |tracker|
51 roles.each do |role|
52 w = records.select {|r|
53 r.old_status_id == old_status_id.to_i &&
54 r.new_status_id == new_status_id.to_i &&
55 r.tracker_id == tracker.id &&
56 r.role_id == role.id &&
57 !r.destroyed?
58 }
59
60 if rule == 'always'
61 w = w.select {|r| !r.author && !r.assignee}
62 else
63 w = w.select {|r| r.author || r.assignee}
64 end
65 if w.size > 1
66 w[1..-1].each(&:destroy)
67 end
68 w = w.first
69
70 if transition == "1" || transition == true
71 unless w
72 w = WorkflowTransition.new(:old_status_id => old_status_id, :new_status_id => new_status_id, :tracker_id => tracker.id, :role_id => role.id)
73 records << w
74 end
75 w.author = true if rule == "author"
76 w.assignee = true if rule == "assignee"
77 w.save if w.changed?
78 elsif w
79 if rule == 'always'
80 w.destroy
81 elsif rule == 'author'
82 if w.assignee
83 w.author = false
84 w.save if w.changed?
85 else
86 w.destroy
87 end
88 elsif rule == 'assignee'
89 if w.author
90 w.assignee = false
91 w.save if w.changed?
92 else
93 w.destroy
94 end
95 end
96 end
97 end
98 end
99 end
100 end
101 end
102 end
103 end
39 end
104 end
@@ -1,41 +1,40
1 <table class="list transitions transitions-<%= name %>">
1 <table class="list workflows transitions transitions-<%= name %>">
2 <thead>
2 <thead>
3 <tr>
3 <tr>
4 <th>
4 <th>
5 <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input')",
5 <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input')",
6 :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
6 :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
7 <%=l(:label_current_status)%>
7 <%=l(:label_current_status)%>
8 </th>
8 </th>
9 <th colspan="<%= @statuses.length %>"><%=l(:label_new_statuses_allowed)%></th>
9 <th colspan="<%= @statuses.length %>"><%=l(:label_new_statuses_allowed)%></th>
10 </tr>
10 </tr>
11 <tr>
11 <tr>
12 <td></td>
12 <td></td>
13 <% for new_status in @statuses %>
13 <% for new_status in @statuses %>
14 <td style="width:<%= 75 / @statuses.size %>%;">
14 <td style="width:<%= 75 / @statuses.size %>%;">
15 <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')",
15 <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')",
16 :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
16 :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
17 <%=h new_status.name %>
17 <%=h new_status.name %>
18 </td>
18 </td>
19 <% end %>
19 <% end %>
20 </tr>
20 </tr>
21 </thead>
21 </thead>
22 <tbody>
22 <tbody>
23 <% for old_status in @statuses %>
23 <% for old_status in @statuses %>
24 <tr class="<%= cycle("odd", "even") %>">
24 <tr class="<%= cycle("odd", "even") %>">
25 <td class="name">
25 <td class="name">
26 <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')",
26 <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')",
27 :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
27 :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
28
28
29 <%=h old_status.name %>
29 <%=h old_status.name %>
30 </td>
30 </td>
31 <% for new_status in @statuses -%>
31 <% for new_status in @statuses -%>
32 <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %>
32 <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %>
33 <td class="<%= checked ? 'enabled' : '' %>">
33 <td class="<%= checked ? 'enabled' : '' %>">
34 <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, checked,
34 <%= transition_tag workflows, old_status, new_status, name %>
35 :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %>
36 </td>
35 </td>
37 <% end -%>
36 <% end -%>
38 </tr>
37 </tr>
39 <% end %>
38 <% end %>
40 </tbody>
39 </tbody>
41 </table>
40 </table>
@@ -1,56 +1,75
1 <%= render :partial => 'action_menu' %>
1 <%= render :partial => 'action_menu' %>
2
2
3 <%= title l(:label_workflow) %>
3 <%= title l(:label_workflow) %>
4
4
5 <div class="tabs">
5 <div class="tabs">
6 <ul>
6 <ul>
7 <li><%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li>
7 <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li>
8 <li><%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker} %></li>
8 <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers) %></li>
9 </ul>
9 </ul>
10 </div>
10 </div>
11
11
12 <p><%=l(:text_workflow_edit)%>:</p>
12 <p><%=l(:text_workflow_edit)%>:</p>
13
13
14 <%= form_tag({}, :method => 'get') do %>
14 <%= form_tag({}, :method => 'get') do %>
15 <p>
15 <p>
16 <label><%=l(:label_role)%>:
16 <label><%=l(:label_role)%>:
17 <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %></label>
17 <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %>
18 </label>
19 <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
18
20
19 <label><%=l(:label_tracker)%>:
21 <label><%=l(:label_tracker)%>:
20 <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label>
22 <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
23 </label>
24 <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
21
25
22 <%= submit_tag l(:button_edit), :name => nil %>
26 <%= submit_tag l(:button_edit), :name => nil %>
23
27
24 <%= hidden_field_tag 'used_statuses_only', '0' %>
28 <%= hidden_field_tag 'used_statuses_only', '0' %>
25 <label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label>
29 <label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label>
26
30
27 </p>
31 </p>
28 <% end %>
32 <% end %>
29
33
30 <% if @tracker && @role && @statuses.any? %>
34 <% if @trackers && @roles && @statuses.any? %>
31 <%= form_tag({}, :id => 'workflow_form' ) do %>
35 <%= form_tag({}, :id => 'workflow_form' ) do %>
32 <%= hidden_field_tag 'tracker_id', @tracker.id %>
36 <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id}.join.html_safe %>
33 <%= hidden_field_tag 'role_id', @role.id %>
37 <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id}.join.html_safe %>
34 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
38 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
35 <div class="autoscroll">
39 <div class="autoscroll">
36 <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %>
40 <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %>
37
41
38 <fieldset class="collapsible" style="padding: 0; margin-top: 0.5em;">
42 <fieldset class="collapsible" style="padding: 0; margin-top: 0.5em;">
39 <legend onclick="toggleFieldset(this);"><%= l(:label_additional_workflow_transitions_for_author) %></legend>
43 <legend onclick="toggleFieldset(this);"><%= l(:label_additional_workflow_transitions_for_author) %></legend>
40 <div id="author_workflows" style="margin: 0.5em 0 0.5em 0;">
44 <div id="author_workflows" style="margin: 0.5em 0 0.5em 0;">
41 <%= render :partial => 'form', :locals => {:name => 'author', :workflows => @workflows['author']} %>
45 <%= render :partial => 'form', :locals => {:name => 'author', :workflows => @workflows['author']} %>
42 </div>
46 </div>
43 </fieldset>
47 </fieldset>
44 <%= javascript_tag "hideFieldset($('#author_workflows'))" unless @workflows['author'].present? %>
48 <%= javascript_tag "hideFieldset($('#author_workflows'))" unless @workflows['author'].present? %>
45
49
46 <fieldset class="collapsible" style="padding: 0;">
50 <fieldset class="collapsible" style="padding: 0;">
47 <legend onclick="toggleFieldset(this);"><%= l(:label_additional_workflow_transitions_for_assignee) %></legend>
51 <legend onclick="toggleFieldset(this);"><%= l(:label_additional_workflow_transitions_for_assignee) %></legend>
48 <div id="assignee_workflows" style="margin: 0.5em 0 0.5em 0;">
52 <div id="assignee_workflows" style="margin: 0.5em 0 0.5em 0;">
49 <%= render :partial => 'form', :locals => {:name => 'assignee', :workflows => @workflows['assignee']} %>
53 <%= render :partial => 'form', :locals => {:name => 'assignee', :workflows => @workflows['assignee']} %>
50 </div>
54 </div>
51 </fieldset>
55 </fieldset>
52 <%= javascript_tag "hideFieldset($('#assignee_workflows'))" unless @workflows['assignee'].present? %>
56 <%= javascript_tag "hideFieldset($('#assignee_workflows'))" unless @workflows['assignee'].present? %>
53 </div>
57 </div>
54 <%= submit_tag l(:button_save) %>
58 <%= submit_tag l(:button_save) %>
55 <% end %>
59 <% end %>
56 <% end %>
60 <% end %>
61
62 <%= javascript_tag do %>
63 $("a[data-expands]").click(function(e){
64 e.preventDefault();
65 var target = $($(this).attr("data-expands"));
66 if (target.attr("multiple")) {
67 target.attr("multiple", false);
68 target.find("option[value=all]").show();
69 } else {
70 target.attr("multiple", true);
71 target.find("option[value=all]").attr("selected", false).hide();
72 }
73 });
74
75 <% end %>
@@ -1,106 +1,123
1 <%= render :partial => 'action_menu' %>
1 <%= render :partial => 'action_menu' %>
2
2
3 <%= title l(:label_workflow) %>
3 <%= title l(:label_workflow) %>
4
4
5 <div class="tabs">
5 <div class="tabs">
6 <ul>
6 <ul>
7 <li><%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker} %></li>
7 <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers) %></li>
8 <li><%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li>
8 <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li>
9 </ul>
9 </ul>
10 </div>
10 </div>
11
11
12 <p><%=l(:text_workflow_edit)%>:</p>
12 <p><%=l(:text_workflow_edit)%>:</p>
13
13
14 <%= form_tag({}, :method => 'get') do %>
14 <%= form_tag({}, :method => 'get') do %>
15 <p>
15 <p>
16 <label><%=l(:label_role)%>:
16 <label><%=l(:label_role)%>:
17 <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %></label>
17 <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %>
18 </label>
19 <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
18
20
19 <label><%=l(:label_tracker)%>:
21 <label><%=l(:label_tracker)%>:
20 <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label>
22 <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
23 </label>
24 <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
21
25
22 <%= submit_tag l(:button_edit), :name => nil %>
26 <%= submit_tag l(:button_edit), :name => nil %>
23
27
24 <%= hidden_field_tag 'used_statuses_only', '0' %>
28 <%= hidden_field_tag 'used_statuses_only', '0' %>
25 <label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label>
29 <label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label>
26 </p>
30 </p>
27 <% end %>
31 <% end %>
28
32
29 <% if @tracker && @role && @statuses.any? %>
33 <% if @trackers && @roles && @statuses.any? %>
30 <%= form_tag({}, :id => 'workflow_form' ) do %>
34 <%= form_tag({}, :id => 'workflow_form' ) do %>
31 <%= hidden_field_tag 'tracker_id', @tracker.id %>
35 <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id}.join.html_safe %>
32 <%= hidden_field_tag 'role_id', @role.id %>
36 <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id}.join.html_safe %>
33 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
37 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
34 <div class="autoscroll">
38 <div class="autoscroll">
35 <table class="list fields_permissions">
39 <table class="list workflows fields_permissions">
36 <thead>
40 <thead>
37 <tr>
41 <tr>
38 <th>
42 <th>
39 </th>
43 </th>
40 <th colspan="<%= @statuses.length %>"><%=l(:label_issue_status)%></th>
44 <th colspan="<%= @statuses.length %>"><%=l(:label_issue_status)%></th>
41 </tr>
45 </tr>
42 <tr>
46 <tr>
43 <td></td>
47 <td></td>
44 <% for status in @statuses %>
48 <% for status in @statuses %>
45 <td style="width:<%= 75 / @statuses.size %>%;">
49 <td style="width:<%= 75 / @statuses.size %>%;">
46 <%=h status.name %>
50 <%=h status.name %>
47 </td>
51 </td>
48 <% end %>
52 <% end %>
49 </tr>
53 </tr>
50 </thead>
54 </thead>
51 <tbody>
55 <tbody>
52 <tr class="group open">
56 <tr class="group open">
53 <td colspan="<%= @statuses.size + 1 %>">
57 <td colspan="<%= @statuses.size + 1 %>">
54 <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
58 <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
55 <%= l(:field_core_fields) %>
59 <%= l(:field_core_fields) %>
56 </td>
60 </td>
57 </tr>
61 </tr>
58 <% @fields.each do |field, name| %>
62 <% @fields.each do |field, name| %>
59 <tr class="<%= cycle("odd", "even") %>">
63 <tr class="<%= cycle("odd", "even") %>">
60 <td class="name">
64 <td class="name">
61 <%=h name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %>
65 <%=h name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %>
62 </td>
66 </td>
63 <% for status in @statuses -%>
67 <% for status in @statuses -%>
64 <td class="<%= @permissions[status.id][field] %>">
68 <td class="<%= @permissions[status.id][field] %>">
65 <%= field_permission_tag(@permissions, status, field, @role) %>
69 <%= field_permission_tag(@permissions, status, field, @roles) %>
66 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
70 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
67 </td>
71 </td>
68 <% end -%>
72 <% end -%>
69 </tr>
73 </tr>
70 <% end %>
74 <% end %>
71 <% if @custom_fields.any? %>
75 <% if @custom_fields.any? %>
72 <tr class="group open">
76 <tr class="group open">
73 <td colspan="<%= @statuses.size + 1 %>">
77 <td colspan="<%= @statuses.size + 1 %>">
74 <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
78 <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
75 <%= l(:label_custom_field_plural) %>
79 <%= l(:label_custom_field_plural) %>
76 </td>
80 </td>
77 </tr>
81 </tr>
78 <% @custom_fields.each do |field| %>
82 <% @custom_fields.each do |field| %>
79 <tr class="<%= cycle("odd", "even") %>">
83 <tr class="<%= cycle("odd", "even") %>">
80 <td class="name">
84 <td class="name">
81 <%=h field.name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %>
85 <%=h field.name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %>
82 </td>
86 </td>
83 <% for status in @statuses -%>
87 <% for status in @statuses -%>
84 <td class="<%= @permissions[status.id][field.id.to_s] %>">
88 <td class="<%= @permissions[status.id][field.id.to_s] %>">
85 <%= field_permission_tag(@permissions, status, field, @role) %>
89 <%= field_permission_tag(@permissions, status, field, @roles) %>
86 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
90 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
87 </td>
91 </td>
88 <% end -%>
92 <% end -%>
89 </tr>
93 </tr>
90 <% end %>
94 <% end %>
91 <% end %>
95 <% end %>
92 </tbody>
96 </tbody>
93 </table>
97 </table>
94 </div>
98 </div>
95 <%= submit_tag l(:button_save) %>
99 <%= submit_tag l(:button_save) %>
96 <% end %>
100 <% end %>
97 <% end %>
101 <% end %>
98
102
99 <%= javascript_tag do %>
103 <%= javascript_tag do %>
100 $("a.repeat-value").click(function(e){
104 $("a.repeat-value").click(function(e){
101 e.preventDefault();
105 e.preventDefault();
102 var td = $(this).closest('td');
106 var td = $(this).closest('td');
103 var selected = td.find("select").find(":selected").val();
107 var selected = td.find("select").find(":selected").val();
104 td.nextAll('td').find("select").val(selected);
108 td.nextAll('td').find("select").val(selected);
105 });
109 });
110
111 $("a[data-expands]").click(function(e){
112 e.preventDefault();
113 var target = $($(this).attr("data-expands"));
114 if (target.attr("multiple")) {
115 target.attr("multiple", false);
116 target.find("option[value=all]").show();
117 } else {
118 target.attr("multiple", true);
119 target.find("option[value=all]").attr("selected", false).hide();
120 }
121 });
122
106 <% end %>
123 <% end %>
@@ -1,1187 +1,1191
1 html {overflow-y:scroll;}
1 html {overflow-y:scroll;}
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
3
3
4 h1, h2, h3, h4 {font-family: "Trebuchet MS", Verdana, sans-serif;padding: 2px 10px 1px 0px;margin: 0 0 10px 0;}
4 h1, h2, h3, h4 {font-family: "Trebuchet MS", Verdana, sans-serif;padding: 2px 10px 1px 0px;margin: 0 0 10px 0;}
5 #content h1, h2, h3, h4 {color: #555;}
5 #content h1, h2, h3, h4 {color: #555;}
6 h2, .wiki h1 {font-size: 20px;}
6 h2, .wiki h1 {font-size: 20px;}
7 h3, .wiki h2 {font-size: 16px;}
7 h3, .wiki h2 {font-size: 16px;}
8 h4, .wiki h3 {font-size: 13px;}
8 h4, .wiki h3 {font-size: 13px;}
9 h4 {border-bottom: 1px dotted #bbb;}
9 h4 {border-bottom: 1px dotted #bbb;}
10
10
11 /***** Layout *****/
11 /***** Layout *****/
12 #wrapper {background: white;}
12 #wrapper {background: white;}
13
13
14 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
14 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
15 #top-menu ul {margin: 0; padding: 0;}
15 #top-menu ul {margin: 0; padding: 0;}
16 #top-menu li {
16 #top-menu li {
17 float:left;
17 float:left;
18 list-style-type:none;
18 list-style-type:none;
19 margin: 0px 0px 0px 0px;
19 margin: 0px 0px 0px 0px;
20 padding: 0px 0px 0px 0px;
20 padding: 0px 0px 0px 0px;
21 white-space:nowrap;
21 white-space:nowrap;
22 }
22 }
23 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
23 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
24 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
24 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
25
25
26 #account {float:right;}
26 #account {float:right;}
27
27
28 #header {min-height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 20px 6px; position:relative;}
28 #header {min-height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 20px 6px; position:relative;}
29 #header a {color:#f8f8f8;}
29 #header a {color:#f8f8f8;}
30 #header h1 a.ancestor { font-size: 80%; }
30 #header h1 a.ancestor { font-size: 80%; }
31 #quick-search {float:right;}
31 #quick-search {float:right;}
32
32
33 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
33 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
34 #main-menu ul {margin: 0; padding: 0;}
34 #main-menu ul {margin: 0; padding: 0;}
35 #main-menu li {
35 #main-menu li {
36 float:left;
36 float:left;
37 list-style-type:none;
37 list-style-type:none;
38 margin: 0px 2px 0px 0px;
38 margin: 0px 2px 0px 0px;
39 padding: 0px 0px 0px 0px;
39 padding: 0px 0px 0px 0px;
40 white-space:nowrap;
40 white-space:nowrap;
41 }
41 }
42 #main-menu li a {
42 #main-menu li a {
43 display: block;
43 display: block;
44 color: #fff;
44 color: #fff;
45 text-decoration: none;
45 text-decoration: none;
46 font-weight: bold;
46 font-weight: bold;
47 margin: 0;
47 margin: 0;
48 padding: 4px 10px 4px 10px;
48 padding: 4px 10px 4px 10px;
49 }
49 }
50 #main-menu li a:hover {background:#759FCF; color:#fff;}
50 #main-menu li a:hover {background:#759FCF; color:#fff;}
51 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
51 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
52
52
53 #admin-menu ul {margin: 0; padding: 0;}
53 #admin-menu ul {margin: 0; padding: 0;}
54 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
54 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
55
55
56 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
56 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
57 #admin-menu a.projects { background-image: url(../images/projects.png); }
57 #admin-menu a.projects { background-image: url(../images/projects.png); }
58 #admin-menu a.users { background-image: url(../images/user.png); }
58 #admin-menu a.users { background-image: url(../images/user.png); }
59 #admin-menu a.groups { background-image: url(../images/group.png); }
59 #admin-menu a.groups { background-image: url(../images/group.png); }
60 #admin-menu a.roles { background-image: url(../images/database_key.png); }
60 #admin-menu a.roles { background-image: url(../images/database_key.png); }
61 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
61 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
62 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
62 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
63 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
63 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
64 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
64 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
65 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
65 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
66 #admin-menu a.settings { background-image: url(../images/changeset.png); }
66 #admin-menu a.settings { background-image: url(../images/changeset.png); }
67 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
67 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
68 #admin-menu a.info { background-image: url(../images/help.png); }
68 #admin-menu a.info { background-image: url(../images/help.png); }
69 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
69 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
70
70
71 #main {background-color:#EEEEEE;}
71 #main {background-color:#EEEEEE;}
72
72
73 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
73 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
74 * html #sidebar{ width: 22%; }
74 * html #sidebar{ width: 22%; }
75 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
75 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
76 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
76 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
77 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
77 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
78 #sidebar .contextual { margin-right: 1em; }
78 #sidebar .contextual { margin-right: 1em; }
79 #sidebar ul {margin: 0; padding: 0;}
79 #sidebar ul {margin: 0; padding: 0;}
80 #sidebar ul li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
80 #sidebar ul li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
81
81
82 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
82 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
83 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
83 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
84 html>body #content { min-height: 600px; }
84 html>body #content { min-height: 600px; }
85 * html body #content { height: 600px; } /* IE */
85 * html body #content { height: 600px; } /* IE */
86
86
87 #main.nosidebar #sidebar{ display: none; }
87 #main.nosidebar #sidebar{ display: none; }
88 #main.nosidebar #content{ width: auto; border-right: 0; }
88 #main.nosidebar #content{ width: auto; border-right: 0; }
89
89
90 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
90 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
91
91
92 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
92 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
93 #login-form table td {padding: 6px;}
93 #login-form table td {padding: 6px;}
94 #login-form label {font-weight: bold;}
94 #login-form label {font-weight: bold;}
95 #login-form input#username, #login-form input#password { width: 300px; }
95 #login-form input#username, #login-form input#password { width: 300px; }
96
96
97 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
97 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
98 div.modal h3.title {display:none;}
98 div.modal h3.title {display:none;}
99 div.modal p.buttons {text-align:right; margin-bottom:0;}
99 div.modal p.buttons {text-align:right; margin-bottom:0;}
100
100
101 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
101 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
102
102
103 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
103 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
104
104
105 /***** Links *****/
105 /***** Links *****/
106 a, a:link, a:visited{ color: #169; text-decoration: none; }
106 a, a:link, a:visited{ color: #169; text-decoration: none; }
107 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
107 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
108 a img{ border: 0; }
108 a img{ border: 0; }
109
109
110 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
110 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
111 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
111 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
112 a.user.locked, a.user.locked:link, a.user.locked:visited {color: #999;}
112 a.user.locked, a.user.locked:link, a.user.locked:visited {color: #999;}
113
113
114 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
114 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
115 #sidebar a.selected:hover {text-decoration:none;}
115 #sidebar a.selected:hover {text-decoration:none;}
116 #admin-menu a {line-height:1.7em;}
116 #admin-menu a {line-height:1.7em;}
117 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
117 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
118
118
119 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
119 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
120 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
120 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
121
121
122 a#toggle-completed-versions {color:#999;}
122 a#toggle-completed-versions {color:#999;}
123 /***** Tables *****/
123 /***** Tables *****/
124 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
124 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
125 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
125 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
126 table.list td {text-align:center; vertical-align:top; padding-right:10px;}
126 table.list td {text-align:center; vertical-align:top; padding-right:10px;}
127 table.list td.id { width: 2%; text-align: center;}
127 table.list td.id { width: 2%; text-align: center;}
128 table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles {text-align: left;}
128 table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles {text-align: left;}
129 table.list td.tick {width:15%}
129 table.list td.tick {width:15%}
130 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
130 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
131 table.list td.checkbox input {padding:0px;}
131 table.list td.checkbox input {padding:0px;}
132 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
132 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
133 table.list td.buttons a { padding-right: 0.6em; }
133 table.list td.buttons a { padding-right: 0.6em; }
134 table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
134 table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
135 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
135 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
136
136
137 tr.project td.name a { white-space:nowrap; }
137 tr.project td.name a { white-space:nowrap; }
138 tr.project.closed, tr.project.archived { color: #aaa; }
138 tr.project.closed, tr.project.archived { color: #aaa; }
139 tr.project.closed a, tr.project.archived a { color: #aaa; }
139 tr.project.closed a, tr.project.archived a { color: #aaa; }
140
140
141 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
141 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
142 tr.project.idnt-1 td.name {padding-left: 0.5em;}
142 tr.project.idnt-1 td.name {padding-left: 0.5em;}
143 tr.project.idnt-2 td.name {padding-left: 2em;}
143 tr.project.idnt-2 td.name {padding-left: 2em;}
144 tr.project.idnt-3 td.name {padding-left: 3.5em;}
144 tr.project.idnt-3 td.name {padding-left: 3.5em;}
145 tr.project.idnt-4 td.name {padding-left: 5em;}
145 tr.project.idnt-4 td.name {padding-left: 5em;}
146 tr.project.idnt-5 td.name {padding-left: 6.5em;}
146 tr.project.idnt-5 td.name {padding-left: 6.5em;}
147 tr.project.idnt-6 td.name {padding-left: 8em;}
147 tr.project.idnt-6 td.name {padding-left: 8em;}
148 tr.project.idnt-7 td.name {padding-left: 9.5em;}
148 tr.project.idnt-7 td.name {padding-left: 9.5em;}
149 tr.project.idnt-8 td.name {padding-left: 11em;}
149 tr.project.idnt-8 td.name {padding-left: 11em;}
150 tr.project.idnt-9 td.name {padding-left: 12.5em;}
150 tr.project.idnt-9 td.name {padding-left: 12.5em;}
151
151
152 tr.issue { text-align: center; white-space: nowrap; }
152 tr.issue { text-align: center; white-space: nowrap; }
153 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text, tr.issue td.relations { white-space: normal; }
153 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text, tr.issue td.relations { white-space: normal; }
154 tr.issue td.relations { text-align: left; }
154 tr.issue td.relations { text-align: left; }
155 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
155 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
156 tr.issue td.relations span {white-space: nowrap;}
156 tr.issue td.relations span {white-space: nowrap;}
157 table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;}
157 table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;}
158 table.issues td.description pre {white-space:normal;}
158 table.issues td.description pre {white-space:normal;}
159
159
160 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
160 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
161 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
161 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
162 tr.issue.idnt-2 td.subject {padding-left: 2em;}
162 tr.issue.idnt-2 td.subject {padding-left: 2em;}
163 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
163 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
164 tr.issue.idnt-4 td.subject {padding-left: 5em;}
164 tr.issue.idnt-4 td.subject {padding-left: 5em;}
165 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
165 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
166 tr.issue.idnt-6 td.subject {padding-left: 8em;}
166 tr.issue.idnt-6 td.subject {padding-left: 8em;}
167 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
167 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
168 tr.issue.idnt-8 td.subject {padding-left: 11em;}
168 tr.issue.idnt-8 td.subject {padding-left: 11em;}
169 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
169 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
170
170
171 table.issue-report {table-layout:fixed;}
171 table.issue-report {table-layout:fixed;}
172
172
173 tr.entry { border: 1px solid #f8f8f8; }
173 tr.entry { border: 1px solid #f8f8f8; }
174 tr.entry td { white-space: nowrap; }
174 tr.entry td { white-space: nowrap; }
175 tr.entry td.filename {width:30%; text-align:left;}
175 tr.entry td.filename {width:30%; text-align:left;}
176 tr.entry td.filename_no_report {width:70%; text-align:left;}
176 tr.entry td.filename_no_report {width:70%; text-align:left;}
177 tr.entry td.size { text-align: right; font-size: 90%; }
177 tr.entry td.size { text-align: right; font-size: 90%; }
178 tr.entry td.revision, tr.entry td.author { text-align: center; }
178 tr.entry td.revision, tr.entry td.author { text-align: center; }
179 tr.entry td.age { text-align: right; }
179 tr.entry td.age { text-align: right; }
180 tr.entry.file td.filename a { margin-left: 16px; }
180 tr.entry.file td.filename a { margin-left: 16px; }
181 tr.entry.file td.filename_no_report a { margin-left: 16px; }
181 tr.entry.file td.filename_no_report a { margin-left: 16px; }
182
182
183 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
183 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
184 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
184 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
185
185
186 tr.changeset { height: 20px }
186 tr.changeset { height: 20px }
187 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
187 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
188 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
188 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
189 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
189 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
190 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
190 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
191
191
192 table.files tbody th {text-align:left;}
192 table.files tbody th {text-align:left;}
193 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
193 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
194 table.files tr.file td.digest { font-size: 80%; }
194 table.files tr.file td.digest { font-size: 80%; }
195
195
196 table.members td.roles, table.memberships td.roles { width: 45%; }
196 table.members td.roles, table.memberships td.roles { width: 45%; }
197
197
198 tr.message { height: 2.6em; }
198 tr.message { height: 2.6em; }
199 tr.message td.subject { padding-left: 20px; }
199 tr.message td.subject { padding-left: 20px; }
200 tr.message td.created_on { white-space: nowrap; }
200 tr.message td.created_on { white-space: nowrap; }
201 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
201 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
202 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
202 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
203 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
203 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
204
204
205 tr.version.closed, tr.version.closed a { color: #999; }
205 tr.version.closed, tr.version.closed a { color: #999; }
206 tr.version td.name { padding-left: 20px; }
206 tr.version td.name { padding-left: 20px; }
207 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
207 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
208 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
208 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
209
209
210 tr.user td {width:13%;white-space: nowrap;}
210 tr.user td {width:13%;white-space: nowrap;}
211 tr.user td.username, tr.user td.firstname, tr.user td.lastname, tr.user td.email {text-align:left;}
211 tr.user td.username, tr.user td.firstname, tr.user td.lastname, tr.user td.email {text-align:left;}
212 tr.user td.email { width:18%; }
212 tr.user td.email { width:18%; }
213 tr.user.locked, tr.user.registered { color: #aaa; }
213 tr.user.locked, tr.user.registered { color: #aaa; }
214 tr.user.locked a, tr.user.registered a { color: #aaa; }
214 tr.user.locked a, tr.user.registered a { color: #aaa; }
215
215
216 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
216 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
217
217
218 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
218 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
219
219
220 tr.time-entry { text-align: center; white-space: nowrap; }
220 tr.time-entry { text-align: center; white-space: nowrap; }
221 tr.time-entry td.issue, tr.time-entry td.comments { text-align: left; white-space: normal; }
221 tr.time-entry td.issue, tr.time-entry td.comments { text-align: left; white-space: normal; }
222 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
222 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
223 td.hours .hours-dec { font-size: 0.9em; }
223 td.hours .hours-dec { font-size: 0.9em; }
224
224
225 table.plugins td { vertical-align: middle; }
225 table.plugins td { vertical-align: middle; }
226 table.plugins td.configure { text-align: right; padding-right: 1em; }
226 table.plugins td.configure { text-align: right; padding-right: 1em; }
227 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
227 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
228 table.plugins span.description { display: block; font-size: 0.9em; }
228 table.plugins span.description { display: block; font-size: 0.9em; }
229 table.plugins span.url { display: block; font-size: 0.9em; }
229 table.plugins span.url { display: block; font-size: 0.9em; }
230
230
231 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; text-align:left; }
231 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; text-align:left; }
232 table.list tbody tr.group span.count {position:relative; top:-1px; color:#fff; font-size:10px; background:#9DB9D5; padding:0px 6px 1px 6px; border-radius:3px; margin-left:4px;}
232 table.list tbody tr.group span.count {position:relative; top:-1px; color:#fff; font-size:10px; background:#9DB9D5; padding:0px 6px 1px 6px; border-radius:3px; margin-left:4px;}
233 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
233 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
234 tr.group:hover a.toggle-all { display:inline;}
234 tr.group:hover a.toggle-all { display:inline;}
235 a.toggle-all:hover {text-decoration:none;}
235 a.toggle-all:hover {text-decoration:none;}
236
236
237 table.list tbody tr:hover { background-color:#ffffdd; }
237 table.list tbody tr:hover { background-color:#ffffdd; }
238 table.list tbody tr.group:hover { background-color:inherit; }
238 table.list tbody tr.group:hover { background-color:inherit; }
239 table td {padding:2px;}
239 table td {padding:2px;}
240 table p {margin:0;}
240 table p {margin:0;}
241 .odd {background-color:#f6f7f8;}
241 .odd {background-color:#f6f7f8;}
242 .even {background-color: #fff;}
242 .even {background-color: #fff;}
243
243
244 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
244 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
245 a.sort.asc { background-image: url(../images/sort_asc.png); }
245 a.sort.asc { background-image: url(../images/sort_asc.png); }
246 a.sort.desc { background-image: url(../images/sort_desc.png); }
246 a.sort.desc { background-image: url(../images/sort_desc.png); }
247
247
248 table.attributes { width: 100% }
248 table.attributes { width: 100% }
249 table.attributes th { vertical-align: top; text-align: left; }
249 table.attributes th { vertical-align: top; text-align: left; }
250 table.attributes td { vertical-align: top; }
250 table.attributes td { vertical-align: top; }
251
251
252 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
252 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
253 table.boards td.last-message {text-align:left;font-size:80%;}
253 table.boards td.last-message {text-align:left;font-size:80%;}
254
254
255 table.messages td.last_message {text-align:left;}
255 table.messages td.last_message {text-align:left;}
256
256
257 #query_form_content {font-size:90%;}
258
257 table.query-columns {
259 table.query-columns {
258 border-collapse: collapse;
260 border-collapse: collapse;
259 border: 0;
261 border: 0;
260 }
262 }
261
263
262 table.query-columns td.buttons {
264 table.query-columns td.buttons {
263 vertical-align: middle;
265 vertical-align: middle;
264 text-align: center;
266 text-align: center;
265 }
267 }
266
268
267 td.center {text-align:center;}
269 td.center {text-align:center;}
268
270
269 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
271 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
270
272
271 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
273 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
272 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
274 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
273 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
275 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
274 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
276 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
275
277
276 #watchers select {width: 95%; display: block;}
278 #watchers select {width: 95%; display: block;}
277 #watchers a.delete {opacity: 0.4; vertical-align: middle;}
279 #watchers a.delete {opacity: 0.4; vertical-align: middle;}
278 #watchers a.delete:hover {opacity: 1;}
280 #watchers a.delete:hover {opacity: 1;}
279 #watchers img.gravatar {margin: 0 4px 2px 0;}
281 #watchers img.gravatar {margin: 0 4px 2px 0;}
280
282
281 span#watchers_inputs {overflow:auto; display:block;}
283 span#watchers_inputs {overflow:auto; display:block;}
282 span.search_for_watchers {display:block;}
284 span.search_for_watchers {display:block;}
283 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
285 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
284 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
286 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
285
287
286
288
287 .highlight { background-color: #FCFD8D;}
289 .highlight { background-color: #FCFD8D;}
288 .highlight.token-1 { background-color: #faa;}
290 .highlight.token-1 { background-color: #faa;}
289 .highlight.token-2 { background-color: #afa;}
291 .highlight.token-2 { background-color: #afa;}
290 .highlight.token-3 { background-color: #aaf;}
292 .highlight.token-3 { background-color: #aaf;}
291
293
292 .box{
294 .box{
293 padding:6px;
295 padding:6px;
294 margin-bottom: 10px;
296 margin-bottom: 10px;
295 background-color:#f6f6f6;
297 background-color:#f6f6f6;
296 color:#505050;
298 color:#505050;
297 line-height:1.5em;
299 line-height:1.5em;
298 border: 1px solid #e4e4e4;
300 border: 1px solid #e4e4e4;
299 }
301 }
300
302
301 div.square {
303 div.square {
302 border: 1px solid #999;
304 border: 1px solid #999;
303 float: left;
305 float: left;
304 margin: .3em .4em 0 .4em;
306 margin: .3em .4em 0 .4em;
305 overflow: hidden;
307 overflow: hidden;
306 width: .6em; height: .6em;
308 width: .6em; height: .6em;
307 }
309 }
308 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
310 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
309 .contextual input, .contextual select {font-size:0.9em;}
311 .contextual input, .contextual select {font-size:0.9em;}
310 .message .contextual { margin-top: 0; }
312 .message .contextual { margin-top: 0; }
311
313
312 .splitcontent {overflow:auto;}
314 .splitcontent {overflow:auto;}
313 .splitcontentleft{float:left; width:49%;}
315 .splitcontentleft{float:left; width:49%;}
314 .splitcontentright{float:right; width:49%;}
316 .splitcontentright{float:right; width:49%;}
315 form {display: inline;}
317 form {display: inline;}
316 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
318 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
317 fieldset {border: 1px solid #e4e4e4; margin:0;}
319 fieldset {border: 1px solid #e4e4e4; margin:0;}
318 legend {color: #484848;}
320 legend {color: #484848;}
319 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
321 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
320 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
322 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
321 blockquote blockquote { margin-left: 0;}
323 blockquote blockquote { margin-left: 0;}
322 abbr { border-bottom: 1px dotted; cursor: help; }
324 abbr { border-bottom: 1px dotted; cursor: help; }
323 textarea.wiki-edit {width:99%; resize:vertical;}
325 textarea.wiki-edit {width:99%; resize:vertical;}
324 li p {margin-top: 0;}
326 li p {margin-top: 0;}
325 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
327 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
326 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
328 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
327 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
329 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
328 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
330 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
329
331
330 div.issue div.subject div div { padding-left: 16px; }
332 div.issue div.subject div div { padding-left: 16px; }
331 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
333 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
332 div.issue div.subject>div>p { margin-top: 0.5em; }
334 div.issue div.subject>div>p { margin-top: 0.5em; }
333 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
335 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
334 div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;}
336 div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;}
335 div.issue .next-prev-links {color:#999;}
337 div.issue .next-prev-links {color:#999;}
336 div.issue table.attributes th {width:22%;}
338 div.issue table.attributes th {width:22%;}
337 div.issue table.attributes td {width:28%;}
339 div.issue table.attributes td {width:28%;}
338
340
339 #issue_tree table.issues, #relations table.issues { border: 0; }
341 #issue_tree table.issues, #relations table.issues { border: 0; }
340 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
342 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
341 #relations td.buttons {padding:0;}
343 #relations td.buttons {padding:0;}
342
344
343 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
345 fieldset.collapsible {border-width: 1px 0 0 0;}
344 fieldset.collapsible>legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
346 fieldset.collapsible>legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
345 fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); }
347 fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); }
346
348
347 fieldset#date-range p { margin: 2px 0 2px 0; }
349 fieldset#date-range p { margin: 2px 0 2px 0; }
348 fieldset#filters table { border-collapse: collapse; }
350 fieldset#filters table { border-collapse: collapse; }
349 fieldset#filters table td { padding: 0; vertical-align: middle; }
351 fieldset#filters table td { padding: 0; vertical-align: middle; }
350 fieldset#filters tr.filter { height: 2.1em; }
352 fieldset#filters tr.filter { height: 2.1em; }
351 fieldset#filters td.field { width:230px; }
353 fieldset#filters td.field { width:230px; }
352 fieldset#filters td.operator { width:180px; }
354 fieldset#filters td.operator { width:180px; }
353 fieldset#filters td.operator select {max-width:170px;}
355 fieldset#filters td.operator select {max-width:170px;}
354 fieldset#filters td.values { white-space:nowrap; }
356 fieldset#filters td.values { white-space:nowrap; }
355 fieldset#filters td.values select {min-width:130px;}
357 fieldset#filters td.values select {min-width:130px;}
356 fieldset#filters td.values input {height:1em;}
358 fieldset#filters td.values input {height:1em;}
357 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
359 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
358
360
359 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
361 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
360 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
362 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
361
363
362 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
364 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
363 div#issue-changesets div.changeset { padding: 4px;}
365 div#issue-changesets div.changeset { padding: 4px;}
364 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
366 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
365 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
367 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
366
368
367 .journal ul.details img {margin:0 0 -3px 4px;}
369 .journal ul.details img {margin:0 0 -3px 4px;}
368 div.journal {overflow:auto;}
370 div.journal {overflow:auto;}
369 div.journal.private-notes {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;}
371 div.journal.private-notes {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;}
370
372
371 div#activity dl, #search-results { margin-left: 2em; }
373 div#activity dl, #search-results { margin-left: 2em; }
372 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
374 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
373 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
375 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
374 div#activity dt.me .time { border-bottom: 1px solid #999; }
376 div#activity dt.me .time { border-bottom: 1px solid #999; }
375 div#activity dt .time { color: #777; font-size: 80%; }
377 div#activity dt .time { color: #777; font-size: 80%; }
376 div#activity dd .description, #search-results dd .description { font-style: italic; }
378 div#activity dd .description, #search-results dd .description { font-style: italic; }
377 div#activity span.project:after, #search-results span.project:after { content: " -"; }
379 div#activity span.project:after, #search-results span.project:after { content: " -"; }
378 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
380 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
379 div#activity dt.grouped {margin-left:5em;}
381 div#activity dt.grouped {margin-left:5em;}
380 div#activity dd.grouped {margin-left:9em;}
382 div#activity dd.grouped {margin-left:9em;}
381
383
382 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
384 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
383
385
384 div#search-results-counts {float:right;}
386 div#search-results-counts {float:right;}
385 div#search-results-counts ul { margin-top: 0.5em; }
387 div#search-results-counts ul { margin-top: 0.5em; }
386 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
388 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
387
389
388 dt.issue { background-image: url(../images/ticket.png); }
390 dt.issue { background-image: url(../images/ticket.png); }
389 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
391 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
390 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
392 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
391 dt.issue-note { background-image: url(../images/ticket_note.png); }
393 dt.issue-note { background-image: url(../images/ticket_note.png); }
392 dt.changeset { background-image: url(../images/changeset.png); }
394 dt.changeset { background-image: url(../images/changeset.png); }
393 dt.news { background-image: url(../images/news.png); }
395 dt.news { background-image: url(../images/news.png); }
394 dt.message { background-image: url(../images/message.png); }
396 dt.message { background-image: url(../images/message.png); }
395 dt.reply { background-image: url(../images/comments.png); }
397 dt.reply { background-image: url(../images/comments.png); }
396 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
398 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
397 dt.attachment { background-image: url(../images/attachment.png); }
399 dt.attachment { background-image: url(../images/attachment.png); }
398 dt.document { background-image: url(../images/document.png); }
400 dt.document { background-image: url(../images/document.png); }
399 dt.project { background-image: url(../images/projects.png); }
401 dt.project { background-image: url(../images/projects.png); }
400 dt.time-entry { background-image: url(../images/time.png); }
402 dt.time-entry { background-image: url(../images/time.png); }
401
403
402 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
404 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
403
405
404 div#roadmap .related-issues { margin-bottom: 1em; }
406 div#roadmap .related-issues { margin-bottom: 1em; }
405 div#roadmap .related-issues td.checkbox { display: none; }
407 div#roadmap .related-issues td.checkbox { display: none; }
406 div#roadmap .wiki h1:first-child { display: none; }
408 div#roadmap .wiki h1:first-child { display: none; }
407 div#roadmap .wiki h1 { font-size: 120%; }
409 div#roadmap .wiki h1 { font-size: 120%; }
408 div#roadmap .wiki h2 { font-size: 110%; }
410 div#roadmap .wiki h2 { font-size: 110%; }
409 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
411 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
410
412
411 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
413 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
412 div#version-summary fieldset { margin-bottom: 1em; }
414 div#version-summary fieldset { margin-bottom: 1em; }
413 div#version-summary fieldset.time-tracking table { width:100%; }
415 div#version-summary fieldset.time-tracking table { width:100%; }
414 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
416 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
415
417
416 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
418 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
417 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
419 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
418 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
420 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
419 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
421 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
420 table#time-report .hours-dec { font-size: 0.9em; }
422 table#time-report .hours-dec { font-size: 0.9em; }
421
423
422 div.wiki-page .contextual a {opacity: 0.4}
424 div.wiki-page .contextual a {opacity: 0.4}
423 div.wiki-page .contextual a:hover {opacity: 1}
425 div.wiki-page .contextual a:hover {opacity: 1}
424
426
425 form .attributes select { width: 60%; }
427 form .attributes select { width: 60%; }
426 input#issue_subject { width: 99%; }
428 input#issue_subject { width: 99%; }
427 select#issue_done_ratio { width: 95px; }
429 select#issue_done_ratio { width: 95px; }
428
430
429 ul.projects {margin:0; padding-left:1em;}
431 ul.projects {margin:0; padding-left:1em;}
430 ul.projects ul {padding-left:1.6em;}
432 ul.projects ul {padding-left:1.6em;}
431 ul.projects.root {margin:0; padding:0;}
433 ul.projects.root {margin:0; padding:0;}
432 ul.projects li {list-style-type:none;}
434 ul.projects li {list-style-type:none;}
433
435
434 #projects-index ul.projects ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;}
436 #projects-index ul.projects ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;}
435 #projects-index ul.projects li.root {margin-bottom: 1em;}
437 #projects-index ul.projects li.root {margin-bottom: 1em;}
436 #projects-index ul.projects li.child {margin-top: 1em;}
438 #projects-index ul.projects li.child {margin-top: 1em;}
437 #projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
439 #projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
438 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
440 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
439
441
440 #notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;}
442 #notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;}
441
443
442 #related-issues li img {vertical-align:middle;}
444 #related-issues li img {vertical-align:middle;}
443
445
444 ul.properties {padding:0; font-size: 0.9em; color: #777;}
446 ul.properties {padding:0; font-size: 0.9em; color: #777;}
445 ul.properties li {list-style-type:none;}
447 ul.properties li {list-style-type:none;}
446 ul.properties li span {font-style:italic;}
448 ul.properties li span {font-style:italic;}
447
449
448 .total-hours { font-size: 110%; font-weight: bold; }
450 .total-hours { font-size: 110%; font-weight: bold; }
449 .total-hours span.hours-int { font-size: 120%; }
451 .total-hours span.hours-int { font-size: 120%; }
450
452
451 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
453 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
452 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
454 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
453
455
454 #workflow_copy_form select { width: 200px; }
456 #workflow_copy_form select { width: 200px; }
455 table.transitions td.enabled {background: #bfb;}
457 table.transitions td.enabled {background: #bfb;}
456 table.fields_permissions select {font-size:90%}
458 #workflow_form table select {font-size:90%; max-width:100px;}
457 table.fields_permissions td.readonly {background:#ddd;}
459 table.fields_permissions td.readonly {background:#ddd;}
458 table.fields_permissions td.required {background:#d88;}
460 table.fields_permissions td.required {background:#d88;}
459
461
462 select.expandable {vertical-align:top;}
463
460 textarea#custom_field_possible_values {width: 95%; resize:vertical}
464 textarea#custom_field_possible_values {width: 95%; resize:vertical}
461 textarea#custom_field_default_value {width: 95%; resize:vertical}
465 textarea#custom_field_default_value {width: 95%; resize:vertical}
462
466
463 input#content_comments {width: 99%}
467 input#content_comments {width: 99%}
464
468
465 p.pagination {margin-top:8px; font-size: 90%}
469 p.pagination {margin-top:8px; font-size: 90%}
466
470
467 /***** Tabular forms ******/
471 /***** Tabular forms ******/
468 .tabular p{
472 .tabular p{
469 margin: 0;
473 margin: 0;
470 padding: 3px 0 3px 0;
474 padding: 3px 0 3px 0;
471 padding-left: 180px; /* width of left column containing the label elements */
475 padding-left: 180px; /* width of left column containing the label elements */
472 min-height: 1.8em;
476 min-height: 1.8em;
473 clear:left;
477 clear:left;
474 }
478 }
475
479
476 html>body .tabular p {overflow:hidden;}
480 html>body .tabular p {overflow:hidden;}
477
481
478 .tabular input, .tabular select {max-width:95%}
482 .tabular input, .tabular select {max-width:95%}
479 .tabular textarea {width:95%; resize:vertical;}
483 .tabular textarea {width:95%; resize:vertical;}
480 .tabular span[title] {border-bottom:1px dotted #aaa;}
484 .tabular span[title] {border-bottom:1px dotted #aaa;}
481
485
482 .tabular label{
486 .tabular label{
483 font-weight: bold;
487 font-weight: bold;
484 float: left;
488 float: left;
485 text-align: right;
489 text-align: right;
486 /* width of left column */
490 /* width of left column */
487 margin-left: -180px;
491 margin-left: -180px;
488 /* width of labels. Should be smaller than left column to create some right margin */
492 /* width of labels. Should be smaller than left column to create some right margin */
489 width: 175px;
493 width: 175px;
490 }
494 }
491
495
492 .tabular label.floating{
496 .tabular label.floating{
493 font-weight: normal;
497 font-weight: normal;
494 margin-left: 0px;
498 margin-left: 0px;
495 text-align: left;
499 text-align: left;
496 width: 270px;
500 width: 270px;
497 }
501 }
498
502
499 .tabular label.block{
503 .tabular label.block{
500 font-weight: normal;
504 font-weight: normal;
501 margin-left: 0px !important;
505 margin-left: 0px !important;
502 text-align: left;
506 text-align: left;
503 float: none;
507 float: none;
504 display: block;
508 display: block;
505 width: auto !important;
509 width: auto !important;
506 }
510 }
507
511
508 .tabular label.inline{
512 .tabular label.inline{
509 font-weight: normal;
513 font-weight: normal;
510 float:none;
514 float:none;
511 margin-left: 5px !important;
515 margin-left: 5px !important;
512 width: auto;
516 width: auto;
513 }
517 }
514
518
515 label.no-css {
519 label.no-css {
516 font-weight: inherit;
520 font-weight: inherit;
517 float:none;
521 float:none;
518 text-align:left;
522 text-align:left;
519 margin-left:0px;
523 margin-left:0px;
520 width:auto;
524 width:auto;
521 }
525 }
522 input#time_entry_comments { width: 90%;}
526 input#time_entry_comments { width: 90%;}
523
527
524 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
528 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
525
529
526 .tabular.settings p{ padding-left: 300px; }
530 .tabular.settings p{ padding-left: 300px; }
527 .tabular.settings label{ margin-left: -300px; width: 295px; }
531 .tabular.settings label{ margin-left: -300px; width: 295px; }
528 .tabular.settings textarea { width: 99%; }
532 .tabular.settings textarea { width: 99%; }
529
533
530 .settings.enabled_scm table {width:100%}
534 .settings.enabled_scm table {width:100%}
531 .settings.enabled_scm td.scm_name{ font-weight: bold; }
535 .settings.enabled_scm td.scm_name{ font-weight: bold; }
532
536
533 fieldset.settings label { display: block; }
537 fieldset.settings label { display: block; }
534 fieldset#notified_events .parent { padding-left: 20px; }
538 fieldset#notified_events .parent { padding-left: 20px; }
535
539
536 span.required {color: #bb0000;}
540 span.required {color: #bb0000;}
537 .summary {font-style: italic;}
541 .summary {font-style: italic;}
538
542
539 .check_box_group {
543 .check_box_group {
540 display:block;
544 display:block;
541 width:95%;
545 width:95%;
542 max-height:300px;
546 max-height:300px;
543 overflow-y:auto;
547 overflow-y:auto;
544 padding:2px 4px 4px 2px;
548 padding:2px 4px 4px 2px;
545 background:#fff;
549 background:#fff;
546 border:1px solid #9EB1C2;
550 border:1px solid #9EB1C2;
547 border-radius:2px
551 border-radius:2px
548 }
552 }
549 .check_box_group label {
553 .check_box_group label {
550 font-weight: normal;
554 font-weight: normal;
551 margin-left: 0px !important;
555 margin-left: 0px !important;
552 text-align: left;
556 text-align: left;
553 float: none;
557 float: none;
554 display: block;
558 display: block;
555 width: auto;
559 width: auto;
556 }
560 }
557 .check_box_group.bool_cf {border:0; background:inherit;}
561 .check_box_group.bool_cf {border:0; background:inherit;}
558 .check_box_group.bool_cf label {display: inline;}
562 .check_box_group.bool_cf label {display: inline;}
559
563
560 #attachments_fields input.description {margin-left:4px; width:340px;}
564 #attachments_fields input.description {margin-left:4px; width:340px;}
561 #attachments_fields span {display:block; white-space:nowrap;}
565 #attachments_fields span {display:block; white-space:nowrap;}
562 #attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;}
566 #attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;}
563 #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
567 #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
564 #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
568 #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
565 #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
569 #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
566 a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;}
570 a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;}
567 a.remove-upload:hover {text-decoration:none !important;}
571 a.remove-upload:hover {text-decoration:none !important;}
568
572
569 div.fileover { background-color: lavender; }
573 div.fileover { background-color: lavender; }
570
574
571 div.attachments { margin-top: 12px; }
575 div.attachments { margin-top: 12px; }
572 div.attachments p { margin:4px 0 2px 0; }
576 div.attachments p { margin:4px 0 2px 0; }
573 div.attachments img { vertical-align: middle; }
577 div.attachments img { vertical-align: middle; }
574 div.attachments span.author { font-size: 0.9em; color: #888; }
578 div.attachments span.author { font-size: 0.9em; color: #888; }
575
579
576 div.thumbnails {margin-top:0.6em;}
580 div.thumbnails {margin-top:0.6em;}
577 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
581 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
578 div.thumbnails img {margin: 3px;}
582 div.thumbnails img {margin: 3px;}
579
583
580 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
584 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
581 .other-formats span + span:before { content: "| "; }
585 .other-formats span + span:before { content: "| "; }
582
586
583 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
587 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
584
588
585 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
589 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
586 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
590 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
587
591
588 textarea.text_cf {width:95%; resize:vertical;}
592 textarea.text_cf {width:95%; resize:vertical;}
589 input.string_cf, input.link_cf {width:95%;}
593 input.string_cf, input.link_cf {width:95%;}
590 select.bool_cf {width:auto !important;}
594 select.bool_cf {width:auto !important;}
591
595
592 #tab-content-modules fieldset p {margin:3px 0 4px 0;}
596 #tab-content-modules fieldset p {margin:3px 0 4px 0;}
593
597
594 #tab-content-members .splitcontentleft, #tab-content-memberships .splitcontentleft, #tab-content-users .splitcontentleft {width: 64%;}
598 #tab-content-members .splitcontentleft, #tab-content-memberships .splitcontentleft, #tab-content-users .splitcontentleft {width: 64%;}
595 #tab-content-members .splitcontentright, #tab-content-memberships .splitcontentright, #tab-content-users .splitcontentright {width: 34%;}
599 #tab-content-members .splitcontentright, #tab-content-memberships .splitcontentright, #tab-content-users .splitcontentright {width: 34%;}
596 #tab-content-members fieldset, #tab-content-memberships fieldset, #tab-content-users fieldset {padding:1em; margin-bottom: 1em;}
600 #tab-content-members fieldset, #tab-content-memberships fieldset, #tab-content-users fieldset {padding:1em; margin-bottom: 1em;}
597 #tab-content-members fieldset legend, #tab-content-memberships fieldset legend, #tab-content-users fieldset legend {font-weight: bold;}
601 #tab-content-members fieldset legend, #tab-content-memberships fieldset legend, #tab-content-users fieldset legend {font-weight: bold;}
598 #tab-content-members fieldset label, #tab-content-memberships fieldset label, #tab-content-users fieldset label {display: block;}
602 #tab-content-members fieldset label, #tab-content-memberships fieldset label, #tab-content-users fieldset label {display: block;}
599 #tab-content-members #principals, #tab-content-users #principals {max-height: 400px; overflow: auto;}
603 #tab-content-members #principals, #tab-content-users #principals {max-height: 400px; overflow: auto;}
600
604
601 #tab-content-memberships .splitcontentright select {width:90%}
605 #tab-content-memberships .splitcontentright select {width:90%}
602
606
603 #users_for_watcher {height: 200px; overflow:auto;}
607 #users_for_watcher {height: 200px; overflow:auto;}
604 #users_for_watcher label {display: block;}
608 #users_for_watcher label {display: block;}
605
609
606 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
610 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
607
611
608 input#principal_search, input#user_search {width:90%}
612 input#principal_search, input#user_search {width:90%}
609
613
610 input.autocomplete {
614 input.autocomplete {
611 background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px !important;
615 background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px !important;
612 border:1px solid #9EB1C2; border-radius:2px; height:1.5em;
616 border:1px solid #9EB1C2; border-radius:2px; height:1.5em;
613 }
617 }
614 input.autocomplete.ajax-loading {
618 input.autocomplete.ajax-loading {
615 background-image: url(../images/loading.gif);
619 background-image: url(../images/loading.gif);
616 }
620 }
617
621
618 .role-visibility {padding-left:2em;}
622 .role-visibility {padding-left:2em;}
619
623
620 /***** Flash & error messages ****/
624 /***** Flash & error messages ****/
621 #errorExplanation, div.flash, .nodata, .warning, .conflict {
625 #errorExplanation, div.flash, .nodata, .warning, .conflict {
622 padding: 4px 4px 4px 30px;
626 padding: 4px 4px 4px 30px;
623 margin-bottom: 12px;
627 margin-bottom: 12px;
624 font-size: 1.1em;
628 font-size: 1.1em;
625 border: 2px solid;
629 border: 2px solid;
626 }
630 }
627
631
628 div.flash {margin-top: 8px;}
632 div.flash {margin-top: 8px;}
629
633
630 div.flash.error, #errorExplanation {
634 div.flash.error, #errorExplanation {
631 background: url(../images/exclamation.png) 8px 50% no-repeat;
635 background: url(../images/exclamation.png) 8px 50% no-repeat;
632 background-color: #ffe3e3;
636 background-color: #ffe3e3;
633 border-color: #dd0000;
637 border-color: #dd0000;
634 color: #880000;
638 color: #880000;
635 }
639 }
636
640
637 div.flash.notice {
641 div.flash.notice {
638 background: url(../images/true.png) 8px 5px no-repeat;
642 background: url(../images/true.png) 8px 5px no-repeat;
639 background-color: #dfffdf;
643 background-color: #dfffdf;
640 border-color: #9fcf9f;
644 border-color: #9fcf9f;
641 color: #005f00;
645 color: #005f00;
642 }
646 }
643
647
644 div.flash.warning, .conflict {
648 div.flash.warning, .conflict {
645 background: url(../images/warning.png) 8px 5px no-repeat;
649 background: url(../images/warning.png) 8px 5px no-repeat;
646 background-color: #FFEBC1;
650 background-color: #FFEBC1;
647 border-color: #FDBF3B;
651 border-color: #FDBF3B;
648 color: #A6750C;
652 color: #A6750C;
649 text-align: left;
653 text-align: left;
650 }
654 }
651
655
652 .nodata, .warning {
656 .nodata, .warning {
653 text-align: center;
657 text-align: center;
654 background-color: #FFEBC1;
658 background-color: #FFEBC1;
655 border-color: #FDBF3B;
659 border-color: #FDBF3B;
656 color: #A6750C;
660 color: #A6750C;
657 }
661 }
658
662
659 #errorExplanation ul { font-size: 0.9em;}
663 #errorExplanation ul { font-size: 0.9em;}
660 #errorExplanation h2, #errorExplanation p { display: none; }
664 #errorExplanation h2, #errorExplanation p { display: none; }
661
665
662 .conflict-details {font-size:80%;}
666 .conflict-details {font-size:80%;}
663
667
664 /***** Ajax indicator ******/
668 /***** Ajax indicator ******/
665 #ajax-indicator {
669 #ajax-indicator {
666 position: absolute; /* fixed not supported by IE */
670 position: absolute; /* fixed not supported by IE */
667 background-color:#eee;
671 background-color:#eee;
668 border: 1px solid #bbb;
672 border: 1px solid #bbb;
669 top:35%;
673 top:35%;
670 left:40%;
674 left:40%;
671 width:20%;
675 width:20%;
672 font-weight:bold;
676 font-weight:bold;
673 text-align:center;
677 text-align:center;
674 padding:0.6em;
678 padding:0.6em;
675 z-index:100;
679 z-index:100;
676 opacity: 0.5;
680 opacity: 0.5;
677 }
681 }
678
682
679 html>body #ajax-indicator { position: fixed; }
683 html>body #ajax-indicator { position: fixed; }
680
684
681 #ajax-indicator span {
685 #ajax-indicator span {
682 background-position: 0% 40%;
686 background-position: 0% 40%;
683 background-repeat: no-repeat;
687 background-repeat: no-repeat;
684 background-image: url(../images/loading.gif);
688 background-image: url(../images/loading.gif);
685 padding-left: 26px;
689 padding-left: 26px;
686 vertical-align: bottom;
690 vertical-align: bottom;
687 }
691 }
688
692
689 /***** Calendar *****/
693 /***** Calendar *****/
690 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
694 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
691 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
695 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
692 table.cal thead th.week-number {width: auto;}
696 table.cal thead th.week-number {width: auto;}
693 table.cal tbody tr {height: 100px;}
697 table.cal tbody tr {height: 100px;}
694 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
698 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
695 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
699 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
696 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
700 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
697 table.cal td.odd p.day-num {color: #bbb;}
701 table.cal td.odd p.day-num {color: #bbb;}
698 table.cal td.today {background:#ffffdd;}
702 table.cal td.today {background:#ffffdd;}
699 table.cal td.today p.day-num {font-weight: bold;}
703 table.cal td.today p.day-num {font-weight: bold;}
700 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
704 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
701 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
705 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
702 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
706 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
703 p.cal.legend span {display:block;}
707 p.cal.legend span {display:block;}
704
708
705 /***** Tooltips ******/
709 /***** Tooltips ******/
706 .tooltip{position:relative;z-index:24;}
710 .tooltip{position:relative;z-index:24;}
707 .tooltip:hover{z-index:25;color:#000;}
711 .tooltip:hover{z-index:25;color:#000;}
708 .tooltip span.tip{display: none; text-align:left;}
712 .tooltip span.tip{display: none; text-align:left;}
709
713
710 div.tooltip:hover span.tip{
714 div.tooltip:hover span.tip{
711 display:block;
715 display:block;
712 position:absolute;
716 position:absolute;
713 top:12px; left:24px; width:270px;
717 top:12px; left:24px; width:270px;
714 border:1px solid #555;
718 border:1px solid #555;
715 background-color:#fff;
719 background-color:#fff;
716 padding: 4px;
720 padding: 4px;
717 font-size: 0.8em;
721 font-size: 0.8em;
718 color:#505050;
722 color:#505050;
719 }
723 }
720
724
721 img.ui-datepicker-trigger {
725 img.ui-datepicker-trigger {
722 cursor: pointer;
726 cursor: pointer;
723 vertical-align: middle;
727 vertical-align: middle;
724 margin-left: 4px;
728 margin-left: 4px;
725 }
729 }
726
730
727 /***** Progress bar *****/
731 /***** Progress bar *****/
728 table.progress {
732 table.progress {
729 border-collapse: collapse;
733 border-collapse: collapse;
730 border-spacing: 0pt;
734 border-spacing: 0pt;
731 empty-cells: show;
735 empty-cells: show;
732 text-align: center;
736 text-align: center;
733 float:left;
737 float:left;
734 margin: 1px 6px 1px 0px;
738 margin: 1px 6px 1px 0px;
735 }
739 }
736
740
737 table.progress td { height: 1em; }
741 table.progress td { height: 1em; }
738 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
742 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
739 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
743 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
740 table.progress td.todo { background: #eee none repeat scroll 0%; }
744 table.progress td.todo { background: #eee none repeat scroll 0%; }
741 p.percent {font-size: 80%;}
745 p.percent {font-size: 80%;}
742 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
746 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
743
747
744 #roadmap table.progress td { height: 1.2em; }
748 #roadmap table.progress td { height: 1.2em; }
745 /***** Tabs *****/
749 /***** Tabs *****/
746 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
750 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
747 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
751 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
748 #content .tabs ul li {
752 #content .tabs ul li {
749 float:left;
753 float:left;
750 list-style-type:none;
754 list-style-type:none;
751 white-space:nowrap;
755 white-space:nowrap;
752 margin-right:4px;
756 margin-right:4px;
753 background:#fff;
757 background:#fff;
754 position:relative;
758 position:relative;
755 margin-bottom:-1px;
759 margin-bottom:-1px;
756 }
760 }
757 #content .tabs ul li a{
761 #content .tabs ul li a{
758 display:block;
762 display:block;
759 font-size: 0.9em;
763 font-size: 0.9em;
760 text-decoration:none;
764 text-decoration:none;
761 line-height:1.3em;
765 line-height:1.3em;
762 padding:4px 6px 4px 6px;
766 padding:4px 6px 4px 6px;
763 border: 1px solid #ccc;
767 border: 1px solid #ccc;
764 border-bottom: 1px solid #bbbbbb;
768 border-bottom: 1px solid #bbbbbb;
765 background-color: #f6f6f6;
769 background-color: #f6f6f6;
766 color:#999;
770 color:#999;
767 font-weight:bold;
771 font-weight:bold;
768 border-top-left-radius:3px;
772 border-top-left-radius:3px;
769 border-top-right-radius:3px;
773 border-top-right-radius:3px;
770 }
774 }
771
775
772 #content .tabs ul li a:hover {
776 #content .tabs ul li a:hover {
773 background-color: #ffffdd;
777 background-color: #ffffdd;
774 text-decoration:none;
778 text-decoration:none;
775 }
779 }
776
780
777 #content .tabs ul li a.selected {
781 #content .tabs ul li a.selected {
778 background-color: #fff;
782 background-color: #fff;
779 border: 1px solid #bbbbbb;
783 border: 1px solid #bbbbbb;
780 border-bottom: 1px solid #fff;
784 border-bottom: 1px solid #fff;
781 color:#444;
785 color:#444;
782 }
786 }
783
787
784 #content .tabs ul li a.selected:hover {background-color: #fff;}
788 #content .tabs ul li a.selected:hover {background-color: #fff;}
785
789
786 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
790 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
787
791
788 button.tab-left, button.tab-right {
792 button.tab-left, button.tab-right {
789 font-size: 0.9em;
793 font-size: 0.9em;
790 cursor: pointer;
794 cursor: pointer;
791 height:24px;
795 height:24px;
792 border: 1px solid #ccc;
796 border: 1px solid #ccc;
793 border-bottom: 1px solid #bbbbbb;
797 border-bottom: 1px solid #bbbbbb;
794 position:absolute;
798 position:absolute;
795 padding:4px;
799 padding:4px;
796 width: 20px;
800 width: 20px;
797 bottom: -1px;
801 bottom: -1px;
798 }
802 }
799
803
800 button.tab-left {
804 button.tab-left {
801 right: 20px;
805 right: 20px;
802 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
806 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
803 border-top-left-radius:3px;
807 border-top-left-radius:3px;
804 }
808 }
805
809
806 button.tab-right {
810 button.tab-right {
807 right: 0;
811 right: 0;
808 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
812 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
809 border-top-right-radius:3px;
813 border-top-right-radius:3px;
810 }
814 }
811
815
812 /***** Diff *****/
816 /***** Diff *****/
813 .diff_out { background: #fcc; }
817 .diff_out { background: #fcc; }
814 .diff_out span { background: #faa; }
818 .diff_out span { background: #faa; }
815 .diff_in { background: #cfc; }
819 .diff_in { background: #cfc; }
816 .diff_in span { background: #afa; }
820 .diff_in span { background: #afa; }
817
821
818 .text-diff {
822 .text-diff {
819 padding: 1em;
823 padding: 1em;
820 background-color:#f6f6f6;
824 background-color:#f6f6f6;
821 color:#505050;
825 color:#505050;
822 border: 1px solid #e4e4e4;
826 border: 1px solid #e4e4e4;
823 }
827 }
824
828
825 /***** Wiki *****/
829 /***** Wiki *****/
826 div.wiki table {
830 div.wiki table {
827 border-collapse: collapse;
831 border-collapse: collapse;
828 margin-bottom: 1em;
832 margin-bottom: 1em;
829 }
833 }
830
834
831 div.wiki table, div.wiki td, div.wiki th {
835 div.wiki table, div.wiki td, div.wiki th {
832 border: 1px solid #bbb;
836 border: 1px solid #bbb;
833 padding: 4px;
837 padding: 4px;
834 }
838 }
835
839
836 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
840 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
837
841
838 div.wiki .external {
842 div.wiki .external {
839 background-position: 0% 60%;
843 background-position: 0% 60%;
840 background-repeat: no-repeat;
844 background-repeat: no-repeat;
841 padding-left: 12px;
845 padding-left: 12px;
842 background-image: url(../images/external.png);
846 background-image: url(../images/external.png);
843 }
847 }
844
848
845 div.wiki a.new {color: #b73535;}
849 div.wiki a.new {color: #b73535;}
846
850
847 div.wiki ul, div.wiki ol {margin-bottom:1em;}
851 div.wiki ul, div.wiki ol {margin-bottom:1em;}
848
852
849 div.wiki pre {
853 div.wiki pre {
850 margin: 1em 1em 1em 1.6em;
854 margin: 1em 1em 1em 1.6em;
851 padding: 8px;
855 padding: 8px;
852 background-color: #fafafa;
856 background-color: #fafafa;
853 border: 1px solid #e2e2e2;
857 border: 1px solid #e2e2e2;
854 width:auto;
858 width:auto;
855 overflow-x: auto;
859 overflow-x: auto;
856 overflow-y: hidden;
860 overflow-y: hidden;
857 }
861 }
858
862
859 div.wiki ul.toc {
863 div.wiki ul.toc {
860 background-color: #ffffdd;
864 background-color: #ffffdd;
861 border: 1px solid #e4e4e4;
865 border: 1px solid #e4e4e4;
862 padding: 4px;
866 padding: 4px;
863 line-height: 1.2em;
867 line-height: 1.2em;
864 margin-bottom: 12px;
868 margin-bottom: 12px;
865 margin-right: 12px;
869 margin-right: 12px;
866 margin-left: 0;
870 margin-left: 0;
867 display: table
871 display: table
868 }
872 }
869 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
873 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
870
874
871 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
875 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
872 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
876 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
873 div.wiki ul.toc ul { margin: 0; padding: 0; }
877 div.wiki ul.toc ul { margin: 0; padding: 0; }
874 div.wiki ul.toc li {list-style-type:none; margin: 0; font-size:12px;}
878 div.wiki ul.toc li {list-style-type:none; margin: 0; font-size:12px;}
875 div.wiki ul.toc li li {margin-left: 1.5em; font-size:10px;}
879 div.wiki ul.toc li li {margin-left: 1.5em; font-size:10px;}
876 div.wiki ul.toc a {
880 div.wiki ul.toc a {
877 font-size: 0.9em;
881 font-size: 0.9em;
878 font-weight: normal;
882 font-weight: normal;
879 text-decoration: none;
883 text-decoration: none;
880 color: #606060;
884 color: #606060;
881 }
885 }
882 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
886 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
883
887
884 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
888 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
885 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
889 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
886 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
890 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
887
891
888 div.wiki img {vertical-align:middle; max-width:100%;}
892 div.wiki img {vertical-align:middle; max-width:100%;}
889
893
890 /***** My page layout *****/
894 /***** My page layout *****/
891 .block-receiver {
895 .block-receiver {
892 border:1px dashed #c0c0c0;
896 border:1px dashed #c0c0c0;
893 margin-bottom: 20px;
897 margin-bottom: 20px;
894 padding: 15px 0 15px 0;
898 padding: 15px 0 15px 0;
895 }
899 }
896
900
897 .mypage-box {
901 .mypage-box {
898 margin:0 0 20px 0;
902 margin:0 0 20px 0;
899 color:#505050;
903 color:#505050;
900 line-height:1.5em;
904 line-height:1.5em;
901 }
905 }
902
906
903 .handle {cursor: move;}
907 .handle {cursor: move;}
904
908
905 a.close-icon {
909 a.close-icon {
906 display:block;
910 display:block;
907 margin-top:3px;
911 margin-top:3px;
908 overflow:hidden;
912 overflow:hidden;
909 width:12px;
913 width:12px;
910 height:12px;
914 height:12px;
911 background-repeat: no-repeat;
915 background-repeat: no-repeat;
912 cursor:pointer;
916 cursor:pointer;
913 background-image:url('../images/close.png');
917 background-image:url('../images/close.png');
914 }
918 }
915 a.close-icon:hover {background-image:url('../images/close_hl.png');}
919 a.close-icon:hover {background-image:url('../images/close_hl.png');}
916
920
917 /***** Gantt chart *****/
921 /***** Gantt chart *****/
918 .gantt_hdr {
922 .gantt_hdr {
919 position:absolute;
923 position:absolute;
920 top:0;
924 top:0;
921 height:16px;
925 height:16px;
922 border-top: 1px solid #c0c0c0;
926 border-top: 1px solid #c0c0c0;
923 border-bottom: 1px solid #c0c0c0;
927 border-bottom: 1px solid #c0c0c0;
924 border-right: 1px solid #c0c0c0;
928 border-right: 1px solid #c0c0c0;
925 text-align: center;
929 text-align: center;
926 overflow: hidden;
930 overflow: hidden;
927 }
931 }
928
932
929 .gantt_hdr.nwday {background-color:#f1f1f1;}
933 .gantt_hdr.nwday {background-color:#f1f1f1;}
930
934
931 .gantt_subjects { font-size: 0.8em; }
935 .gantt_subjects { font-size: 0.8em; }
932 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
936 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
933
937
934 .task {
938 .task {
935 position: absolute;
939 position: absolute;
936 height:8px;
940 height:8px;
937 font-size:0.8em;
941 font-size:0.8em;
938 color:#888;
942 color:#888;
939 padding:0;
943 padding:0;
940 margin:0;
944 margin:0;
941 line-height:16px;
945 line-height:16px;
942 white-space:nowrap;
946 white-space:nowrap;
943 }
947 }
944
948
945 .task.label {width:100%;}
949 .task.label {width:100%;}
946 .task.label.project, .task.label.version { font-weight: bold; }
950 .task.label.project, .task.label.version { font-weight: bold; }
947
951
948 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
952 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
949 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
953 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
950 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
954 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
951
955
952 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
956 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
953 .task_late.parent, .task_done.parent { height: 3px;}
957 .task_late.parent, .task_done.parent { height: 3px;}
954 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
958 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
955 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
959 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
956
960
957 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
961 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
958 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
962 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
959 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
963 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
960 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
964 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
961
965
962 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
966 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
963 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
967 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
964 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
968 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
965 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
969 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
966
970
967 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
971 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
968 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
972 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
969
973
970 /***** Icons *****/
974 /***** Icons *****/
971 .icon {
975 .icon {
972 background-position: 0% 50%;
976 background-position: 0% 50%;
973 background-repeat: no-repeat;
977 background-repeat: no-repeat;
974 padding-left: 20px;
978 padding-left: 20px;
975 padding-top: 2px;
979 padding-top: 2px;
976 padding-bottom: 3px;
980 padding-bottom: 3px;
977 }
981 }
978
982
979 .icon-add { background-image: url(../images/add.png); }
983 .icon-add { background-image: url(../images/add.png); }
980 .icon-edit { background-image: url(../images/edit.png); }
984 .icon-edit { background-image: url(../images/edit.png); }
981 .icon-copy { background-image: url(../images/copy.png); }
985 .icon-copy { background-image: url(../images/copy.png); }
982 .icon-duplicate { background-image: url(../images/duplicate.png); }
986 .icon-duplicate { background-image: url(../images/duplicate.png); }
983 .icon-del { background-image: url(../images/delete.png); }
987 .icon-del { background-image: url(../images/delete.png); }
984 .icon-move { background-image: url(../images/move.png); }
988 .icon-move { background-image: url(../images/move.png); }
985 .icon-save { background-image: url(../images/save.png); }
989 .icon-save { background-image: url(../images/save.png); }
986 .icon-cancel { background-image: url(../images/cancel.png); }
990 .icon-cancel { background-image: url(../images/cancel.png); }
987 .icon-multiple { background-image: url(../images/table_multiple.png); }
991 .icon-multiple { background-image: url(../images/table_multiple.png); }
988 .icon-folder { background-image: url(../images/folder.png); }
992 .icon-folder { background-image: url(../images/folder.png); }
989 .open .icon-folder { background-image: url(../images/folder_open.png); }
993 .open .icon-folder { background-image: url(../images/folder_open.png); }
990 .icon-package { background-image: url(../images/package.png); }
994 .icon-package { background-image: url(../images/package.png); }
991 .icon-user { background-image: url(../images/user.png); }
995 .icon-user { background-image: url(../images/user.png); }
992 .icon-projects { background-image: url(../images/projects.png); }
996 .icon-projects { background-image: url(../images/projects.png); }
993 .icon-help { background-image: url(../images/help.png); }
997 .icon-help { background-image: url(../images/help.png); }
994 .icon-attachment { background-image: url(../images/attachment.png); }
998 .icon-attachment { background-image: url(../images/attachment.png); }
995 .icon-history { background-image: url(../images/history.png); }
999 .icon-history { background-image: url(../images/history.png); }
996 .icon-time { background-image: url(../images/time.png); }
1000 .icon-time { background-image: url(../images/time.png); }
997 .icon-time-add { background-image: url(../images/time_add.png); }
1001 .icon-time-add { background-image: url(../images/time_add.png); }
998 .icon-stats { background-image: url(../images/stats.png); }
1002 .icon-stats { background-image: url(../images/stats.png); }
999 .icon-warning { background-image: url(../images/warning.png); }
1003 .icon-warning { background-image: url(../images/warning.png); }
1000 .icon-fav { background-image: url(../images/fav.png); }
1004 .icon-fav { background-image: url(../images/fav.png); }
1001 .icon-fav-off { background-image: url(../images/fav_off.png); }
1005 .icon-fav-off { background-image: url(../images/fav_off.png); }
1002 .icon-reload { background-image: url(../images/reload.png); }
1006 .icon-reload { background-image: url(../images/reload.png); }
1003 .icon-lock { background-image: url(../images/locked.png); }
1007 .icon-lock { background-image: url(../images/locked.png); }
1004 .icon-unlock { background-image: url(../images/unlock.png); }
1008 .icon-unlock { background-image: url(../images/unlock.png); }
1005 .icon-checked { background-image: url(../images/true.png); }
1009 .icon-checked { background-image: url(../images/true.png); }
1006 .icon-details { background-image: url(../images/zoom_in.png); }
1010 .icon-details { background-image: url(../images/zoom_in.png); }
1007 .icon-report { background-image: url(../images/report.png); }
1011 .icon-report { background-image: url(../images/report.png); }
1008 .icon-comment { background-image: url(../images/comment.png); }
1012 .icon-comment { background-image: url(../images/comment.png); }
1009 .icon-summary { background-image: url(../images/lightning.png); }
1013 .icon-summary { background-image: url(../images/lightning.png); }
1010 .icon-server-authentication { background-image: url(../images/server_key.png); }
1014 .icon-server-authentication { background-image: url(../images/server_key.png); }
1011 .icon-issue { background-image: url(../images/ticket.png); }
1015 .icon-issue { background-image: url(../images/ticket.png); }
1012 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
1016 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
1013 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
1017 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
1014 .icon-passwd { background-image: url(../images/textfield_key.png); }
1018 .icon-passwd { background-image: url(../images/textfield_key.png); }
1015 .icon-test { background-image: url(../images/bullet_go.png); }
1019 .icon-test { background-image: url(../images/bullet_go.png); }
1016
1020
1017 .icon-file { background-image: url(../images/files/default.png); }
1021 .icon-file { background-image: url(../images/files/default.png); }
1018 .icon-file.text-plain { background-image: url(../images/files/text.png); }
1022 .icon-file.text-plain { background-image: url(../images/files/text.png); }
1019 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
1023 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
1020 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
1024 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
1021 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
1025 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
1022 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
1026 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
1023 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
1027 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
1024 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
1028 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
1025 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
1029 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
1026 .icon-file.text-css { background-image: url(../images/files/css.png); }
1030 .icon-file.text-css { background-image: url(../images/files/css.png); }
1027 .icon-file.text-html { background-image: url(../images/files/html.png); }
1031 .icon-file.text-html { background-image: url(../images/files/html.png); }
1028 .icon-file.image-gif { background-image: url(../images/files/image.png); }
1032 .icon-file.image-gif { background-image: url(../images/files/image.png); }
1029 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
1033 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
1030 .icon-file.image-png { background-image: url(../images/files/image.png); }
1034 .icon-file.image-png { background-image: url(../images/files/image.png); }
1031 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
1035 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
1032 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
1036 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
1033 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
1037 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
1034 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
1038 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
1035
1039
1036 img.gravatar {
1040 img.gravatar {
1037 padding: 2px;
1041 padding: 2px;
1038 border: solid 1px #d5d5d5;
1042 border: solid 1px #d5d5d5;
1039 background: #fff;
1043 background: #fff;
1040 vertical-align: middle;
1044 vertical-align: middle;
1041 }
1045 }
1042
1046
1043 div.issue img.gravatar {
1047 div.issue img.gravatar {
1044 float: left;
1048 float: left;
1045 margin: 0 6px 0 0;
1049 margin: 0 6px 0 0;
1046 padding: 5px;
1050 padding: 5px;
1047 }
1051 }
1048
1052
1049 div.issue table img.gravatar {
1053 div.issue table img.gravatar {
1050 height: 14px;
1054 height: 14px;
1051 width: 14px;
1055 width: 14px;
1052 padding: 2px;
1056 padding: 2px;
1053 float: left;
1057 float: left;
1054 margin: 0 0.5em 0 0;
1058 margin: 0 0.5em 0 0;
1055 }
1059 }
1056
1060
1057 h2 img.gravatar {margin: -2px 4px -4px 0;}
1061 h2 img.gravatar {margin: -2px 4px -4px 0;}
1058 h3 img.gravatar {margin: -4px 4px -4px 0;}
1062 h3 img.gravatar {margin: -4px 4px -4px 0;}
1059 h4 img.gravatar {margin: -6px 4px -4px 0;}
1063 h4 img.gravatar {margin: -6px 4px -4px 0;}
1060 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1064 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1061 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1065 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1062 /* Used on 12px Gravatar img tags without the icon background */
1066 /* Used on 12px Gravatar img tags without the icon background */
1063 .icon-gravatar {float: left; margin-right: 4px;}
1067 .icon-gravatar {float: left; margin-right: 4px;}
1064
1068
1065 #activity dt, .journal {clear: left;}
1069 #activity dt, .journal {clear: left;}
1066
1070
1067 .journal-link {float: right;}
1071 .journal-link {float: right;}
1068
1072
1069 h2 img { vertical-align:middle; }
1073 h2 img { vertical-align:middle; }
1070
1074
1071 .hascontextmenu { cursor: context-menu; }
1075 .hascontextmenu { cursor: context-menu; }
1072
1076
1073 /* Custom JQuery styles */
1077 /* Custom JQuery styles */
1074 .ui-datepicker-title select {width:70px !important; margin-top:-2px !important; margin-right:4px !important;}
1078 .ui-datepicker-title select {width:70px !important; margin-top:-2px !important; margin-right:4px !important;}
1075
1079
1076
1080
1077 /************* CodeRay styles *************/
1081 /************* CodeRay styles *************/
1078 .syntaxhl div {display: inline;}
1082 .syntaxhl div {display: inline;}
1079 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1083 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1080 .syntaxhl .code pre { overflow: auto }
1084 .syntaxhl .code pre { overflow: auto }
1081 .syntaxhl .debug { color: white !important; background: blue !important; }
1085 .syntaxhl .debug { color: white !important; background: blue !important; }
1082
1086
1083 .syntaxhl .annotation { color:#007 }
1087 .syntaxhl .annotation { color:#007 }
1084 .syntaxhl .attribute-name { color:#b48 }
1088 .syntaxhl .attribute-name { color:#b48 }
1085 .syntaxhl .attribute-value { color:#700 }
1089 .syntaxhl .attribute-value { color:#700 }
1086 .syntaxhl .binary { color:#509 }
1090 .syntaxhl .binary { color:#509 }
1087 .syntaxhl .char .content { color:#D20 }
1091 .syntaxhl .char .content { color:#D20 }
1088 .syntaxhl .char .delimiter { color:#710 }
1092 .syntaxhl .char .delimiter { color:#710 }
1089 .syntaxhl .char { color:#D20 }
1093 .syntaxhl .char { color:#D20 }
1090 .syntaxhl .class { color:#258; font-weight:bold }
1094 .syntaxhl .class { color:#258; font-weight:bold }
1091 .syntaxhl .class-variable { color:#369 }
1095 .syntaxhl .class-variable { color:#369 }
1092 .syntaxhl .color { color:#0A0 }
1096 .syntaxhl .color { color:#0A0 }
1093 .syntaxhl .comment { color:#385 }
1097 .syntaxhl .comment { color:#385 }
1094 .syntaxhl .comment .char { color:#385 }
1098 .syntaxhl .comment .char { color:#385 }
1095 .syntaxhl .comment .delimiter { color:#385 }
1099 .syntaxhl .comment .delimiter { color:#385 }
1096 .syntaxhl .complex { color:#A08 }
1100 .syntaxhl .complex { color:#A08 }
1097 .syntaxhl .constant { color:#258; font-weight:bold }
1101 .syntaxhl .constant { color:#258; font-weight:bold }
1098 .syntaxhl .decorator { color:#B0B }
1102 .syntaxhl .decorator { color:#B0B }
1099 .syntaxhl .definition { color:#099; font-weight:bold }
1103 .syntaxhl .definition { color:#099; font-weight:bold }
1100 .syntaxhl .delimiter { color:black }
1104 .syntaxhl .delimiter { color:black }
1101 .syntaxhl .directive { color:#088; font-weight:bold }
1105 .syntaxhl .directive { color:#088; font-weight:bold }
1102 .syntaxhl .doc { color:#970 }
1106 .syntaxhl .doc { color:#970 }
1103 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1107 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1104 .syntaxhl .doctype { color:#34b }
1108 .syntaxhl .doctype { color:#34b }
1105 .syntaxhl .entity { color:#800; font-weight:bold }
1109 .syntaxhl .entity { color:#800; font-weight:bold }
1106 .syntaxhl .error { color:#F00; background-color:#FAA }
1110 .syntaxhl .error { color:#F00; background-color:#FAA }
1107 .syntaxhl .escape { color:#666 }
1111 .syntaxhl .escape { color:#666 }
1108 .syntaxhl .exception { color:#C00; font-weight:bold }
1112 .syntaxhl .exception { color:#C00; font-weight:bold }
1109 .syntaxhl .float { color:#06D }
1113 .syntaxhl .float { color:#06D }
1110 .syntaxhl .function { color:#06B; font-weight:bold }
1114 .syntaxhl .function { color:#06B; font-weight:bold }
1111 .syntaxhl .global-variable { color:#d70 }
1115 .syntaxhl .global-variable { color:#d70 }
1112 .syntaxhl .hex { color:#02b }
1116 .syntaxhl .hex { color:#02b }
1113 .syntaxhl .imaginary { color:#f00 }
1117 .syntaxhl .imaginary { color:#f00 }
1114 .syntaxhl .include { color:#B44; font-weight:bold }
1118 .syntaxhl .include { color:#B44; font-weight:bold }
1115 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1119 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1116 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1120 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1117 .syntaxhl .instance-variable { color:#33B }
1121 .syntaxhl .instance-variable { color:#33B }
1118 .syntaxhl .integer { color:#06D }
1122 .syntaxhl .integer { color:#06D }
1119 .syntaxhl .key .char { color: #60f }
1123 .syntaxhl .key .char { color: #60f }
1120 .syntaxhl .key .delimiter { color: #404 }
1124 .syntaxhl .key .delimiter { color: #404 }
1121 .syntaxhl .key { color: #606 }
1125 .syntaxhl .key { color: #606 }
1122 .syntaxhl .keyword { color:#939; font-weight:bold }
1126 .syntaxhl .keyword { color:#939; font-weight:bold }
1123 .syntaxhl .label { color:#970; font-weight:bold }
1127 .syntaxhl .label { color:#970; font-weight:bold }
1124 .syntaxhl .local-variable { color:#963 }
1128 .syntaxhl .local-variable { color:#963 }
1125 .syntaxhl .namespace { color:#707; font-weight:bold }
1129 .syntaxhl .namespace { color:#707; font-weight:bold }
1126 .syntaxhl .octal { color:#40E }
1130 .syntaxhl .octal { color:#40E }
1127 .syntaxhl .operator { }
1131 .syntaxhl .operator { }
1128 .syntaxhl .predefined { color:#369; font-weight:bold }
1132 .syntaxhl .predefined { color:#369; font-weight:bold }
1129 .syntaxhl .predefined-constant { color:#069 }
1133 .syntaxhl .predefined-constant { color:#069 }
1130 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1134 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1131 .syntaxhl .preprocessor { color:#579 }
1135 .syntaxhl .preprocessor { color:#579 }
1132 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1136 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1133 .syntaxhl .regexp .content { color:#808 }
1137 .syntaxhl .regexp .content { color:#808 }
1134 .syntaxhl .regexp .delimiter { color:#404 }
1138 .syntaxhl .regexp .delimiter { color:#404 }
1135 .syntaxhl .regexp .modifier { color:#C2C }
1139 .syntaxhl .regexp .modifier { color:#C2C }
1136 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1140 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1137 .syntaxhl .reserved { color:#080; font-weight:bold }
1141 .syntaxhl .reserved { color:#080; font-weight:bold }
1138 .syntaxhl .shell .content { color:#2B2 }
1142 .syntaxhl .shell .content { color:#2B2 }
1139 .syntaxhl .shell .delimiter { color:#161 }
1143 .syntaxhl .shell .delimiter { color:#161 }
1140 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1144 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1141 .syntaxhl .string .char { color: #46a }
1145 .syntaxhl .string .char { color: #46a }
1142 .syntaxhl .string .content { color: #46a }
1146 .syntaxhl .string .content { color: #46a }
1143 .syntaxhl .string .delimiter { color: #46a }
1147 .syntaxhl .string .delimiter { color: #46a }
1144 .syntaxhl .string .modifier { color: #46a }
1148 .syntaxhl .string .modifier { color: #46a }
1145 .syntaxhl .symbol .content { color:#d33 }
1149 .syntaxhl .symbol .content { color:#d33 }
1146 .syntaxhl .symbol .delimiter { color:#d33 }
1150 .syntaxhl .symbol .delimiter { color:#d33 }
1147 .syntaxhl .symbol { color:#d33 }
1151 .syntaxhl .symbol { color:#d33 }
1148 .syntaxhl .tag { color:#070 }
1152 .syntaxhl .tag { color:#070 }
1149 .syntaxhl .type { color:#339; font-weight:bold }
1153 .syntaxhl .type { color:#339; font-weight:bold }
1150 .syntaxhl .value { color: #088; }
1154 .syntaxhl .value { color: #088; }
1151 .syntaxhl .variable { color:#037 }
1155 .syntaxhl .variable { color:#037 }
1152
1156
1153 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1157 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1154 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1158 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1155 .syntaxhl .change { color: #bbf; background: #007; }
1159 .syntaxhl .change { color: #bbf; background: #007; }
1156 .syntaxhl .head { color: #f8f; background: #505 }
1160 .syntaxhl .head { color: #f8f; background: #505 }
1157 .syntaxhl .head .filename { color: white; }
1161 .syntaxhl .head .filename { color: white; }
1158
1162
1159 .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
1163 .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
1160 .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
1164 .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
1161
1165
1162 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1166 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1163 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1167 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1164 .syntaxhl .change .change { color: #88f }
1168 .syntaxhl .change .change { color: #88f }
1165 .syntaxhl .head .head { color: #f4f }
1169 .syntaxhl .head .head { color: #f4f }
1166
1170
1167 /***** Media print specific styles *****/
1171 /***** Media print specific styles *****/
1168 @media print {
1172 @media print {
1169 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1173 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1170 #main { background: #fff; }
1174 #main { background: #fff; }
1171 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1175 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1172 #wiki_add_attachment { display:none; }
1176 #wiki_add_attachment { display:none; }
1173 .hide-when-print { display: none; }
1177 .hide-when-print { display: none; }
1174 .autoscroll {overflow-x: visible;}
1178 .autoscroll {overflow-x: visible;}
1175 table.list {margin-top:0.5em;}
1179 table.list {margin-top:0.5em;}
1176 table.list th, table.list td {border: 1px solid #aaa;}
1180 table.list th, table.list td {border: 1px solid #aaa;}
1177 }
1181 }
1178
1182
1179 /* Accessibility specific styles */
1183 /* Accessibility specific styles */
1180 .hidden-for-sighted {
1184 .hidden-for-sighted {
1181 position:absolute;
1185 position:absolute;
1182 left:-10000px;
1186 left:-10000px;
1183 top:auto;
1187 top:auto;
1184 width:1px;
1188 width:1px;
1185 height:1px;
1189 height:1px;
1186 overflow:hidden;
1190 overflow:hidden;
1187 }
1191 }
@@ -1,344 +1,354
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class WorkflowsControllerTest < ActionController::TestCase
20 class WorkflowsControllerTest < ActionController::TestCase
21 fixtures :roles, :trackers, :workflows, :users, :issue_statuses
21 fixtures :roles, :trackers, :workflows, :users, :issue_statuses
22
22
23 def setup
23 def setup
24 User.current = nil
24 User.current = nil
25 @request.session[:user_id] = 1 # admin
25 @request.session[:user_id] = 1 # admin
26 end
26 end
27
27
28 def test_index
28 def test_index
29 get :index
29 get :index
30 assert_response :success
30 assert_response :success
31 assert_template 'index'
31 assert_template 'index'
32
32
33 count = WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count
33 count = WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count
34 assert_tag :tag => 'a', :content => count.to_s,
34 assert_tag :tag => 'a', :content => count.to_s,
35 :attributes => { :href => '/workflows/edit?role_id=1&amp;tracker_id=2' }
35 :attributes => { :href => '/workflows/edit?role_id=1&amp;tracker_id=2' }
36 end
36 end
37
37
38 def test_get_edit
38 def test_get_edit
39 get :edit
39 get :edit
40 assert_response :success
40 assert_response :success
41 assert_template 'edit'
41 assert_template 'edit'
42 assert_not_nil assigns(:roles)
43 assert_not_nil assigns(:trackers)
44 end
42 end
45
43
46 def test_get_edit_with_role_and_tracker
44 def test_get_edit_with_role_and_tracker
47 WorkflowTransition.delete_all
45 WorkflowTransition.delete_all
48 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
46 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
49 WorkflowTransition.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5)
47 WorkflowTransition.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5)
50
48
51 get :edit, :role_id => 2, :tracker_id => 1
49 get :edit, :role_id => 2, :tracker_id => 1
52 assert_response :success
50 assert_response :success
53 assert_template 'edit'
51 assert_template 'edit'
54
52
55 # used status only
53 # used status only
56 assert_not_nil assigns(:statuses)
54 assert_not_nil assigns(:statuses)
57 assert_equal [2, 3, 5], assigns(:statuses).collect(&:id)
55 assert_equal [2, 3, 5], assigns(:statuses).collect(&:id)
58
56
59 # allowed transitions
57 # allowed transitions
60 assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
58 assert_select 'input[type=checkbox][name=?][value=1][checked=checked]', 'transitions[3][5][always]'
61 :name => 'issue_status[3][5][]',
62 :value => 'always',
63 :checked => 'checked' }
64 # not allowed
59 # not allowed
65 assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
60 assert_select 'input[type=checkbox][name=?][value=1]:not([checked=checked])', 'transitions[3][2][always]'
66 :name => 'issue_status[3][2][]',
67 :value => 'always',
68 :checked => nil }
69 # unused
61 # unused
70 assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox',
62 assert_select 'input[type=checkbox][name=?]', 'transitions[1][1][always]', 0
71 :name => 'issue_status[1][1][]' }
72 end
63 end
73
64
74 def test_get_edit_with_role_and_tracker_and_all_statuses
65 def test_get_edit_with_role_and_tracker_and_all_statuses
75 WorkflowTransition.delete_all
66 WorkflowTransition.delete_all
76
67
77 get :edit, :role_id => 2, :tracker_id => 1, :used_statuses_only => '0'
68 get :edit, :role_id => 2, :tracker_id => 1, :used_statuses_only => '0'
78 assert_response :success
69 assert_response :success
79 assert_template 'edit'
70 assert_template 'edit'
80
71
81 assert_not_nil assigns(:statuses)
72 assert_not_nil assigns(:statuses)
82 assert_equal IssueStatus.count, assigns(:statuses).size
73 assert_equal IssueStatus.count, assigns(:statuses).size
83
74
84 assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
75 assert_select 'input[type=checkbox][name=?]', 'transitions[1][1][always]'
85 :name => 'issue_status[1][1][]',
86 :value => 'always',
87 :checked => nil }
88 end
76 end
89
77
90 def test_post_edit
78 def test_post_edit
79 WorkflowTransition.delete_all
80
91 post :edit, :role_id => 2, :tracker_id => 1,
81 post :edit, :role_id => 2, :tracker_id => 1,
92 :issue_status => {
82 :transitions => {
93 '4' => {'5' => ['always']},
83 '4' => {'5' => {'always' => '1'}},
94 '3' => {'1' => ['always'], '2' => ['always']}
84 '3' => {'1' => {'always' => '1'}, '2' => {'always' => '1'}}
95 }
85 }
96 assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1'
86 assert_response 302
97
87
98 assert_equal 3, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
88 assert_equal 3, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
99 assert_not_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first
89 assert_not_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first
100 assert_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4).first
90 assert_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4).first
101 end
91 end
102
92
103 def test_post_edit_with_additional_transitions
93 def test_post_edit_with_additional_transitions
94 WorkflowTransition.delete_all
95
104 post :edit, :role_id => 2, :tracker_id => 1,
96 post :edit, :role_id => 2, :tracker_id => 1,
105 :issue_status => {
97 :transitions => {
106 '4' => {'5' => ['always']},
98 '4' => {'5' => {'always' => '1', 'author' => '0', 'assignee' => '0'}},
107 '3' => {'1' => ['author'], '2' => ['assignee'], '4' => ['author', 'assignee']}
99 '3' => {'1' => {'always' => '0', 'author' => '1', 'assignee' => '0'},
100 '2' => {'always' => '0', 'author' => '0', 'assignee' => '1'},
101 '4' => {'always' => '0', 'author' => '1', 'assignee' => '1'}}
108 }
102 }
109 assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1'
103 assert_response 302
110
104
111 assert_equal 4, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
105 assert_equal 4, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
112
106
113 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5).first
107 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5).first
114 assert ! w.author
108 assert ! w.author
115 assert ! w.assignee
109 assert ! w.assignee
116 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1).first
110 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1).first
117 assert w.author
111 assert w.author
118 assert ! w.assignee
112 assert ! w.assignee
119 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first
113 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first
120 assert ! w.author
114 assert ! w.author
121 assert w.assignee
115 assert w.assignee
122 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4).first
116 w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4).first
123 assert w.author
117 assert w.author
124 assert w.assignee
118 assert w.assignee
125 end
119 end
126
120
127 def test_clear_workflow
128 assert WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count > 0
129
130 post :edit, :role_id => 1, :tracker_id => 2
131 assert_equal 0, WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count
132 end
133
134 def test_get_permissions
121 def test_get_permissions
135 get :permissions
122 get :permissions
136
123
137 assert_response :success
124 assert_response :success
138 assert_template 'permissions'
125 assert_template 'permissions'
139 assert_not_nil assigns(:roles)
140 assert_not_nil assigns(:trackers)
141 end
126 end
142
127
143 def test_get_permissions_with_role_and_tracker
128 def test_get_permissions_with_role_and_tracker
144 WorkflowPermission.delete_all
129 WorkflowPermission.delete_all
145 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required')
130 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required')
146 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
131 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
147 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly')
132 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly')
148
133
149 get :permissions, :role_id => 1, :tracker_id => 2
134 get :permissions, :role_id => 1, :tracker_id => 2
150 assert_response :success
135 assert_response :success
151 assert_template 'permissions'
136 assert_template 'permissions'
152
137
153 assert_select 'input[name=role_id][value=1]'
138 assert_select 'input[name=?][value=1]', 'role_id[]'
154 assert_select 'input[name=tracker_id][value=2]'
139 assert_select 'input[name=?][value=2]', 'tracker_id[]'
155
140
156 # Required field
141 # Required field
157 assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do
142 assert_select 'select[name=?]', 'permissions[2][assigned_to_id]' do
158 assert_select 'option[value=]'
143 assert_select 'option[value=]'
159 assert_select 'option[value=][selected=selected]', 0
144 assert_select 'option[value=][selected=selected]', 0
160 assert_select 'option[value=readonly]', :text => 'Read-only'
145 assert_select 'option[value=readonly]', :text => 'Read-only'
161 assert_select 'option[value=readonly][selected=selected]', 0
146 assert_select 'option[value=readonly][selected=selected]', 0
162 assert_select 'option[value=required]', :text => 'Required'
147 assert_select 'option[value=required]', :text => 'Required'
163 assert_select 'option[value=required][selected=selected]'
148 assert_select 'option[value=required][selected=selected]'
164 end
149 end
165
150
166 # Read-only field
151 # Read-only field
167 assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do
152 assert_select 'select[name=?]', 'permissions[3][fixed_version_id]' do
168 assert_select 'option[value=]'
153 assert_select 'option[value=]'
169 assert_select 'option[value=][selected=selected]', 0
154 assert_select 'option[value=][selected=selected]', 0
170 assert_select 'option[value=readonly]', :text => 'Read-only'
155 assert_select 'option[value=readonly]', :text => 'Read-only'
171 assert_select 'option[value=readonly][selected=selected]'
156 assert_select 'option[value=readonly][selected=selected]'
172 assert_select 'option[value=required]', :text => 'Required'
157 assert_select 'option[value=required]', :text => 'Required'
173 assert_select 'option[value=required][selected=selected]', 0
158 assert_select 'option[value=required][selected=selected]', 0
174 end
159 end
175
160
176 # Other field
161 # Other field
177 assert_select 'select[name=?]', 'permissions[due_date][3]' do
162 assert_select 'select[name=?]', 'permissions[3][due_date]' do
178 assert_select 'option[value=]'
163 assert_select 'option[value=]'
179 assert_select 'option[value=][selected=selected]', 0
164 assert_select 'option[value=][selected=selected]', 0
180 assert_select 'option[value=readonly]', :text => 'Read-only'
165 assert_select 'option[value=readonly]', :text => 'Read-only'
181 assert_select 'option[value=readonly][selected=selected]', 0
166 assert_select 'option[value=readonly][selected=selected]', 0
182 assert_select 'option[value=required]', :text => 'Required'
167 assert_select 'option[value=required]', :text => 'Required'
183 assert_select 'option[value=required][selected=selected]', 0
168 assert_select 'option[value=required][selected=selected]', 0
184 end
169 end
185 end
170 end
186
171
187 def test_get_permissions_with_required_custom_field_should_not_show_required_option
172 def test_get_permissions_with_required_custom_field_should_not_show_required_option
188 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true)
173 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true)
189
174
190 get :permissions, :role_id => 1, :tracker_id => 1
175 get :permissions, :role_id => 1, :tracker_id => 1
191 assert_response :success
176 assert_response :success
192 assert_template 'permissions'
177 assert_template 'permissions'
193
178
194 # Custom field that is always required
179 # Custom field that is always required
195 # The default option is "(Required)"
180 # The default option is "(Required)"
196 assert_select 'select[name=?]', "permissions[#{cf.id}][3]" do
181 assert_select 'select[name=?]', "permissions[3][#{cf.id}]" do
197 assert_select 'option[value=]'
182 assert_select 'option[value=]'
198 assert_select 'option[value=readonly]', :text => 'Read-only'
183 assert_select 'option[value=readonly]', :text => 'Read-only'
199 assert_select 'option[value=required]', 0
184 assert_select 'option[value=required]', 0
200 end
185 end
201 end
186 end
202
187
203 def test_get_permissions_should_disable_hidden_custom_fields
188 def test_get_permissions_should_disable_hidden_custom_fields
204 cf1 = IssueCustomField.generate!(:tracker_ids => [1], :visible => true)
189 cf1 = IssueCustomField.generate!(:tracker_ids => [1], :visible => true)
205 cf2 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1])
190 cf2 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1])
206 cf3 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1, 2])
191 cf3 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1, 2])
207
192
208 get :permissions, :role_id => 2, :tracker_id => 1
193 get :permissions, :role_id => 2, :tracker_id => 1
209 assert_response :success
194 assert_response :success
210 assert_template 'permissions'
195 assert_template 'permissions'
211
196
212 assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf1.id}][1]"
197 assert_select 'select[name=?]:not(.disabled)', "permissions[1][#{cf1.id}]"
213 assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf3.id}][1]"
198 assert_select 'select[name=?]:not(.disabled)', "permissions[1][#{cf3.id}]"
214
199
215 assert_select 'select[name=?][disabled=disabled]', "permissions[#{cf2.id}][1]" do
200 assert_select 'select[name=?][disabled=disabled]', "permissions[1][#{cf2.id}]" do
216 assert_select 'option[value=][selected=selected]', :text => 'Hidden'
201 assert_select 'option[value=][selected=selected]', :text => 'Hidden'
217 end
202 end
218 end
203 end
219
204
220 def test_get_permissions_with_role_and_tracker_and_all_statuses
205 def test_get_permissions_with_missing_permissions_for_roles_should_default_to_no_change
206 WorkflowPermission.delete_all
207 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
208
209 get :permissions, :role_id => [1, 2], :tracker_id => 2
210 assert_response :success
211
212 assert_select 'select[name=?]', 'permissions[1][assigned_to_id]' do
213 assert_select 'option[selected]', 1
214 assert_select 'option[selected][value=no_change]'
215 end
216 end
217
218 def test_get_permissions_with_different_permissions_for_roles_should_default_to_no_change
219 WorkflowPermission.delete_all
220 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
221 WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'readonly')
222
223 get :permissions, :role_id => [1, 2], :tracker_id => 2
224 assert_response :success
225
226 assert_select 'select[name=?]', 'permissions[1][assigned_to_id]' do
227 assert_select 'option[selected]', 1
228 assert_select 'option[selected][value=no_change]'
229 end
230 end
231
232 def test_get_permissions_with_same_permissions_for_roles_should_default_to_permission
233 WorkflowPermission.delete_all
234 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
235 WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
236
237 get :permissions, :role_id => [1, 2], :tracker_id => 2
238 assert_response :success
239
240 assert_select 'select[name=?]', 'permissions[1][assigned_to_id]' do
241 assert_select 'option[selected]', 1
242 assert_select 'option[selected][value=required]'
243 end
244 end
245
246 def test_get_permissions_with_role_and_tracker_and_all_statuses_should_show_all_statuses
221 WorkflowTransition.delete_all
247 WorkflowTransition.delete_all
222
248
223 get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0'
249 get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0'
224 assert_response :success
250 assert_response :success
225 assert_equal IssueStatus.sorted.all, assigns(:statuses)
251 assert_equal IssueStatus.sorted.all, assigns(:statuses)
226 end
252 end
227
253
228 def test_post_permissions
254 def test_post_permissions
229 WorkflowPermission.delete_all
255 WorkflowPermission.delete_all
230
256
231 post :permissions, :role_id => 1, :tracker_id => 2, :permissions => {
257 post :permissions, :role_id => 1, :tracker_id => 2, :permissions => {
232 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''},
258 '1' => {'assigned_to_id' => '', 'fixed_version_id' => 'required', 'due_date' => ''},
233 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''},
259 '2' => {'assigned_to_id' => 'readonly', 'fixed_version_id' => 'readonly', 'due_date' => ''},
234 'due_date' => {'1' => '', '2' => '', '3' => ''},
260 '3' => {'assigned_to_id' => '', 'fixed_version_id' => '', 'due_date' => ''}
235 }
261 }
236 assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2'
262 assert_response 302
237
263
238 workflows = WorkflowPermission.all
264 workflows = WorkflowPermission.all
239 assert_equal 3, workflows.size
265 assert_equal 3, workflows.size
240 workflows.each do |workflow|
266 workflows.each do |workflow|
241 assert_equal 1, workflow.role_id
267 assert_equal 1, workflow.role_id
242 assert_equal 2, workflow.tracker_id
268 assert_equal 2, workflow.tracker_id
243 end
269 end
244 assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'}
270 assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'}
245 assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'}
271 assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'}
246 assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'}
272 assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'}
247 end
273 end
248
274
249 def test_post_permissions_should_clear_permissions
250 WorkflowPermission.delete_all
251 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required')
252 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
253 wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
254 wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly')
255
256 post :permissions, :role_id => 1, :tracker_id => 2
257 assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2'
258
259 workflows = WorkflowPermission.all
260 assert_equal 2, workflows.size
261 assert wf1.reload
262 assert wf2.reload
263 end
264
265 def test_get_copy
275 def test_get_copy
266 get :copy
276 get :copy
267 assert_response :success
277 assert_response :success
268 assert_template 'copy'
278 assert_template 'copy'
269 assert_select 'select[name=source_tracker_id]' do
279 assert_select 'select[name=source_tracker_id]' do
270 assert_select 'option[value=1]', :text => 'Bug'
280 assert_select 'option[value=1]', :text => 'Bug'
271 end
281 end
272 assert_select 'select[name=source_role_id]' do
282 assert_select 'select[name=source_role_id]' do
273 assert_select 'option[value=2]', :text => 'Developer'
283 assert_select 'option[value=2]', :text => 'Developer'
274 end
284 end
275 assert_select 'select[name=?]', 'target_tracker_ids[]' do
285 assert_select 'select[name=?]', 'target_tracker_ids[]' do
276 assert_select 'option[value=3]', :text => 'Support request'
286 assert_select 'option[value=3]', :text => 'Support request'
277 end
287 end
278 assert_select 'select[name=?]', 'target_role_ids[]' do
288 assert_select 'select[name=?]', 'target_role_ids[]' do
279 assert_select 'option[value=1]', :text => 'Manager'
289 assert_select 'option[value=1]', :text => 'Manager'
280 end
290 end
281 end
291 end
282
292
283 def test_post_copy_one_to_one
293 def test_post_copy_one_to_one
284 source_transitions = status_transitions(:tracker_id => 1, :role_id => 2)
294 source_transitions = status_transitions(:tracker_id => 1, :role_id => 2)
285
295
286 post :copy, :source_tracker_id => '1', :source_role_id => '2',
296 post :copy, :source_tracker_id => '1', :source_role_id => '2',
287 :target_tracker_ids => ['3'], :target_role_ids => ['1']
297 :target_tracker_ids => ['3'], :target_role_ids => ['1']
288 assert_response 302
298 assert_response 302
289 assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1)
299 assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1)
290 end
300 end
291
301
292 def test_post_copy_one_to_many
302 def test_post_copy_one_to_many
293 source_transitions = status_transitions(:tracker_id => 1, :role_id => 2)
303 source_transitions = status_transitions(:tracker_id => 1, :role_id => 2)
294
304
295 post :copy, :source_tracker_id => '1', :source_role_id => '2',
305 post :copy, :source_tracker_id => '1', :source_role_id => '2',
296 :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3']
306 :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3']
297 assert_response 302
307 assert_response 302
298 assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 1)
308 assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 1)
299 assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1)
309 assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1)
300 assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 3)
310 assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 3)
301 assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 3)
311 assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 3)
302 end
312 end
303
313
304 def test_post_copy_many_to_many
314 def test_post_copy_many_to_many
305 source_t2 = status_transitions(:tracker_id => 2, :role_id => 2)
315 source_t2 = status_transitions(:tracker_id => 2, :role_id => 2)
306 source_t3 = status_transitions(:tracker_id => 3, :role_id => 2)
316 source_t3 = status_transitions(:tracker_id => 3, :role_id => 2)
307
317
308 post :copy, :source_tracker_id => 'any', :source_role_id => '2',
318 post :copy, :source_tracker_id => 'any', :source_role_id => '2',
309 :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3']
319 :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3']
310 assert_response 302
320 assert_response 302
311 assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 1)
321 assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 1)
312 assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 1)
322 assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 1)
313 assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 3)
323 assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 3)
314 assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3)
324 assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3)
315 end
325 end
316
326
317 def test_post_copy_with_incomplete_source_specification_should_fail
327 def test_post_copy_with_incomplete_source_specification_should_fail
318 assert_no_difference 'WorkflowRule.count' do
328 assert_no_difference 'WorkflowRule.count' do
319 post :copy,
329 post :copy,
320 :source_tracker_id => '', :source_role_id => '2',
330 :source_tracker_id => '', :source_role_id => '2',
321 :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3']
331 :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3']
322 assert_response 200
332 assert_response 200
323 assert_select 'div.flash.error', :text => 'Please select a source tracker or role'
333 assert_select 'div.flash.error', :text => 'Please select a source tracker or role'
324 end
334 end
325 end
335 end
326
336
327 def test_post_copy_with_incomplete_target_specification_should_fail
337 def test_post_copy_with_incomplete_target_specification_should_fail
328 assert_no_difference 'WorkflowRule.count' do
338 assert_no_difference 'WorkflowRule.count' do
329 post :copy,
339 post :copy,
330 :source_tracker_id => '1', :source_role_id => '2',
340 :source_tracker_id => '1', :source_role_id => '2',
331 :target_tracker_ids => ['2', '3']
341 :target_tracker_ids => ['2', '3']
332 assert_response 200
342 assert_response 200
333 assert_select 'div.flash.error', :text => 'Please select target tracker(s) and role(s)'
343 assert_select 'div.flash.error', :text => 'Please select target tracker(s) and role(s)'
334 end
344 end
335 end
345 end
336
346
337 # Returns an array of status transitions that can be compared
347 # Returns an array of status transitions that can be compared
338 def status_transitions(conditions)
348 def status_transitions(conditions)
339 WorkflowTransition.
349 WorkflowTransition.
340 where(conditions).
350 where(conditions).
341 order('tracker_id, role_id, old_status_id, new_status_id').
351 order('tracker_id, role_id, old_status_id, new_status_id').
342 collect {|w| [w.old_status, w.new_status_id]}
352 collect {|w| [w.old_status, w.new_status_id]}
343 end
353 end
344 end
354 end
General Comments 0
You need to be logged in to leave comments. Login now