summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Provaznik <jprovaznik@gitlab.com>2019-09-04 10:56:19 +0200
committerJan Provaznik <jprovaznik@gitlab.com>2019-09-10 20:29:57 +0200
commitc2fb226900dd3bfdb91d3c2b7c4a4f4625de636c (patch)
treeb874e6d85548ce7c2c8b915f630a1481a3bcaacd
parent797848c0c8ab7fafd0712f52aee33053b34ae551 (diff)
downloadgitlab-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.rb13
-rw-r--r--app/models/event.rb13
-rw-r--r--app/models/event_collection.rb68
-rw-r--r--app/views/shared/_event_filter.html.haml1
-rw-r--r--lib/event_filter.rb11
-rw-r--r--lib/gitlab/database.rb4
-rw-r--r--spec/controllers/groups_controller_spec.rb15
-rw-r--r--spec/features/projects/features_visibility_spec.rb2
-rw-r--r--spec/lib/event_filter_spec.rb6
-rw-r--r--spec/lib/gitlab/database_spec.rb14
-rw-r--r--spec/models/event_collection_spec.rb83
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