Project

General

Profile

Feature #27672 » 0001~0003-v2.patch

Mizuki ISHIKAWA, 2019-04-24 06:43

View differences:

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

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

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

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

  
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>
......
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}px;"
......
146 160
      style  = ""
147 161
      style += "width: #{subject_width}px;"
148 162
      style += "height: #{t_height}px;"
149
      style += 'border-left: 1px solid #c0c0c0;'
150 163
      style += 'overflow: hidden;'
151 164
    %>
152 165
    <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %>
......
157 170
    <% end %>
158 171
  <% end %>
159 172
</td>
160

  
173
<% @query.columns.each do |column| %>
174
  <% next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) %>
175
  <td class="gantt_<%= column.name %>_column gantt_selected_column <%= 'last_gantt_selected_column' if @query.columns.last == column %>" id="<%= column.name %>">
176
    <%
177
      style = "position: relative;"
178
      style += "height: #{t_height + 24}px;"
179
    %>
180
    <%= content_tag(:div, :style => style, :class => "gantt_#{column.name}_container gantt_selected_column_container") do %>
181
      <%
182
        style = "height: #{t_height}px;"
183
        style += 'overflow: hidden;'
184
      %>
185
      <%= content_tag(:div, '', :style => style, :class => "gantt_hdr") %>
186
      <%
187
        style = "height: #{headers_height}px;"
188
        style += 'background: #eee;'
189
      %>
190
      <%= content_tag(:div, content_tag(:p, column.caption, :class => 'gantt_hdr_selected_column_name'), :style => style, :class => "gantt_hdr") %>
191
      <%= content_tag(:div, :class => "gantt_#{column.name} gantt_selected_column_content") do %>
192
        <%= @gantt.selected_column_content({:column => column, :top => headers_height + 8, :zoom => zoom, :g_width => g_width}).html_safe %>
193
      <% end %>
194
    <% end %>
195
  </td>
196
<% end %>
161 197
<td>
162 198
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area">
163 199
<%
......
371 407
<%= javascript_tag do %>
372 408
  var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>;
373 409
  $(function() {
410
    disable_unavailable_columns('<%= Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.map(&:to_s).join(',') %>'.split(','));
374 411
    drawGanttHandler();
375 412
    resizableSubjectColumn();
376
    $("#draw_relations").change(drawGanttHandler);
377
    $("#draw_progress_line").change(drawGanttHandler);
413
    drawSelectedColumns();
414
    $("#draw_relations, #draw_progress_line, #draw_selected_columns").change(drawGanttHandler);
378 415
    $('div.gantt_subjects .expander').on('click', ganttEntryClick);
379 416
  });
380 417
  $(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 %> <%= 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
981 981
  label_cross_project_hierarchy: With project hierarchy
982 982
  label_cross_project_system: With all projects
983 983
  label_gantt_progress_line: Progress line
984
  label_gantt_selected_columns: Selected columns
984 985
  label_visibility_private: to me only
985 986
  label_visibility_roles: to these roles only
986 987
  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
        var alsoResizeElements = '.gantt_' + column_name + '_container, .gantt_' + column_name + '_container > .gantt_hdr';
178
        $(this).resizable({
179
          alsoResize: alsoResizeElements,
180
          minWidth: 20,
181
          handles: "e",
182
          create: function() {
183
            $(".ui-resizable-e").css("cursor","ew-resize");
184
          },
185
          stop: function () {
186
            $(alsoResizeElements).width($(this).width());
187
          }
188
        }).on('resize', function (e) {
189
            e.stopPropagation();
190
        });
191
      });
192
    }
193
  }else{
194
    $('td.gantt_selected_column').each(function (i) {
195
      $(this).hide();
196
      $('.gantt_subjects_container').removeClass('draw_selected_columns');
197
    });
198
  }
199
}
200

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

  
179 216
function resizableSubjectColumn(){
......
184 221
    alsoResize: '.gantt_subjects_container, .gantt_subjects_container>.gantt_hdr, .project-name, .issue-subject, .version-name',
185 222
    minWidth: 100,
186 223
    handles: 'e',
187
    containment: '#content',
188 224
    create: function( event, ui ) {
189 225
      $('.ui-resizable-e').css('cursor','ew-resize');
226
    },
227
    stop: function () {
228
      $('.gantt_subjects_container, .gantt_subjects_container>.gantt_hdr').width($(this).width());
190 229
    }
191 230
  }).on('resize', function (e) {
192 231
      e.stopPropagation();
......
224 263

  
225 264
      var new_top_val = parseInt(el.css('top')) + total_height * (target_shown ? -1 : 1);
226 265
      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);
266
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"], td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, el){
267
        $(el).css('top', new_top_val);
229 268
      });
