##// END OF EJS Templates
Added the ability to customize columns of a saved query....
Jean-Philippe Lang -
r771:e5f5671d6629
parent child
Show More
@@ -0,0 +1,20
1 <table class="list">
2 <thead><tr>
3 <th></th>
4 <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
5 <% query.columns.each do |column| %>
6 <%= column_header(column) %>
7 <% end %>
8 </tr></thead>
9 <tbody>
10 <% issues.each do |issue| %>
11 <tr class="issue <%= cycle('odd', 'even') %>">
12 <th class="checkbox"><%= check_box_tag "issue_ids[]", issue.id, false, :id => "issue_#{issue.id}" %></th>
13 <td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
14 <% query.columns.each do |column| %>
15 <%= content_tag 'td', column_content(column, issue), :class => column.name %>
16 <% end %>
17 </tr>
18 <% end %>
19 </tbody>
20 </table>
@@ -0,0 +1,9
1 class AddQueriesColumnNames < ActiveRecord::Migration
2 def self.up
3 add_column :queries, :column_names, :text
4 end
5
6 def self.down
7 remove_column :queries, :column_names
8 end
9 end
@@ -1,6 +1,45
1 module QueriesHelper
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2
17
18 module QueriesHelper
19
3 def operators_for_select(filter_type)
20 def operators_for_select(filter_type)
4 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
5 end
22 end
23
24 def column_header(column)
25 if column.sortable
26 sort_header_tag(column.sortable, :caption => l("field_#{column.name}"))
27 else
28 content_tag('th', l("field_#{column.name}"))
29 end
30 end
31
32 def column_content(column, issue)
33 value = issue.send(column.name)
34 if value.is_a?(Date)
35 format_date(value)
36 elsif value.is_a?(Time)
37 format_time(value)
38 elsif column.name == :subject
39 ((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') +
40 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
41 else
42 h(value)
43 end
44 end
6 end
45 end
@@ -37,6 +37,8 class Enumeration < ActiveRecord::Base
37 OPTIONS[self.opt]
37 OPTIONS[self.opt]
38 end
38 end
39
39
40 def to_s; name end
41
40 private
42 private
41 def check_integrity
43 def check_integrity
42 case self.opt
44 case self.opt
@@ -34,4 +34,6 class IssueCategory < ActiveRecord::Base
34 end
34 end
35 destroy_without_reassign
35 destroy_without_reassign
36 end
36 end
37
38 def to_s; name end
37 end
39 end
@@ -51,7 +51,9 class IssueStatus < ActiveRecord::Base
51 :conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker
51 :conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker
52 new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
52 new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
53 end
53 end
54
54
55 def to_s; name end
56
55 private
57 private
56 def check_integrity
58 def check_integrity
57 raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
59 raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
@@ -15,10 +15,23
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
19 attr_accessor :name, :sortable, :default
20
21 def initialize(name, options={})
22 self.name = name
23 self.sortable = options[:sortable]
24 self.default = options[:default]
25 end
26
27 def default?; default end
28 end
29
18 class Query < ActiveRecord::Base
30 class Query < ActiveRecord::Base
19 belongs_to :project
31 belongs_to :project
20 belongs_to :user
32 belongs_to :user
21 serialize :filters
33 serialize :filters
34 serialize :column_names
22
35
23 attr_protected :project, :user
36 attr_protected :project, :user
24 attr_accessor :executed_by
37 attr_accessor :executed_by
@@ -59,6 +72,22 class Query < ActiveRecord::Base
59
72
60 cattr_reader :operators_by_filter_type
73 cattr_reader :operators_by_filter_type
61
74
75 @@available_columns = [
76 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :default => true),
77 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :default => true),
78 QueryColumn.new(:priority, :sortable => "#{Issue.table_name}.priority_id", :default => true),
79 QueryColumn.new(:subject, :default => true),
80 QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname", :default => true),
81 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default => true),
82 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
83 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
84 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
85 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
86 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
87 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on"),
88 ]
89 cattr_reader :available_columns
90
62 def initialize(attributes = nil)
91 def initialize(attributes = nil)
63 super attributes
92 super attributes
64 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
93 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
@@ -173,7 +202,30 class Query < ActiveRecord::Base
173 label = @available_filters[field][:name] if @available_filters.has_key?(field)
202 label = @available_filters[field][:name] if @available_filters.has_key?(field)
174 label ||= field.gsub(/\_id$/, "")
203 label ||= field.gsub(/\_id$/, "")
175 end
204 end
205
206 def available_columns
207 cols = Query.available_columns
208 end
176
209
210 def columns
211 if column_names && !column_names.empty?
212 available_columns.select {|c| column_names.include?(c.name) }
213 else
214 # default columns
215 available_columns.select {|c| c.default? }
216 end
217 end
218
219 def column_names=(names)
220 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
221 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
222 write_attribute(:column_names, names)
223 end
224
225 def has_column?(column)
226 column_names && column_names.include?(column.name)
227 end
228
177 def statement
229 def statement
178 # project/subprojects clause
230 # project/subprojects clause
179 clause = ''
231 clause = ''
@@ -27,6 +27,8 class Tracker < ActiveRecord::Base
27 validates_length_of :name, :maximum => 30
27 validates_length_of :name, :maximum => 30
28 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
28 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
29
29
30 def to_s; name end
31
30 private
32 private
31 def check_integrity
33 def check_integrity
32 raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
34 raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
@@ -23,32 +23,8
23 <p><i><%= l(:label_no_data) %></i></p>
23 <p><i><%= l(:label_no_data) %></i></p>
24 <% else %>
24 <% else %>
25 &nbsp;
25 &nbsp;
26 <table class="list">
26 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
27 <thead><tr>
27
28 <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
29 <%= sort_header_tag("#{Project.table_name}.name", :caption => l(:field_project)) %>
30 <%= sort_header_tag("#{Issue.table_name}.tracker_id", :caption => l(:field_tracker)) %>
31 <%= sort_header_tag("#{IssueStatus.table_name}.name", :caption => l(:field_status)) %>
32 <%= sort_header_tag("#{Issue.table_name}.priority_id", :caption => l(:field_priority)) %>
33 <th><%=l(:field_subject)%></th>
34 <%= sort_header_tag("#{User.table_name}.lastname", :caption => l(:field_assigned_to)) %>
35 <%= sort_header_tag("#{Issue.table_name}.updated_on", :caption => l(:field_updated_on)) %>
36 </tr></thead>
37 <tbody>
38 <% for issue in @issues %>
39 <tr class="<%= cycle("odd", "even") %>">
40 <td align="center" valign="top"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
41 <td align="center" valign="top" nowrap><%=h issue.project.name %></td>
42 <td align="center" valign="top" nowrap><%= issue.tracker.name %></td>
43 <td valign="top"nowrap><%= issue.status.name %></td>
44 <td align="center" valign="top"><%= issue.priority.name %></td>
45 <td><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %></td>
46 <td align="center" valign="top" nowrap><%= issue.assigned_to.name if issue.assigned_to %></td>
47 <td align="center" valign="top" nowrap><%= format_time(issue.updated_on) %></td>
48 </tr>
49 <% end %>
50 </tbody>
51 </table>
52 <p><%= pagination_links_full @issue_pages %>
28 <p><%= pagination_links_full @issue_pages %>
53 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p>
29 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p>
54 <% end %>
30 <% end %>
@@ -45,32 +45,7
45 <% else %>
45 <% else %>
46 &nbsp;
46 &nbsp;
47 <% form_tag({:controller => 'projects', :action => 'move_issues', :id => @project}, :id => 'issues_form' ) do %>
47 <% form_tag({:controller => 'projects', :action => 'move_issues', :id => @project}, :id => 'issues_form' ) do %>
48 <table class="list">
48 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
49 <thead><tr>
50 <th></th>
51 <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
52 <%= sort_header_tag("#{Issue.table_name}.tracker_id", :caption => l(:field_tracker)) %>
53 <%= sort_header_tag("#{IssueStatus.table_name}.name", :caption => l(:field_status)) %>
54 <%= sort_header_tag("#{Issue.table_name}.priority_id", :caption => l(:field_priority)) %>
55 <th><%=l(:field_subject)%></th>
56 <%= sort_header_tag("#{User.table_name}.lastname", :caption => l(:field_assigned_to)) %>
57 <%= sort_header_tag("#{Issue.table_name}.updated_on", :caption => l(:field_updated_on)) %>
58 </tr></thead>
59 <tbody>
60 <% for issue in @issues %>
61 <tr class="<%= cycle("odd", "even") %>">
62 <th style="width:15px;"><%= check_box_tag "issue_ids[]", issue.id, false, :id => "issue_#{issue.id}" %></th>
63 <td align="center"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
64 <td align="center"><%= issue.tracker.name %></td>
65 <td><%= issue.status.name %></td>
66 <td align="center"><%= issue.priority.name %></td>
67 <td><%= "#{issue.project.name} - " unless @project && @project == issue.project %><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %></td>
68 <td align="center"><%= issue.assigned_to.name if issue.assigned_to %></td>
69 <td align="center"><%= format_time(issue.updated_on) %></td>
70 </tr>
71 <% end %>
72 </tbody>
73 </table>
74 <div class="contextual">
49 <div class="contextual">
75 <%= l(:label_export_to) %>
50 <%= l(:label_export_to) %>
76 <%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'icon icon-csv' %>,
51 <%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'icon icon-csv' %>,
@@ -9,6 +9,13
9 <p><label for="query_is_public"><%=l(:field_is_public)%></label>
9 <p><label for="query_is_public"><%=l(:field_is_public)%></label>
10 <%= check_box 'query', 'is_public' %></p>
10 <%= check_box 'query', 'is_public' %></p>
11 <% end %>
11 <% end %>
12
13 <p><label for="query_column_names"><%=l(:field_column_names)%></label>
14 <% @query.available_columns.each do |column| %>
15 <%= check_box_tag 'query[column_names][]', column.name, @query.has_column?(column) %> <%= l("field_#{column.name}") %><br />
16 <% end %>
17 <%= hidden_field_tag 'query[column_names][]', '' %>
18 </p>
12 </div>
19 </div>
13
20
14 <%= render :partial => 'queries/filters', :locals => {:query => query}%>
21 <%= render :partial => 'queries/filters', :locals => {:query => query}%>
@@ -511,3 +511,4 enumeration_doc_categories: Категории документи
511 enumeration_activities: Дейности (time tracking)
511 enumeration_activities: Дейности (time tracking)
512 label_file_plural: Files
512 label_file_plural: Files
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -511,3 +511,4 text_issue_category_destroy_assignments: Remove category assignments
511 label_added_time_by: Added by %s %s ago
511 label_added_time_by: Added by %s %s ago
512 field_estimated_hours: Estimated time
512 field_estimated_hours: Estimated time
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -511,3 +511,4 enumeration_doc_categories: Dokumentenkategorien
511 enumeration_activities: Aktivitäten (Zeiterfassung)
511 enumeration_activities: Aktivitäten (Zeiterfassung)
512 label_file_plural: Files
512 label_file_plural: Files
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -159,6 +159,7 field_delay: Delay
159 field_assignable: Issues can be assigned to this role
159 field_assignable: Issues can be assigned to this role
160 field_redirect_existing_links: Redirect existing links
160 field_redirect_existing_links: Redirect existing links
161 field_estimated_hours: Estimated time
161 field_estimated_hours: Estimated time
162 field_column_names: Columns
162
163
163 setting_app_title: Application title
164 setting_app_title: Application title
164 setting_app_subtitle: Application subtitle
165 setting_app_subtitle: Application subtitle
@@ -511,3 +511,4 enumeration_doc_categories: Categorías del documento
511 enumeration_activities: Activities (time tracking)
511 enumeration_activities: Activities (time tracking)
512 label_file_plural: Files
512 label_file_plural: Files
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -159,6 +159,7 field_delay: Retard
159 field_assignable: Demandes assignables à ce rôle
159 field_assignable: Demandes assignables à ce rôle
160 field_redirect_existing_links: Rediriger les liens existants
160 field_redirect_existing_links: Rediriger les liens existants
161 field_estimated_hours: Temps estimé
161 field_estimated_hours: Temps estimé
162 field_column_names: Colonnes
162
163
163 setting_app_title: Titre de l'application
164 setting_app_title: Titre de l'application
164 setting_app_subtitle: Sous-titre de l'application
165 setting_app_subtitle: Sous-titre de l'application
@@ -511,3 +511,4 enumeration_doc_categories: Categorie di documenti
511 enumeration_activities: Attività (time tracking)
511 enumeration_activities: Attività (time tracking)
512 label_file_plural: Files
512 label_file_plural: Files
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -512,3 +512,4 enumeration_doc_categories: 文書カテゴリ
512 enumeration_activities: 作業分類 (時間トラッキング)
512 enumeration_activities: 作業分類 (時間トラッキング)
513 label_file_plural: Files
513 label_file_plural: Files
514 label_changeset_plural: Changesets
514 label_changeset_plural: Changesets
515 field_column_names: Columns
@@ -512,3 +512,4 enumeration_activities: Activiteiten (tijd tracking)
512 text_comma_separated: Multiple values allowed (comma separated).
512 text_comma_separated: Multiple values allowed (comma separated).
513 label_file_plural: Files
513 label_file_plural: Files
514 label_changeset_plural: Changesets
514 label_changeset_plural: Changesets
515 field_column_names: Columns
@@ -511,3 +511,4 label_added_time_by: Dodane przez %s %s temu
511 field_estimated_hours: Szacowany czas
511 field_estimated_hours: Szacowany czas
512 label_file_plural: Pliki
512 label_file_plural: Pliki
513 label_changeset_plural: Zestawienia zmian
513 label_changeset_plural: Zestawienia zmian
514 field_column_names: Columns
@@ -511,3 +511,4 enumeration_doc_categories: Categorias de documento
511 enumeration_activities: Atividades (time tracking)
511 enumeration_activities: Atividades (time tracking)
512 label_file_plural: Files
512 label_file_plural: Files
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -511,3 +511,4 enumeration_doc_categories: Categorias de documento
511 enumeration_activities: Atividades (time tracking)
511 enumeration_activities: Atividades (time tracking)
512 label_file_plural: Files
512 label_file_plural: Files
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -511,3 +511,4 label_index_by_date: Index by date
511 label_index_by_title: Index by title
511 label_index_by_title: Index by title
512 label_file_plural: Files
512 label_file_plural: Files
513 label_changeset_plural: Changesets
513 label_changeset_plural: Changesets
514 field_column_names: Columns
@@ -512,3 +512,4 enumeration_activities: Aktiviteter (tidsspårning)
512 field_comments: Comment
512 field_comments: Comment
513 label_file_plural: Files
513 label_file_plural: Files
514 label_changeset_plural: Changesets
514 label_changeset_plural: Changesets
515 field_column_names: Columns
@@ -514,3 +514,4 enumeration_activities: Activities (time tracking)
514 label_wiki_page: Wiki page
514 label_wiki_page: Wiki page
515 label_file_plural: Files
515 label_file_plural: Files
516 label_changeset_plural: Changesets
516 label_changeset_plural: Changesets
517 field_column_names: Columns
@@ -133,6 +133,12 margin*/
133
133
134 div.attachments p { margin:4px 0 2px 0; }
134 div.attachments p { margin:4px 0 2px 0; }
135
135
136 /***** Issue list ****/
137 tr.issue { text-align: center; white-space: nowrap; }
138 tr.issue th.checkbox { width: 15px; }
139 tr.issue td.subject, tr.issue td.category { white-space: normal; }
140 tr.issue td.subject { text-align: left; }
141
136 /***** Flash & error messages ****/
142 /***** Flash & error messages ****/
137 #flash div, #errorExplanation, .nodata {
143 #flash div, #errorExplanation, .nodata {
138 padding: 4px 4px 4px 30px;
144 padding: 4px 4px 4px 30px;
@@ -28,4 +28,17 class QueryTest < Test::Unit::TestCase
28 assert_equal 1, issues.length
28 assert_equal 1, issues.length
29 assert_equal Issue.find(3), issues.first
29 assert_equal Issue.find(3), issues.first
30 end
30 end
31
32 def test_default_columns
33 q = Query.new
34 assert !q.columns.empty?
35 end
36
37 def test_set_column_names
38 q = Query.new
39 q.column_names = ['tracker', :subject, '', 'unknonw_column']
40 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
41 c = q.columns.first
42 assert q.has_column?(c)
43 end
31 end
44 end
General Comments 0
You need to be logged in to leave comments. Login now