]> source.dussan.org Git - redmine.git/commitdiff
Let user choose columns and sort order of issue lists on "My page" (#1565).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Tue, 14 Mar 2017 18:18:19 +0000 (18:18 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Tue, 14 Mar 2017 18:18:19 +0000 (18:18 +0000)
git-svn-id: http://svn.redmine.org/redmine/trunk@16400 e93f8b46-1217-0410-a6f0-8f06a7374b81

17 files changed:
app/controllers/my_controller.rb
app/helpers/my_helper.rb
app/helpers/queries_helper.rb
app/models/query.rb
app/models/user_preference.rb
app/views/issues/_list.html.erb
app/views/my/blocks/_issues.erb [new file with mode: 0644]
app/views/my/blocks/_issuesassignedtome.html.erb [deleted file]
app/views/my/blocks/_issuesreportedbyme.html.erb [deleted file]
app/views/my/blocks/_issueswatched.html.erb [deleted file]
app/views/my/page.html.erb
app/views/queries/_columns.html.erb
lib/redmine/my_page.rb
public/stylesheets/application.css
test/functional/issues_controller_test.rb
test/functional/my_controller_test.rb
test/functional/settings_controller_test.rb

index 66bd965baf4cdd7be9dcd6c030da44a1f8f9779a..f0b56e44f5c75d49da29a98af8a9df58a78c0ded 100644 (file)
@@ -27,6 +27,7 @@ class MyController < ApplicationController
   helper :issues
   helper :users
   helper :custom_fields
+  helper :queries
 
   def index
     page
index 3975d80b123041f4c24efc5549a2991b9ac67776..2a20a7bf2e88eff7189a64ac9821e8e2fe479a91 100644 (file)
@@ -44,16 +44,17 @@ module MyHelper
 
   # Renders a single block content
   def render_block_content(block, user)
-    unless Redmine::MyPage.blocks.key?(block)
+    unless block_definition = Redmine::MyPage.blocks[block]
       Rails.logger.warn("Unknown block \"#{block}\" found in #{user.login} (id=#{user.id}) preferences")
       return
     end
 
     settings = user.pref.my_page_settings(block)
+    partial = block_definition[:partial]
     begin
-      render(:partial => "my/blocks/#{block}", :locals => {:user => user, :settings => settings})
+      render(:partial => partial, :locals => {:user => user, :settings => settings, :block => block})
     rescue ActionView::MissingTemplate
-      Rails.logger.warn("Template missing for block \"#{block}\" found in #{user.login} (id=#{user.id}) preferences")
+      Rails.logger.warn("Partial \"#{partial}\" missing for block \"#{block}\" found in #{user.login} (id=#{user.id}) preferences")
       return nil
     end
   end
@@ -80,30 +81,38 @@ module MyHelper
     Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).to_a
   end
 
-  def issuesassignedtome_items
-    Issue.visible.open.
-      assigned_to(User.current).
-      limit(10).
-      includes(:status, :project, :tracker, :priority).
-      references(:status, :project, :tracker, :priority).
-      order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC")
+  def issues_items(block, settings)
+    send "#{block}_items", settings
   end
 
-  def issuesreportedbyme_items
-    Issue.visible.open.
-      where(:author_id => User.current.id).
-      limit(10).
-      includes(:status, :project, :tracker, :priority).
-      references(:status, :project, :tracker).
-      order("#{Issue.table_name}.updated_on DESC")
+  def issuesassignedtome_items(settings)
+    query = IssueQuery.new(:name => l(:label_assigned_to_me_issues), :user => User.current)
+    query.add_filter 'assigned_to_id', '=', ['me']
+    query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
+    query.sort_criteria = settings[:sort].presence || [['priority', 'desc'], ['updated_on', 'desc']]
+    issues = query.issues(:limit => 10)
+
+    return issues, query
   end
 
-  def issueswatched_items
-    Issue.visible.open.
-      on_active_project.watched_by(User.current.id).
-      preload(:status, :project, :tracker, :priority).
-      recently_updated.
-      limit(10)
+  def issuesreportedbyme_items(settings)
+    query = IssueQuery.new(:name => l(:label_reported_issues), :user => User.current)
+    query.add_filter 'author_id', '=', ['me']
+    query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
+    query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
+    issues = query.issues(:limit => 10)
+
+    return issues, query
+  end
+
+  def issueswatched_items(settings)
+    query = IssueQuery.new(:name => l(:label_watched_issues), :user => User.current)
+    query.add_filter 'watcher_id', '=', ['me']
+    query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
+    query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
+    issues = query.issues(:limit => 10)
+
+    return issues, query
   end
 
   def news_items
