Project

General

Profile

Feature #23954 » 0001-Adds-last-activity-column-to-projects-list.patch

Marius BĂLTEANU, 2020-04-05 12:17

View differences:

app/controllers/projects_controller.rb
48 48
    end
49 49

  
50 50
    retrieve_project_query
51
    scope = project_scope
52 51

  
53 52
    respond_to do |format|
54 53
      format.html {
55 54
        # TODO: see what to do with the board view and pagination
56 55
        if @query.display_type == 'board'
57
          @entries = scope.to_a
56
          @entries = project_scope.to_a
58 57
        else
59
          @entry_count = scope.count
58
          @entry_count = @query.result_count
60 59
          @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
61
          @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
60
          @entries = project_scope(:offset => @entry_pages.offset, :limit => @entry_pages.per_page).to_a
62 61
        end
63 62
      }
64 63
      format.api  {
65 64
        @offset, @limit = api_offset_and_limit
66
        @project_count = scope.count
67
        @projects = scope.offset(@offset).limit(@limit).to_a
65
        @project_count = @query.result_count
66
        @projects = project_scope(:offset => @offset, :limit => @limit)
68 67
      }
69 68
      format.atom {
70
        projects = scope.reorder(:created_on => :desc).limit(Setting.feeds_limit.to_i).to_a
69
        projects = project_scope(:order => {:created_on => :desc}, :limit => Setting.feeds_limit.to_i).to_a
71 70
        render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
72 71
      }
73 72
      format.csv {
74 73
        # Export all entries
75
        @entries = scope.to_a
74
        @entries = project_scope.to_a
76 75
        send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'projects.csv')
77 76
      }
78 77
    end
app/models/project.rb
348 348
    @due_date = nil
349 349
    @override_members = nil
350 350
    @assignable_users = nil
351
    @last_activity_date = nil
351 352
    base_reload(*args)
352 353
  end
353 354

  
......
904 905
    end
905 906
  end
906 907

  
908
  def last_activity_date
909
    @last_activity_date || fetch_last_activity_date
910
  end
911

  
912
  # Preloads last activity date for a collection of projects
913
  def self.load_last_activity_date(projects, user=User.current)
914
    if projects.any?
915
      last_activities = Redmine::Activity::Fetcher.new(User.current).events(nil, nil, :last_by_project => true).to_h
916
      projects.each do |project|
917
        project.instance_variable_set("@last_activity_date", last_activities[project.id])
918
      end
919
    end
920
  end
921

  
907 922
  private
908 923

  
909 924
  def update_inherited_members
......
1179 1194
    end
1180 1195
    update_attribute :status, STATUS_ARCHIVED
1181 1196
  end
1197

  
1198
  def fetch_last_activity_date
1199
    latest_activities = Redmine::Activity::Fetcher.new(User.current, :project => self).events(nil, nil, :last_by_project => true)
1200
    latest_activities.empty? ? nil : latest_activities.to_h[self.id]
1201
  end
1182 1202
end
app/models/project_query.rb
34 34
    QueryColumn.new(:identifier, :sortable => "#{Project.table_name}.identifier"),
35 35
    QueryColumn.new(:parent_id, :sortable => "#{Project.table_name}.lft ASC", :default_order => 'desc', :caption => :field_parent),
36 36
    QueryColumn.new(:is_public, :sortable => "#{Project.table_name}.is_public", :groupable => true),
37
    QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc')
37
    QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc'),
38
    QueryColumn.new(:last_activity_date)
38 39
  ]
39 40

  
40 41
  def initialize(attributes=nil, *args)
......
94 95
    Project.visible.where(statement)
95 96
  end
96 97

  
98
  # Returns the project count
99
  def result_count
100
    base_scope.count
101
  rescue ::ActiveRecord::StatementInvalid => e
102
    raise StatementInvalid, e.message
103
  end
104

  
97 105
  def results_scope(options={})
98 106
    order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?)
