]> source.dussan.org Git - redmine.git/commitdiff
Added the ability to customize columns of a saved query.
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Mon, 1 Oct 2007 08:44:17 +0000 (08:44 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Mon, 1 Oct 2007 08:44:17 +0000 (08:44 +0000)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@782 e93f8b46-1217-0410-a6f0-8f06a7374b81

28 files changed:
app/helpers/queries_helper.rb
app/models/enumeration.rb
app/models/issue_category.rb
app/models/issue_status.rb
app/models/query.rb
app/models/tracker.rb
app/views/issues/_list.rhtml [new file with mode: 0644]
app/views/issues/index.rhtml
app/views/projects/list_issues.rhtml
app/views/queries/_form.rhtml
db/migrate/071_add_queries_column_names.rb [new file with mode: 0644]
lang/bg.yml
lang/cs.yml
lang/de.yml
lang/en.yml
lang/es.yml
lang/fr.yml
lang/it.yml
lang/ja.yml
lang/nl.yml
lang/pl.yml
lang/pt-br.yml
lang/pt.yml
lang/ro.yml
lang/sv.yml
lang/zh.yml
public/stylesheets/application.css
test/unit/query_test.rb

index 1c0b5957092b52d12b332489b3af3f86e6328bec..7869328551e5a99df33397aa57784ce34bd6b12a 100644 (file)
@@ -1,6 +1,45 @@
-module QueriesHelper
+# redMine - project management software
+# Copyright (C) 2006-2007  Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
+module QueriesHelper
+  
   def operators_for_select(filter_type)
     Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
   end
+  
+  def column_header(column)
+    if column.sortable
+      sort_header_tag(column.sortable, :caption => l("field_#{column.name}"))
+    else
+      content_tag('th', l("field_#{column.name}"))
+    end
+  end
+  
+  def column_content(column, issue)
+    value = issue.send(column.name)
+    if value.is_a?(Date)
+      format_date(value)
+    elsif value.is_a?(Time)
+      format_time(value)
+    elsif column.name == :subject
+      ((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') +
+        link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
+    else
+      h(value)
+    end
+  end
 end
index c4f0f98cbe3e108b09b09737a2992d9744c8557c..46d350a215ae33412335287f699d73554e9e99ec 100644 (file)
@@ -37,6 +37,8 @@ class Enumeration < ActiveRecord::Base
     OPTIONS[self.opt]
   end
   
+  def to_s; name end
+  
 private
   def check_integrity
     case self.opt
index b184c132f79590fa3661c3e0f651dfd54460c622..9478504f16780b7f5322c375bc94b29b27518cd0 100644 (file)
@@ -34,4 +34,6 @@ class IssueCategory < ActiveRecord::Base
     end
     destroy_without_reassign
   end
+  
+  def to_s; name end
 end
index 13ed27f6c5e9d56f0e4b6f4a21744495b66d48d1..3929c677454f9c3958c23d82e1c1b78c39074596 100644 (file)
@@ -51,7 +51,9 @@ class IssueStatus < ActiveRecord::Base
                                    :conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact  if role && tracker
     new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
   end
-  
+
+  def to_s; name end
+
 private
   def check_integrity
     raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
index 11460c1cbe0cbd078625b5755e0f362ffdb2decd..c3d9d56e3c2a8b7367c567233b823233e4cad70f 100644 (file)
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
+class QueryColumn  
+  attr_accessor :name, :sortable, :default
+  
+  def initialize(name, options={})
+    self.name = name
+    self.sortable = options[:sortable]
+    self.default = options[:default]
+  end
+  
+  def default?; default end
+end
+
 class Query < ActiveRecord::Base
   belongs_to :project
   belongs_to :user
   serialize :filters
+  serialize :column_names
   
   attr_protected :project, :user
   attr_accessor :executed_by
@@ -59,6 +72,22 @@ class Query < ActiveRecord::Base
 
   cattr_reader :operators_by_filter_type
 
+  @@available_columns = [
+    QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :default => true),
+    QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :default => true),
+    QueryColumn.new(:priority, :sortable => "#{Issue.table_name}.priority_id", :default => true),
+    QueryColumn.new(:subject, :default => true),
+    QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname", :default => true),
+    QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default => true),
+    QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
+    QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
+    QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
+    QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
+    QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
+    QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on"),
+  ]
+  cattr_reader :available_columns
+  
   def initialize(attributes = nil)
     super attributes
     self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
@@ -173,7 +202,30 @@ class Query < ActiveRecord::Base
     label = @available_filters[field][:name] if @available_filters.has_key?(field)
     label ||= field.gsub(/\_id$/, "")
   end
