diff --git a/app/controllers/dashboard/application_controller.rb b/app/controllers/dashboard/application_controller.rb index 80c65948fff14348c7d161f1b52310433e8bbd77..d035a67d9b5335ddf5603c865f5030ce99cfdcf1 100644 --- a/app/controllers/dashboard/application_controller.rb +++ b/app/controllers/dashboard/application_controller.rb @@ -11,7 +11,7 @@ class Dashboard::ApplicationController < ApplicationController private def projects - @projects ||= current_user.authorized_projects.sorted_by_updated_desc.non_archived + @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived end end diff --git a/app/models/project.rb b/app/models/project.rb index 7f1c3cd475c2f31f5e02a6974870d435978900ab..5e17fe6bc4f091eba920f43b00b6297c67f9000e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -107,6 +107,9 @@ class Project < ApplicationRecord snippets: gitlab_config_features.snippets }.freeze + # List of attributes that, when updated, should be considered as Project Activity + PROJECT_ACTIVITY_ATTRIBUTES = %w[description name].freeze + cache_markdown_field :description, pipeline: :description attribute :packages_enabled, default: true @@ -132,6 +135,7 @@ class Project < ApplicationRecord before_validation :ensure_project_namespace_in_sync before_validation :set_package_registry_access_level, if: :packages_enabled_changed? before_validation :remove_leading_spaces_on_name + before_validation :set_last_activity_at after_validation :check_pending_delete before_save :ensure_runners_token @@ -630,8 +634,9 @@ def self.integration_association_name(name) .or(arel_table[:storage_version].eq(nil))) end - scope :sorted_by_updated_asc, -> { reorder(self.arel_table['updated_at'].asc) } - scope :sorted_by_updated_desc, -> { reorder(self.arel_table['updated_at'].desc) } + scope :sorted_by_activity, -> { reorder(self.arel_table['last_activity_at'].desc) } + scope :sorted_by_updated_asc, -> { reorder(self.arel_table['last_activity_at'].asc) } + scope :sorted_by_updated_desc, -> { reorder(self.arel_table['last_activity_at'].desc) } scope :sorted_by_stars_desc, -> { reorder(self.arel_table['star_count'].desc) } scope :sorted_by_stars_asc, -> { reorder(self.arel_table['star_count'].asc) } scope :sorted_by_path_asc, -> { reorder(self.arel_table['path'].asc) } @@ -1676,10 +1681,6 @@ def last_activity last_event end - def last_activity_date - updated_at - end - def project_id self.id end @@ -3357,7 +3358,7 @@ def repository_with_same_path_already_exists? end def set_timestamps_for_create - update_columns(last_activity_at: self.created_at, last_repository_updated_at: self.created_at) + update_columns(last_repository_updated_at: self.created_at) end def cross_namespace_reference?(from) @@ -3501,6 +3502,16 @@ def remove_leading_spaces_on_name name&.lstrip! end + def set_last_activity_at + return if last_activity_at_changed? + + if new_record? || (changed & PROJECT_ACTIVITY_ATTRIBUTES).any? + self.last_activity_at = Time.current + elsif last_activity_at.nil? + self.last_activity_at = created_at + end + end + def set_package_registry_access_level return if !project_feature || project_feature.package_registry_access_level_changed? diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 5bb15c2f5ca56f4123ecdc17db11e5a4fb095cb9..5e60e66bf844523220afdfe89e5988000ec4faf8 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -9,7 +9,7 @@ - compact_mode = false unless local_assigns[:compact_mode] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project) - css_class = "gl-sm-display-flex gl-align-items-center gl-vertical-align-middle!" if project.description.blank? && !show_last_commit_as_description -- updated_tooltip = time_ago_with_tooltip(project.last_activity_date) +- updated_tooltip = time_ago_with_tooltip(project.last_activity_at || project.updated_at) - show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project) - last_pipeline = last_pipeline_from_status_cache(project) if show_pipeline_status_icon - css_controls_class = "with-pipeline-status" if show_pipeline_status_icon && last_pipeline.present? diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb index 68ae1ca218bf41d2a0096497fc72917ac00bfa53..273b96526958571a14ca6c47cd5a46ca3b54745f 100644 --- a/spec/controllers/explore/projects_controller_spec.rb +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -75,9 +75,9 @@ end context 'projects aimed for deletion' do - let(:project1) { create(:project, :public, updated_at: 3.days.ago) } - let(:project2) { create(:project, :public, updated_at: 1.day.ago) } - let(:aimed_for_deletion_project) { create(:project, :public, :archived, updated_at: 2.days.ago, marked_for_deletion_at: 2.days.ago) } + let_it_be(:project1) { create(:project, :public, path: 'project-1') } + let_it_be(:project2) { create(:project, :public, path: 'project-2') } + let_it_be(:aimed_for_deletion_project) { create(:project, :public, :archived, marked_for_deletion_at: 2.days.ago) } before do create(:trending_project, project: project1) diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index e9b55ab2900d77c9fa5aa35ff87e653cb15d6deb..bd878c5e3ab2cde9b78d4bc5dfb46e37c36b4866 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -54,15 +54,7 @@ end end - context 'when last_repository_updated_at, last_activity_at and update_at are present' do - it 'shows the last_repository_updated_at attribute as the update date' do - project.update!(last_repository_updated_at: Time.zone.now, last_activity_at: 1.hour.ago) - - visit dashboard_projects_path - - expect(page).to have_xpath("//time[@datetime='#{project.last_repository_updated_at.getutc.iso8601}']") - end - + context 'when last_activity_at and update_at are present' do it 'shows the last_activity_at attribute as the update date' do project.update!(last_repository_updated_at: 1.hour.ago, last_activity_at: Time.zone.now) @@ -72,9 +64,9 @@ end end - context 'when last_repository_updated_at and last_activity_at are missing' do + context 'when last_activity_at is missing' do it 'shows the updated_at attribute as the update date' do - project.update!(last_repository_updated_at: nil, last_activity_at: nil) + project.update!(last_activity_at: nil) project.touch visit dashboard_projects_path diff --git a/spec/finders/namespaces/projects_finder_spec.rb b/spec/finders/namespaces/projects_finder_spec.rb index 10d8145d15ac2be3b31c1f90bf9d530479f7c5f8..64fb45940d3515f26d4ab060a4de57ba448c83fb 100644 --- a/spec/finders/namespaces/projects_finder_spec.rb +++ b/spec/finders/namespaces/projects_finder_spec.rb @@ -144,6 +144,7 @@ let(:params) { { sort: :latest_activity_desc } } before do + project_7.update!(last_activity_at: 20.minutes.ago) project_6.update!(last_activity_at: 15.minutes.ago) project_2.update!(last_activity_at: 10.minutes.ago) project_1.update!(last_activity_at: 5.minutes.ago) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 6eb69bf958a6e41dffe934227f7407c6e620082c..fa7468655197fef03bf9f52685989296df17ad1a 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -31,15 +31,22 @@ describe 'after_create :set_last_repository_updated_at' do context 'with a push event' do - it 'updates the project last_repository_updated_at and updated_at' do - project.touch(:last_repository_updated_at, time: 1.year.ago) + it 'updates the project last_repository_updated_at' do + project.update!(last_repository_updated_at: 1.year.ago) event = create_push_event(project, project.first_owner) project.reload expect(project.last_repository_updated_at).to be_like_time(event.created_at) - expect(project.updated_at).to be_like_time(event.created_at) + end + + it 'calls the reset_project_activity method' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:reset_project_activity) + end + + create_push_event(project, project.first_owner) end end @@ -916,14 +923,13 @@ def visible_to_all_except(*roles) end it 'updates the project' do - project.touch(:last_activity_at, time: 1.year.ago) + project.update!(last_activity_at: 1.year.ago) event = create_push_event(project, project.first_owner) project.reload expect(project.last_activity_at).to be_like_time(event.created_at) - expect(project.updated_at).to be_like_time(event.created_at) end it "deletes the redis key for if the project was inactive" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3d5ccbc6feb177950e1f15481bb2a3b4c4078608..651c01a036080056de774b9f20c1ab32498d1728 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -612,6 +612,53 @@ end end end + + context 'when last_activity_at is being set' do + let(:last_activity_at) { 1.day.ago } + let(:project) { build(:project, last_activity_at: last_activity_at) } + + it 'will use supplied timestamp' do + expect { project.valid? }.not_to change(project, :last_activity_at) + end + end + + context 'when last_activity_at is not being set' do + context 'and one of PROJECT_ACTIVITY_ATTRIBUTES is updated' do + let(:project) { build(:project) } + + before do + project.name = "Name Changed" + end + + it 'sets last_activity_at to the current time' do + freeze_time do + expect { project.valid? }.to change(project, :last_activity_at).to(Time.current) + end + end + end + + context 'and the record is new' do + let(:project) { build(:project) } + + it 'sets last_activity_at to the current time' do + freeze_time do + expect { project.valid? }.to change(project, :last_activity_at).from(nil).to(Time.current) + end + end + end + + context 'and the last_activity_at is nil' do + let_it_be(:project) { create(:project) } + + before do + project.update_column(:last_activity_at, nil) + end + + it 'sets last_activity_at to created_at' do + expect { project.valid? }.to change(project, :last_activity_at).from(nil).to(project.created_at) + end + end + end end describe 'validation' do @@ -1603,12 +1650,6 @@ expect(project.last_activity).to eq(last_event) end end - - describe 'last_activity_date' do - it 'returns the project\'s last update date' do - expect(project.last_activity_date).to be_like_time(project.updated_at) - end - end end describe '#get_issue' do @@ -2091,9 +2132,9 @@ def has_external_wiki end describe '.sort_by_attribute' do - let_it_be(:project1) { create(:project, star_count: 2, updated_at: 1.minute.ago) } + let_it_be(:project1) { create(:project, star_count: 2, last_activity_at: 1.minute.ago) } let_it_be(:project2) { create(:project, star_count: 1) } - let_it_be(:project3) { create(:project, updated_at: 2.minutes.ago) } + let_it_be(:project3) { create(:project, last_activity_at: 2.minutes.ago) } it 'reorders the input relation by start count desc' do projects = described_class.sort_by_attribute(:stars_desc)