Commit 8fc94a17 authored by jplang's avatar jplang

Ticket grouping (#2679).

git-svn-id: https://svn.redmine.org/redmine/trunk@2696 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent 1cccd521
...@@ -58,16 +58,27 @@ class IssuesController < ApplicationController ...@@ -58,16 +58,27 @@ class IssuesController < ApplicationController
end end
@issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
@issue_pages = Paginator.new self, @issue_count, limit, params['page'] @issue_pages = Paginator.new self, @issue_count, limit, params['page']
@issues = Issue.find :all, :order => sort_clause, @issues = Issue.find :all, :order => [@query.group_by_sort_order, sort_clause].compact.join(','),
:include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ], :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
:conditions => @query.statement, :conditions => @query.statement,
:limit => limit, :limit => limit,
:offset => @issue_pages.current.offset :offset => @issue_pages.current.offset
respond_to do |format| respond_to do |format|
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } format.html {
if @query.grouped?
# Retrieve the issue count by group
@issue_count_by_group = begin
Issue.count(:group => @query.group_by, :include => [:status, :project], :conditions => @query.statement)
# Rails will raise an (unexpected) error if there's only a nil group value
rescue ActiveRecord::RecordNotFound
{nil => @issue_count}
end
end
render :template => 'issues/index.rhtml', :layout => !request.xhr?
}
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') } format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
end end
else else
# Send html if the query is not valid # Send html if the query is not valid
...@@ -483,10 +494,11 @@ private ...@@ -483,10 +494,11 @@ private
@query.add_short_filter(field, params[field]) if params[field] @query.add_short_filter(field, params[field]) if params[field]
end end
end end
session[:query] = {:project_id => @query.project_id, :filters => @query.filters} @query.group_by = params[:group_by]
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by}
else else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id] @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters]) @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by])
@query.project = @project @query.project = @project
end end
end end
......
...@@ -30,6 +30,7 @@ class QueriesController < ApplicationController ...@@ -30,6 +30,7 @@ class QueriesController < ApplicationController
params[:fields].each do |field| params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field]) @query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields] end if params[:fields]
@query.group_by ||= params[:group_by]
if request.post? && params[:confirm] && @query.save if request.post? && params[:confirm] && @query.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
......
...@@ -16,12 +16,13 @@ ...@@ -16,12 +16,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class QueryColumn class QueryColumn
attr_accessor :name, :sortable, :default_order attr_accessor :name, :sortable, :groupable, :default_order
include Redmine::I18n include Redmine::I18n
def initialize(name, options={}) def initialize(name, options={})
self.name = name self.name = name
self.sortable = options[:sortable] self.sortable = options[:sortable]
self.groupable = options[:groupable] || false
self.default_order = options[:default_order] self.default_order = options[:default_order]
end end
...@@ -98,20 +99,20 @@ class Query < ActiveRecord::Base ...@@ -98,20 +99,20 @@ class Query < ActiveRecord::Base
cattr_reader :operators_by_filter_type cattr_reader :operators_by_filter_type
@@available_columns = [ @@available_columns = [
QueryColumn.new(:project, :sortable => "#{Project.table_name}.name"), QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"), QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"), QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'), QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc', :groupable => true),
QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
QueryColumn.new(:author), QueryColumn.new(:author),
QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname"]), QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"), QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc'), QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"), QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
] ]
cattr_reader :available_columns cattr_reader :available_columns
...@@ -241,6 +242,11 @@ class Query < ActiveRecord::Base ...@@ -241,6 +242,11 @@ class Query < ActiveRecord::Base
).collect {|cf| QueryCustomFieldColumn.new(cf) } ).collect {|cf| QueryCustomFieldColumn.new(cf) }
end end
# Returns an array of columns that can be used to group the results
def groupable_columns
available_columns.select {|c| c.groupable}
end
def columns def columns
if has_default_columns? if has_default_columns?
available_columns.select do |c| available_columns.select do |c|
...@@ -288,6 +294,24 @@ class Query < ActiveRecord::Base ...@@ -288,6 +294,24 @@ class Query < ActiveRecord::Base
sort_criteria && sort_criteria[arg] && sort_criteria[arg].last sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
end end
# Returns the SQL sort order that should be prepended for grouping
def group_by_sort_order
if grouped? && (column = group_by_column)
column.sortable.is_a?(Array) ?
column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
"#{column.sortable} #{column.default_order}"
end
end
# Returns true if the query is a grouped query
def grouped?
!group_by.blank?
end
def group_by_column
groupable_columns.detect {|c| c.name.to_s == group_by}
end
def project_statement def project_statement
project_clauses = [] project_clauses = []
if project && !@project.descendants.active.empty? if project && !@project.descendants.active.empty?
......
...@@ -9,8 +9,18 @@ ...@@ -9,8 +9,18 @@
<%= column_header(column) %> <%= column_header(column) %>
<% end %> <% end %>
</tr></thead> </tr></thead>
<% group = false %>
<tbody> <tbody>
<% issues.each do |issue| -%> <% issues.each do |issue| -%>
<% if @query.grouped? && issue.send(@query.group_by) != group %>
<% group = issue.send(@query.group_by) %>
<% reset_cycle %>
<tr class="group">
<td colspan="<%= query.columns.size + 2 %>">
<%= group.blank? ? 'None' : group %> <span class="count">(<%= @issue_count_by_group[group] %>)</span>
</td>
</tr>
<% end %>
<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>"> <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>">
<td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td> <td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td> <td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
......
...@@ -4,9 +4,15 @@ ...@@ -4,9 +4,15 @@
<% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %> <% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %>
<%= hidden_field_tag('project_id', @project.to_param) if @project %> <%= hidden_field_tag('project_id', @project.to_param) if @project %>
<div id="query_form_content">
<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend> <fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</fieldset>
<p><%= l(:field_group_by) %>
<%= select_tag('group_by', options_for_select([[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, @query.group_by)) %></p>
</div>
<p class="buttons"> <p class="buttons">
<%= link_to_remote l(:button_apply), <%= link_to_remote l(:button_apply),
{ :url => { :set_filter => 1 }, { :url => { :set_filter => 1 },
:update => "content", :update => "content",
...@@ -23,7 +29,6 @@ ...@@ -23,7 +29,6 @@
<%= link_to l(:button_save), {}, :onclick => "$('query_form').submit(); return false;", :class => 'icon icon-save' %> <%= link_to l(:button_save), {}, :onclick => "$('query_form').submit(); return false;", :class => 'icon icon-save' %>
<% end %> <% end %>
</p> </p>
</fieldset>
<% end %> <% end %>
<% else %> <% else %>
<div class="contextual"> <div class="contextual">
...@@ -36,6 +41,7 @@ ...@@ -36,6 +41,7 @@
<div id="query_form"></div> <div id="query_form"></div>
<% html_title @query.name %> <% html_title @query.name %>
<% end %> <% end %>
<%= error_messages_for 'query' %> <%= error_messages_for 'query' %>
<% if @query.valid? %> <% if @query.valid? %>
<% if @issues.empty? %> <% if @issues.empty? %>
......
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
<p><label for="query_default_columns"><%=l(:label_default_columns)%></label> <p><label for="query_default_columns"><%=l(:label_default_columns)%></label>
<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns',
:onclick => 'if (this.checked) {Element.hide("columns")} else {Element.show("columns")}' %></p> :onclick => 'if (this.checked) {Element.hide("columns")} else {Element.show("columns")}' %></p>
<p><label for="query_group_by"><%= l(:field_group_by) %></label>
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p>
</div> </div>
<fieldset><legend><%= l(:label_filter_plural) %></legend> <fieldset><legend><%= l(:label_filter_plural) %></legend>
......
...@@ -789,3 +789,4 @@ bg: ...@@ -789,3 +789,4 @@ bg:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -822,3 +822,4 @@ bs: ...@@ -822,3 +822,4 @@ bs:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -792,3 +792,4 @@ ca: ...@@ -792,3 +792,4 @@ ca:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -795,3 +795,4 @@ cs: ...@@ -795,3 +795,4 @@ cs:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -822,3 +822,4 @@ da: ...@@ -822,3 +822,4 @@ da:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -821,3 +821,4 @@ de: ...@@ -821,3 +821,4 @@ de:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -242,6 +242,7 @@ en: ...@@ -242,6 +242,7 @@ en:
field_watcher: Watcher field_watcher: Watcher
field_identity_url: OpenID URL field_identity_url: OpenID URL
field_content: Content field_content: Content
field_group_by: Group results by
setting_app_title: Application title setting_app_title: Application title
setting_app_subtitle: Application subtitle setting_app_subtitle: Application subtitle
......
...@@ -842,3 +842,4 @@ es: ...@@ -842,3 +842,4 @@ es:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -832,3 +832,4 @@ fi: ...@@ -832,3 +832,4 @@ fi:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -274,6 +274,7 @@ fr: ...@@ -274,6 +274,7 @@ fr:
field_watcher: Observateur field_watcher: Observateur
field_identity_url: URL OpenID field_identity_url: URL OpenID
field_content: Contenu field_content: Contenu
field_group_by: Grouper par
setting_app_title: Titre de l'application setting_app_title: Titre de l'application
setting_app_subtitle: Sous-titre de l'application setting_app_subtitle: Sous-titre de l'application
......
...@@ -821,3 +821,4 @@ gl: ...@@ -821,3 +821,4 @@ gl:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -804,3 +804,4 @@ he: ...@@ -804,3 +804,4 @@ he:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -827,3 +827,4 @@ ...@@ -827,3 +827,4 @@
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -807,3 +807,4 @@ it: ...@@ -807,3 +807,4 @@ it:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -820,3 +820,4 @@ ja: ...@@ -820,3 +820,4 @@ ja:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -851,3 +851,4 @@ ko: ...@@ -851,3 +851,4 @@ ko:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -832,3 +832,4 @@ lt: ...@@ -832,3 +832,4 @@ lt:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -777,3 +777,4 @@ nl: ...@@ -777,3 +777,4 @@ nl:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -794,3 +794,4 @@ ...@@ -794,3 +794,4 @@
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -825,3 +825,4 @@ pl: ...@@ -825,3 +825,4 @@ pl:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -827,3 +827,4 @@ pt-BR: ...@@ -827,3 +827,4 @@ pt-BR:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -813,3 +813,4 @@ pt: ...@@ -813,3 +813,4 @@ pt:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -792,3 +792,4 @@ ro: ...@@ -792,3 +792,4 @@ ro:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -919,3 +919,4 @@ ru: ...@@ -919,3 +919,4 @@ ru:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -793,3 +793,4 @@ sk: ...@@ -793,3 +793,4 @@ sk:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -791,3 +791,4 @@ sl: ...@@ -791,3 +791,4 @@ sl:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -815,3 +815,4 @@ ...@@ -815,3 +815,4 @@
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -849,3 +849,4 @@ sv: ...@@ -849,3 +849,4 @@ sv:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -792,3 +792,4 @@ th: ...@@ -792,3 +792,4 @@ th:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -828,3 +828,4 @@ tr: ...@@ -828,3 +828,4 @@ tr:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -791,3 +791,4 @@ uk: ...@@ -791,3 +791,4 @@ uk:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -861,3 +861,4 @@ vi: ...@@ -861,3 +861,4 @@ vi:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -899,3 +899,4 @@ ...@@ -899,3 +899,4 @@
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
...@@ -824,3 +824,4 @@ zh: ...@@ -824,3 +824,4 @@ zh:
text_wiki_page_nullify_children: Keep child pages as root pages text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length setting_password_min_length: Minimum password length
field_group_by: Group results by
class AddQueriesGroupBy < ActiveRecord::Migration
def self.up
add_column :queries, :group_by, :string
end
def self.down
remove_column :queries, :group_by
end
end
...@@ -108,7 +108,7 @@ module Redmine ...@@ -108,7 +108,7 @@ module Redmine
end end