Patch by Frederico Camara (@fredsdc) and Marius BĂLTEANU (@marius.balteanu). git-svn-id: https://svn.redmine.org/redmine/trunk@22811 e93f8b46-1217-0410-a6f0-8f06a7374b81pull/131/merge
@@ -38,11 +38,10 @@ class AdminController < ApplicationController | |||
def projects | |||
retrieve_query(ProjectQuery, false, :defaults => @default_columns_names) | |||
@query.admin_projects = 1 | |||
scope = @query.results_scope | |||
@entry_count = scope.count | |||
@entry_count = @query.result_count | |||
@entry_pages = Paginator.new @entry_count, per_page_option, params['page'] | |||
@projects = scope.limit(@entry_pages.per_page).offset(@entry_pages.offset).to_a | |||
@projects = @query.results_scope(:limit => @entry_pages.per_page, :offset => @entry_pages.offset).to_a | |||
render :action => "projects", :layout => false if request.xhr? | |||
end |
@@ -53,31 +53,30 @@ class ProjectsController < ApplicationController | |||
retrieve_default_query | |||
retrieve_project_query | |||
scope = project_scope | |||
respond_to do |format| | |||
format.html do | |||
# TODO: see what to do with the board view and pagination | |||
if @query.display_type == 'board' | |||
@entries = scope.to_a | |||
@entries = project_scope.to_a | |||
else | |||
@entry_count = scope.count | |||
@entry_count = @query.result_count | |||
@entry_pages = Paginator.new @entry_count, per_page_option, params['page'] | |||
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a | |||
@entries = project_scope(:offset => @entry_pages.offset, :limit => @entry_pages.per_page).to_a | |||
end | |||
end | |||
format.api do | |||
@offset, @limit = api_offset_and_limit | |||
@project_count = scope.count | |||
@projects = scope.offset(@offset).limit(@limit).to_a | |||
@project_count = @query.result_count | |||
@projects = project_scope(:offset => @offset, :limit => @limit) | |||
end | |||
format.atom do | |||
projects = scope.reorder(:created_on => :desc).limit(Setting.feeds_limit.to_i).to_a | |||
projects = project_scope(:order => {:created_on => :desc}, :limit => Setting.feeds_limit.to_i).to_a | |||
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") | |||
end | |||
format.csv do | |||
# Export all entries | |||
entries = scope.to_a | |||
entries = project_scope.to_a | |||
send_data(query_to_csv(entries, @query, params), :type => 'text/csv; header=present', :filename => 'projects.csv') | |||
end | |||
end |
@@ -379,6 +379,7 @@ class Project < ApplicationRecord | |||
@due_date = nil | |||
@override_members = nil | |||
@assignable_users = nil | |||
@last_activity_date = nil | |||
base_reload(*args) | |||
end | |||
@@ -1005,6 +1006,20 @@ class Project < ApplicationRecord | |||
end | |||
end | |||
def last_activity_date | |||
@last_activity_date || fetch_last_activity_date | |||
end | |||
# Preloads last activity date for a collection of projects | |||
def self.load_last_activity_date(projects, user=User.current) | |||
if projects.any? | |||
last_activities = Redmine::Activity::Fetcher.new(User.current).events(nil, nil, :last_by_project => true).to_h | |||
projects.each do |project| | |||
project.instance_variable_set(:@last_activity_date, last_activities[project.id]) | |||
end | |||
end | |||
end | |||
private | |||
def update_inherited_members | |||
@@ -1312,4 +1327,9 @@ class Project < ApplicationRecord | |||
end | |||
update_attribute :status, STATUS_ARCHIVED | |||
end | |||
def fetch_last_activity_date | |||
latest_activities = Redmine::Activity::Fetcher.new(User.current, :project => self).events(nil, nil, :last_by_project => true) | |||
latest_activities.empty? ? nil : latest_activities.to_h[self.id] | |||
end | |||
end |
@@ -36,7 +36,8 @@ class ProjectQuery < Query | |||
QueryColumn.new(:identifier, :sortable => "#{Project.table_name}.identifier"), | |||
QueryColumn.new(:parent_id, :sortable => "#{Project.table_name}.lft ASC", :default_order => 'desc', :caption => :field_parent), | |||
QueryColumn.new(:is_public, :sortable => "#{Project.table_name}.is_public", :groupable => true), | |||
QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc') | |||
QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc'), | |||
QueryColumn.new(:last_activity_date) | |||
] | |||
def self.default(project: nil, user: User.current) | |||
@@ -140,6 +141,13 @@ class ProjectQuery < Query | |||
end | |||
end | |||
# Returns the project count | |||
def result_count | |||
base_scope.count | |||
rescue ::ActiveRecord::StatementInvalid => e | |||
raise StatementInvalid, e.message | |||
end | |||
def results_scope(options={}) | |||
order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?) | |||
@@ -156,6 +164,11 @@ class ProjectQuery < Query | |||
scope = scope.preload(:parent) | |||
end | |||
scope | |||
projects = scope.to_a | |||
if has_column?(:last_activity_date) | |||
Project.load_last_activity_date(scope) | |||
end | |||
projects | |||
end | |||
end |
@@ -419,6 +419,7 @@ en: | |||
field_default_time_entry_activity: Default spent time activity | |||
field_any_searchable: Any searchable text | |||
field_estimated_remaining_hours: Estimated remaining time | |||
field_last_activity_date: Last activity | |||
setting_app_title: Application title | |||
setting_welcome_text: Welcome text |
@@ -299,6 +299,7 @@ pt-BR: | |||
field_default_value: Padrão | |||
field_comments_sorting: Visualizar comentários | |||
field_parent_title: Página pai | |||
field_last_activity_date: Última atividade | |||
setting_app_title: Título da aplicação | |||
setting_welcome_text: Texto de boas-vindas |
@@ -64,9 +64,8 @@ module Redmine | |||
ActiveSupport::Deprecation.warn "acts_as_activity_provider with implicit :scope option is deprecated. Please pass a scope on the #{self.name} as a proc." | |||
end | |||
if from && to | |||
scope = scope.where("#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to) | |||
end | |||
scope = scope.where("#{provider_options[:timestamp]} >= ?", from) if from | |||
scope = scope.where("#{provider_options[:timestamp]} <= ?", to) if to | |||
if options[:author] | |||
return [] if provider_options[:author_key].nil? | |||
@@ -87,6 +86,10 @@ module Redmine | |||
scope = scope.where(Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym, options)) | |||
end | |||
if options[:last_by_project] | |||
scope = scope.group("#{Project.table_name}.id").maximum(provider_options[:timestamp]) | |||
end | |||
scope.to_a | |||
end | |||
end |
@@ -87,6 +87,7 @@ module Redmine | |||
def events(from = nil, to = nil, options={}) | |||
e = [] | |||
@options[:limit] = options[:limit] | |||
@options[:last_by_project] = options[:last_by_project] if options[:last_by_project] | |||
@scope.each do |event_type| | |||
constantized_providers(event_type).each do |provider| | |||
@@ -94,10 +95,14 @@ module Redmine | |||
end | |||
end | |||
e.sort! {|a, b| b.event_datetime <=> a.event_datetime} | |||
if options[:last_by_project] | |||
e.sort! | |||
else | |||
e.sort! {|a, b| b.event_datetime <=> a.event_datetime} | |||
if options[:limit] | |||
e = e.slice(0, options[:limit]) | |||
if options[:limit] | |||
e = e.slice(0, options[:limit]) | |||
end | |||
end | |||
e | |||
end |
@@ -252,6 +252,18 @@ class ProjectsControllerTest < Redmine::ControllerTest | |||
assert_select ".total-for-cf-#{field.id} span.value", :text => '9' | |||
end | |||
def test_index_with_last_activity_date_column | |||
with_settings :project_list_defaults => {'column_names' => %w(name short_description last_activity_date)} do | |||
get :index, :params => { | |||
:display_type => 'list' | |||
} | |||
assert_response :success | |||
end | |||
assert_equal ['Name', 'Description', 'Last activity'], columns_in_list | |||
assert_select 'tr#project-1 td.last_activity_date', :text => format_time(Journal.find(3).created_on) | |||
assert_select 'tr#project-4 td.last_activity_date', :text => '' | |||
end | |||
def test_index_should_retrieve_default_query | |||
query = ProjectQuery.find(11) | |||
ProjectQuery.stubs(:default).returns query |
@@ -1161,4 +1161,11 @@ class ProjectTest < ActiveSupport::TestCase | |||
r = Project.like('eco_k') | |||
assert_include project, r | |||
end | |||
def test_last_activity_date | |||
# Note with id 3 is the last activity on Project 1 | |||
assert_equal Journal.find(3).created_on, Project.find(1).last_activity_date | |||
# Project without activity should return nil | |||
assert_nil Project.find(4).last_activity_date | |||
end | |||
end |