Project

General

Profile

Feature #27672 » 0001~0003-v4.patch

Mizuki ISHIKAWA, 2019-05-16 04:07

View differences:

app/models/issue_query.rb
76 76
    options[:draw_progress_line] = (arg == '1' ? '1' : nil)
77 77
  end
78 78

  
79
  def draw_selected_columns
80
    r = options[:draw_selected_columns]
81
    r == '1'
82
  end
83

  
84
  def draw_selected_columns=(arg)
85
    options[:draw_selected_columns] = (arg == '1' ? '1' : nil)
86
  end
87

  
79 88
  def build_from_params(params, defaults={})
80 89
    super
81 90
    self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations]) || options[:draw_relations]
82 91
    self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line]) || options[:draw_progress_line]
92
    self.draw_selected_columns = params[:draw_selected_columns] || (params[:query] && params[:query][:draw_selected_columns]) || options[:draw_progress_line]
83 93
    self
84 94
  end
85 95

  
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', 'data-enables' => 'span.query-columns select, span.query-columns input' %>
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>
......
125 139
  <p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p>
126 140
<% end %>
127 141

  
128
<table style="width:100%; border:0; border-collapse: collapse;">
142
<table class='gantt-table'>
129 143
<tr>
130
<td style="width:<%= subject_width %>px; padding:0px;" class="gantt_subjects_column">
144
<td style="width:<%= subject_width %>px;" class="gantt_subjects_column">
131 145
  <%
132 146
    style  = ""
133 147
    style += "position:relative;"
134 148
    style += "height: #{t_height + 24}px;"
135
    style += "width: #{subject_width + 1}px;"
149
    style += "width: #{subject_width}px;"
136 150
  %>
137
  <%= content_tag(:div, :style => style, :class => "gantt_subjects_container") do %>
151
  <%= content_tag(:div, :style => style, :class => "gantt_subjects_container #{'draw_selected_columns' if @query.draw_selected_columns}") do %>
138 152
    <%
139 153
      style  = ""
140 154
      style += "width: #{subject_width + 1}px;"
......
147 161
      style += "z-index: 1;"
148 162
      style += "width: #{subject_width}px;"
149 163
      style += "height: #{t_height}px;"
150
      style += 'border-left: 1px solid #c0c0c0;'
151 164
      style += 'overflow: hidden;'
152 165
    %>
153 166
    <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %>
......
158 171
    <% end %>
159 172
  <% end %>
160 173
</td>
161

  
174
<% @query.columns.each do |column| %>
175
  <% next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) %>
176
  <td class="gantt_<%= column.name %>_column gantt_selected_column <%= 'last_gantt_selected_column' if @query.columns.last == column %>" id="<%= column.name %>">
177
    <%
178
      style = "position: relative;"
179
      style += "height: #{t_height + 24}px;"
180
    %>
181
    <%= content_tag(:div, :style => style, :class => "gantt_#{column.name}_container gantt_selected_column_container") do %>
182
      <%
183
        style = "height: #{t_height}px;"
184
        style += 'overflow: hidden;'
185
      %>
186
      <%= content_tag(:div, '', :style => style, :class => "gantt_hdr") %>
187
      <%
188
        style = "height: #{headers_height}px;"
189
        style += 'background: #eee;'
190
      %>
191
      <%= content_tag(:div, content_tag(:p, column.caption, :class => 'gantt_hdr_selected_column_name'), :style => style, :class => "gantt_hdr") %>
192
      <%= content_tag(:div, :class => "gantt_#{column.name} gantt_selected_column_content") do %>
193
        <%= @gantt.selected_column_content({:column => column, :top => headers_height + 8, :zoom => zoom, :g_width => g_width}).html_safe %>
194
      <% end %>
195
    <% end %>
196
  </td>
197
<% end %>
162 198
<td>
163 199
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area">
164 200
<%
......
372 408
<%= javascript_tag do %>
373 409
  var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>;
