@@ -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 | 20 | def operators_for_select(filter_type) |
|
4 | 21 | Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} |
|
5 | 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 | 45 | end |
@@ -37,6 +37,8 class Enumeration < ActiveRecord::Base | |||
|
37 | 37 | OPTIONS[self.opt] |
|
38 | 38 | end |
|
39 | 39 | |
|
40 | def to_s; name end | |
|
41 | ||
|
40 | 42 | private |
|
41 | 43 | def check_integrity |
|
42 | 44 | case self.opt |
@@ -34,4 +34,6 class IssueCategory < ActiveRecord::Base | |||
|
34 | 34 | end |
|
35 | 35 | destroy_without_reassign |
|
36 | 36 | end |
|
37 | ||
|
38 | def to_s; name end | |
|
37 | 39 | end |
@@ -51,7 +51,9 class IssueStatus < ActiveRecord::Base | |||
|
51 | 51 | :conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker |
|
52 | 52 | new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : [] |
|
53 | 53 | end |
|
54 | ||
|
54 | ||
|
55 | def to_s; name end | |
|
56 | ||
|
55 | 57 | private |
|
56 | 58 | def check_integrity |
|
57 | 59 | raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id]) |
@@ -15,10 +15,23 | |||
|
15 | 15 | # along with this program; if not, write to the Free Software |
|
16 | 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 | 30 | class Query < ActiveRecord::Base |
|
19 | 31 | belongs_to :project |
|
20 | 32 | belongs_to :user |
|
21 | 33 | serialize :filters |
|
34 | serialize :column_names | |
|
22 | 35 | |
|
23 | 36 | attr_protected :project, :user |
|
24 | 37 | attr_accessor :executed_by |
@@ -59,6 +72,22 class Query < ActiveRecord::Base | |||
|
59 | 72 | |
|
60 | 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 | 91 | def initialize(attributes = nil) |
|
63 | 92 | super attributes |
|
64 | 93 | self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } |
@@ -173,7 +202,30 class Query < ActiveRecord::Base | |||
|
173 | 202 | label = @available_filters[field][:name] if @available_filters.has_key?(field) |
|
174 | 203 | label ||= field.gsub(/\_id$/, "") |
|
175 | 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 | 229 | def statement |
|
178 | 230 | # project/subprojects clause |
|
179 | 231 | clause = '' |
@@ -27,6 +27,8 class Tracker < ActiveRecord::Base | |||
|
27 | 27 | validates_length_of :name, :maximum => 30 |
|
28 | 28 | validates_format_of :name, :with => /^[\w\s\'\-]*$/i |
|
29 | 29 | |
|
30 | def to_s; name end | |
|
31 | ||
|
30 | 32 | private |
|
31 | 33 | def check_integrity |
|
32 | 34 | raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id]) |
@@ -23,32 +23,8 | |||
|
23 | 23 | <p><i><%= l(:label_no_data) %></i></p> |
|
24 | 24 | <% else %> |
|
25 | 25 | |
|
26 | <table class="list"> | |
|
27 | <thead><tr> | |
|
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> | |
|
26 | <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %> | |
|
27 | ||
|
52 | 28 | <p><%= pagination_links_full @issue_pages %> |
|
53 | 29 | [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p> |
|
54 | 30 | <% end %> |
@@ -45,32 +45,7 | |||
|
45 | 45 | <% else %> |
|
46 | 46 | |
|
47 | 47 | <% form_tag({:controller => 'projects', :action => 'move_issues', :id => @project}, :id => 'issues_form' ) do %> |
|
48 | <table class="list"> | |
|
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> | |
|
48 | <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %> | |
|
74 | 49 | <div class="contextual"> |
|
75 | 50 | <%= l(:label_export_to) %> |
|
76 | 51 | <%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'icon icon-csv' %>, |
@@ -9,6 +9,13 | |||
|
9 | 9 | <p><label for="query_is_public"><%=l(:field_is_public)%></label> |
|
10 | 10 | <%= check_box 'query', 'is_public' %></p> |
|
11 | 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 | 19 | </div> |
|
13 | 20 | |
|
14 | 21 | <%= render :partial => 'queries/filters', :locals => {:query => query}%> |
@@ -511,3 +511,4 enumeration_doc_categories: Категории документи | |||
|
511 | 511 | enumeration_activities: Дейности (time tracking) |
|
512 | 512 | label_file_plural: Files |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -511,3 +511,4 text_issue_category_destroy_assignments: Remove category assignments | |||
|
511 | 511 | label_added_time_by: Added by %s %s ago |
|
512 | 512 | field_estimated_hours: Estimated time |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -511,3 +511,4 enumeration_doc_categories: Dokumentenkategorien | |||
|
511 | 511 | enumeration_activities: Aktivitäten (Zeiterfassung) |
|
512 | 512 | label_file_plural: Files |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -159,6 +159,7 field_delay: Delay | |||
|
159 | 159 | field_assignable: Issues can be assigned to this role |
|
160 | 160 | field_redirect_existing_links: Redirect existing links |
|
161 | 161 | field_estimated_hours: Estimated time |
|
162 | field_column_names: Columns | |
|
162 | 163 | |
|
163 | 164 | setting_app_title: Application title |
|
164 | 165 | setting_app_subtitle: Application subtitle |
@@ -511,3 +511,4 enumeration_doc_categories: Categorías del documento | |||
|
511 | 511 | enumeration_activities: Activities (time tracking) |
|
512 | 512 | label_file_plural: Files |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -159,6 +159,7 field_delay: Retard | |||
|
159 | 159 | field_assignable: Demandes assignables à ce rôle |
|
160 | 160 | field_redirect_existing_links: Rediriger les liens existants |
|
161 | 161 | field_estimated_hours: Temps estimé |
|
162 | field_column_names: Colonnes | |
|
162 | 163 | |
|
163 | 164 | setting_app_title: Titre de l'application |
|
164 | 165 | setting_app_subtitle: Sous-titre de l'application |
@@ -511,3 +511,4 enumeration_doc_categories: Categorie di documenti | |||
|
511 | 511 | enumeration_activities: Attività (time tracking) |
|
512 | 512 | label_file_plural: Files |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -512,3 +512,4 enumeration_doc_categories: 文書カテゴリ | |||
|
512 | 512 | enumeration_activities: 作業分類 (時間トラッキング) |
|
513 | 513 | label_file_plural: Files |
|
514 | 514 | label_changeset_plural: Changesets |
|
515 | field_column_names: Columns |
@@ -512,3 +512,4 enumeration_activities: Activiteiten (tijd tracking) | |||
|
512 | 512 | text_comma_separated: Multiple values allowed (comma separated). |
|
513 | 513 | label_file_plural: Files |
|
514 | 514 | label_changeset_plural: Changesets |
|
515 | field_column_names: Columns |
@@ -511,3 +511,4 label_added_time_by: Dodane przez %s %s temu | |||
|
511 | 511 | field_estimated_hours: Szacowany czas |
|
512 | 512 | label_file_plural: Pliki |
|
513 | 513 | label_changeset_plural: Zestawienia zmian |
|
514 | field_column_names: Columns |
@@ -511,3 +511,4 enumeration_doc_categories: Categorias de documento | |||
|
511 | 511 | enumeration_activities: Atividades (time tracking) |
|
512 | 512 | label_file_plural: Files |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -511,3 +511,4 enumeration_doc_categories: Categorias de documento | |||
|
511 | 511 | enumeration_activities: Atividades (time tracking) |
|
512 | 512 | label_file_plural: Files |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -511,3 +511,4 label_index_by_date: Index by date | |||
|
511 | 511 | label_index_by_title: Index by title |
|
512 | 512 | label_file_plural: Files |
|
513 | 513 | label_changeset_plural: Changesets |
|
514 | field_column_names: Columns |
@@ -512,3 +512,4 enumeration_activities: Aktiviteter (tidsspårning) | |||
|
512 | 512 | field_comments: Comment |
|
513 | 513 | label_file_plural: Files |
|
514 | 514 | label_changeset_plural: Changesets |
|
515 | field_column_names: Columns |
@@ -514,3 +514,4 enumeration_activities: Activities (time tracking) | |||
|
514 | 514 | label_wiki_page: Wiki page |
|
515 | 515 | label_file_plural: Files |
|
516 | 516 | label_changeset_plural: Changesets |
|
517 | field_column_names: Columns |
@@ -133,6 +133,12 margin*/ | |||
|
133 | 133 | |
|
134 | 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 | 142 | /***** Flash & error messages ****/ |
|
137 | 143 | #flash div, #errorExplanation, .nodata { |
|
138 | 144 | padding: 4px 4px 4px 30px; |
@@ -28,4 +28,17 class QueryTest < Test::Unit::TestCase | |||
|
28 | 28 | assert_equal 1, issues.length |
|
29 | 29 | assert_equal Issue.find(3), issues.first |
|
30 | 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 | 44 | end |
General Comments 0
You need to be logged in to leave comments.
Login now