230 269
      return true;
231 270
    }
......
237 276
      total_height = 0;
238 277
    }
239 278
    if(is_shown == target_shown){
240
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task){
279
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task) {
241 280
        var el_task = $(task);
242 281
        if(!is_shown)
243 282
          el_task.css('top', target_top + total_height);
244 283
        if(!el_task.hasClass('tooltip'))
245 284
          el_task.toggle(!is_shown);
246 285
      });
286
      $('td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"]'
287
          ).each(function (_, attr) {
288
        var el_attr = $(attr);
289
        if (!is_shown)
290
          el_attr.css('top', target_top + total_height);
291
          el_attr.toggle(!is_shown);
292
      });
247 293
      if(!is_shown)
248 294
        el.css('top', target_top + total_height);
249 295
      iconChange(el);
......
253 299
  });
254 300
  drawGanttHandler();
255 301
};
302

  
303
function disable_unavailable_columns(unavailable_columns) {
304
  $.each(unavailable_columns, function (index, value) {
305
    $('#available_c, #selected_c').children("[value='" + value + "']").prop('disabled', true);
306
  });
307
}
public/stylesheets/application.css
1238 1238

  
1239 1239
#my-page .list th.checkbox, #my-page .list td.checkbox {display:none;}
1240 1240
/***** Gantt chart *****/
1241
div.gantt_content {
1242
  overflow: scroll;
1243
}
1244
table.gantt-table {
1245
  width: 100%;
1246
  border-collapse: collapse;
1247
}
1248
table.gantt-table td {
1249
  padding: 0px;
1250
}
1241 1251
.gantt_hdr {
1242 1252
  position:absolute;
1243 1253
  top:0;
1244 1254
  height:16px;
1245 1255
  border-top: 1px solid #c0c0c0;
1246 1256
  border-bottom: 1px solid #c0c0c0;
1247
  border-right: 1px solid #c0c0c0;
1257
  border-left: 1px solid #c0c0c0;
1248 1258
  text-align: center;
1249 1259
  overflow: hidden;
1250 1260
}
1261
#gantt_area .gantt_hdr {
1262
  border-left: 0px;
1263
  border-right: 1px solid #c0c0c0;
1264
}
1265
.gantt_subjects_container:not(.draw_selected_columns) .gantt_hdr,
1266
.last_gantt_selected_column .gantt_hdr {
1267
  z-index: 10;
1268
  border-right: 1px solid #c0c0c0;
1269
}
1270
.gantt_subjects_container .gantt_subjects * {
1271
  z-index: 10;
1272
}
1251 1273

  
1252 1274
.gantt_hdr.nwday {background-color:#f1f1f1; color:#999;}
1253

  
1254
.gantt_subjects { font-size: 0.8em; }
1255
.gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
1275
.gantt_subjects, .gantt_selected_column_content.gantt_hdr { font-size: 0.8em; }
1276
.gantt_subjects div, .gantt_selected_column_content div {
1277
  line-height: 16px;
1278
  height: 16px;
1279
  white-space: nowrap;
1280
  text-overflow: ellipsis;
1281
  overflow: hidden !important;
1282
  width: 100%;
1283
}
1256 1284
.gantt_subjects div.issue-subject:hover { background-color:#ffffdd; }
1257

  
1285
.gantt_selected_column_content { padding-left: 3px; padding-right: 3px;}
1258 1286
.gantt_subjects .issue-subject img.icon-gravatar {
1259 1287
  margin: 2px 5px 0px 2px;
1260 1288
}
1289
.gantt_hdr_selected_column_name {
1290
  position: absolute;
1291
  top: 50%;
1292
  width:100%;
1293
  transform: translateY(-50%);
1294
  -webkit- transform: translateY(-50%);
1295
  font-size: 0.8em;
1296
  overflow: hidden;
1297
  text-overflow: ellipsis;
1298
  white-space: nowrap;
1299

  
1300
}
1301
td.gantt_selected_column, td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container {
1302
  width: 50px;
1303
}
1261 1304

  
1262 1305
.task {
1263 1306
  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)
(7-7/14)