+
+  def available_columns
+    cols = Query.available_columns
+  end
   
+  def columns
+    if column_names && !column_names.empty?
+      available_columns.select {|c| column_names.include?(c.name) }
+    else
+      # default columns
+      available_columns.select {|c| c.default? }
+    end
+  end
+  
+  def column_names=(names)
+    names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
+    names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
+    write_attribute(:column_names, names)
+  end
+  
+  def has_column?(column)
+    column_names && column_names.include?(column.name)
+  end
+
   def statement
     # project/subprojects clause
     clause = ''
index c024c09118eb58a7383dc72566f87655b9d18866..90ef319126188cdedfbd7767821255695742531d 100644 (file)
@@ -27,6 +27,8 @@ class Tracker < ActiveRecord::Base
   validates_length_of :name, :maximum => 30
   validates_format_of :name, :with => /^[\w\s\'\-]*$/i
 
+  def to_s; name end
+  
 private
   def check_integrity
     raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
diff --git a/app/views/issues/_list.rhtml b/app/views/issues/_list.rhtml
new file mode 100644 (file)
index 0000000..0af4159
--- /dev/null
@@ -0,0 +1,20 @@
+<table class="list">
+    <thead><tr>
+        <th></th>
+               <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
+        <% query.columns.each do |column| %>
+          <%= column_header(column) %>
+        <% end %>
+       </tr></thead>
+       <tbody>
+       <% issues.each do |issue| %>
+       <tr class="issue <%= cycle('odd', 'even') %>">
+           <th class="checkbox"><%= check_box_tag "issue_ids[]", issue.id, false, :id => "issue_#{issue.id}" %></th>
+               <td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
+        <% query.columns.each do |column| %>
+          <%= content_tag 'td', column_content(column, issue), :class => column.name %>
+        <% end %>
+       </tr>
+       <% end %>
+       </tbody>
+</table>
index 270f9b21531129322a22e3ff567d6b16fd739fad..eb083535798b139cf6c94c84d54b6dedee3779e6 100644 (file)
 <p><i><%= l(:label_no_data) %></i></p>
 <% else %>
 &nbsp;
-<table class="list">
-    <thead><tr>
-               <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
-               <%= sort_header_tag("#{Project.table_name}.name", :caption => l(:field_project)) %>
-               <%= sort_header_tag("#{Issue.table_name}.tracker_id", :caption => l(:field_tracker)) %>
-               <%= sort_header_tag("#{IssueStatus.table_name}.name", :caption => l(:field_status)) %>
-               <%= sort_header_tag("#{Issue.table_name}.priority_id", :caption => l(:field_priority)) %>
-               <th><%=l(:field_subject)%></th>
-               <%= sort_header_tag("#{User.table_name}.lastname", :caption => l(:field_assigned_to)) %>
-               <%= sort_header_tag("#{Issue.table_name}.updated_on", :caption => l(:field_updated_on)) %>
-       </tr></thead>
-       <tbody>
-       <% for issue in @issues %>
-       <tr class="<%= cycle("odd", "even") %>">
-               <td align="center" valign="top"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
-               <td align="center" valign="top" nowrap><%=h issue.project.name %></td>
-               <td align="center" valign="top" nowrap><%= issue.tracker.name %></td>
-               <td  valign="top"nowrap><%= issue.status.name %></td>
-               <td align="center" valign="top"><%= issue.priority.name %></td>
-               <td><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %></td>
-               <td align="center" valign="top" nowrap><%= issue.assigned_to.name if issue.assigned_to %></td>
-               <td align="center" valign="top" nowrap><%= format_time(issue.updated_on) %></td>
-       </tr>
-       <% end %>
-       </tbody>
-</table>
+<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
+
 <p><%= pagination_links_full @issue_pages %>
 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p>
 <% end %>
index e17e8bc37de047afb5b2cf3da531ab1efb5d2322..3c90c30f9f084acfd37c1730d941e667aeadfc87 100644 (file)
 <% else %>
 &nbsp;
 <% form_tag({:controller => 'projects', :action => 'move_issues', :id => @project}, :id => 'issues_form' ) do %>       
-<table class="list">
-    <thead><tr>
-        <th></th>
-               <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
-               <%= sort_header_tag("#{Issue.table_name}.tracker_id", :caption => l(:field_tracker)) %>
-               <%= sort_header_tag("#{IssueStatus.table_name}.name", :caption => l(:field_status)) %>
-               <%= sort_header_tag("#{Issue.table_name}.priority_id", :caption => l(:field_priority)) %>
-               <th><%=l(:field_subject)%></th>
-               <%= sort_header_tag("#{User.table_name}.lastname", :caption => l(:field_assigned_to)) %>
-               <%= sort_header_tag("#{Issue.table_name}.updated_on", :caption => l(:field_updated_on)) %>
-       </tr></thead>
-       <tbody>
-       <% for issue in @issues %>
-       <tr class="<%= cycle("odd", "even") %>">
-           <th style="width:15px;"><%= check_box_tag "issue_ids[]", issue.id, false, :id => "issue_#{issue.id}" %></th>
-               <td align="center"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
-               <td align="center"><%= issue.tracker.name %></td>
-               <td><%= issue.status.name %></td>
-               <td align="center"><%= issue.priority.name %></td>
-               <td><%= "#{issue.project.name} - " unless @project && @project == issue.project %><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %></td>
-               <td align="center"><%= issue.assigned_to.name if issue.assigned_to %></td>
-               <td align="center"><%= format_time(issue.updated_on) %></td>
-       </tr>
-       <% end %>
-       </tbody>
-</table>
+<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
 <div class="contextual">
 <%= l(:label_export_to) %>
 <%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'icon icon-csv' %>,
index 28b6479e5cfaaa0aefa5bed0776d0aa3da0665c7..b4a4987cdf3ee661471b8ae0c9ba297b987dc2d8 100644 (file)
@@ -9,6 +9,13 @@
   <p><label for="query_is_public"><%=l(:field_is_public)%></label>
   <%= check_box 'query', 'is_public' %></p>
 <% end %>
+
+<p><label for="query_column_names"><%=l(:field_column_names)%></label>
+<% @query.available_columns.each do |column| %>
+<%= check_box_tag  'query[column_names][]', column.name, @query.has_column?(column) %> <%= l("field_#{column.name}") %><br />
+<% end %>
+<%= hidden_field_tag 'query[column_names][]', '' %>
+</p>
 </div>
 
 <%= render :partial => 'queries/filters', :locals => {:query => query}%>
diff --git a/db/migrate/071_add_queries_column_names.rb b/db/migrate/071_add_queries_column_names.rb
new file mode 100644 (file)
index 0000000..acaf4da
--- /dev/null
@@ -0,0 +1,9 @@
+class AddQueriesColumnNames < ActiveRecord::Migration
+  def self.up
+    add_column :queries, :column_names, :text
+  end
+
+  def self.down
+    remove_column :queries, :column_names
+  end
+end
index c15abfea4b47e51a510874d40d0bec0f66260d2c..d576142d1d87ace2389ffe06f035b2a15cd8e64b 100644 (file)
@@ -511,3 +511,4 @@ enumeration_doc_categories: Категории документи
 enumeration_activities: Дейности (time tracking)
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index 8a68d95b89c4db96cac24a1a7e715bc01c2a3723..569002636c459065da54391cec2805667afcc959 100644 (file)
@@ -511,3 +511,4 @@ text_issue_category_destroy_assignments: Remove category assignments
 label_added_time_by: Added by %s %s ago
 field_estimated_hours: Estimated time
 label_changeset_plural: Changesets
+field_column_names: Columns
index 2fb1b1e92cdfb87880eca5df5ace2c8f00bad782..f2b5b069fe6301dc33d97c6b0befd6e642bc906b 100644 (file)
@@ -511,3 +511,4 @@ enumeration_doc_categories: Dokumentenkategorien
 enumeration_activities: Aktivitäten (Zeiterfassung)
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index dc818e37c9950ab4e3c9d59a1c795d15fc6ff774..6e23f377fae86a747a57219496d0aa5fb8ec8b88 100644 (file)
@@ -159,6 +159,7 @@ field_delay: Delay
 field_assignable: Issues can be assigned to this role
 field_redirect_existing_links: Redirect existing links
 field_estimated_hours: Estimated time
+field_column_names: Columns
 
 setting_app_title: Application title
 setting_app_subtitle: Application subtitle
index 09017909d7aac5feb4b141d971a6ff5442f3c5c9..82b0908a76dfb5cf58ac01bee8a55e2d1f780353 100644 (file)
@@ -511,3 +511,4 @@ enumeration_doc_categories: Categorías del documento
 enumeration_activities: Activities (time tracking)
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index 3f5371daa3f6bd4aa3505b2c0fb095472374a9e7..68d329774f67c506ac77899676239b313d0175e5 100644 (file)
@@ -159,6 +159,7 @@ field_delay: Retard
 field_assignable: Demandes assignables à ce rôle
 field_redirect_existing_links: Rediriger les liens existants
 field_estimated_hours: Temps estimé
+field_column_names: Colonnes
 
 setting_app_title: Titre de l'application
 setting_app_subtitle: Sous-titre de l'application
index 73aa3437b749337a4ea759cb2967c6769a8c2bf6..54b008580573e43b9f95511dafc5df1f78b87e47 100644 (file)
@@ -511,3 +511,4 @@ enumeration_doc_categories: Categorie di documenti
 enumeration_activities: Attività (time tracking)
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index 1e497790803f00ebe05003e4f231bc19201c2eb1..0e2628c609b84f07f8d9ae6adab6ca34098430ec 100644 (file)
@@ -512,3 +512,4 @@ enumeration_doc_categories: 文書カテゴリ
 enumeration_activities: 作業分類 (時間トラッキング)
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index 0c862ab30038e6b19cec4ffe747b5b642c63f3ee..916040747f2026bb59736e4b9bb5080ec7aa9d6d 100644 (file)
@@ -512,3 +512,4 @@ enumeration_activities: Activiteiten (tijd tracking)
 text_comma_separated: Multiple values allowed (comma separated).
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index e5d380d1ed764652fc59d16b0be4beba3055b1a9..a61974afa9d8141f5b88b2c2103b28363ba9f223 100644 (file)
@@ -511,3 +511,4 @@ label_added_time_by: Dodane przez %s %s temu
 field_estimated_hours: Szacowany czas
 label_file_plural: Pliki
 label_changeset_plural: Zestawienia zmian
+field_column_names: Columns
index afcd4cb27da7a93572780997f866030d1af5ff93..e8f34b6d761ecd0c2ddce00a736af0cf69f42db1 100644 (file)
@@ -511,3 +511,4 @@ enumeration_doc_categories: Categorias de documento
 enumeration_activities: Atividades (time tracking)\r
 label_file_plural: Files\r
 label_changeset_plural: Changesets\r
+field_column_names: Columns\r
index 2ce44f49b96e3204f5736209472b039a00ccd526..f7cb5f91a5248e98c11f058b18d6fbcbb29824f5 100644 (file)
@@ -511,3 +511,4 @@ enumeration_doc_categories: Categorias de documento
 enumeration_activities: Atividades (time tracking)
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index 19d35c11defa2bb1d26e332408db894600e5967e..d4cd69b37aaf4ca7eb6104a9c656bc4436eaa689 100644 (file)
@@ -511,3 +511,4 @@ label_index_by_date: Index by date
 label_index_by_title: Index by title
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index adcd28879fcccf317c77ca4cc373f2bcad8fcb22..ed5106a138ff2643b98df6ca796f6ed3078f438b 100644 (file)
@@ -512,3 +512,4 @@ enumeration_activities: Aktiviteter (tidsspårning)
 field_comments: Comment
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index c3edc0308393fc5e7926ef26118264aa02c676f7..ec286878dbbf1cdd93361e8ec20b3099b0d5bee8 100644 (file)
@@ -514,3 +514,4 @@ enumeration_activities: Activities (time tracking)
 label_wiki_page: Wiki page
 label_file_plural: Files
 label_changeset_plural: Changesets
+field_column_names: Columns
index 307eaa024a236da4c423a18e11512d91c1bc5b28..8a17600ef4e12ab84a5140d8d13982f731cea76d 100644 (file)
@@ -133,6 +133,12 @@ margin*/
 
 div.attachments p { margin:4px 0 2px 0; }
 
+/***** Issue list ****/
+tr.issue { text-align: center; white-space: nowrap; }
+tr.issue th.checkbox { width: 15px; }
+tr.issue td.subject, tr.issue td.category { white-space: normal; }
+tr.issue td.subject { text-align: left; }
+
 /***** Flash & error messages ****/
 #flash div, #errorExplanation, .nodata {
     padding: 4px 4px 4px 30px;
index ea004e39ebfef9e600ba4a6c3da63f90de08a2c3..c00f47e5d761375c7ff9c0eb5fffb4d4e1a73454 100644 (file)
@@ -28,4 +28,17 @@ class QueryTest < Test::Unit::TestCase
     assert_equal 1, issues.length
     assert_equal Issue.find(3), issues.first
   end
+  
+  def test_default_columns
+    q = Query.new
+    assert !q.columns.empty? 
+  end
+  
+  def test_set_column_names
+    q = Query.new
+    q.column_names = ['tracker', :subject, '', 'unknonw_column']
+    assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
+    c = q.columns.first
+    assert q.has_column?(c)
+  end
 end