summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/helpers/queries_helper.rb41
-rw-r--r--app/models/enumeration.rb2
-rw-r--r--app/models/issue_category.rb2
-rw-r--r--app/models/issue_status.rb4
-rw-r--r--app/models/query.rb52
-rw-r--r--app/models/tracker.rb2
-rw-r--r--app/views/issues/_list.rhtml20
-rw-r--r--app/views/issues/index.rhtml28
-rw-r--r--app/views/projects/list_issues.rhtml27
-rw-r--r--app/views/queries/_form.rhtml7
-rw-r--r--db/migrate/071_add_queries_column_names.rb9
-rw-r--r--lang/bg.yml1
-rw-r--r--lang/cs.yml1
-rw-r--r--lang/de.yml1
-rw-r--r--lang/en.yml1
-rw-r--r--lang/es.yml1
-rw-r--r--lang/fr.yml1
-rw-r--r--lang/it.yml1
-rw-r--r--lang/ja.yml1
-rw-r--r--lang/nl.yml1
-rw-r--r--lang/pl.yml1
-rw-r--r--lang/pt-br.yml1
-rw-r--r--lang/pt.yml1
-rw-r--r--lang/ro.yml1
-rw-r--r--lang/sv.yml1
-rw-r--r--lang/zh.yml1
-rw-r--r--public/stylesheets/application.css6
-rw-r--r--test/unit/query_test.rb13
28 files changed, 174 insertions, 54 deletions
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb
index 1c0b59570..786932855 100644
--- a/app/helpers/queries_helper.rb
+++ b/app/helpers/queries_helper.rb
@@ -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
diff --git a/app/models/enumeration.rb b/app/models/enumeration.rb
index c4f0f98cb..46d350a21 100644
--- a/app/models/enumeration.rb
+++ b/app/models/enumeration.rb
@@ -37,6 +37,8 @@ class Enumeration < ActiveRecord::Base
OPTIONS[self.opt]
end
+ def to_s; name end
+
private
def check_integrity
case self.opt
diff --git a/app/models/issue_category.rb b/app/models/issue_category.rb
index b184c132f..9478504f1 100644
--- a/app/models/issue_category.rb
+++ b/app/models/issue_category.rb
@@ -34,4 +34,6 @@ class IssueCategory < ActiveRecord::Base
end
destroy_without_reassign
end
+
+ def to_s; name end
end
diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb
index 13ed27f6c..3929c6774 100644
--- a/app/models/issue_status.rb
+++ b/app/models/issue_status.rb
@@ -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])
diff --git a/app/models/query.rb b/app/models/query.rb
index 11460c1cb..c3d9d56e3 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -15,10 +15,23 @@
# 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 = ''
diff --git a/app/models/tracker.rb b/app/models/tracker.rb
index c024c0911..90ef31912 100644
--- a/app/models/tracker.rb
+++ b/app/models/tracker.rb
@@ -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
index 000000000..0af415946
--- /dev/null
+++ b/app/views/issues/_list.rhtml
@@ -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>
diff --git a/app/views/issues/index.rhtml b/app/views/issues/index.rhtml
index 270f9b215..eb0835357 100644
--- a/app/views/issues/index.rhtml
+++ b/app/views/issues/index.rhtml
@@ -23,32 +23,8 @@
<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 %>
diff --git a/app/views/projects/list_issues.rhtml b/app/views/projects/list_issues.rhtml
index e17e8bc37..3c90c30f9 100644
--- a/app/views/projects/list_issues.rhtml
+++ b/app/views/projects/list_issues.rhtml
@@ -45,32 +45,7 @@
<% 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' %>,
diff --git a/app/views/queries/_form.rhtml b/app/views/queries/_form.rhtml
index 28b6479e5..b4a4987cd 100644
--- a/app/views/queries/_form.rhtml
+++ b/app/views/queries/_form.rhtml
@@ -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
index 000000000..acaf4dab0
--- /dev/null
+++ b/db/migrate/071_add_queries_column_names.rb
@@ -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
diff --git a/lang/bg.yml b/lang/bg.yml
index c15abfea4..d576142d1 100644
--- a/lang/bg.yml
+++ b/lang/bg.yml
@@ -511,3 +511,4 @@ enumeration_doc_categories: Категории документи
enumeration_activities: Дейности (time tracking)
label_file_plural: Files
label_changeset_plural: Changesets
+field_column_names: Columns
diff --git a/lang/cs.yml b/lang/cs.yml
index 8a68d95b8..569002636 100644
--- a/lang/cs.yml
+++ b/lang/cs.yml
@@ -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
diff --git a/lang/de.yml b/lang/de.yml
index 2fb1b1e92..f2b5b069f 100644
--- a/lang/de.yml
+++ b/lang/de.yml
@@ -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
diff --git a/lang/en.yml b/lang/en.yml
index dc818e37c..6e23f377f 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -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
diff --git a/lang/es.yml b/lang/es.yml
index 09017909d..82b0908a7 100644
--- a/lang/es.yml
+++ b/lang/es.yml
@@ -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
diff --git a/lang/fr.yml b/lang/fr.yml
index 3f5371daa..68d329774 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -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
diff --git a/lang/it.yml b/lang/it.yml
index 73aa3437b..54b008580 100644
--- a/lang/it.yml
+++ b/lang/it.yml
@@ -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
diff --git a/lang/ja.yml b/lang/ja.yml
index 1e4977908..0e2628c60 100644
--- a/lang/ja.yml
+++ b/lang/ja.yml
@@ -512,3 +512,4 @@ enumeration_doc_categories: 文書カテゴリ
enumeration_activities: 作業分類 (時間トラッキング)
label_file_plural: Files
label_changeset_plural: Changesets
+field_column_names: Columns
diff --git a/lang/nl.yml b/lang/nl.yml
index 0c862ab30..916040747 100644
--- a/lang/nl.yml
+++ b/lang/nl.yml
@@ -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
diff --git a/lang/pl.yml b/lang/pl.yml
index e5d380d1e..a61974afa 100644
--- a/lang/pl.yml
+++ b/lang/pl.yml
@@ -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
diff --git a/lang/pt-br.yml b/lang/pt-br.yml
index afcd4cb27..e8f34b6d7 100644
--- a/lang/pt-br.yml
+++ b/lang/pt-br.yml
@@ -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
diff --git a/lang/pt.yml b/lang/pt.yml
index 2ce44f49b..f7cb5f91a 100644
--- a/lang/pt.yml
+++ b/lang/pt.yml
@@ -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
diff --git a/lang/ro.yml b/lang/ro.yml
index 19d35c11d..d4cd69b37 100644
--- a/lang/ro.yml
+++ b/lang/ro.yml
@@ -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
diff --git a/lang/sv.yml b/lang/sv.yml
index adcd28879..ed5106a13 100644
--- a/lang/sv.yml
+++ b/lang/sv.yml
@@ -512,3 +512,4 @@ enumeration_activities: Aktiviteter (tidsspårning)
field_comments: Comment
label_file_plural: Files
label_changeset_plural: Changesets
+field_column_names: Columns
diff --git a/lang/zh.yml b/lang/zh.yml
index c3edc0308..ec286878d 100644
--- a/lang/zh.yml
+++ b/lang/zh.yml
@@ -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
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 307eaa024..8a17600ef 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -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;
diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb
index ea004e39e..c00f47e5d 100644
--- a/test/unit/query_test.rb
+++ b/test/unit/query_test.rb
@@ -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