374 410
  $(function() {
411
    disable_unavailable_columns('<%= Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.map(&:to_s).join(',') %>'.split(','));
375 412
    drawGanttHandler();
376 413
    resizableSubjectColumn();
377
    $("#draw_relations").change(drawGanttHandler);
378
    $("#draw_progress_line").change(drawGanttHandler);
414
    drawSelectedColumns();
415
    $("#draw_relations, #draw_progress_line, #draw_selected_columns").change(drawGanttHandler);
379 416
    $('div.gantt_subjects .expander').on('click', ganttEntryClick);
380 417
  });
381 418
  $(window).resize(function() {
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
  <%= hidden_field_tag 'query[draw_relations]', '0' %>
44 42
  <%= hidden_field_tag 'query[draw_progress_line]', '0' %>
43
  <%= hidden_field_tag 'query[draw_selected_columns]', '0' %>
45 44
  <label class="inline"><%= check_box_tag "query[draw_relations]", "1", @query.draw_relations %> <%= l(:label_related_issues) %></label>
46 45
  <label class="inline"><%= check_box_tag "query[draw_progress_line]", "1", @query.draw_progress_line %> <%= l(:label_gantt_progress_line) %></label>
46
  <label class="inline"><%= check_box_tag "query[draw_selected_columns]", "1", @query.draw_selected_columns, :data => { :enables => 'span.query-columns select, span.query-columns input'} %> <%= l(:label_gantt_selected_columns) %></label>
47 47
  </p>
48
</fieldset>
49 48
<% end %>
49
</fieldset>
50 50
</div>
51 51

  
52 52
<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>
......
72 72
</fieldset>
73 73
<% end %>
74 74

  
75
<% unless params[:gantt] %>
76 75
<%= content_tag 'fieldset', :id => 'columns' do %>
77 76
<legend><%= l(:field_column_names) %></legend>
78 77
<%= render_query_columns_selection(query) %>
79 78
<% end %>
80
<% end %>
81 79

  
82 80
</div>
83 81

  
config/locales/en.yml
985 985
  label_cross_project_hierarchy: With project hierarchy
986 986
  label_cross_project_system: With all projects
987 987
  label_gantt_progress_line: Progress line
988
  label_gantt_selected_columns: Selected columns
988 989
  label_visibility_private: to me only
989 990
  label_visibility_roles: to these roles only
990 991
  label_visibility_public: to any users
lib/redmine/helpers/gantt.rb
34 34
        IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' }
35 35
      }.freeze
36 36

  
37
      UNAVAILABLE_COLUMNS = [:id, :subject]
38

  
37 39
      # Some utility methods for the PDF export
38 40
      # @private
39 41
      class PDF
......
78 80
        @date_to = (@date_from >> @months) - 1
79 81
        @subjects = +''
80 82
        @lines = +''
83
        @columns ||= {}
81 84
        @number_of_rows = nil
82 85
        @truncated = false
83 86
        if options.has_key?(:max_rows)
......
137 140
        @lines
138 141
      end
139 142

  
143
      # Renders the selected column of the Gantt chart, the right side of subjects.
144
      def selected_column_content(options={})
145
        render(options.merge(:only => :selected_columns)) unless @columns.has_key?(options[:column].name)
146
        @columns[options[:column].name]
147
      end
148

  
140 149
      # Returns issues that will be rendered
141 150
      def issues
142 151
        @issues ||= @query.issues(
......
198 207
                   :indent_increment => 20, :render => :subject,
199 208
                   :format => :html}.merge(options)
200 209
        indent = options[:indent] || 4
201
        @subjects = +'' unless options[:only] == :lines
202
        @lines = +'' unless options[:only] == :subjects
210
        @subjects = +'' unless options[:only] == :lines || options[:only] == :selected_columns
211
        @lines = +'' unless options[:only] == :subjects || options[:only] == :selected_columns
212
        @columns[options[:column].name] = +'' if options[:only] == :selected_columns && @columns.has_key?(options[:column]) == false
203 213
        @number_of_rows = 0
204 214
        begin
205 215
          Project.project_tree(projects) do |project, level|
......
209 219
        rescue MaxLinesLimitReached
210 220
          @truncated = true
211 221
        end
212
        @subjects_rendered = true unless options[:only] == :lines
213
        @lines_rendered = true unless options[:only] == :subjects
222
        @subjects_rendered = true unless options[:only] == :lines || options[:only] == :selected_columns
223
        @lines_rendered = true unless options[:only] == :subjects || options[:only] == :selected_columns
214 224
        render_end(options)
215 225
      end
216 226

  
......
256 266

  
257 267
      def render_object_row(object, options)
258 268
        class_name = object.class.name.downcase
259
        send("subject_for_#{class_name}", object, options) unless options[:only] == :lines
260
        send("line_for_#{class_name}", object, options) unless options[:only] == :subjects
269
        send("subject_for_#{class_name}", object, options) unless options[:only] == :lines || options[:only] == :selected_columns
270
        send("line_for_#{class_name}", object, options) unless options[:only] == :subjects || options[:only] == :selected_columns
271
        column_content_for_issue(object, options) if options[:only] == :selected_columns && options[:column].present? && object.is_a?(Issue)
261 272
        options[:top] += options[:top_increment]
262 273
        @number_of_rows += 1
263 274
        if @max_rows && @number_of_rows >= @max_rows
......
325 336
        end
326 337
      end
327 338

  
339
      def column_content_for_issue(issue, options)
340
        if options[:format] == :html
341
          data_options = {}
342
          data_options[:collapse_expand] = "issue-#{issue.id}"
343
          style = "position: absolute;top: #{options[:top]}px; font-size: 0.8em;"
344
          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}", :data => data_options)