99 107

  
......
110 118
      scope = scope.preload(:parent)
111 119
    end
112 120

  
113
    scope
121
    projects = scope.to_a
122
    if has_column?(:last_activity_date)
123
      Project.load_last_activity_date(scope)
124
    end
125
    projects
114 126
  end
115 127
end
config/locales/en.yml
273 273
  field_downloads: Downloads
274 274
  field_author: Author
275 275
  field_created_on: Created
276
  field_last_activity_date: Last activity
276 277
  field_updated_on: Updated
277 278
  field_closed_on: Closed
278 279
  field_field_format: Format
config/locales/pt-BR.yml
228 228
  field_downloads: Downloads
229 229
  field_author: Autor
230 230
  field_created_on: Criado em
231
  field_last_activity_date: Última atividade
231 232
  field_updated_on: Alterado em
232 233
  field_field_format: Formato
233 234
  field_is_for_all: Para todos os projetos
lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb
57 57

  
58 58
            scope = (provider_options[:scope] || self)
59 59

  
60
            if from && to
61
              scope = scope.where("#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to)
62
            end
60
            scope = scope.where("#{provider_options[:timestamp]} >= ?", from) if from
61
            scope = scope.where("#{provider_options[:timestamp]} <= ?", to) if to
63 62

  
64 63
            if options[:author]
65 64
              return [] if provider_options[:author_key].nil?
......
80 79
              scope = scope.where(Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym, options))
81 80
            end
82 81

  
82
            if options[:last_by_project]
83
              scope = scope.group("#{Project.table_name}.id").maximum(provider_options[:timestamp])
84
            end
85

  
83 86
            scope.to_a
84 87
          end
85 88
        end
lib/redmine/activity/fetcher.rb
87 87
      def events(from = nil, to = nil, options={})
88 88
        e = []
89 89
        @options[:limit] = options[:limit]
90
        @options[:last_by_project] = options[:last_by_project] if options[:last_by_project]
90 91

  
91 92
        @scope.each do |event_type|
92 93
          constantized_providers(event_type).each do |provider|
......
94 95
          end
95 96
        end
96 97

  
97
        e.sort! {|a,b| b.event_datetime <=> a.event_datetime}
98
        if options[:last_by_project]
99
          e.sort!
100
        else
101
          e.sort! {|a,b| b.event_datetime <=> a.event_datetime}
98 102

  
99
        if options[:limit]
100
          e = e.slice(0, options[:limit])
103
          if options[:limit]
104
            e = e.slice(0, options[:limit])
105
          end
101 106
        end
102 107
        e
103 108
      end
test/functional/projects_controller_test.rb
248 248
    assert_select ".total-for-cf-#{field.id} span.value", :text => '9'
249 249
  end
250 250

  
251
  def test_index_with_last_activity_date_column
252
    with_settings :project_list_defaults => {'column_names' => %w(name short_description last_activity_date)} do
253
      get :index, :params => {
254
        :display_type => 'list'
255
      }
256
      assert_response :success
257
    end
258

  
259
    assert_equal ['Name', 'Description', 'Last activity'], columns_in_list
260

  
261
    assert_select 'tr#project-1 td.last_activity_date', :text => format_time(Journal.find(3).created_on)
262
    assert_select 'tr#project-4 td.last_activity_date', :text => ''
263
  end
264

  
251 265
  def test_autocomplete_js
252 266
    get(
253 267
      :autocomplete,
test/unit/project_test.rb
1081 1081
    assert_equal 'valuea', project.custom_field_value(cf1)
1082 1082
    assert_nil project.custom_field_value(cf2)
1083 1083
  end
1084

  
1085
  def test_last_activity_date
1086
    # Note with id 3 is the last activity on Project 1
1087
    assert_equal Journal.find(3).created_on, Project.find(1).last_activity_date
1088

  
1089
    # Project without activity should return nil
1090
    assert_equal nil, Project.find(4).last_activity_date
1091
  end
1084 1092
end
(9-9/9)