index 1d0db28771e2638be70b326abe6e6aa3d27c9dca..b4a0a0b75d319636fbad3e6007fa39ab16a858fa 100644 (file)
@@ -161,7 +161,7 @@ module QueriesHelper
     content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
   end
 
-  def column_header(query, column)
+  def column_header(query, column, options={})
     if column.sortable?
       css, order = nil, column.default_order
       if column.name.to_s == query.sort_criteria.first_key
@@ -173,11 +173,21 @@ module QueriesHelper
           order = 'asc'
         end
       end
-      sort_param = { :sort => query.sort_criteria.add(column.name, order).to_param }
-      content = link_to(column.caption,
-          {:params => request.query_parameters.merge(sort_param)},
+      param_key = options[:sort_param] || :sort
+      sort_param = { param_key => query.sort_criteria.add(column.name, order).to_param }
+      while sort_param.keys.first.to_s =~ /^(.+)\[(.+)\]$/
+        sort_param = {$1 => {$2 => sort_param.values.first}}
+      end
+      link_options = {
           :title => l(:label_sort_by, "\"#{column.caption}\""),
           :class => css
+        }
+      if options[:sort_link_options]
+        link_options.merge! options[:sort_link_options]
+      end
+      content = link_to(column.caption,
+          {:params => request.query_parameters.deep_merge(sort_param)},
+          link_options
         )
     else
       content = column.caption
index 192502024a6be1f1c353885280c8d9b43617275a..23eca02781617222c99bb53e601ad434ebd5fa82 100644 (file)
@@ -386,6 +386,22 @@ class Query < ActiveRecord::Base
     new(attributes).build_from_params(params)
   end
 
+  def as_params
+    params = {}
+    filters.each do |field, options|
+      params[:f] ||= []
+      params[:f] << field
+      params[:op] ||= {}
+      params[:op][field] = options[:operator]
+      params[:v] ||= {}
+      params[:v][field] = options[:values]
+    end
+    params[:c] = column_names
+    params[:sort] = sort_criteria.to_param
+    params[:set_filter] = 1
+    params
+  end
+
   def validate_query_filters
     filters.each_key do |field|
       if values_for(field)
index 41c0f0b4b1c01f7c750d0ef3895113ff7d235dd4..0ebafa839d0c344312fbef4c571b6e4357417241 100644 (file)
@@ -128,6 +128,7 @@ class UserPreference < ActiveRecord::Base
   end
 
   def update_block_settings(block, settings)
+    block = block.to_s
     block_settings = my_page_settings(block).merge(settings.symbolize_keys)
     my_page_settings[block] = block_settings
   end
index f9172143bd1254a60307639e4df101a76e2728ba..5488fb295906343c8c26eb4d1c739e786e76aa71 100644 (file)
@@ -1,3 +1,6 @@
+<% query_options = nil unless defined?(query_options) %>
+<% query_options ||= {} %>
+
 <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
 <%= hidden_field_tag 'back_url', url_for(:params => request.query_parameters), :id => nil %>
 <div class="autoscroll">
@@ -9,7 +12,7 @@
               :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
       </th>
       <% query.inline_columns.each do |column| %>
-        <%= column_header(query, column) %>
+        <%= column_header(query, column, query_options) %>
       <% end %>
     </tr>
   </thead>
diff --git a/app/views/my/blocks/_issues.erb b/app/views/my/blocks/_issues.erb
new file mode 100644 (file)
index 0000000..bbd10af
--- /dev/null
@@ -0,0 +1,42 @@
+<% issues, query = issues_items(block, settings) %>
+
+<div class="contextual">
+  <%= link_to_function l(:label_options), "$('##{block}-settings').toggle();", :class => 'icon-only icon-settings' %>
+</div>
+
+<h3>
+  <%= link_to query.name, issues_path(query.as_params) %>
+  (<%= query.issue_count %>)
+</h3>
+
+<div id="<%= block %>-settings" style="display:none;">
+  <%= form_tag(my_page_path, :remote => true) do %>
+    <div class="box">
+      <%= render_query_columns_selection(query, :name => "settings[#{block}][columns]") %>
+    </div>
+    <p>
+      <%= submit_tag l(:button_save) %>
+      <%= link_to_function l(:button_cancel), "$('##{block}-settings').toggle();" %>
+    </p>
+  <% end %>
+</div>
+
+<% if issues.any? %>
+  <%= render :partial => 'issues/list',
+             :locals => {
+               :issues => issues,
+               :query => query,
+               :query_options => {
+                 :sort_param => "settings[#{block}][sort]",
+                 :sort_link_options => {:method => :post, :remote => true}
+               }
+             } %>
+<% else %>
+  <p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
+
+<% content_for :header_tags do %>
+<%= auto_discovery_link_tag(:atom,
+                            issues_path(query.as_params.merge(:format => 'atom', :key => User.current.rss_key)),
+                            {:title => query.name}) %>
+<% end %>
diff --git a/app/views/my/blocks/_issuesassignedtome.html.erb b/app/views/my/blocks/_issuesassignedtome.html.erb
deleted file mode 100644 (file)
index 94948b7..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<% assigned_issues = issuesassignedtome_items %>
-<h3>
-  <%= link_to l(:label_assigned_to_me_issues),
-        issues_path(:set_filter => 1, :assigned_to_id => 'me', :sort => 'priority:desc,updated_on:desc') %>
-  (<%= assigned_issues.limit(nil).count %>)
-</h3>
-
-<%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues.to_a } %>
-
-<% content_for :header_tags do %>
-<%= auto_discovery_link_tag(:atom,
-                            {:controller => 'issues', :action => 'index', :set_filter => 1,
-                             :assigned_to_id => 'me', :format => 'atom', :key => User.current.rss_key},
-                            {:title => l(:label_assigned_to_me_issues)}) %>
-<% end %>
diff --git a/app/views/my/blocks/_issuesreportedbyme.html.erb b/app/views/my/blocks/_issuesreportedbyme.html.erb
deleted file mode 100644 (file)
index 91557c0..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<% reported_issues = issuesreportedbyme_items %>
-<h3>
-  <%= link_to l(:label_reported_issues),
-        issues_path(:set_filter => 1, :status_id => 'o', :author_id => 'me', :sort => 'updated_on:desc') %>
-  (<%= reported_issues.limit(nil).count %>)
-</h3>
-
-<%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues.to_a } %>
-
-<% content_for :header_tags do %>
-<%= auto_discovery_link_tag(:atom,
-                            {:controller => 'issues', :action => 'index', :set_filter => 1,
-                             :author_id => 'me', :format => 'atom', :key => User.current.rss_key},
-                            {:title => l(:label_reported_issues)}) %>
-<% end %>
diff --git a/app/views/my/blocks/_issueswatched.html.erb b/app/views/my/blocks/_issueswatched.html.erb
deleted file mode 100644 (file)
index 512a522..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<% watched_issues = issueswatched_items %>
-<h3>
-  <%= link_to l(:label_watched_issues),
-        issues_path(:set_filter => 1, :watcher_id => 'me', :sort => 'updated_on:desc') %>
-  (<%= watched_issues.limit(nil).count %>)
-</h3>
-
-
-<%= render :partial => 'issues/list_simple', :locals => { :issues => watched_issues.to_a } %>
index 1210c471a3dbfd1a371ace9d054245bf307332f0..7f67d0a90e6a9c0ae3099977b6eb941a5d431a9e 100644 (file)
@@ -25,6 +25,7 @@
 
 <%= javascript_tag do %>
 $(document).ready(function(){
+  $('#block-select').val('');
   $('#list-top, #list-left, #list-right').sortable({
     connectWith: '.block-receiver',
     tolerance: 'pointer',
index d102341c12ac39f4fb7c0f26b60193e3c1eda3ff..26a46078060df6c99da3bb391d1777fe73e28cc4 100644 (file)
@@ -1,32 +1,38 @@
+<% tag_id = tag_name.gsub(/[\[\]]+/, '_').sub(/_+$/, '') %>
+<% available_tag_id = "available_#{tag_id}" %>
+<% selected_tag_id = "selected_#{tag_id}" %>
+
 <table class="query-columns">
   <tr>
     <td style="padding-left:0">
-      <%= label_tag "available_columns", l(:description_available_columns) %>
+      <%= label_tag available_tag_id, l(:description_available_columns) %>
       <br />
       <%= select_tag 'available_columns',
               options_for_select(query_available_inline_columns_options(query)),
+              :id => available_tag_id,
               :multiple => true, :size => 10, :style => "width:150px",
-              :ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %>
+              :ondblclick => "moveOptions(this.form.#{available_tag_id}, this.form.#{selected_tag_id});" %>
     </td>
     <td class="buttons">
       <input type="button" value="&#8594;"
-       onclick="moveOptions(this.form.available_columns, this.form.selected_columns);" /><br />
+       onclick="moveOptions(this.form.<%= available_tag_id %>, this.form.<%= selected_tag_id %>);" /><br />
       <input type="button" value="&#8592;"
-       onclick="moveOptions(this.form.selected_columns, this.form.available_columns);" />
+       onclick="moveOptions(this.form.<%= selected_tag_id %>, this.form.<%= available_tag_id %>);" />
     </td>
     <td>
-      <%= label_tag "selected_columns", l(:description_selected_columns) %>
+      <%= label_tag selected_tag_id, l(:description_selected_columns) %>
       <br />
       <%= select_tag tag_name,
               options_for_select(query_selected_inline_columns_options(query)),
-              :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px",
-              :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);" %>
+              :id => selected_tag_id,
+              :multiple => true, :size => 10, :style => "width:150px",
+              :ondblclick => "moveOptions(this.form.#{selected_tag_id}, this.form.#{available_tag_id});" %>
     </td>
     <td class="buttons">
-      <input type="button" value="&#8648;" onclick="moveOptionTop(this.form.selected_columns);" /><br />
-      <input type="button" value="&#8593;" onclick="moveOptionUp(this.form.selected_columns);" /><br />
-      <input type="button" value="&#8595;" onclick="moveOptionDown(this.form.selected_columns);" /><br />
-      <input type="button" value="&#8650;" onclick="moveOptionBottom(this.form.selected_columns);" />
+      <input type="button" value="&#8648;" onclick="moveOptionTop(this.form.<%= selected_tag_id %>);" /><br />
+      <input type="button" value="&#8593;" onclick="moveOptionUp(this.form.<%= selected_tag_id %>);" /><br />
+      <input type="button" value="&#8595;" onclick="moveOptionDown(this.form.<%= selected_tag_id %>);" /><br />
+      <input type="button" value="&#8650;" onclick="moveOptionBottom(this.form.<%= selected_tag_id %>);" />
     </td>
   </tr>
 </table>
@@ -34,7 +40,7 @@
 <%= javascript_tag do %>
 $(document).ready(function(){
   $('.query-columns').closest('form').submit(function(){
-    $('#selected_columns option').prop('selected', true);
+    $('#<%= selected_tag_id %> option').prop('selected', true);
   });
 });
 <% end %>
index 0c11459e46e0f7581f49a62e2a15c26142930d8d..06f750f4e434d29010a994793f5f79c1193ac3ce 100644 (file)
@@ -20,13 +20,13 @@ module Redmine
     include Redmine::I18n
 
     CORE_BLOCKS = {
-        'issuesassignedtome' => :label_assigned_to_me_issues,
-        'issuesreportedbyme' => :label_reported_issues,
-        'issueswatched' => :label_watched_issues,
-        'news' => :label_news_latest,
-        'calendar' => :label_calendar,
-        'documents' => :label_document_plural,
-        'timelog' => :label_spent_time
+        'issuesassignedtome' => {:label => :label_assigned_to_me_issues, :partial => 'my/blocks/issues'},
+        'issuesreportedbyme' => {:label => :label_reported_issues, :partial => 'my/blocks/issues'},
+        'issueswatched' => {:label => :label_watched_issues, :partial => 'my/blocks/issues'},
+        'news' => {:label => :label_news_latest, :partial => 'my/blocks/news'},
+        'calendar' => {:label => :label_calendar, :partial => 'my/blocks/calendar'},
+        'documents' => {:label => :label_document_plural, :partial => 'my/blocks/documents'},
+        'timelog' => {:label => :label_spent_time, :partial => 'my/blocks/timelog'}
       }
 
     # Returns the available blocks
@@ -36,8 +36,9 @@ module Redmine
 
     def self.block_options
       options = []
-      blocks.each do |k, v|
-        options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
+      blocks.each do |block, block_options|
+        label = block_options[:label]
+        options << [l("my.blocks.#{label}", :default => [label, label.to_s.humanize]), block.dasherize]
       end
       options
     end
@@ -46,7 +47,7 @@ module Redmine
     def self.additional_blocks
       @@additional_blocks ||= Dir.glob("#{Redmine::Plugin.directory}/*/app/views/my/blocks/_*.{rhtml,erb}").inject({}) do |h,file|
         name = File.basename(file).split('.').first.gsub(/^_/, '')
-        h[name] = name.to_sym
+        h[name] = {:label => name.to_sym, :partial => "my/blocks/#{name}"}
         h
       end
     end
index b60f6a51b3c6e9c1fbf5f670c65a395e25885840..1b0206d7c58c82aae869c2c49b0f8e49730b1751 100644 (file)
@@ -1132,6 +1132,7 @@ div.wiki img {vertical-align:middle; max-width:100%;}
 
 .handle {cursor: move;}
 
+#my-page .list th.checkbox, #my-page .list td.checkbox {display:none;}
 /***** Gantt chart *****/
 .gantt_hdr {
   position:absolute;
index 914d7c386edd12cdf022e0fe925c777da1b8e471..10ed24c1c0bbc28c0b383659766aaf5b9ddd0e3a 100644 (file)
@@ -797,7 +797,7 @@ class IssuesControllerTest < Redmine::ControllerTest
     assert_equal columns, session[:issue_query][:column_names].map(&:to_s)
 
     # ensure only these columns are kept in the selected columns list
-    assert_select 'select#selected_columns option' do
+    assert_select 'select[name=?] option', 'c[]' do
       assert_select 'option', 3
       assert_select 'option[value=tracker]'
       assert_select 'option[value=project]', 0
index 76237dfb52d37621c4e34ed6321145218dc187ae..f79cca5d9b0982b58d0cd35a94132e046f8f6a5a 100644 (file)
@@ -51,6 +51,47 @@ class MyControllerTest < Redmine::ControllerTest
     end
   end
 
+  def test_page_with_assigned_issues_block_and_no_custom_settings
+    preferences = User.find(2).pref
+    preferences.my_page_layout = {'top' => ['issuesassignedtome']}
+    preferences.my_page_settings = nil
+    preferences.save!
+
+    get :page
+    assert_select '#block-issuesassignedtome' do
+      assert_select 'table.issues' do
+        assert_select 'th a[data-remote=true][data-method=post]', :text => 'Tracker'
+      end
+      assert_select '#issuesassignedtome-settings' do
+        assert_select 'select[name=?]', 'settings[issuesassignedtome][columns][]'
+      end
+    end
+  end
+
+  def test_page_with_assigned_issues_block_and_custom_columns
+    preferences = User.find(2).pref
+    preferences.my_page_layout = {'top' => ['issuesassignedtome']}
+    preferences.my_page_settings = {'issuesassignedtome' => {:columns => ['tracker', 'subject', 'due_date']}}
+    preferences.save!
+
+    get :page
+    assert_select '#block-issuesassignedtome' do
+      assert_select 'table.issues td.due_date'
+    end
+  end
+
+  def test_page_with_assigned_issues_block_and_custom_sort
+    preferences = User.find(2).pref
+    preferences.my_page_layout = {'top' => ['issuesassignedtome']}
+    preferences.my_page_settings = {'issuesassignedtome' => {:sort => 'due_date'}}
+    preferences.save!
+
+    get :page
+    assert_select '#block-issuesassignedtome' do
+      assert_select 'table.issues.sort-by-due-date'
+    end
+  end
+
   def test_page_with_all_blocks
     blocks = Redmine::MyPage.blocks.keys
     preferences = User.find(2).pref
index d7dacc0eeee463de2242aaa9baa467487803ffce..098c4b55ba5a393c291f7e21bacee34d77459101 100644 (file)
@@ -51,7 +51,7 @@ class SettingsControllerTest < Redmine::ControllerTest
       assert_response :success
     end
 
-    assert_select 'select[id=selected_columns][name=?]', 'settings[issue_list_default_columns][]' do
+    assert_select 'select[name=?]', 'settings[issue_list_default_columns][]' do
       assert_select 'option', 4
       assert_select 'option[value=tracker]', :text => 'Tracker'
       assert_select 'option[value=subject]', :text => 'Subject'
@@ -59,7 +59,7 @@ class SettingsControllerTest < Redmine::ControllerTest
       assert_select 'option[value=updated_on]', :text => 'Updated'
     end
 
-    assert_select 'select[id=available_columns]' do
+    assert_select 'select[name=?]', 'available_columns[]' do
       assert_select 'option[value=tracker]', 0
       assert_select 'option[value=priority]', :text => 'Priority'
     end