345
          @columns[options[:column].name] << content if @columns.has_key?(options[:column].name)
346
          content
347
        end
348
      end
349

  
328 350
      def subject(label, options, object=nil)
329 351
        send "#{options[:format]}_subject", options, label, object
330 352
      end
public/javascripts/gantt.js
163 163
  }
164 164
}
165 165

  
166
function drawSelectedColumns(){
167
  if ($("#draw_selected_columns").prop('checked')) {
168
    if(isMobile()) {
169
      $('td.gantt_selected_column').each(function(i) {
170
        $(this).hide();
171
      });
172
    }else{
173
      $('.gantt_subjects_container').addClass('draw_selected_columns');
174
      $('td.gantt_selected_column').each(function() {
175
        $(this).show();
176
        var column_name = $(this).attr('id');
177
        $(this).resizable({
178
          alsoResize: '.gantt_' + column_name + '_container, .gantt_' + column_name + '_container > .gantt_hdr',
179
          minWidth: 20,
180
          handles: "e",
181
          create: function() {
182
            $(".ui-resizable-e").css("cursor","ew-resize");
183
          }
184
        }).on('resize', function (e) {
185
            e.stopPropagation();
186
        });
187
      });
188
    }
189
  }else{
190
    $('td.gantt_selected_column').each(function (i) {
191
      $(this).hide();
192
      $('.gantt_subjects_container').removeClass('draw_selected_columns');
193
    });
194
  }
195
}
196

  
166 197
function drawGanttHandler() {
167 198
  var folder = document.getElementById('gantt_draw_area');
168 199
  if(draw_gantt != null)
......
170 201
  else
171 202
    draw_gantt = Raphael(folder);
172 203
  setDrawArea();
204
  drawSelectedColumns();
173 205
  if ($("#draw_progress_line").prop('checked'))
174 206
    try{drawGanttProgressLines();}catch(e){}
175 207
  if ($("#draw_relations").prop('checked'))
176 208
    drawRelations();
209
  $('#content').addClass('gantt_content');
177 210
}
178 211

  
179 212
function resizableSubjectColumn(){
......
184 217
    alsoResize: '.gantt_subjects_container, .gantt_subjects_container>.gantt_hdr, .project-name, .issue-subject, .version-name',
185 218
    minWidth: 100,
186 219
    handles: 'e',
187
    containment: '#content',
188 220
    create: function( event, ui ) {
189 221
      $('.ui-resizable-e').css('cursor','ew-resize');
190 222
    }
......
224 256

  
225 257
      var new_top_val = parseInt(el.css('top')) + total_height * (target_shown ? -1 : 1);
226 258
      el.css('top', new_top_val);
227
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task){
228
        $(task).css('top', new_top_val);
259
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"], td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, el){
260
        $(el).css('top', new_top_val);
229 261
      });
