]> source.dussan.org Git - redmine.git/commitdiff
Adds the date of the last activity to the list of available columns for Projects...
authorMarius Balteanu <marius.balteanu@zitec.com>
Fri, 3 May 2024 13:14:18 +0000 (13:14 +0000)
committerMarius Balteanu <marius.balteanu@zitec.com>
Fri, 3 May 2024 13:14:18 +0000 (13:14 +0000)
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-8f06a7374b81

app/controllers/admin_controller.rb
app/controllers/projects_controller.rb
app/models/project.rb
app/models/project_query.rb
config/locales/en.yml
config/locales/pt-BR.yml
lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb
lib/redmine/activity/fetcher.rb
test/functional/projects_controller_test.rb
test/unit/project_test.rb

index 7fcb6ac5d5dab01482b33f88e8306864dce5d55b..7e36d52939c4f6ae024128d2a8e4af0e744f75b0 100644 (file)
@@ -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
index 66d54db38f1e5e7ac5df85a617eb4cedeebed1af..91616d619bbb755ae9d3b736965893da9bb1baac 100644 (file)
@@ -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
index f9000829b9ca26348a647806ad12db4104a252cd..abac2a1f0d2aa1db41ffb97cabfbe12a64d1d238 100644 (file)
@@ -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
index 113287ed80659543b1a3a7d425adebaaf45743e5..dde3e9cc1f3b43c28b30e0de3a46c2215d79c84f 100644 (file)
@@ -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
index 3ae06f745f82b9ac5ead8cc241f701f35b1c7b2a..c842e4d7b9baa09aced10228cc24d311d1e0a6ae 100644 (file)
@@ -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
index 93d5fad01428bd774a5a30fdcab3d4207d3c50ce..0a422ce86a72cdc8e03522b7d39d7e0ff4d05be5 100644 (file)
@@ -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
index 5954d501dbd3eb583697a3619c082db43a10b1f3..ac334c19a5038972a2cc7a3e903559f1b0bd7ca3 100644 (file)
@@ -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
index cbd20425d2e1898df07e616a89f3278808136aae..4264c2e5d772cb2212a87e697c0779eafa51a8ed 100644 (file)
@@ -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
index a2356185bfd1be5f3a258ae5344602e57ffb7d91..d1de3b631cab04e0ffd2ddc111cfaa1217f2da9f 100644 (file)
@@ -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
index eacbf0c92e9b3a098d481e91700e8b4d53df097f..a7047d71c0f2afb59560e572be280368e59bed71 100644 (file)
@@ -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