diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml index 487e0e7cd20..5238b1095ee 100644 --- a/.rubocop_todo/style/percent_literal_delimiters.yml +++ b/.rubocop_todo/style/percent_literal_delimiters.yml @@ -1003,7 +1003,6 @@ Style/PercentLiteralDelimiters: - 'spec/models/project_setting_spec.rb' - 'spec/models/project_spec.rb' - 'spec/models/project_team_spec.rb' - - 'spec/models/project_tracing_setting_spec.rb' - 'spec/models/projects/topic_spec.rb' - 'spec/models/prometheus_metric_spec.rb' - 'spec/models/releases/link_spec.rb' diff --git a/.rubocop_todo/style/redundant_self.yml b/.rubocop_todo/style/redundant_self.yml index 1aba23c90ae..9951160ae02 100644 --- a/.rubocop_todo/style/redundant_self.yml +++ b/.rubocop_todo/style/redundant_self.yml @@ -143,7 +143,6 @@ Style/RedundantSelf: - 'app/models/project_group_link.rb' - 'app/models/project_import_data.rb' - 'app/models/project_label.rb' - - 'app/models/project_tracing_setting.rb' - 'app/models/prometheus_alert.rb' - 'app/models/protected_branch.rb' - 'app/models/protected_branch/push_access_level.rb' diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue index 701ef89304c..a45823823f0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue @@ -4,7 +4,7 @@ import StatusIcon from '../mr_widget_status_icon.vue'; export default { i18n: { - approvalNeeded: s__('mrWidget|Merge blocked: this merge request must be approved.'), + approvalNeeded: s__('mrWidget|Merge blocked: all required approvals must be given.'), blockingMergeRequests: s__( 'mrWidget|Merge blocked: you can only merge after the above items are resolved.', ), diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb index 0f7bf893bb2..3e06dbb2e2c 100644 --- a/app/finders/user_recent_events_finder.rb +++ b/app/finders/user_recent_events_finder.rb @@ -46,24 +46,6 @@ class UserRecentEventsFinder .order_created_desc) end - # rubocop: disable CodeReuse/ActiveRecord - def execute_optimized_multi(users) - Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder.new( - scope: Event.reorder(id: :desc), - array_scope: User.select(:id).where(id: users), - # Event model has a default scope { reorder(nil) } - # When a relation is rordered and used as a target when merging scope, - # its order takes a precedence and _overwrites_ the original scope's order. - # Thus we have to explicitly provide `reorder` for array_mapping_scope here. - array_mapping_scope: -> (author_id_expression) { Event.where(Event.arel_table[:author_id].eq(author_id_expression)).reorder(id: :desc) }, - finder_query: -> (id_expression) { Event.where(Event.arel_table[:id].eq(id_expression)) } - ) - .execute - .limit(limit) - .offset(params[:offset] || 0) - end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def execute_multi users = [] @@ -73,26 +55,18 @@ class UserRecentEventsFinder return Event.none if users.empty? - if Feature.enabled?(:optimized_followed_users_queries, current_user) - array_data = { - scope_ids: users, - scope_model: User, - mapping_column: :author_id - } - query_builder_params = event_filter.in_operator_query_builder_params(array_data) + array_data = { + scope_ids: users, + scope_model: User, + mapping_column: :author_id + } + query_builder_params = event_filter.in_operator_query_builder_params(array_data) - Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder - .new(**query_builder_params) - .execute - .limit(limit) - .offset(params[:offset] || 0) - else - if event_filter.filter == EventFilter::ALL - execute_optimized_multi(users) - else - event_filter.apply_filter(Event.where(author: users).limit_recent(limit, params[:offset] || 0)) - end - end + Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder + .new(**query_builder_params) + .execute + .limit(limit) + .offset(params[:offset] || 0) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/config/feature_flags/development/optimized_followed_users_queries.yml b/config/feature_flags/development/optimized_followed_users_queries.yml deleted file mode 100644 index 9e08810659b..00000000000 --- a/config/feature_flags/development/optimized_followed_users_queries.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: optimized_followed_users_queries -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84856 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358649 -milestone: '15.0' -type: development -group: group::optimize -default_enabled: true diff --git a/db/docs/project_tracing_settings.yml b/db/docs/project_tracing_settings.yml deleted file mode 100644 index 1a864dc7eae..00000000000 --- a/db/docs/project_tracing_settings.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -table_name: project_tracing_settings -classes: -- ProjectTracingSetting -feature_categories: -- tracing -description: TODO -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7903 -milestone: '11.5' diff --git a/db/migrate/20220704034050_add_users_allowlist_to_git_rate_limits.rb b/db/migrate/20220704034050_add_users_allowlist_to_git_rate_limits.rb new file mode 100644 index 00000000000..7dd1bb45e4c --- /dev/null +++ b/db/migrate/20220704034050_add_users_allowlist_to_git_rate_limits.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddUsersAllowlistToGitRateLimits < Gitlab::Database::Migration[2.0] + def change + add_column :application_settings, :git_rate_limit_users_allowlist, + :text, + array: true, + default: [], + null: false + end +end diff --git a/db/migrate/20220704034105_add_application_settings_git_users_allowlist_max_usernames_constraint.rb b/db/migrate/20220704034105_add_application_settings_git_users_allowlist_max_usernames_constraint.rb new file mode 100644 index 00000000000..ceb3807f817 --- /dev/null +++ b/db/migrate/20220704034105_add_application_settings_git_users_allowlist_max_usernames_constraint.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddApplicationSettingsGitUsersAllowlistMaxUsernamesConstraint < Gitlab::Database::Migration[2.0] + CONSTRAINT_NAME = 'app_settings_git_rate_limit_users_allowlist_max_usernames' + + disable_ddl_transaction! + + def up + add_check_constraint :application_settings, 'CARDINALITY(git_rate_limit_users_allowlist) <= 100', CONSTRAINT_NAME + end + + def down + remove_check_constraint :application_settings, CONSTRAINT_NAME + end +end diff --git a/db/post_migrate/20220704044408_remove_foreign_key_in_project_tracing_settings.rb b/db/post_migrate/20220704044408_remove_foreign_key_in_project_tracing_settings.rb new file mode 100644 index 00000000000..95eb461238f --- /dev/null +++ b/db/post_migrate/20220704044408_remove_foreign_key_in_project_tracing_settings.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class RemoveForeignKeyInProjectTracingSettings < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + def up + with_lock_retries do + remove_foreign_key_if_exists(:project_tracing_settings, column: :project_id) + end + end + + def down + add_concurrent_foreign_key :project_tracing_settings, :projects, + column: :project_id, on_delete: :cascade, name: 'fk_rails_fe56f57fc6' + end +end diff --git a/db/post_migrate/20220704045440_drop_project_tracing_settings_table.rb b/db/post_migrate/20220704045440_drop_project_tracing_settings_table.rb new file mode 100644 index 00000000000..1ce8b05a08d --- /dev/null +++ b/db/post_migrate/20220704045440_drop_project_tracing_settings_table.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class DropProjectTracingSettingsTable < Gitlab::Database::Migration[2.0] + def up + drop_table :project_tracing_settings + end + + def down + create_table :project_tracing_settings, id: :bigserial do |t| + t.timestamps_with_timezone null: false + + t.references :project, type: :integer, null: false, index: { unique: true } + + t.string :external_url, null: false + end + end +end diff --git a/db/schema_migrations/20220704034050 b/db/schema_migrations/20220704034050 new file mode 100644 index 00000000000..4ddb8267bf5 --- /dev/null +++ b/db/schema_migrations/20220704034050 @@ -0,0 +1 @@ +3c2e9dfb0bbd31f01a9f1b3bc7d5e1865b0ae0c94dcfd6e900890677ca276e6c \ No newline at end of file diff --git a/db/schema_migrations/20220704034105 b/db/schema_migrations/20220704034105 new file mode 100644 index 00000000000..5d08da1b417 --- /dev/null +++ b/db/schema_migrations/20220704034105 @@ -0,0 +1 @@ +45347ab01c723358a736268c40f04efd7f4ce4be0570072f3740acdc73b6a203 \ No newline at end of file diff --git a/db/schema_migrations/20220704044408 b/db/schema_migrations/20220704044408 new file mode 100644 index 00000000000..083554d2ffd --- /dev/null +++ b/db/schema_migrations/20220704044408 @@ -0,0 +1 @@ +4fd3bd4f3f3fd521b5491c38636c4c6e73470367b7510ebe517e7557c6b341ff \ No newline at end of file diff --git a/db/schema_migrations/20220704045440 b/db/schema_migrations/20220704045440 new file mode 100644 index 00000000000..3d8be03c87f --- /dev/null +++ b/db/schema_migrations/20220704045440 @@ -0,0 +1 @@ +0494aa671826be96811e2985560c70b0ba4bce4272ca7d94222eff6761d305ed \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 2013a434bab..fb278f182b7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11329,10 +11329,12 @@ CREATE TABLE application_settings ( encrypted_feishu_app_key_iv bytea, encrypted_feishu_app_secret bytea, encrypted_feishu_app_secret_iv bytea, + git_rate_limit_users_allowlist text[] DEFAULT '{}'::text[] NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), + CONSTRAINT app_settings_git_rate_limit_users_allowlist_max_usernames CHECK ((cardinality(git_rate_limit_users_allowlist) <= 100)), CONSTRAINT app_settings_p_cleanup_package_file_worker_capacity_positive CHECK ((packages_cleanup_package_file_worker_capacity >= 0)), CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), CONSTRAINT app_settings_yaml_max_depth_positive CHECK ((max_yaml_depth > 0)), @@ -19721,23 +19723,6 @@ CREATE SEQUENCE project_topics_id_seq ALTER SEQUENCE project_topics_id_seq OWNED BY project_topics.id; -CREATE TABLE project_tracing_settings ( - id bigint NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - project_id integer NOT NULL, - external_url character varying NOT NULL -); - -CREATE SEQUENCE project_tracing_settings_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - -ALTER SEQUENCE project_tracing_settings_id_seq OWNED BY project_tracing_settings.id; - CREATE TABLE projects ( id integer NOT NULL, name character varying, @@ -23398,8 +23383,6 @@ ALTER TABLE ONLY project_statistics ALTER COLUMN id SET DEFAULT nextval('project ALTER TABLE ONLY project_topics ALTER COLUMN id SET DEFAULT nextval('project_topics_id_seq'::regclass); -ALTER TABLE ONLY project_tracing_settings ALTER COLUMN id SET DEFAULT nextval('project_tracing_settings_id_seq'::regclass); - ALTER TABLE ONLY projects ALTER COLUMN id SET DEFAULT nextval('projects_id_seq'::regclass); ALTER TABLE ONLY projects_sync_events ALTER COLUMN id SET DEFAULT nextval('projects_sync_events_id_seq'::regclass); @@ -25554,9 +25537,6 @@ ALTER TABLE ONLY project_statistics ALTER TABLE ONLY project_topics ADD CONSTRAINT project_topics_pkey PRIMARY KEY (id); -ALTER TABLE ONLY project_tracing_settings - ADD CONSTRAINT project_tracing_settings_pkey PRIMARY KEY (id); - ALTER TABLE ONLY projects ADD CONSTRAINT projects_pkey PRIMARY KEY (id); @@ -29248,8 +29228,6 @@ CREATE UNIQUE INDEX index_project_topics_on_project_id_and_topic_id ON project_t CREATE INDEX index_project_topics_on_topic_id ON project_topics USING btree (topic_id); -CREATE UNIQUE INDEX index_project_tracing_settings_on_project_id ON project_tracing_settings USING btree (project_id); - CREATE INDEX index_projects_aimed_for_deletion ON projects USING btree (marked_for_deletion_at) WHERE ((marked_for_deletion_at IS NOT NULL) AND (pending_delete = false)); CREATE INDEX index_projects_api_created_at_id_desc ON projects USING btree (created_at, id DESC); @@ -34022,9 +34000,6 @@ ALTER TABLE ONLY experiment_users ALTER TABLE ONLY cluster_groups ADD CONSTRAINT fk_rails_fdb8648a96 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE; -ALTER TABLE ONLY project_tracing_settings - ADD CONSTRAINT fk_rails_fe56f57fc6 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; - ALTER TABLE ONLY resource_label_events ADD CONSTRAINT fk_rails_fe91ece594 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; diff --git a/doc/api/settings.md b/doc/api/settings.md index 2fda2555e32..7b7ceb1e986 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -381,6 +381,7 @@ listed in the descriptions of the relevant settings. | `metrics_method_call_threshold` | integer | no | A method call is only tracked when it takes longer than the given amount of milliseconds. | | `max_number_of_repository_downloads` **(ULTIMATE SELF)** | integer | no | Maximum number of unique repositories a user can download in the specified time period before they are banned. Default: 0, Maximum: 10,000 repositories. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87980) in GitLab 15.1. | | `max_number_of_repository_downloads_within_time_period` **(ULTIMATE SELF)** | integer | no | Reporting time period (in seconds). Default: 0, Maximum: 864000 seconds (10 days). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87980) in GitLab 15.1. | +| `git_rate_limit_users_allowlist` **(ULTIMATE SELF)** | array of strings | no | List of usernames excluded from Git anti-abuse rate limits. Default: [], Maximum: 100 usernames. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90815) in GitLab 15.2. | | `mirror_available` | boolean | no | Allow repository mirroring to configured by project Maintainers. If disabled, only Administrators can configure repository mirroring. | | `mirror_capacity_threshold` **(PREMIUM)** | integer | no | Minimum capacity to be available before scheduling more mirrors preemptively. | | `mirror_max_capacity` **(PREMIUM)** | integer | no | Maximum number of mirrors that can be synchronizing at the same time. | diff --git a/doc/user/project/deploy_keys/index.md b/doc/user/project/deploy_keys/index.md index 8f1da4b278a..c64afd95d8d 100644 --- a/doc/user/project/deploy_keys/index.md +++ b/doc/user/project/deploy_keys/index.md @@ -82,7 +82,7 @@ Prerequisites: A project deploy key is enabled when it is created. You can modify only a project deploy key's name and permissions. -## Create a public deploy key +## Create a public deploy key **(FREE SELF)** Prerequisites: diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml index 2a3ce6152ab..6ddbb64b956 100644 --- a/lib/gitlab/database/gitlab_schemas.yml +++ b/lib/gitlab/database/gitlab_schemas.yml @@ -437,7 +437,6 @@ projects: :gitlab_main projects_sync_events: :gitlab_main project_statistics: :gitlab_main project_topics: :gitlab_main -project_tracing_settings: :gitlab_main prometheus_alert_events: :gitlab_main prometheus_alerts: :gitlab_main prometheus_metrics: :gitlab_main diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 61b600d52a4..7b8ab4328ee 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -46137,6 +46137,9 @@ msgstr[1] "" msgid "mrWidget|Merge" msgstr "" +msgid "mrWidget|Merge blocked: all required approvals must be given." +msgstr "" + msgid "mrWidget|Merge blocked: all threads must be resolved." msgstr "" @@ -46155,9 +46158,6 @@ msgstr "" msgid "mrWidget|Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}" msgstr "" -msgid "mrWidget|Merge blocked: this merge request must be approved." -msgstr "" - msgid "mrWidget|Merge blocked: you can only merge after the above items are resolved." msgstr "" @@ -46621,6 +46621,9 @@ msgstr "" msgid "should be an array of %{object_name} objects" msgstr "" +msgid "should be an array of existing usernames. %{invalid} does not exist" +msgstr "" + msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}" msgstr "" diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index d7f7bb9cebe..dbd52ec4d18 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -18,266 +18,248 @@ RSpec.describe UserRecentEventsFinder do subject(:finder) { described_class.new(current_user, project_owner, nil, params) } - shared_examples 'UserRecentEventsFinder examples' do - describe '#execute' do - context 'when profile is public' do - it 'returns all the events' do - expect(finder.execute).to include(private_event, internal_event, public_event) - end + describe '#execute' do + context 'when profile is public' do + it 'returns all the events' do + expect(finder.execute).to include(private_event, internal_event, public_event) end + end - context 'when profile is private' do - it 'returns no event' do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false) - - expect(finder.execute).to be_empty - end - end - - it 'does not include the events if the user cannot read cross project' do + context 'when profile is private' do + it 'returns no event' do allow(Ability).to receive(:allowed?).and_call_original - expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } + allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false) expect(finder.execute).to be_empty end + end - context 'events from multiple users' do - let_it_be(:second_user, reload: true) { create(:user) } - let_it_be(:private_project_second_user) { create(:project, :private, creator: second_user) } + it 'does not include the events if the user cannot read cross project' do + allow(Ability).to receive(:allowed?).and_call_original + expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } - let_it_be(:internal_project_second_user) { create(:project, :internal, creator: second_user) } - let_it_be(:public_project_second_user) { create(:project, :public, creator: second_user) } - let_it_be(:private_event_second_user) { create(:event, project: private_project_second_user, author: second_user) } - let_it_be(:internal_event_second_user) { create(:event, project: internal_project_second_user, author: second_user) } - let_it_be(:public_event_second_user) { create(:event, project: public_project_second_user, author: second_user) } + expect(finder.execute).to be_empty + end - it 'includes events from all users', :aggregate_failures do - events = described_class.new(current_user, [project_owner, second_user], nil, params).execute + context 'events from multiple users' do + let_it_be(:second_user, reload: true) { create(:user) } + let_it_be(:private_project_second_user) { create(:project, :private, creator: second_user) } - expect(events).to include(private_event, internal_event, public_event) - expect(events).to include(private_event_second_user, internal_event_second_user, public_event_second_user) - expect(events.size).to eq(6) + let_it_be(:internal_project_second_user) { create(:project, :internal, creator: second_user) } + let_it_be(:public_project_second_user) { create(:project, :public, creator: second_user) } + let_it_be(:private_event_second_user) { create(:event, project: private_project_second_user, author: second_user) } + let_it_be(:internal_event_second_user) { create(:event, project: internal_project_second_user, author: second_user) } + let_it_be(:public_event_second_user) { create(:event, project: public_project_second_user, author: second_user) } + + it 'includes events from all users', :aggregate_failures do + events = described_class.new(current_user, [project_owner, second_user], nil, params).execute + + expect(events).to include(private_event, internal_event, public_event) + expect(events).to include(private_event_second_user, internal_event_second_user, public_event_second_user) + expect(events.size).to eq(6) + end + + context 'selected events' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:push_event1) { create(:push_event, project: public_project, author: project_owner) } + let_it_be(:push_event2) { create(:push_event, project: public_project_second_user, author: second_user) } + let_it_be(:merge_event1) { create(:event, :merged, target_type: MergeRequest.to_s, project: public_project, author: project_owner) } + let_it_be(:merge_event2) { create(:event, :merged, target_type: MergeRequest.to_s, project: public_project_second_user, author: second_user) } + let_it_be(:comment_event1) { create(:event, :commented, target_type: Note.to_s, project: public_project, author: project_owner) } + let_it_be(:comment_event2) { create(:event, :commented, target_type: DiffNote.to_s, project: public_project, author: project_owner) } + let_it_be(:comment_event3) { create(:event, :commented, target_type: DiscussionNote.to_s, project: public_project_second_user, author: second_user) } + let_it_be(:issue_event1) { create(:event, :created, project: public_project, target: issue, author: project_owner) } + let_it_be(:issue_event2) { create(:event, :updated, project: public_project, target: issue, author: project_owner) } + let_it_be(:issue_event3) { create(:event, :closed, project: public_project_second_user, target: issue, author: second_user) } + let_it_be(:wiki_event1) { create(:wiki_page_event, project: public_project, author: project_owner) } + let_it_be(:wiki_event2) { create(:wiki_page_event, project: public_project_second_user, author: second_user) } + let_it_be(:design_event1) { create(:design_event, project: public_project, author: project_owner) } + let_it_be(:design_event2) { create(:design_updated_event, project: public_project_second_user, author: second_user) } + + where(:event_filter, :ordered_expected_events) do + EventFilter.new(EventFilter::PUSH) | lazy { [push_event1, push_event2] } + EventFilter.new(EventFilter::MERGED) | lazy { [merge_event1, merge_event2] } + EventFilter.new(EventFilter::COMMENTS) | lazy { [comment_event1, comment_event2, comment_event3] } + EventFilter.new(EventFilter::TEAM) | lazy { [private_event, internal_event, public_event, private_event_second_user, internal_event_second_user, public_event_second_user] } + EventFilter.new(EventFilter::ISSUE) | lazy { [issue_event1, issue_event2, issue_event3] } + EventFilter.new(EventFilter::WIKI) | lazy { [wiki_event1, wiki_event2] } + EventFilter.new(EventFilter::DESIGNS) | lazy { [design_event1, design_event2] } end - context 'selected events' do - using RSpec::Parameterized::TableSyntax + with_them do + it 'only returns selected events from all users (id DESC)' do + events = described_class.new(current_user, [project_owner, second_user], event_filter, params).execute - let_it_be(:push_event1) { create(:push_event, project: public_project, author: project_owner) } - let_it_be(:push_event2) { create(:push_event, project: public_project_second_user, author: second_user) } - let_it_be(:merge_event1) { create(:event, :merged, target_type: MergeRequest.to_s, project: public_project, author: project_owner) } - let_it_be(:merge_event2) { create(:event, :merged, target_type: MergeRequest.to_s, project: public_project_second_user, author: second_user) } - let_it_be(:comment_event1) { create(:event, :commented, target_type: Note.to_s, project: public_project, author: project_owner) } - let_it_be(:comment_event2) { create(:event, :commented, target_type: DiffNote.to_s, project: public_project, author: project_owner) } - let_it_be(:comment_event3) { create(:event, :commented, target_type: DiscussionNote.to_s, project: public_project_second_user, author: second_user) } - let_it_be(:issue_event1) { create(:event, :created, project: public_project, target: issue, author: project_owner) } - let_it_be(:issue_event2) { create(:event, :updated, project: public_project, target: issue, author: project_owner) } - let_it_be(:issue_event3) { create(:event, :closed, project: public_project_second_user, target: issue, author: second_user) } - let_it_be(:wiki_event1) { create(:wiki_page_event, project: public_project, author: project_owner) } - let_it_be(:wiki_event2) { create(:wiki_page_event, project: public_project_second_user, author: second_user) } - let_it_be(:design_event1) { create(:design_event, project: public_project, author: project_owner) } - let_it_be(:design_event2) { create(:design_updated_event, project: public_project_second_user, author: second_user) } - - where(:event_filter, :ordered_expected_events) do - EventFilter.new(EventFilter::PUSH) | lazy { [push_event1, push_event2] } - EventFilter.new(EventFilter::MERGED) | lazy { [merge_event1, merge_event2] } - EventFilter.new(EventFilter::COMMENTS) | lazy { [comment_event1, comment_event2, comment_event3] } - EventFilter.new(EventFilter::TEAM) | lazy { [private_event, internal_event, public_event, private_event_second_user, internal_event_second_user, public_event_second_user] } - EventFilter.new(EventFilter::ISSUE) | lazy { [issue_event1, issue_event2, issue_event3] } - EventFilter.new(EventFilter::WIKI) | lazy { [wiki_event1, wiki_event2] } - EventFilter.new(EventFilter::DESIGNS) | lazy { [design_event1, design_event2] } - end - - with_them do - it 'only returns selected events from all users (id DESC)' do - events = described_class.new(current_user, [project_owner, second_user], event_filter, params).execute - - expect(events).to eq(ordered_expected_events.reverse) - end - end - end - - it 'does not include events from users with private profile', :aggregate_failures do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, second_user).and_return(false) - - events = described_class.new(current_user, [project_owner, second_user], nil, params).execute - - expect(events).to contain_exactly(private_event, internal_event, public_event) - end - - context 'with pagination params' do - using RSpec::Parameterized::TableSyntax - - where(:limit, :offset, :ordered_expected_events) do - nil | nil | lazy { [public_event_second_user, internal_event_second_user, private_event_second_user, public_event, internal_event, private_event] } - 2 | nil | lazy { [public_event_second_user, internal_event_second_user] } - nil | 4 | lazy { [internal_event, private_event] } - 2 | 2 | lazy { [private_event_second_user, public_event] } - end - - with_them do - let(:params) { { limit: limit, offset: offset }.compact } - - it 'returns paginated events sorted by id (DESC)' do - events = described_class.new(current_user, [project_owner, second_user], nil, params).execute - - expect(events).to eq(ordered_expected_events) - end + expect(events).to eq(ordered_expected_events.reverse) end end end - context 'filter activity events' do - let_it_be(:push_event) { create(:push_event, project: public_project, author: project_owner) } - let_it_be(:merge_event) { create(:event, :merged, project: public_project, author: project_owner) } - let_it_be(:issue_event) { create(:event, :closed, project: public_project, target: issue, author: project_owner) } - let_it_be(:comment_event) { create(:event, :commented, project: public_project, author: project_owner) } - let_it_be(:wiki_event) { create(:wiki_page_event, project: public_project, author: project_owner) } - let_it_be(:design_event) { create(:design_event, project: public_project, author: project_owner) } - let_it_be(:team_event) { create(:event, :joined, project: public_project, author: project_owner) } + it 'does not include events from users with private profile', :aggregate_failures do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, second_user).and_return(false) - it 'includes all events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::ALL) - events = described_class.new(current_user, project_owner, event_filter, params).execute + events = described_class.new(current_user, [project_owner, second_user], nil, params).execute + + expect(events).to contain_exactly(private_event, internal_event, public_event) + end + + context 'with pagination params' do + using RSpec::Parameterized::TableSyntax + + where(:limit, :offset, :ordered_expected_events) do + nil | nil | lazy { [public_event_second_user, internal_event_second_user, private_event_second_user, public_event, internal_event, private_event] } + 2 | nil | lazy { [public_event_second_user, internal_event_second_user] } + nil | 4 | lazy { [internal_event, private_event] } + 2 | 2 | lazy { [private_event_second_user, public_event] } + end + + with_them do + let(:params) { { limit: limit, offset: offset }.compact } + + it 'returns paginated events sorted by id (DESC)' do + events = described_class.new(current_user, [project_owner, second_user], nil, params).execute + + expect(events).to eq(ordered_expected_events) + end + end + end + end + + context 'filter activity events' do + let_it_be(:push_event) { create(:push_event, project: public_project, author: project_owner) } + let_it_be(:merge_event) { create(:event, :merged, project: public_project, author: project_owner) } + let_it_be(:issue_event) { create(:event, :closed, project: public_project, target: issue, author: project_owner) } + let_it_be(:comment_event) { create(:event, :commented, project: public_project, author: project_owner) } + let_it_be(:wiki_event) { create(:wiki_page_event, project: public_project, author: project_owner) } + let_it_be(:design_event) { create(:design_event, project: public_project, author: project_owner) } + let_it_be(:team_event) { create(:event, :joined, project: public_project, author: project_owner) } + + it 'includes all events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::ALL) + events = described_class.new(current_user, project_owner, event_filter, params).execute + + expect(events).to include(private_event, internal_event, public_event) + expect(events).to include(push_event, merge_event, issue_event, comment_event, wiki_event, design_event, team_event) + expect(events.size).to eq(10) + end + + context 'when unknown filter is given' do + it 'includes returns all events', :aggregate_failures do + event_filter = EventFilter.new('unknown') + allow(event_filter).to receive(:filter).and_return('unknown') + + events = described_class.new(current_user, [project_owner], event_filter, params).execute expect(events).to include(private_event, internal_event, public_event) expect(events).to include(push_event, merge_event, issue_event, comment_event, wiki_event, design_event, team_event) expect(events.size).to eq(10) end + end - context 'when unknown filter is given' do - it 'includes returns all events', :aggregate_failures do - event_filter = EventFilter.new('unknown') - allow(event_filter).to receive(:filter).and_return('unknown') + it 'only includes push events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::PUSH) + events = described_class.new(current_user, project_owner, event_filter, params).execute - events = described_class.new(current_user, [project_owner], event_filter, params).execute + expect(events).to include(push_event) + expect(events.size).to eq(1) + end - expect(events).to include(private_event, internal_event, public_event) - expect(events).to include(push_event, merge_event, issue_event, comment_event, wiki_event, design_event, team_event) - expect(events.size).to eq(10) - end - end + it 'only includes merge events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::MERGED) + events = described_class.new(current_user, project_owner, event_filter, params).execute - it 'only includes push events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::PUSH) - events = described_class.new(current_user, project_owner, event_filter, params).execute + expect(events).to include(merge_event) + expect(events.size).to eq(1) + end - expect(events).to include(push_event) - expect(events.size).to eq(1) - end + it 'only includes issue events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::ISSUE) + events = described_class.new(current_user, project_owner, event_filter, params).execute - it 'only includes merge events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::MERGED) - events = described_class.new(current_user, project_owner, event_filter, params).execute + expect(events).to include(issue_event) + expect(events.size).to eq(1) + end - expect(events).to include(merge_event) - expect(events.size).to eq(1) - end + it 'only includes comments events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::COMMENTS) + events = described_class.new(current_user, project_owner, event_filter, params).execute - it 'only includes issue events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::ISSUE) - events = described_class.new(current_user, project_owner, event_filter, params).execute + expect(events).to include(comment_event) + expect(events.size).to eq(1) + end - expect(events).to include(issue_event) - expect(events.size).to eq(1) - end + it 'only includes wiki events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::WIKI) + events = described_class.new(current_user, project_owner, event_filter, params).execute - it 'only includes comments events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::COMMENTS) - events = described_class.new(current_user, project_owner, event_filter, params).execute + expect(events).to include(wiki_event) + expect(events.size).to eq(1) + end - expect(events).to include(comment_event) - expect(events.size).to eq(1) - end + it 'only includes design events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::DESIGNS) + events = described_class.new(current_user, project_owner, event_filter, params).execute - it 'only includes wiki events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::WIKI) - events = described_class.new(current_user, project_owner, event_filter, params).execute + expect(events).to include(design_event) + expect(events.size).to eq(1) + end - expect(events).to include(wiki_event) - expect(events.size).to eq(1) - end + it 'only includes team events', :aggregate_failures do + event_filter = EventFilter.new(EventFilter::TEAM) + events = described_class.new(current_user, project_owner, event_filter, params).execute - it 'only includes design events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::DESIGNS) - events = described_class.new(current_user, project_owner, event_filter, params).execute + expect(events).to include(private_event, internal_event, public_event, team_event) + expect(events.size).to eq(4) + end + end - expect(events).to include(design_event) - expect(events.size).to eq(1) - end + describe 'issue activity events' do + let(:issue) { create(:issue, project: public_project) } + let(:note) { create(:note_on_issue, noteable: issue, project: public_project) } + let!(:event_a) { create(:event, :commented, target: note, author: project_owner) } + let!(:event_b) { create(:event, :closed, target: issue, author: project_owner) } - it 'only includes team events', :aggregate_failures do - event_filter = EventFilter.new(EventFilter::TEAM) - events = described_class.new(current_user, project_owner, event_filter, params).execute + it 'includes all issue related events', :aggregate_failures do + events = finder.execute - expect(events).to include(private_event, internal_event, public_event, team_event) - expect(events.size).to eq(4) + expect(events).to include(event_a) + expect(events).to include(event_b) + end + end + + context 'limits' do + before do + stub_const("#{described_class}::DEFAULT_LIMIT", 1) + stub_const("#{described_class}::MAX_LIMIT", 3) + end + + context 'when limit is not set' do + it 'returns events limited to DEFAULT_LIMIT' do + expect(finder.execute.size).to eq(described_class::DEFAULT_LIMIT) end end - describe 'issue activity events' do - let(:issue) { create(:issue, project: public_project) } - let(:note) { create(:note_on_issue, noteable: issue, project: public_project) } - let!(:event_a) { create(:event, :commented, target: note, author: project_owner) } - let!(:event_b) { create(:event, :closed, target: issue, author: project_owner) } + context 'when limit is set' do + let(:limit) { 2 } - it 'includes all issue related events', :aggregate_failures do - events = finder.execute - - expect(events).to include(event_a) - expect(events).to include(event_b) + it 'returns events limited to specified limit' do + expect(finder.execute.size).to eq(limit) end end - context 'limits' do + context 'when limit is set to a number that exceeds maximum limit' do + let(:limit) { 4 } + before do - stub_const("#{described_class}::DEFAULT_LIMIT", 1) - stub_const("#{described_class}::MAX_LIMIT", 3) + create(:event, project: public_project, author: project_owner) end - context 'when limit is not set' do - it 'returns events limited to DEFAULT_LIMIT' do - expect(finder.execute.size).to eq(described_class::DEFAULT_LIMIT) - end - end - - context 'when limit is set' do - let(:limit) { 2 } - - it 'returns events limited to specified limit' do - expect(finder.execute.size).to eq(limit) - end - end - - context 'when limit is set to a number that exceeds maximum limit' do - let(:limit) { 4 } - - before do - create(:event, project: public_project, author: project_owner) - end - - it 'returns events limited to MAX_LIMIT' do - expect(finder.execute.size).to eq(described_class::MAX_LIMIT) - end + it 'returns events limited to MAX_LIMIT' do + expect(finder.execute.size).to eq(described_class::MAX_LIMIT) end end end end - - context 'when the optimized_followed_users_queries FF is on' do - before do - stub_feature_flags(optimized_followed_users_queries: true) - end - - it_behaves_like 'UserRecentEventsFinder examples' - end - - context 'when the optimized_followed_users_queries FF is off' do - before do - stub_feature_flags(optimized_followed_users_queries: false) - end - - it_behaves_like 'UserRecentEventsFinder examples' - end end