230 262
      return true;
231 263
    }
......
237 269
      total_height = 0;
238 270
    }
239 271
    if(is_shown == target_shown){
240
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task){
272
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task) {
241 273
        var el_task = $(task);
242 274
        if(!is_shown)
243 275
          el_task.css('top', target_top + total_height);
244 276
        if(!el_task.hasClass('tooltip'))
245 277
          el_task.toggle(!is_shown);
246 278
      });
279
      $('td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"]'
280
          ).each(function (_, attr) {
281
        var el_attr = $(attr);
282
        if (!is_shown)
283
          el_attr.css('top', target_top + total_height);
284
          el_attr.toggle(!is_shown);
285
      });
247 286
      if(!is_shown)
248 287
        el.css('top', target_top + total_height);
249 288
      iconChange(el);
......
253 292
  });
254 293
  drawGanttHandler();
255 294
};
295

  
296
function disable_unavailable_columns(unavailable_columns) {
297
  $.each(unavailable_columns, function (index, value) {
298
    $('#available_c, #selected_c').children("[value='" + value + "']").prop('disabled', true);
299
  });
300
}
public/stylesheets/application.css
1253 1253

  
1254 1254
#my-page .list th.checkbox, #my-page .list td.checkbox {display:none;}
1255 1255
/***** Gantt chart *****/
1256
div.gantt_content {
1257
  overflow: scroll;
1258
}
1259
table.gantt-table {
1260
  width: 100%;
1261
  border-collapse: collapse;
1262
}
1263
table.gantt-table td {
1264
  padding: 0px;
1265
}
1256 1266
.gantt_hdr {
1257 1267
  position:absolute;
1258 1268
  top:0;
1259 1269
  height:16px;
1260 1270
  border-top: 1px solid #c0c0c0;
1261 1271
  border-bottom: 1px solid #c0c0c0;
1262
  border-right: 1px solid #c0c0c0;
1272
  border-left: 1px solid #c0c0c0;
1263 1273
  text-align: center;
1264 1274
  overflow: hidden;
1265 1275
}
1276
#gantt_area .gantt_hdr {
1277
  border-left: 0px;
1278
  border-right: 1px solid #c0c0c0;
