Commit acbf9f56 authored by Alex Kalderimis's avatar Alex Kalderimis Committed by Imre Farkas

Add view support for design activity

This adds support in views for design activity, specifically ensuring
that we can render design events as part of the event stream. Most
changes are in the event helpers, but we also add filter support.
parent a30e935f
......@@ -329,13 +329,6 @@ class ApplicationController < ActionController::Base
end
end
def event_filter
@event_filter ||=
EventFilter.new(params[:event_filter].presence || cookies[:event_filter]).tap do |new_event_filter|
cookies[:event_filter] = new_event_filter.filter
end
end
# JSON for infinite scroll via Pager object
def pager_json(partial, count, locals = {})
html = render_to_string(
......
# frozen_string_literal: true
module FiltersEvents
def event_filter
@event_filter ||= new_event_filter.tap { |ef| cookies[:event_filter] = ef.filter }
end
private
def new_event_filter
active_filter = params[:event_filter].presence || cookies[:event_filter]
EventFilter.new(active_filter)
end
end
......@@ -6,6 +6,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
include OnboardingExperimentHelper
include SortingHelper
include SortingPreference
include FiltersEvents
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
......
......@@ -2,6 +2,7 @@
class DashboardController < Dashboard::ApplicationController
include IssuableCollectionsAction
include FiltersEvents
prepend_before_action(only: [:issues]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
......
......@@ -7,6 +7,7 @@ class GroupsController < Groups::ApplicationController
include PreviewMarkdown
include RecordUserLastActivity
include SendFileUpload
include FiltersEvents
extend ::Gitlab::Utils::Override
respond_to :html
......
......@@ -8,6 +8,7 @@ class ProjectsController < Projects::ApplicationController
include SendFileUpload
include RecordUserLastActivity
include ImportUrlParams
include FiltersEvents
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
......
......@@ -46,7 +46,7 @@ class UserRecentEventsFinder
SQL
# Workaround for https://github.com/rails/rails/issues/24193
Event.from([Arel.sql(sql)])
ensure_design_visibility(Event.from([Arel.sql(sql)]))
end
# rubocop: enable CodeReuse/ActiveRecord
......@@ -59,4 +59,11 @@ class UserRecentEventsFinder
def projects
target_user.project_interactions.to_sql
end
# TODO: remove when the :design_activity_events feature flag is removed.
def ensure_design_visibility(events)
return events if Feature.enabled?(:design_activity_events)
events.not_design
end
end
......@@ -29,7 +29,11 @@ module EventsHelper
def event_action_name(event)
target = if event.target_type
if event.note?
if event.design? || event.design_note?
'design'
elsif event.wiki_page?
'wiki page'
elsif event.note?
event.note_target_type
else
event.target_type.titleize.downcase
......@@ -58,11 +62,30 @@ module EventsHelper
end
def event_filter_visible(feature_key)
return designs_visible? if feature_key == :designs
return true unless @project
@project.feature_available?(feature_key, current_user)
end
def designs_visible?
return false unless Feature.enabled?(:design_activity_events)
if @project
design_activity_enabled?(@project)
elsif @group
design_activity_enabled?(@group)
elsif @projects
@projects.with_namespace.include_project_feature.any? { |p| design_activity_enabled?(p) }
else
true
end
end
def design_activity_enabled?(project)
Ability.allowed?(current_user, :read_design_activity, project)
end
def comments_visible?
event_filter_visible(:repository) ||
event_filter_visible(:merge_requests) ||
......@@ -94,6 +117,12 @@ module EventsHelper
elsif event.milestone?
words << "##{event.target_iid}" if event.target_iid
words << "in"
elsif event.design?
words << event.design.to_reference
words << "in"
elsif event.wiki_page?
words << event.target_title
words << "in"
elsif event.target
prefix =
if event.merge_request?
......@@ -187,6 +216,15 @@ module EventsHelper
end
end
def event_design_title_html(event)
capture do
concat content_tag(:span, _('design'), class: "event-target-type append-right-4")
concat link_to(event.design.reference_link_text, design_url(event.design),
title: event.target_title,
class: 'has-tooltip event-design event-target-link append-right-4')
end
end
def event_wiki_page_target_url(event)
project_wiki_url(event.project, event.target&.canonical_slug || Wiki::HOMEPAGE)
end
......@@ -214,6 +252,18 @@ module EventsHelper
sprite_icon(icon_name, size: size) if icon_name
end
DESIGN_ICONS = {
'created' => 'upload',
'updated' => 'pencil',
'destroyed' => ICON_NAMES_BY_EVENT_TYPE['destroyed'],
'archived' => 'archive'
}.freeze
def design_event_icon(action, size: 24)
icon_name = DESIGN_ICONS[action]
sprite_icon(icon_name, size: size) if icon_name
end
def icon_for_profile_event(event)
if current_path?('users#show')
content_tag :div, class: "system-note-image #{event.action_name.parameterize}-icon" do
......@@ -229,6 +279,8 @@ module EventsHelper
def inline_event_icon(event)
unless current_path?('users#show')
content_tag :span, class: "system-note-image-inline d-none d-sm-flex append-right-4 #{event.action_name.parameterize}-icon align-self-center" do
next design_event_icon(event.action, size: 14) if event.design?
icon_for_event(event.action_name, size: 14)
end
end
......@@ -244,7 +296,7 @@ module EventsHelper
private
def design_url(design, opts)
def design_url(design, opts = {})
designs_project_issue_url(
design.project,
design.issue,
......
......@@ -45,9 +45,10 @@ class EventCollection
private
def apply_feature_flags(events)
return events if ::Feature.enabled?(:wiki_events)
events = events.not_wiki_page unless ::Feature.enabled?(:wiki_events)
events = events.not_design unless ::Feature.enabled?(:design_activity_events)
events.not_wiki_page
events
end
def project_events
......
......@@ -455,6 +455,7 @@ class Project < ApplicationRecord
scope :with_statistics, -> { includes(:statistics) }
scope :with_namespace, -> { includes(:namespace) }
scope :with_import_state, -> { includes(:import_state) }
scope :include_project_feature, -> { includes(:project_feature) }
scope :with_service, ->(service) { joins(service).eager_load(service) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :with_container_registry, -> { where(container_registry_enabled: true) }
......
......@@ -3,11 +3,11 @@
module FindGroupProjects
extend ActiveSupport::Concern
def group_projects_for(user:, group:)
def group_projects_for(user:, group:, only_owned: true)
GroupProjectsFinder.new(
group: group,
current_user: user,
options: { include_subgroups: true, only_owned: true }
options: { include_subgroups: true, only_owned: only_owned }
).execute
end
end
......@@ -42,6 +42,14 @@ class GroupPolicy < BasePolicy
@subject.subgroup_creation_level == ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS
end
condition(:design_management_enabled) do
group_projects_for(user: @user, group: @subject, only_owned: false).any? { |p| p.design_management_enabled? }
end
rule { design_management_enabled }.policy do
enable :read_design_activity
end
rule { public_group }.policy do
enable :read_group
enable :read_package
......@@ -70,6 +78,10 @@ class GroupPolicy < BasePolicy
enable :read_board
end
rule { ~can?(:read_group) }.policy do
prevent :read_design_activity
end
rule { has_access }.enable :read_namespace
rule { developer }.policy do
......
......@@ -545,11 +545,13 @@ class ProjectPolicy < BasePolicy
rule { can?(:read_issue) }.policy do
enable :read_design
enable :read_design_activity
end
# Design abilities could also be prevented in the issue policy.
rule { design_management_disabled }.policy do
prevent :read_design
prevent :read_design_activity
prevent :create_design
prevent :destroy_design
end
......
......@@ -97,23 +97,16 @@ class EventCreateService
end
def save_designs(current_user, create: [], update: [])
created = create.group_by(&:project).flat_map do |project, designs|
Feature.enabled?(:design_activity_events, project) ? designs : []
end.to_set
updated = update.group_by(&:project).flat_map do |project, designs|
Feature.enabled?(:design_activity_events, project) ? designs : []
end.to_set
return [] if created.empty? && updated.empty?
return [] unless Feature.enabled?(:design_activity_events)
records = created.zip([:created].cycle) + updated.zip([:updated].cycle)
records = create.zip([:created].cycle) + update.zip([:updated].cycle)
return [] if records.empty?
create_record_events(records, current_user)
end
def destroy_designs(designs, current_user)
designs = designs.select do |design|
Feature.enabled?(:design_activity_events, design.project)
end
return [] unless Feature.enabled?(:design_activity_events)
return [] unless designs.present?
create_record_events(designs.zip([:destroyed].cycle), current_user)
......
......@@ -7,6 +7,8 @@
- if event.wiki_page?
= render "events/event/wiki", event: event
- elsif event.design?
= render 'events/event/design', event: event
- elsif event.created_project_action?
= render "events/event/created_project", event: event
- elsif event.push_action?
......
= icon_for_profile_event(event)
= event_user_info(event)
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
%span.event-type.d-inline-block.append-right-4{ class: event.action_name }
= event.action_name
= event_design_title_html(event)
= render "events/event_scope", event: event
......@@ -17,4 +17,6 @@
= event_filter_link EventFilter::COMMENTS, _('Comments'), s_('EventFilterBy|Filter by comments')
- if Feature.enabled?(:wiki_events) && (@project.nil? || @project.has_wiki?)
= event_filter_link EventFilter::WIKI, _('Wiki'), s_('EventFilterBy|Filter by wiki')
- if event_filter_visible(:designs)
= event_filter_link EventFilter::DESIGNS, _('Designs'), s_('EventFilterBy|Filter by designs')
= event_filter_link EventFilter::TEAM, _('Team'), s_('EventFilterBy|Filter by team')
---
title: Add design activity in event streams
merge_request: 33534
author:
type: added
......@@ -242,3 +242,35 @@ To disable it:
```ruby
Feature.disable(:design_management_reference_filter_gfm_pipeline)
```
## Design activity records
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33051) in GitLab 13.1
> - It's deployed behind a feature flag, disabled by default.
> - It's enabled on GitLab.com.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-design-events-core-only). **(CORE ONLY)**
User activity events on designs (creation, deletion, and updates) are tracked by GitLab and
displayed on the [user profile](../../profile/index.md#user-profile),
[group](../../group/index.md#view-group-activity),
and [project](../index.md#project-activity) activity pages.
### Enable or disable Design Events **(CORE ONLY)**
User activity for designs is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session)
can enable it for your instance. You're welcome to test it, but use it at your
own risk.
To enable it:
```ruby
Feature.enable(:design_activity_events)
```
To disable it:
```ruby
Feature.disable(:design_activity_events)
```
# frozen_string_literal: true
class EventFilter
include Gitlab::Utils::StrongMemoize
attr_accessor :filter
ALL = 'all'
......@@ -10,6 +12,7 @@ class EventFilter
COMMENTS = 'comments'
TEAM = 'team'
WIKI = 'wiki'
DESIGNS = 'designs'
def initialize(filter)
# Split using comma to maintain backward compatibility Ex/ "filter1,filter2"
......@@ -38,6 +41,8 @@ class EventFilter
events.where(action: [:created, :updated, :closed, :reopened], target_type: 'Issue')
when WIKI
wiki_events(events)
when DESIGNS
design_events(events)
else
events
end
......@@ -47,7 +52,8 @@ class EventFilter
private
def apply_feature_flags(events)
return events.not_wiki_page unless Feature.enabled?(:wiki_events)
events = events.not_wiki_page unless Feature.enabled?(:wiki_events)
events = events.not_design unless can_view_design_activity?
events
end
......@@ -58,8 +64,18 @@ class EventFilter
events.for_wiki_page
end
def design_events(events)
return events.for_design if can_view_design_activity?
events
end
def filters
[ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM, WIKI]
[ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM, WIKI, DESIGNS]
end
def can_view_design_activity?
Feature.enabled?(:design_activity_events)
end
end
......
......@@ -9104,6 +9104,9 @@ msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
msgid "EventFilterBy|Filter by designs"
msgstr ""
msgid "EventFilterBy|Filter by epic events"
msgstr ""
......
......@@ -5,13 +5,14 @@ require 'spec_helper'
RSpec.describe Dashboard::ProjectsController do
include ExternalAuthorizationServiceHelpers
let_it_be(:user) { create(:user) }
describe '#index' do
context 'user not logged in' do
it_behaves_like 'authenticates sessionless user', :index, :atom
end
context 'user logged in' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:project2) { create(:project) }
......@@ -71,8 +72,6 @@ RSpec.describe Dashboard::ProjectsController do
context 'json requests' do
render_views
let(:user) { create(:user) }
before do
sign_in(user)
end
......@@ -114,16 +113,14 @@ RSpec.describe Dashboard::ProjectsController do
end
context 'atom requests' do
let(:user) { create(:user) }
before do
sign_in(user)
end
describe '#index' do
context 'project pagination' do
let(:projects) { create_list(:project, 2, creator: user) }
let_it_be(:projects) { create_list(:project, 2, creator: user) }
context 'project pagination' do
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
......@@ -138,6 +135,37 @@ RSpec.describe Dashboard::ProjectsController do
expect(assigns(:events).count).to eq(2)
end
end
describe 'rendering' do
include DesignManagementTestHelpers
render_views
let(:project) { projects.first }
let!(:design_event) { create(:design_event, project: project) }
let!(:wiki_page_event) { create(:wiki_page_event, project: project) }
let!(:issue_event) { create(:closed_issue_event, project: project) }
let(:design) { design_event.design }
let(:wiki_page) { wiki_page_event.wiki_page }
let(:issue) { issue_event.issue }
before do
enable_design_management
project.add_developer(user)
end
it 'renders all kinds of event without error', :aggregate_failures do
get :index, format: :atom
expect(assigns(:events)).to include(design_event, wiki_page_event, issue_event)
expect(response).to render_template('dashboard/projects/index')
expect(response.body).to include(
"uploaded design #{design.to_reference}",
"created wiki page #{wiki_page.title}",
"joined project #{project.full_name}",
"closed issue #{issue.to_reference}"
)
end
end
end
end
end
......@@ -24,15 +24,20 @@ RSpec.describe DashboardController do
end
describe "GET activity as JSON" do
include DesignManagementTestHelpers
render_views
let(:user) { create(:user) }
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
let(:other_project) { create(:project, :public) }
before do
enable_design_management
create(:event, :created, project: project, target: create(:issue))
create(:wiki_page_event, :created, project: project)
create(:wiki_page_event, :updated, project: project)
create(:design_event, project: project)
create(:design_event, author: user, project: other_project)
sign_in(user)
......@@ -42,12 +47,27 @@ RSpec.describe DashboardController do
context 'when user has permission to see the event' do
before do
project.add_developer(user)
other_project.add_developer(user)
end
it 'returns count' do
get :activity, params: { format: :json }
expect(json_response['count']).to eq(3)
expect(json_response['count']).to eq(6)
end
describe 'design_activity_events feature flag' do
context 'it is off' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'excludes design activity' do
get :activity, params: { format: :json }
expect(json_response['count']).to eq(4)
end
end
end
end
......
......@@ -1138,6 +1138,48 @@ RSpec.describe GroupsController do
it_behaves_like 'disabled when using an external authorization service'
end
describe "GET #activity as JSON" do
include DesignManagementTestHelpers
render_views
let(:project) { create(:project, :public, group: group) }
let(:other_project) { create(:project, :public, group: group) }
def get_activity
get :activity, params: { format: :json, id: group.to_param }
end
before do
enable_design_management
issue = create(:issue, project: project)
create(:event, :created, project: project, target: issue)
create(:design_event, project: project)
create(:design_event, project: other_project)
sign_in(user)
request.cookies[:event_filter] = 'all'
end
it 'returns count' do
get_activity
expect(json_response['count']).to eq(3)
end
context 'the design_activity_events feature flag is disabled' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'does not include the design activity' do
get_activity
expect(json_response['count']).to eq(1)
end
end
end
describe 'GET #issues' do
subject { get :issues, params: { id: group.to_param } }
......
......@@ -86,11 +86,13 @@ RSpec.describe ProjectsController do
end
describe "GET #activity as JSON" do
include DesignManagementTestHelpers
render_views
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
before do
enable_design_management
create(:event, :created, project: project, target: create(:issue))
sign_in(user)
......@@ -103,11 +105,44 @@ RSpec.describe ProjectsController do
project.add_developer(user)
end
it 'returns count' do
def get_activity(project)
get :activity, params: { namespace_id: project.namespace, id: project, format: :json }
end
it 'returns count' do
get_activity(project)
expect(json_response['count']).to eq(1)
end
context 'design events are visible' do
include DesignManagementTestHelpers
let(:other_project) { create(:project, namespace: user.namespace) }
before do
enable_design_management
create(:design_event, project: project)
request.cookies[:event_filter] = EventFilter::DESIGNS
end
it 'returns correct count' do
get_activity(project)
expect(json_response['count']).to eq(1)
end
context 'the feature flag is disabled' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'returns correct count' do
get_activity(project)
expect(json_response['count']).to eq(0)
end
end
end
end
context 'when user has no permission to see the event' do
......
......@@ -21,7 +21,7 @@ FactoryBot.define do
factory :closed_issue_event do
action { :closed }
target factory: :closed_issue
target { association(:closed_issue, project: project) }
end
factory :wiki_page_event do
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Projects > Activity > User sees design Activity', :js do
include DesignManagementTestHelpers
let_it_be(:uploader) { create(:user) }
let_it_be(:editor) { create(:user) }
let_it_be(:deleter) { create(:user) }
let_it_be(:archiver) { create(:user) }
def design_activity(user, action)
[user.name, user.to_reference, action, 'design'].join(' ')
end
shared_examples 'being able to see design activity' do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, issue: issue) }
before_all do
project.add_developer(user) # implicitly adds a project join event.
common_attrs = { project: project, design: design }
create(:design_event, :created, author: uploader, **common_attrs)
create(:design_event, :updated, author: editor, **common_attrs)
create(:design_event, :destroyed, author: deleter, **common_attrs)
create(:design_event, :archived, author: archiver, **common_attrs)
end
before do
enable_design_management
sign_in(user)
end
it 'shows the design comment action in the activity page' do
visit activity_project_path(project)
expect(page).to have_content('joined project')
expect(page).to have_content(design_activity(uploader, 'uploaded'))
expect(page).to have_content(design_activity(editor, 'revised'))
expect(page).to have_content(design_activity(deleter, 'deleted'))
expect(page).to have_content(design_activity(archiver, 'archived'))
end
it 'allows filtering out the design events', :aggregate_failures do
visit activity_project_path(project, event_filter: EventFilter::ISSUE)
expect(page).not_to have_content(design_activity(uploader, 'uploaded'))
expect(page).not_to have_content(design_activity(editor, 'revised'))
expect(page).not_to have_content(design_activity(deleter, 'deleted'))
expect(page).not_to have_content(design_activity(archiver, 'archived'))
end
it 'allows filtering in the design events', :aggregate_failures do
visit activity_project_path(project, event_filter: EventFilter::DESIGNS)
expect(page).not_to have_content('joined project')
expect(page).to have_content(design_activity(uploader, 'uploaded'))
expect(page).to have_content(design_activity(editor, 'revised'))
expect(page).to have_content(design_activity(deleter, 'deleted'))
expect(page).to have_content(design_activity(archiver, 'archived'))
end
end
context 'the project is public' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:user) { create(:user) }
it_behaves_like 'being able to see design activity'
end
context 'the project is private' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
it_behaves_like 'being able to see design activity'
end
end
......@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe UserRecentEventsFinder do
let(:current_user) { create(:user) }
let(:project_owner) { create(:user) }
let_it_be(:project_owner, reload: true) { create(:user) }
let_it_be(:current_user, reload: true) { create(:user) }
let(:private_project) { create(:project, :private, creator: project_owner) }
let(:internal_project) { create(:project, :internal, creator: project_owner) }
let(:public_project) { create(:project, :public, creator: project_owner) }
......@@ -36,5 +36,30 @@ RSpec.describe UserRecentEventsFinder do
expect(finder.execute).to be_empty
end
describe 'design_activity_events feature flag' do
let_it_be(:event_a) { create(:design_event, author: project_owner) }
let_it_be(:event_b) { create(:design_event, author: project_owner) }
context 'the design_activity_events feature-flag is enabled' do
it 'only includes design events in enabled projects', :aggregate_failures do
events = finder.execute
expect(events).to include(event_a)
expect(events).to include(event_b)
end
end
context 'the design_activity_events feature-flag is disabled' do
it 'excludes design events', :aggregate_failures do
stub_feature_flags(design_activity_events: false)
events = finder.execute
expect(events).not_to include(event_a)
expect(events).not_to include(event_b)
end
end
end
end
end
......@@ -227,4 +227,133 @@ describe EventsHelper do
end
end
end
describe '#event_filter_visible' do
include DesignManagementTestHelpers
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
subject { helper.event_filter_visible(key) }
before do
enable_design_management
project.add_reporter(current_user)
allow(helper).to receive(:current_user).and_return(current_user)
end
def disable_read_design_activity(object)
allow(Ability).to receive(:allowed?)
.with(current_user, :read_design_activity, eq(object))
.and_return(false)
end
context 'for :designs' do
let(:key) { :designs }
context 'there is no relevant instance variable' do
it { is_expected.to be(true) }
end
context 'the feature flag is off' do
before do
stub_feature_flags(design_activity_events: false)
end
it { is_expected.to be(false) }
end
context 'a project has been assigned' do
before do
assign(:project, project)
end
it { is_expected.to be(true) }
context 'the current user cannot read design activity' do
before do
disable_read_design_activity(project)
end
it { is_expected.to be(false) }
end
context 'the feature flag is off' do
before do
stub_feature_flags(design_activity_events: false)
end
it { is_expected.to be(false) }
end
end
context 'projects have been assigned' do
before do
assign(:projects, Project.where(id: project.id))
end
it { is_expected.to be(true) }
context 'the collection is empty' do
before do
assign(:projects, Project.none)
end
it { is_expected.to be(false) }
end
context 'the current user cannot read design activity' do
before do
disable_read_design_activity(project)
end
it { is_expected.to be(false) }
end
context 'the feature flag is off' do
before do
stub_feature_flags(design_activity_events: false)
end
it { is_expected.to be(false) }
end
end
context 'a group has been assigned' do
let_it_be(:group) { create(:group) }
before do
assign(:group, group)
end
context 'there are no projects in the group' do
it { is_expected.to be(false) }
end
context 'the group has at least one project' do
before do
create(:project_group_link, project: project, group: group)
end
it { is_expected.to be(true) }
context 'the current user cannot read design activity' do
before do
disable_read_design_activity(group)
end
it { is_expected.to be(false) }
end
context 'the feature flag is off' do
before do
stub_feature_flags(design_activity_events: false)
end
it { is_expected.to be(false) }
end
end
end
end
end
end
......@@ -30,6 +30,7 @@ describe EventFilter do
let_it_be(:left_event) { create(:event, :left, project: public_project, target: public_project) }
let_it_be(:wiki_page_event) { create(:wiki_page_event) }
let_it_be(:wiki_page_update_event) { create(:wiki_page_event, :updated) }
let_it_be(:design_event) { create(:design_event) }
let(:filtered_events) { described_class.new(filter).apply_filter(Event.all) }
......@@ -91,6 +92,24 @@ describe EventFilter do
end
end
context 'with the "design" filter' do
let(:filter) { described_class::DESIGNS }
it 'returns only design events' do
expect(filtered_events).to contain_exactly(design_event)
end
context 'the :design_activity_events feature is disabled' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'does not return design events' do
expect(filtered_events).to match_array(Event.not_design)
end
end
end
context 'with the "wiki" filter' do
let(:filter) { described_class::WIKI }
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe EventCollection do
include DesignManagementTestHelpers
describe '#to_a' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project_empty_repo, group: group) }
......@@ -10,6 +12,10 @@ describe EventCollection do
let_it_be(:user) { create(:user) }
let_it_be(:merge_request) { create(:merge_request) }
before do
enable_design_management
end
context 'with project events' do
let_it_be(:push_event_payloads) do
Array.new(9) do
......@@ -21,11 +27,13 @@ describe EventCollection do
let_it_be(:merge_request_events) { create_list(:event, 10, :commented, project: project, target: merge_request) }
let_it_be(:closed_issue_event) { create(:closed_issue_event, project: project, author: user) }
let_it_be(:wiki_page_event) { create(:wiki_page_event, project: project) }
let_it_be(:design_event) { create(:design_event, project: project) }
let(:push_events) { push_event_payloads.map(&:event) }
it 'returns an Array of events', :aggregate_failures do
most_recent_20_events = [
wiki_page_event,
design_event,
closed_issue_event,
*push_events,
*merge_request_events
......@@ -54,6 +62,31 @@ describe EventCollection do
end
end
context 'the design_activity_events feature flag is disabled' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'omits the design events when using to_a' do
events = described_class.new(projects).to_a
expect(events).not_to include(design_event)
end
it 'omits the wiki page events when using all_project_events' do
events = described_class.new(projects).all_project_events
expect(events).not_to include(design_event)
end
end
it 'includes the design events' do
collection = described_class.new(projects)
expect(collection.to_a).to include(design_event)
expect(collection.all_project_events).to include(design_event)
end
context 'the wiki_events feature flag is enabled' do
before do
stub_feature_flags(wiki_events: true)
......@@ -81,7 +114,7 @@ describe EventCollection do
it 'can paginate through events' do
events = described_class.new(projects, offset: 20).to_a
expect(events.length).to eq(1)
expect(events.length).to eq(2)
end
it 'returns an empty Array when crossing the maximum page number' do
......
......@@ -661,4 +661,61 @@ describe GroupPolicy do
end
end
end
describe 'design activity' do
let_it_be(:group) { create(:group, :public) }
let(:current_user) { nil }
subject { described_class.new(current_user, group) }
context 'when design management is not available' do
it { is_expected.not_to be_allowed(:read_design_activity) }
context 'even when there are projects in the group' do
before do
create_list(:project_group_link, 2, group: group)
end
it { is_expected.not_to be_allowed(:read_design_activity) }
end
end
context 'when design management is available globally' do
include DesignManagementTestHelpers
before do
enable_design_management
end
context 'the group has no projects' do
it { is_expected.not_to be_allowed(:read_design_activity) }
end
context 'the group has a project' do
let(:project) { create(:project, :public) }
before do
create(:project_group_link, project: project, group: group)
end
it { is_expected.to be_allowed(:read_design_activity) }
context 'which does not have design management enabled' do
before do
project.update(lfs_enabled: false)
end
it { is_expected.not_to be_allowed(:read_design_activity) }
context 'but another project does' do
before do
create(:project_group_link, project: create(:project, :public), group: group)
end
it { is_expected.to be_allowed(:read_design_activity) }
end
end
end
end
end
end
......@@ -855,6 +855,28 @@ describe ProjectPolicy do
end
end
describe 'design permissions' do
subject { described_class.new(guest, project) }
let(:design_permissions) do
%i[read_design_activity read_design]
end
context 'when design management is not available' do
it { is_expected.not_to be_allowed(*design_permissions) }
end
context 'when design management is available' do
include DesignManagementTestHelpers
before do
enable_design_management
end
it { is_expected.to be_allowed(*design_permissions) }
end
end
describe 'read_build_report_results' do
subject { described_class.new(guest, project) }
......
......@@ -275,15 +275,6 @@ describe EventCreateService do
specify { expect { result }.not_to change { Event.count } }
specify { expect { result }.not_to exceed_query_limit(0) }
end
context 'the feature flag is enabled for a single project' do
before do
stub_feature_flags(design_activity_events: project)
end
specify { expect(result).not_to be_empty }
specify { expect { result }.to change { Event.count }.by(1) }
end
end
describe '#save_designs' do
......@@ -310,9 +301,7 @@ describe EventCreateService do
expect(events.map(&:design)).to match_array(updated)
end
it_behaves_like 'feature flag gated multiple event creation' do
let(:project) { created.first.project }
end
it_behaves_like 'feature flag gated multiple event creation'
end
describe '#destroy_designs' do
......@@ -332,9 +321,7 @@ describe EventCreateService do
expect(events.map(&:design)).to match_array(designs)
end
it_behaves_like 'feature flag gated multiple event creation' do
let(:project) { designs.first.project }
end
it_behaves_like 'feature flag gated multiple event creation'
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment