diff options
author | Jan Provaznik <jprovaznik@gitlab.com> | 2019-09-04 10:56:19 +0200 |
---|---|---|
committer | Jan Provaznik <jprovaznik@gitlab.com> | 2019-09-10 20:29:57 +0200 |
commit | c2fb226900dd3bfdb91d3c2b7c4a4f4625de636c (patch) | |
tree | b874e6d85548ce7c2c8b915f630a1481a3bcaacd | |
parent | 797848c0c8ab7fafd0712f52aee33053b34ae551 (diff) | |
download | gitlab-ce-epic-events-list.tar.gz |
Update group activity page to show group eventsepic-events-list
* group events are now inclueded in the 'All' tab and a new 'Epic
events' tab added to this page
* group events are not showed on other activity pages (e.g. user) yet
* this MR also removes old "non-join_lateral" query for getting
events because this was used only for postgres <9.3 and we now
require at least 9.6
-rw-r--r-- | app/controllers/groups_controller.rb | 13 | ||||
-rw-r--r-- | app/models/event.rb | 13 | ||||
-rw-r--r-- | app/models/event_collection.rb | 68 | ||||
-rw-r--r-- | app/views/shared/_event_filter.html.haml | 1 | ||||
-rw-r--r-- | lib/event_filter.rb | 11 | ||||
-rw-r--r-- | lib/gitlab/database.rb | 4 | ||||
-rw-r--r-- | spec/controllers/groups_controller_spec.rb | 15 | ||||
-rw-r--r-- | spec/features/projects/features_visibility_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/event_filter_spec.rb | 6 | ||||
-rw-r--r-- | spec/lib/gitlab/database_spec.rb | 14 | ||||
-rw-r--r-- | spec/models/event_collection_spec.rb | 83 |
11 files changed, 128 insertions, 102 deletions
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 886d1f99d69..d8a3fea2c2e 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -198,15 +198,14 @@ class GroupsController < Groups::ApplicationController def load_events params[:sort] ||= 'latest_activity_desc' - options = {} - options[:include_subgroups] = true - - @projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user) - .execute - .includes(:namespace) + options = { include_subgroups: true } + projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user) + .execute + .includes(:namespace) + groups = @group.self_and_descendants.public_or_visible_to_user(current_user) @events = EventCollection - .new(@projects, offset: params[:offset].to_i, filter: event_filter) + .new(projects, offset: params[:offset].to_i, filter: event_filter, groups: groups) .to_a Events::RenderService diff --git a/app/models/event.rb b/app/models/event.rb index 580bb770599..3cdfaaf6c40 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -77,15 +77,6 @@ class Event < ApplicationRecord scope :recent, -> { reorder(id: :desc) } scope :code_push, -> { where(action: PUSHED) } - scope :in_projects, -> (projects) do - sub_query = projects - .except(:order) - .select(1) - .where('projects.id = events.project_id') - - where('EXISTS (?)', sub_query).recent - end - scope :with_associations, -> do # We're using preload for "push_event_payload" as otherwise the association # is not always available (depending on the query being built). @@ -135,6 +126,10 @@ class Event < ApplicationRecord def target_types TARGET_TYPES.keys end + + def has_group_events? + false + end end def present diff --git a/app/models/event_collection.rb b/app/models/event_collection.rb index a4c69b11781..418ff091d3a 100644 --- a/app/models/event_collection.rb +++ b/app/models/event_collection.rb @@ -6,6 +6,8 @@ # in a controller), it's not suitable for building queries that are used for # building other queries. class EventCollection + include Gitlab::Utils::StrongMemoize + # To prevent users from putting too much pressure on the database by cycling # through thousands of events we put a limit on the number of pages. MAX_PAGE = 10 @@ -13,59 +15,61 @@ class EventCollection # projects - An ActiveRecord::Relation object that returns the projects for # which to retrieve events. # filter - An EventFilter instance to use for filtering events. - def initialize(projects, limit: 20, offset: 0, filter: nil) + def initialize(projects, limit: 20, offset: 0, filter: nil, groups: nil) @projects = projects @limit = limit @offset = offset @filter = filter + @groups = groups end # Returns an Array containing the events. def to_a return [] if current_page > MAX_PAGE - relation = if Gitlab::Database.join_lateral_supported? - relation_with_join_lateral - else - relation_without_join_lateral - end - - relation.with_associations.to_a + relation_with_join_lateral.with_associations.to_a end private - # Returns the events relation to use when JOIN LATERAL is not supported. - # - # This relation simply gets all the events for all authorized projects, then - # limits that set. - def relation_without_join_lateral - events = filtered_events.in_projects(projects) - - paginate_events(events) - end - - # Returns the events relation to use when JOIN LATERAL is supported. - # # This relation is built using JOIN LATERAL, producing faster queries than a # regular LIMIT + OFFSET approach. def relation_with_join_lateral - projects_for_lateral = projects.select(:id).to_sql - - lateral = filtered_events - .limit(limit_for_join_lateral) - .where('events.project_id = projects_for_lateral.id') - .to_sql - # The outer query does not need to re-apply the filters since the JOIN # LATERAL body already takes care of this. outer = base_relation - .from("(#{projects_for_lateral}) projects_for_lateral") - .joins("JOIN LATERAL (#{lateral}) AS #{Event.table_name} ON true") + .from("(#{parents_for_lateral}) parents_for_lateral") + .joins("JOIN LATERAL (#{join_lateral}) AS #{Event.table_name} ON true") paginate_events(outer) end + def join_lateral + condition = if groups.present? + 'events.project_id = parents_for_lateral.project_id OR events.group_id = parents_for_lateral.group_id' + else + 'events.project_id = parents_for_lateral.project_id' + end + + filtered_events + .limit(limit_for_join_lateral) + .where(condition) + .to_sql + end + + def parents_for_lateral + if groups.present? + members = [ + groups.select('NULL as project_id, id as group_id'), + projects.select('id as project_id, NULL as group_id') + ] + + Gitlab::SQL::Union.new(members).to_sql # rubocop: disable Gitlab/Union + else + projects.select('id as project_id').to_sql + end + end + def filtered_events @filter ? @filter.apply_filter(base_relation) : base_relation end @@ -97,4 +101,10 @@ class EventCollection def projects @projects.except(:order) end + + def groups + strong_memoize(:groups) do + groups.except(:order) if @groups && Event.has_group_events? + end + end end diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 6612497e7e2..bec452e3cbb 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -9,6 +9,7 @@ = event_filter_link EventFilter::MERGED, _('Merge events'), s_('EventFilterBy|Filter by merge events') - if event_filter_visible(:issues) = event_filter_link EventFilter::ISSUE, _('Issue events'), s_('EventFilterBy|Filter by issue events') + = render_if_exists 'events/epics_filter' - if comments_visible? = event_filter_link EventFilter::COMMENTS, _('Comments'), s_('EventFilterBy|Filter by comments') = event_filter_link EventFilter::TEAM, _('Team'), s_('EventFilterBy|Filter by team') diff --git a/lib/event_filter.rb b/lib/event_filter.rb index 85bf9c14f26..e062e3ddb1c 100644 --- a/lib/event_filter.rb +++ b/lib/event_filter.rb @@ -9,12 +9,11 @@ class EventFilter ISSUE = 'issue' COMMENTS = 'comments' TEAM = 'team' - FILTERS = [ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM].freeze def initialize(filter) # Split using comma to maintain backward compatibility Ex/ "filter1,filter2" filter = filter.to_s.split(',')[0].to_s - @filter = FILTERS.include?(filter) ? filter : ALL + @filter = filters.include?(filter) ? filter : ALL end def active?(key) @@ -39,4 +38,12 @@ class EventFilter end end # rubocop: enable CodeReuse/ActiveRecord + + private + + def filters + [ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM] + end end + +EventFilter.prepend_if_ee('EE::EventFilter') diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 6ecd506d55b..f55ee3e1ccf 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -87,10 +87,6 @@ module Gitlab version.to_f < 10 end - def self.join_lateral_supported? - version.to_f >= 9.3 - end - def self.replication_slots_supported? version.to_f >= 9.4 end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 404e61c5271..74ca55af3b2 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -125,7 +125,7 @@ describe GroupsController do end context 'as json' do - it 'includes all projects from groups and subgroups in event feed' do + it 'includes events from all projects in group and subgroups' do 2.times do project = create(:project, group: group) create(:event, project: project) @@ -140,6 +140,19 @@ describe GroupsController do expect(json_response['count']).to eq(3) expect(assigns(:projects).limit_value).to be_nil end + + it 'includes events from group and subgroups' do + subgroup = create(:group, parent: group) + subgroup2 = create(:group, parent: subgroup) + create(:event, project: nil, group: group) + create(:event, project: nil, group: subgroup) + create(:event, project: nil, group: subgroup2) + + get :activity, params: { id: group.to_param }, format: :json + + expect(response).to have_gitlab_http_status(200) + expect(json_response['count']).to eq(3) + end end end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index ca383da5f5c..6a15f7fa959 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -211,7 +211,7 @@ describe 'Edit Project Settings' do visit activity_project_path(project) page.within(".event-filter") do - expect(page).to have_selector("a", count: 2) + expect(page).to have_content("All") expect(page).not_to have_content("Push events") expect(page).not_to have_content("Merge events") expect(page).not_to have_content("Comments") diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb index d6eca8d85ff..21b8f726425 100644 --- a/spec/lib/event_filter_spec.rb +++ b/spec/lib/event_filter_spec.rb @@ -3,12 +3,6 @@ require 'spec_helper' describe EventFilter do - describe 'FILTERS' do - it 'returns a definite list of filters' do - expect(described_class::FILTERS).to eq(%w[all push merged issue comments team]) - end - end - describe '#filter' do it 'returns "all" if given filter is nil' do expect(described_class.new(nil).filter).to eq(described_class::ALL) diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 8d37de32179..15fb1503529 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -101,20 +101,6 @@ describe Gitlab::Database do end end - describe '.join_lateral_supported?' do - it 'returns false when using PostgreSQL 9.2' do - allow(described_class).to receive(:version).and_return('9.2.1') - - expect(described_class.join_lateral_supported?).to eq(false) - end - - it 'returns true when using PostgreSQL 9.3.0 or newer' do - allow(described_class).to receive(:version).and_return('9.3.0') - - expect(described_class.join_lateral_supported?).to eq(true) - end - end - describe '.replication_slots_supported?' do it 'returns false when using PostgreSQL 9.3' do allow(described_class).to receive(:version).and_return('9.3.1') diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb index efe511042c3..c421ffa000d 100644 --- a/spec/models/event_collection_spec.rb +++ b/spec/models/event_collection_spec.rb @@ -4,50 +4,75 @@ require 'spec_helper' describe EventCollection do describe '#to_a' do - let(:project) { create(:project_empty_repo) } - let(:projects) { Project.where(id: project.id) } - let(:user) { create(:user) } + set(:group) { create(:group) } + set(:project) { create(:project_empty_repo, group: group) } + set(:projects) { Project.where(id: project.id) } + set(:user) { create(:user) } - before do - 20.times do - event = create(:push_event, project: project, author: user) + context 'with project events' do + before do + 20.times do + event = create(:push_event, project: project, author: user) - create(:push_event_payload, event: event) + create(:push_event_payload, event: event) + end + + create(:closed_issue_event, project: project, author: user) end - create(:closed_issue_event, project: project, author: user) - end + it 'returns an Array of events' do + events = described_class.new(projects).to_a - it 'returns an Array of events' do - events = described_class.new(projects).to_a + expect(events).to be_an_instance_of(Array) + end - expect(events).to be_an_instance_of(Array) - end + it 'applies a limit to the number of events' do + events = described_class.new(projects).to_a - it 'applies a limit to the number of events' do - events = described_class.new(projects).to_a + expect(events.length).to eq(20) + end - expect(events.length).to eq(20) - end + it 'can paginate through events' do + events = described_class.new(projects, offset: 20).to_a - it 'can paginate through events' do - events = described_class.new(projects, offset: 20).to_a + expect(events.length).to eq(1) + end - expect(events.length).to eq(1) - end + it 'returns an empty Array when crossing the maximum page number' do + events = described_class.new(projects, limit: 1, offset: 15).to_a - it 'returns an empty Array when crossing the maximum page number' do - events = described_class.new(projects, limit: 1, offset: 15).to_a + expect(events).to be_empty + end + + it 'allows filtering of events using an EventFilter' do + filter = EventFilter.new(EventFilter::ISSUE) + events = described_class.new(projects, filter: filter).to_a - expect(events).to be_empty + expect(events.length).to eq(1) + expect(events[0].action).to eq(Event::CLOSED) + end end - it 'allows filtering of events using an EventFilter' do - filter = EventFilter.new(EventFilter::ISSUE) - events = described_class.new(projects, filter: filter).to_a + context 'with group events' do + let(:groups) { group.self_and_descendants.public_or_visible_to_user(user) } + let(:subject) { described_class.new(projects, groups: groups).to_a } + + it 'includes also group events' do + subgroup = create(:group, parent: group) + event1 = create(:event, project: project, author: user) + event2 = create(:event, project: nil, group: group, author: user) + event3 = create(:event, project: nil, group: subgroup, author: user) - expect(events.length).to eq(1) - expect(events[0].action).to eq(Event::CLOSED) + expect(subject).to eq([event3, event2, event1]) + end + + it 'does not include events from inaccessible groups' do + subgroup = create(:group, :private, parent: group) + event1 = create(:event, project: nil, group: group, author: user) + create(:event, project: nil, group: subgroup, author: user) + + expect(subject).to eq([event1]) + end end end end |