1279
}
1280
.gantt_subjects_container:not(.draw_selected_columns) .gantt_hdr,
1281
.last_gantt_selected_column .gantt_hdr {
1282
  z-index: 10;
1283
  border-right: 1px solid #c0c0c0;
1284
}
1285
.gantt_subjects_container .gantt_subjects * {
1286
  z-index: 10;
1287
}
1266 1288

  
1267 1289
.gantt_subjects_column + td {
1268 1290
  padding: 0;
......
1270 1292

  
1271 1293
.gantt_hdr.nwday {background-color:#f1f1f1; color:#999;}
1272 1294

  
1273
.gantt_subjects { font-size: 0.8em; position: relative; z-index: 1; }
1274
.gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
1295
.gantt_subjects,
1296
.gantt_selected_column_content.gantt_hdr {
1297
  font-size: 0.8em;
1298
  position: relative;
1299
  z-index: 1;
1300
}
1301
.gantt_subjects div,
1302
.gantt_selected_column_content div {
1303
  line-height: 16px;
1304
  height: 16px;
1305
  overflow: hidden;
1306
  white-space: nowrap;
1307
  text-overflow: ellipsis;
1308
  width: 100%;
1309
}
1275 1310
.gantt_subjects div.issue-subject:hover { background-color:#ffffdd; }
1276

  
1311
.gantt_selected_column_content { padding-left: 3px; padding-right: 3px;}
1277 1312
.gantt_subjects .issue-subject img.icon-gravatar {
1278 1313
  margin: 2px 5px 0px 2px;
1279 1314
}
1315
.gantt_hdr_selected_column_name {
1316
  position: absolute;
1317
  top: 50%;
1318
  width:100%;
1319
  transform: translateY(-50%);
1320
  -webkit- transform: translateY(-50%);
1321
  font-size: 0.8em;
1322
  overflow: hidden;
1323
  text-overflow: ellipsis;
1324
  white-space: nowrap;
1325

  
1326
}
1327
td.gantt_selected_column, td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container {
1328
  width: 50px;
1329
}
1280 1330

  
1281 1331
.task {
1282 1332
  position: absolute;
public/stylesheets/responsive.css
711 711

  
712 712
  .gantt_subjects_column .gantt_hdr {
713 713
    width: 100% !important;
714
    border-right: 1px solid #c0c0c0; /* [2] */
715 714
    right: 0 !important; /* [2] */
716 715
  }
717 716

  
test/functional/queries_controller_test.rb
295 295
          :query => {
296 296
            :name => "test_create_from_gantt",
297 297
            :draw_relations => '1',
298
            :draw_progress_line => '1'
298
            :draw_progress_line => '1',
299
            :draw_selected_columns => '1'
299 300
          }
300 301
        }
301 302
      assert_response 302
......
304 305
    assert_redirected_to "/issues/gantt?query_id=#{query.id}"
305 306
    assert_equal true, query.draw_relations
306 307
    assert_equal true, query.draw_progress_line
308
    assert_equal true, query.draw_selected_columns
307 309
  end
308 310

  
309 311
  def test_create_project_query_from_gantt
......
321 323
          :query => {
322 324
            :name => "test_create_from_gantt",
323 325
            :draw_relations => '0',
324
            :draw_progress_line => '0'
326
            :draw_progress_line => '0',
327
            :draw_selected_columns => '0'
325 328
          }
326 329
        }
327 330
      assert_response 302
......
330 333
    assert_redirected_to "/projects/ecookbook/issues/gantt?query_id=#{query.id}"
331 334
    assert_equal false, query.draw_relations
332 335
    assert_equal false, query.draw_progress_line
336
    assert_equal false, query.draw_selected_columns
333 337
  end
334 338

  
335 339
  def test_create_project_public_query_should_force_private_without_manage_public_queries_permission
test/unit/lib/redmine/helpers/gantt_test.rb
25 25

  
26 26
  include ProjectsHelper
27 27
  include IssuesHelper
28
  include QueriesHelper
28 29
  include ERB::Util
29 30
  include Rails.application.routes.url_helpers
30 31

  
......
241 242
    assert_select "div.tooltip", /#{@issue.subject}/
242 243
  end
243 244

  
245
  test "#selected_column_content" do
246
    create_gantt
247
    issue = Issue.generate!
248
    @gantt.query.column_names = [:assigned_to]
249
    issue.update(:assigned_to_id => issue.assignable_users.first.id)
250
    @project.issues << issue
251
    # :column => assigned_to
252
    @output_buffer = @gantt.selected_column_content({ :column => @gantt.query.columns.last })
253
    assert_select "div.issue_assigned_to#assigned_to_issue_#{issue.id}"
254
  end
255

  
244 256
  test "#subject_for_project" do
245 257
    create_gantt
246 258
    @output_buffer = @gantt.subject_for_project(@project, :format => :html)
......
437 449
    assert_select "div.label", :text => 'line'
438 450
  end
439 451

  
452
  test "#column_content_for_issue" do
453
    create_gantt
454
    @gantt.query.column_names = [:assigned_to]
455
    issue = Issue.generate!
456
    issue.update(:assigned_to_id => issue.assignable_users.first.id)
457
    @project.issues << issue
458
    # :column => assigned_to
459
    options = { :column => @gantt.query.columns.last, :top => 64, :format => :html }
460
    @output_buffer = @gantt.column_content_for_issue(issue, options)
461

  
462
    assert_select "div.issue_assigned_to#assigned_to_issue_#{issue.id}"
463
    assert_includes @output_buffer, column_content(options[:column], issue)
464
  end
465

  
440 466
  def test_sort_issues_no_date
441 467
    project = Project.generate!
442 468
    issue1 = Issue.generate!(:subject => "test", :project => project)
(9-9/14)