Feature #27672 » 0001~0003.patch
app/models/issue_query.rb | ||
---|---|---|
73 | 73 |
options[:draw_progress_line] = (arg == '1' ? '1' : nil) |
74 | 74 |
end |
75 | 75 | |
76 |
def draw_selected_columns |
|
77 |
r = options[:draw_selected_columns] |
|
78 |
r == '1' |
|
79 |
end |
|
80 | ||
81 |
def draw_selected_columns=(arg) |
|
82 |
options[:draw_selected_columns] = (arg == '1' ? '1' : nil) |
|
83 |
end |
|
84 | ||
76 | 85 |
def build_from_params(params, defaults={}) |
77 | 86 |
super |
78 | 87 |
self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations]) || options[:draw_relations] |
79 | 88 |
self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line]) || options[:draw_progress_line] |
89 |
self.draw_selected_columns = params[:draw_selected_columns] || (params[:query] && params[:query][:draw_selected_columns]) || options[:draw_progress_line] |
|
80 | 90 |
self |
81 | 91 |
end |
82 | 92 |
app/views/gantts/show.html.erb | ||
---|---|---|
24 | 24 |
<legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend> |
25 | 25 |
<div style="display: none;"> |
26 | 26 |
<table> |
27 |
<tr> |
|
28 |
<td> |
|
29 |
<fieldset> |
|
30 |
<legend> |
|
31 |
<%= l(:field_column_names) %> |
|
32 |
</legend> |
|
33 |
<label for="draw_selected_columns"> |
|
34 |
<%= check_box 'query', 'draw_selected_columns', :id => 'draw_selected_columns' %> |
|
35 |
<%= l(:label_display) %> |
|
36 |
</label> |
|
37 |
<%= render_query_columns_selection(@query) %> |
|
38 |
</fieldset> |
|
39 |
</td> |
|
40 |
</tr> |
|
27 | 41 |
<tr> |
28 | 42 |
<td> |
29 | 43 |
<fieldset> |
... | ... | |
71 | 85 |
:class => 'icon icon-checked' %> |
72 | 86 |
<%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, |
73 | 87 |
:class => 'icon icon-reload' %> |
74 |
<% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> |
|
75 |
<%= link_to_function l(:button_save), |
|
76 |
"$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit();", |
|
77 |
:class => 'icon icon-save' %> |
|
88 |
<% if @query.new_record? %> |
|
89 |
<% if User.current.allowed_to?(:save_queries, @project, :global => true) %> |
|
90 |
<%= link_to_function l(:button_save), |
|
91 |
"$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit();", |
|
92 |
:class => 'icon icon-save' %> |
|
93 |
<% end %> |
|
94 |
<% else %> |
|
95 |
<% if @query.editable_by?(User.current) %> |
|
96 |
<%= link_to l(:button_edit), edit_query_path(@query, :gantt => 1), :class => 'icon icon-edit' %> |
|
97 |
<%= delete_link query_path(@query, :gantt => 1) %> |
|
98 |
<% end %> |
|
78 | 99 |
<% end %> |
79 | 100 |
<% if !@query.new_record? && @query.editable_by?(User.current) %> |
80 | 101 |
<%= link_to l(:button_edit), edit_query_path(@query, :gantt => 1), :class => 'icon icon-edit' %> |
... | ... | |
92 | 113 | |
93 | 114 |
subject_width = 330 |
94 | 115 |
header_height = 18 |
116 |
selected_column_width = 50 |
|
95 | 117 | |
96 | 118 |
headers_height = header_height |
97 | 119 |
show_weeks = false |
... | ... | |
157 | 179 |
<% end %> |
158 | 180 |
<% end %> |
159 | 181 |
</td> |
160 | ||
161 |
<td> |
|
182 |
<% @query.columns.each do |column| %> |
|
183 |
<% next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) %> |
|
184 |
<td style="width:<%= selected_column_width %>px; padding:0px;" class="gantt_<%= column.name %>_column gantt_selected_column" id="<%= column.name %>"> |
|
185 |
<% |
|
186 |
style = "position:relative;" |
|
187 |
style += "height: #{t_height + 24}px;" |
|
188 |
style += "width: #{selected_column_width + 1}px;" |
|
189 |
%> |
|
190 |
<%= content_tag(:div, :style => style, :class => "gantt_#{column.name}_container") do %> |
|
191 |
<% |
|
192 |
style = "width: #{selected_column_width}px;" |
|
193 |
style += "height: #{t_height}px;" |
|
194 |
style += 'border-left: 0px;' |
|
195 |
style += 'overflow: hidden;' |
|
196 |
%> |
|
197 |
<%= content_tag(:div, '', :style => style, :class => "gantt_hdr") %> |
|
198 |
<% |
|
199 |
style = "width: #{selected_column_width}px;" |
|
200 |
style += "height: #{headers_height}px;" |
|
201 |
style += 'background: #eee;' |
|
202 |
style += 'border-left: 0px !important;' |
|
203 |
%> |
|
204 |
<%= content_tag(:div, content_tag(:p, column.caption, :class => 'gantt_hdr_selected_column_name'), :style => style, :class => "gantt_hdr") %> |
|
205 |
<%= content_tag(:div, :class => "gantt_#{column.name} gantt_selected_column_content") do %> |
|
206 |
<%= @gantt.selected_column_content({:column => column, :top => headers_height + 8, :zoom => zoom, :g_width => g_width}).html_safe %> |
|
207 |
<% end %> |
|
208 |
<% end %> |
|
209 |
</td> |
|
210 |
<% end %> |
|
211 |
<td style='padding: 0px;'> |
|
162 | 212 |
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area"> |
163 | 213 |
<% |
164 | 214 |
style = "" |
... | ... | |
371 | 421 |
<%= javascript_tag do %> |
372 | 422 |
var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>; |
373 | 423 |
$(function() { |
424 |
disable_unavailable_columns('<%= Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.map{|column| column.to_s }.join(',') %>'.split(',')); |
|
374 | 425 |
drawGanttHandler(); |
375 | 426 |
resizableSubjectColumn(); |
376 |
$("#draw_relations").change(drawGanttHandler);
|
|
377 |
$("#draw_progress_line").change(drawGanttHandler);
|
|
427 |
drawSelectedColumns();
|
|
428 |
$("#draw_relations, #draw_progress_line, #draw_selected_columns").change(drawGanttHandler);
|
|
378 | 429 |
}); |
379 | 430 |
$(window).resize(function() { |
380 | 431 |
drawGanttHandler(); |
app/views/queries/_form.html.erb | ||
---|---|---|
22 | 22 |
<p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label> |
23 | 23 |
<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %></p> |
24 | 24 | |
25 |
<% unless params[:gantt] %> |
|
26 | 25 |
<fieldset id="options"><legend><%= l(:label_options) %></legend> |
27 | 26 |
<p><label for="query_default_columns"><%=l(:label_default_columns)%></label> |
28 | 27 |
<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', |
29 | 28 |
:data => {:disables => "#columns, .block_columns input"} %></p> |
30 | 29 | |
30 |
<% unless params[:gantt] %> |
|
31 | 31 |
<p><label for="query_group_by"><%= l(:field_group_by) %></label> |
32 | 32 |
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> |
33 | 33 | |
... | ... | |
36 | 36 | |
37 | 37 |
<p><label><%= l(:label_total_plural) %></label> |
38 | 38 |
<%= available_totalable_columns_tags(@query) %></p> |
39 |
</fieldset> |
|
40 | 39 |
<% else %> |
41 |
<fieldset id="options"><legend><%= l(:label_options) %></legend> |
|
42 | 40 |
<p><label><%= l(:button_show) %></label> |
43 | 41 |
<label class="inline"><%= check_box_tag "query[draw_relations]", "1", @query.draw_relations %> <%= l(:label_related_issues) %></label> |
44 | 42 |
<label class="inline"><%= check_box_tag "query[draw_progress_line]", "1", @query.draw_progress_line %> <%= l(:label_gantt_progress_line) %></label> |
43 |
<label class="inline"><%= check_box_tag "query[draw_selected_columns]", "1", @query.draw_selected_columns %> <%= l(:label_gantt_selected_columns) %></label> |
|
45 | 44 |
</p> |
46 |
</fieldset> |
|
47 | 45 |
<% end %> |
46 |
</fieldset> |
|
48 | 47 |
</div> |
49 | 48 | |
50 | 49 |
<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend> |
... | ... | |
70 | 69 |
</fieldset> |
71 | 70 |
<% end %> |
72 | 71 | |
73 |
<% unless params[:gantt] %> |
|
74 | 72 |
<%= content_tag 'fieldset', :id => 'columns' do %> |
75 | 73 |
<legend><%= l(:field_column_names) %></legend> |
76 | 74 |
<%= render_query_columns_selection(query) %> |
77 | 75 |
<% end %> |
78 |
<% end %> |
|
79 | 76 | |
80 | 77 |
</div> |
81 | 78 |
config/locales/en.yml | ||
---|---|---|
1032 | 1032 |
label_font_proportional: Proportional font |
1033 | 1033 |
label_last_notes: Last notes |
1034 | 1034 |
label_nothing_to_preview: Nothing to preview |
1035 |
label_gantt_selected_columns: Selected columns |
|
1035 | 1036 | |
1036 | 1037 |
button_login: Login |
1037 | 1038 |
button_submit: Submit |
lib/redmine/helpers/gantt.rb | ||
---|---|---|
32 | 32 |
IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' } |
33 | 33 |
}.freeze |
34 | 34 | |
35 |
UNAVAILABLE_COLUMNS = [:id, :subject] |
|
36 | ||
35 | 37 |
# Some utility methods for the PDF export |
36 | 38 |
# @private |
37 | 39 |
class PDF |
... | ... | |
76 | 78 |
@date_to = (@date_from >> @months) - 1 |
77 | 79 |
@subjects = '' |
78 | 80 |
@lines = '' |
81 |
@columns = {} |
|
79 | 82 |
@number_of_rows = nil |
80 | 83 |
@truncated = false |
81 | 84 |
if options.has_key?(:max_rows) |
... | ... | |
135 | 138 |
@lines |
136 | 139 |
end |
137 | 140 | |
141 |
# Renders the selected column of the Gantt chart, the right side of subjects. |
|
142 |
def selected_column_content(options={}) |
|
143 |
render(options.merge(:only => :selected_columns)) unless @columns.has_key?(options[:column].name) |
|
144 |
@columns[options[:column].name] |
|
145 |
end |
|
146 | ||
138 | 147 |
# Returns issues that will be rendered |
139 | 148 |
def issues |
140 | 149 |
@issues ||= @query.issues( |
... | ... | |
196 | 205 |
:indent_increment => 20, :render => :subject, |
197 | 206 |
:format => :html}.merge(options) |
198 | 207 |
indent = options[:indent] || 4 |
199 |
@subjects = '' unless options[:only] == :lines |
|
200 |
@lines = '' unless options[:only] == :subjects |
|
208 |
@subjects = '' unless options[:only] == :lines || options[:only] == :selected_columns |
|
209 |
@lines = '' unless options[:only] == :subjects || options[:only] == :selected_columns |
|
210 |
@columns[options[:column].name] = '' if options[:only] == :selected_columns && @columns.has_key?(options[:column]) == false |
|
201 | 211 |
@number_of_rows = 0 |
202 | 212 |
begin |
203 | 213 |
Project.project_tree(projects) do |project, level| |
... | ... | |
207 | 217 |
rescue MaxLinesLimitReached |
208 | 218 |
@truncated = true |
209 | 219 |
end |
210 |
@subjects_rendered = true unless options[:only] == :lines |
|
211 |
@lines_rendered = true unless options[:only] == :subjects |
|
220 |
@subjects_rendered = true unless options[:only] == :lines || options[:only] == :selected_columns
|
|
221 |
@lines_rendered = true unless options[:only] == :subjects || options[:only] == :selected_columns
|
|
212 | 222 |
render_end(options) |
213 | 223 |
end |
214 | 224 | |
... | ... | |
254 | 264 | |
255 | 265 |
def render_object_row(object, options) |
256 | 266 |
class_name = object.class.name.downcase |
257 |
send("subject_for_#{class_name}", object, options) unless options[:only] == :lines |
|
258 |
send("line_for_#{class_name}", object, options) unless options[:only] == :subjects |
|
267 |
send("subject_for_#{class_name}", object, options) unless options[:only] == :lines || options[:only] == :selected_columns |
|
268 |
send("line_for_#{class_name}", object, options) unless options[:only] == :subjects || options[:only] == :selected_columns |
|
269 |
column_content_for_issue(object, options) if options[:only] == :selected_columns && options[:column].present? && object.is_a?(Issue) |
|
259 | 270 |
options[:top] += options[:top_increment] |
260 | 271 |
@number_of_rows += 1 |
261 | 272 |
if @max_rows && @number_of_rows >= @max_rows |
... | ... | |
323 | 334 |
end |
324 | 335 |
end |
325 | 336 | |
337 |
def column_content_for_issue(issue, options) |
|
338 |
if options[:format] == :html |
|
339 |
style = "position: absolute;top: #{options[:top]}px; font-size: 0.8em;" |
|
340 |
content = view.content_tag(:div, view.column_content(options[:column], issue), :style => style, :class => "issue_#{options[:column].name}", :id => "#{options[:column].name}_issue_#{issue.id}") |
|
341 |
@columns[options[:column].name] << content if @columns.has_key?(options[:column].name) |
|
342 |
content |
|
343 |
end |
|
344 |
end |
|
345 | ||
326 | 346 |
def subject(label, options, object=nil) |
327 | 347 |
send "#{options[:format]}_subject", options, label, object |
328 | 348 |
end |
public/javascripts/gantt.js | ||
---|---|---|
161 | 161 |
} |
162 | 162 |
} |
163 | 163 | |
164 |
function drawSelectedColumns(){ |
|
165 |
if ($("#draw_selected_columns").prop('checked')) { |
|
166 |
if(isMobile()) { |
|
167 |
$('td.gantt_selected_column').each(function(i) { |
|
168 |
$(this).hide(); |
|
169 |
}); |
|
170 |
}else{ |
|
171 |
$('#content').addClass("gantt_content"); |
|
172 |
$('td.gantt_selected_column').each(function() { |
|
173 |
$(this).show(); |
|
174 |
var column_name = $(this).attr('id'); |
|
175 |
$(this).resizable({ |
|
176 |
alsoResize: `.gantt_${column_name}_container, .gantt_${column_name}_container > .gantt_hdr`, |
|
177 |
minWidth: 20, |
|
178 |
handles: "e", |
|
179 |
create: function( event, ui ) { |
|
180 |
$(".ui-resizable-e").css("cursor","ew-resize"); |
|
181 |
} |
|
182 |
}).on('resize', function (e) { |
|
183 |
e.stopPropagation(); |
|
184 |
}); |
|
185 |
}); |
|
186 |
} |
|
187 |
}else{ |
|
188 |
$('td.gantt_selected_column').each(function (i) { |
|
189 |
$(this).hide(); |
|
190 |
}); |
|
191 |
} |
|
192 |
} |
|
193 | ||
164 | 194 |
function drawGanttHandler() { |
165 | 195 |
var folder = document.getElementById('gantt_draw_area'); |
166 | 196 |
if(draw_gantt != null) |
... | ... | |
168 | 198 |
else |
169 | 199 |
draw_gantt = Raphael(folder); |
170 | 200 |
setDrawArea(); |
201 |
drawSelectedColumns(); |
|
171 | 202 |
if ($("#draw_progress_line").prop('checked')) |
172 | 203 |
drawGanttProgressLines(); |
173 | 204 |
if ($("#draw_relations").prop('checked')) |
... | ... | |
195 | 226 |
$('td.gantt_subjects_column').resizable('enable'); |
196 | 227 |
}; |
197 | 228 |
} |
229 | ||
230 |
function disable_unavailable_columns(unavailable_columns){ |
|
231 |
$.each(unavailable_columns, function(index, value) { |
|
232 |
$('#available_c, #selected_c').children("[value='" + value + "']").prop('disabled', true); |
|
233 |
}); |
|
234 |
} |
public/stylesheets/application.css | ||
---|---|---|
1193 | 1193 | |
1194 | 1194 |
.gantt_hdr.nwday {background-color:#f1f1f1; color:#999;} |
1195 | 1195 | |
1196 |
.gantt_subjects { font-size: 0.8em; } |
|
1197 |
.gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; } |
|
1196 |
.gantt_subjects, .gantt_selected_column_content.gantt_hdr { font-size: 0.8em; } |
|
1197 |
.gantt_subjects div, .gantt_selected_column_content div { |
|
1198 |
line-height: 16px; |
|
1199 |
height: 16px; |
|
1200 |
white-space: nowrap; |
|
1201 |
text-overflow: ellipsis; |
|
1202 |
overflow: hidden !important; |
|
1203 |
width: 100%; |
|
1204 |
} |
|
1198 | 1205 |
.gantt_subjects div.issue-subject:hover { background-color:#ffffdd; } |
1206 |
.gantt_selected_column_content { padding-left: 3px; padding-right: 3px;} |
|
1199 | 1207 | |
1200 | 1208 |
.gantt_subjects .issue-subject img.icon-gravatar { |
1201 | 1209 |
margin: 2px 5px 0px 2px; |
1202 | 1210 |
} |
1211 |
.gantt_hdr_selected_column_name { |
|
1212 |
padding-top: 15px; |
|
1213 |
font-size: 0.8em; |
|
1214 |
white-space: nowrap; |
|
1215 |
text-overflow: ellipsis; |
|
1216 |
overflow: hidden; |
|
1217 |
} |
|
1218 |
.gantt_content { |
|
1219 |
overflow: scroll; |
|
1220 |
} |
|
1203 | 1221 | |
1204 | 1222 |
.task { |
1205 | 1223 |
position: absolute; |
test/functional/queries_controller_test.rb | ||
---|---|---|
293 | 293 |
:query => { |
294 | 294 |
:name => "test_create_from_gantt", |
295 | 295 |
:draw_relations => '1', |
296 |
:draw_progress_line => '1' |
|
296 |
:draw_progress_line => '1', |
|
297 |
:draw_selected_columns => '1' |
|
297 | 298 |
} |
298 | 299 |
} |
299 | 300 |
assert_response 302 |
... | ... | |
302 | 303 |
assert_redirected_to "/issues/gantt?query_id=#{query.id}" |
303 | 304 |
assert_equal true, query.draw_relations |
304 | 305 |
assert_equal true, query.draw_progress_line |
306 |
assert_equal true, query.draw_selected_columns |
|
305 | 307 |
end |
306 | 308 | |
307 | 309 |
def test_create_project_query_from_gantt |
... | ... | |
319 | 321 |
:query => { |
320 | 322 |
:name => "test_create_from_gantt", |
321 | 323 |
:draw_relations => '0', |
322 |
:draw_progress_line => '0' |
|
324 |
:draw_progress_line => '0', |
|
325 |
:draw_selected_columns => '0' |
|
323 | 326 |
} |
324 | 327 |
} |
325 | 328 |
assert_response 302 |
... | ... | |
328 | 331 |
assert_redirected_to "/projects/ecookbook/issues/gantt?query_id=#{query.id}" |
329 | 332 |
assert_equal false, query.draw_relations |
330 | 333 |
assert_equal false, query.draw_progress_line |
334 |
assert_equal false, query.draw_selected_columns |
|
331 | 335 |
end |
332 | 336 | |
333 | 337 |
def test_create_project_public_query_should_force_private_without_manage_public_queries_permission |
test/unit/lib/redmine/helpers/gantt_test.rb | ||
---|---|---|
23 | 23 | |
24 | 24 |
include ProjectsHelper |
25 | 25 |
include IssuesHelper |
26 |
include QueriesHelper |
|
26 | 27 |
include ERB::Util |
27 | 28 |
include Rails.application.routes.url_helpers |
28 | 29 | |
... | ... | |
237 | 238 |
assert_select "div.tooltip", /#{@issue.subject}/ |
238 | 239 |
end |
239 | 240 | |
241 |
test "#selected_column_content" do |
|
242 |
create_gantt |
|
243 |
issue = Issue.generate! |
|
244 |
@gantt.query.column_names = [:assigned_to] |
|
245 |
issue.update(:assigned_to_id => issue.assignable_users.first.id) |
|
246 |
@project.issues << issue |
|
247 |
# :column => assigned_to |
|
248 |
@output_buffer = @gantt.selected_column_content({ :column => @gantt.query.columns.last }) |
|
249 |
assert_select "div.issue_assigned_to#assigned_to_issue_#{issue.id}" |
|
250 |
end |
|
251 | ||
240 | 252 |
test "#subject_for_project" do |
241 | 253 |
create_gantt |
242 | 254 |
@output_buffer = @gantt.subject_for_project(@project, :format => :html) |
... | ... | |
432 | 444 |
assert_select "div.label", :text => 'line' |
433 | 445 |
end |
434 | 446 | |
447 |
test "#column_content_for_issue" do |
|
448 |
create_gantt |
|
449 |
@gantt.query.column_names = [:assigned_to] |
|
450 |
issue = Issue.generate! |
|
451 |
issue.update(:assigned_to_id => issue.assignable_users.first.id) |
|
452 |
@project.issues << issue |
|
453 |
# :column => assigned_to |
|
454 |
options = { :column => @gantt.query.columns.last, :top => 64, :format => :html } |
|
455 |
@output_buffer = @gantt.column_content_for_issue(issue, options) |
|
456 | ||
457 |
assert_select "div.issue_assigned_to#assigned_to_issue_#{issue.id}" |
|
458 |
assert_includes @output_buffer, column_content(options[:column], issue) |
|
459 |
end |
|
460 | ||
435 | 461 |
def test_sort_issues_no_date |
436 | 462 |
project = Project.generate! |
437 | 463 |
issue1 = Issue.generate!(:subject => "test", :project => project) |