Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
15e5a05bcd
commit
26891eec2c
|
|
@ -821,10 +821,23 @@
|
|||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
- <<: *if-merge-request-labels-run-review-app
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-build-images-patterns
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
- <<: *if-merge-request
|
||||
changes: *code-qa-patterns
|
||||
# Rules to support .qa:rules:package-and-test-ee
|
||||
- <<: *if-merge-request
|
||||
changes: *dependency-patterns
|
||||
- <<: *if-merge-request-labels-run-all-e2e
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-e2e
|
||||
changes: *feature-flag-development-config-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *feature-flag-development-config-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *nodejs-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-qa-patterns
|
||||
- <<: *if-force-ci
|
||||
|
||||
.build-images:rules:build-qa-image:
|
||||
rules:
|
||||
|
|
@ -843,7 +856,6 @@
|
|||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
variables:
|
||||
ARCH: amd64,arm64
|
||||
- <<: *if-force-ci
|
||||
- <<: *if-ruby2-branch
|
||||
|
||||
.build-images:rules:build-qa-image-as-if-foss:
|
||||
|
|
@ -872,7 +884,6 @@
|
|||
- <<: *if-merge-request-targeting-stable-branch
|
||||
- <<: *if-ruby2-branch
|
||||
- <<: *if-merge-request-labels-run-review-app
|
||||
- <<: *if-merge-request-labels-run-all-e2e
|
||||
- <<: *if-auto-deploy-branches
|
||||
- !reference [".releases:rules:canonical-dot-com-gitlab-stable-branch-only-setup-test-env-patterns", rules]
|
||||
- <<: *if-default-refs
|
||||
|
|
@ -1275,6 +1286,8 @@
|
|||
##########
|
||||
.notify:rules:create-issues-for-failing-tests:
|
||||
rules:
|
||||
- <<: *if-not-canonical-namespace
|
||||
when: never
|
||||
# Don't report child pipeline failures
|
||||
- if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
|
||||
when: never
|
||||
|
|
@ -1331,14 +1344,32 @@
|
|||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
- <<: *if-merge-request
|
||||
changes: *code-backstage-qa-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
allow_failure: true
|
||||
- <<: *if-ruby2-branch
|
||||
# Rules to support .qa:rules:package-and-test-ee
|
||||
- <<: *if-merge-request
|
||||
changes: *dependency-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request-labels-run-all-e2e
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-e2e
|
||||
changes: *feature-flag-development-config-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request
|
||||
changes: *feature-flag-development-config-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request
|
||||
changes: *nodejs-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-qa-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-force-ci
|
||||
allow_failure: true
|
||||
- <<: *if-ruby2-branch
|
||||
|
||||
.qa:rules:package-and-test-common:
|
||||
rules:
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
Gitlab/DeprecateTrackRedisHLLEvent:
|
||||
Exclude:
|
||||
- 'app/controllers/concerns/snippets_actions.rb'
|
||||
- 'app/controllers/concerns/wiki_actions.rb'
|
||||
- 'app/controllers/projects/blob_controller.rb'
|
||||
- 'app/controllers/projects/pipelines_controller.rb'
|
||||
- 'ee/app/controllers/admin/audit_logs_controller.rb'
|
||||
- 'ee/app/controllers/admin/credentials_controller.rb'
|
||||
- 'ee/app/controllers/groups/analytics/ci_cd_analytics_controller.rb'
|
||||
- 'ee/app/controllers/groups/audit_events_controller.rb'
|
||||
- 'ee/app/controllers/groups/epic_boards_controller.rb'
|
||||
- 'spec/controllers/concerns/redis_tracking_spec.rb'
|
||||
|
|
@ -5494,7 +5494,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/rubocop/cop/gitlab/change_timezone_spec.rb'
|
||||
- 'spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb'
|
||||
- 'spec/rubocop/cop/gitlab/delegate_predicate_methods_spec.rb'
|
||||
- 'spec/rubocop/cop/gitlab/deprecate_track_redis_hll_event_spec.rb'
|
||||
- 'spec/rubocop/cop/gitlab/event_store_subscriber_spec.rb'
|
||||
- 'spec/rubocop/cop/gitlab/except_spec.rb'
|
||||
- 'spec/rubocop/cop/gitlab/feature_available_usage_spec.rb'
|
||||
|
|
|
|||
|
|
@ -131,6 +131,10 @@ export default {
|
|||
:message-url="view.message_url"
|
||||
:config="$options.integrationViewConfigs[view.name]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4"></div>
|
||||
<div class="col-lg-8">
|
||||
<hr />
|
||||
</div>
|
||||
<div class="col-sm-12 js-hide-when-nothing-matches-search">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
module ProductAnalyticsTracking
|
||||
include Gitlab::Tracking::Helpers
|
||||
include RedisTracking
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
|
|
@ -39,4 +38,23 @@ module ProductAnalyticsTracking
|
|||
**optional_arguments
|
||||
)
|
||||
end
|
||||
|
||||
def track_unique_redis_hll_event(event_name, &block)
|
||||
custom_id = block ? yield(self) : nil
|
||||
|
||||
unique_id = custom_id || visitor_id
|
||||
|
||||
return unless unique_id
|
||||
|
||||
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: unique_id)
|
||||
end
|
||||
|
||||
def visitor_id
|
||||
return cookies[:visitor_id] if cookies[:visitor_id].present?
|
||||
return unless current_user
|
||||
|
||||
uuid = SecureRandom.uuid
|
||||
cookies[:visitor_id] = { value: uuid, expires: 24.months }
|
||||
uuid
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Example:
|
||||
#
|
||||
# # In controller include module
|
||||
# # Track event for index action
|
||||
#
|
||||
# include RedisTracking
|
||||
#
|
||||
# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score'
|
||||
#
|
||||
# You can also pass custom conditions using `if:`, using the same format as with Rails callbacks.
|
||||
# You can also pass an optional block that calculates and returns a custom id to track.
|
||||
module RedisTracking
|
||||
include Gitlab::Tracking::Helpers
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def track_redis_hll_event(*controller_actions, name:, if: nil, &block)
|
||||
custom_conditions = Array.wrap(binding.local_variable_get('if'))
|
||||
conditions = [:trackable_html_request?, *custom_conditions]
|
||||
|
||||
after_action only: controller_actions, if: conditions do
|
||||
track_unique_redis_hll_event(name, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def track_unique_redis_hll_event(event_name, &block)
|
||||
custom_id = block ? yield(self) : nil
|
||||
|
||||
unique_id = custom_id || visitor_id
|
||||
|
||||
return unless unique_id
|
||||
|
||||
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: unique_id)
|
||||
end
|
||||
|
||||
def visitor_id
|
||||
return cookies[:visitor_id] if cookies[:visitor_id].present?
|
||||
return unless current_user
|
||||
|
||||
uuid = SecureRandom.uuid
|
||||
cookies[:visitor_id] = { value: uuid, expires: 24.months }
|
||||
uuid
|
||||
end
|
||||
end
|
||||
|
|
@ -9,13 +9,13 @@ module SnippetsActions
|
|||
include Gitlab::NoteableMetadata
|
||||
include Snippets::SendBlob
|
||||
include SnippetsSort
|
||||
include RedisTracking
|
||||
include ProductAnalyticsTracking
|
||||
|
||||
included do
|
||||
skip_before_action :verify_authenticity_token,
|
||||
if: -> { action_name == 'show' && js_request? }
|
||||
|
||||
track_redis_hll_event :show, name: 'i_snippets_show'
|
||||
track_event :show, name: 'i_snippets_show'
|
||||
|
||||
respond_to :html
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module WikiActions
|
|||
include PreviewMarkdown
|
||||
include SendsBlob
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include RedisTracking
|
||||
include ProductAnalyticsTracking
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
RESCUE_GIT_TIMEOUTS_IN = %w[show edit history diff pages].freeze
|
||||
|
|
@ -46,7 +46,7 @@ module WikiActions
|
|||
end
|
||||
end
|
||||
|
||||
track_redis_hll_event :show, name: 'wiki_action'
|
||||
track_event :show, name: 'wiki_action'
|
||||
|
||||
helper_method :view_file_button, :diff_file_html_data
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
|
|||
end
|
||||
|
||||
def preferences_param_names
|
||||
[
|
||||
preferences_param_names = [
|
||||
:color_scheme_id,
|
||||
:diffs_deletion_color,
|
||||
:diffs_addition_color,
|
||||
|
|
@ -60,6 +60,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController
|
|||
:use_legacy_web_ide,
|
||||
:use_new_navigation
|
||||
]
|
||||
preferences_param_names << :enabled_following if ::Feature.enabled?(:disable_follow_users, user)
|
||||
preferences_param_names
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class Projects::PipelinesController < Projects::ApplicationController
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
include RedisTracking
|
||||
include ProductAnalyticsTracking
|
||||
include ProductAnalyticsTracking
|
||||
include ProjectStatsRefreshConflictsGuard
|
||||
|
||||
|
|
@ -34,11 +34,11 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
label: 'redis_hll_counters.analytics.analytics_total_unique_counts_monthly',
|
||||
destinations: %i[redis_hll snowplow]
|
||||
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_pipelines', if: -> { should_track_ci_cd_pipelines? }
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_deployment_frequency', if: -> { should_track_ci_cd_deployment_frequency? }
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_lead_time', if: -> { should_track_ci_cd_lead_time? }
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_time_to_restore_service', if: -> { should_track_ci_cd_time_to_restore_service? }
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_change_failure_rate', if: -> { should_track_ci_cd_change_failure_rate? }
|
||||
track_event :charts, name: 'p_analytics_ci_cd_pipelines', conditions: -> { should_track_ci_cd_pipelines? }
|
||||
track_event :charts, name: 'p_analytics_ci_cd_deployment_frequency', conditions: -> { should_track_ci_cd_deployment_frequency? }
|
||||
track_event :charts, name: 'p_analytics_ci_cd_lead_time', conditions: -> { should_track_ci_cd_lead_time? }
|
||||
track_event :charts, name: 'p_analytics_ci_cd_time_to_restore_service', conditions: -> { should_track_ci_cd_time_to_restore_service? }
|
||||
track_event :charts, name: 'p_analytics_ci_cd_change_failure_rate', conditions: -> { should_track_ci_cd_change_failure_rate? }
|
||||
|
||||
wrap_parameters Ci::Pipeline
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class SearchController < ApplicationController
|
||||
include ControllerWithCrossProjectAccessCheck
|
||||
include SearchHelper
|
||||
include RedisTracking
|
||||
include ProductAnalyticsTracking
|
||||
include ProductAnalyticsTracking
|
||||
include SearchRateLimitable
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,12 @@ class UsersController < ApplicationController
|
|||
def follow
|
||||
followee = current_user.follow(user)
|
||||
|
||||
flash[:alert] = followee.errors.full_messages.join(', ') if followee&.errors&.any?
|
||||
if followee
|
||||
flash[:alert] = followee.errors.full_messages.join(', ') if followee&.errors&.any?
|
||||
else
|
||||
flash[:alert] = s_('Action not allowed.')
|
||||
end
|
||||
|
||||
redirect_path = referer_path(request) || @user
|
||||
|
||||
redirect_to redirect_path
|
||||
|
|
|
|||
|
|
@ -349,29 +349,30 @@ class User < ApplicationRecord
|
|||
# User's role
|
||||
enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true
|
||||
|
||||
delegate :notes_filter_for,
|
||||
:set_notes_filter,
|
||||
:first_day_of_week, :first_day_of_week=,
|
||||
:timezone, :timezone=,
|
||||
:time_display_relative, :time_display_relative=,
|
||||
:time_format_in_24h, :time_format_in_24h=,
|
||||
:show_whitespace_in_diffs, :show_whitespace_in_diffs=,
|
||||
:view_diffs_file_by_file, :view_diffs_file_by_file=,
|
||||
:pass_user_identities_to_ci_jwt, :pass_user_identities_to_ci_jwt=,
|
||||
:tab_width, :tab_width=,
|
||||
:sourcegraph_enabled, :sourcegraph_enabled=,
|
||||
:gitpod_enabled, :gitpod_enabled=,
|
||||
:setup_for_company, :setup_for_company=,
|
||||
:render_whitespace_in_code, :render_whitespace_in_code=,
|
||||
:markdown_surround_selection, :markdown_surround_selection=,
|
||||
:markdown_automatic_lists, :markdown_automatic_lists=,
|
||||
:diffs_deletion_color, :diffs_deletion_color=,
|
||||
:diffs_addition_color, :diffs_addition_color=,
|
||||
:use_legacy_web_ide, :use_legacy_web_ide=,
|
||||
:use_new_navigation, :use_new_navigation=,
|
||||
:pinned_nav_items, :pinned_nav_items=,
|
||||
:achievements_enabled, :achievements_enabled=,
|
||||
to: :user_preference
|
||||
delegate :notes_filter_for,
|
||||
:set_notes_filter,
|
||||
:first_day_of_week, :first_day_of_week=,
|
||||
:timezone, :timezone=,
|
||||
:time_display_relative, :time_display_relative=,
|
||||
:time_format_in_24h, :time_format_in_24h=,
|
||||
:show_whitespace_in_diffs, :show_whitespace_in_diffs=,
|
||||
:view_diffs_file_by_file, :view_diffs_file_by_file=,
|
||||
:pass_user_identities_to_ci_jwt, :pass_user_identities_to_ci_jwt=,
|
||||
:tab_width, :tab_width=,
|
||||
:sourcegraph_enabled, :sourcegraph_enabled=,
|
||||
:gitpod_enabled, :gitpod_enabled=,
|
||||
:setup_for_company, :setup_for_company=,
|
||||
:render_whitespace_in_code, :render_whitespace_in_code=,
|
||||
:markdown_surround_selection, :markdown_surround_selection=,
|
||||
:markdown_automatic_lists, :markdown_automatic_lists=,
|
||||
:diffs_deletion_color, :diffs_deletion_color=,
|
||||
:diffs_addition_color, :diffs_addition_color=,
|
||||
:use_legacy_web_ide, :use_legacy_web_ide=,
|
||||
:use_new_navigation, :use_new_navigation=,
|
||||
:pinned_nav_items, :pinned_nav_items=,
|
||||
:achievements_enabled, :achievements_enabled=,
|
||||
:enabled_following, :enabled_following=,
|
||||
to: :user_preference
|
||||
|
||||
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
||||
delegate :job_title, :job_title=, to: :user_detail, allow_nil: true
|
||||
|
|
@ -1694,7 +1695,7 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def follow(user)
|
||||
return false if self.id == user.id
|
||||
return false unless following_users_allowed?(user)
|
||||
|
||||
begin
|
||||
followee = Users::UserFollowUser.create(follower_id: self.id, followee_id: user.id)
|
||||
|
|
@ -1713,6 +1714,18 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def following_users_allowed?(user)
|
||||
return false if self.id == user.id
|
||||
|
||||
following_users_enabled? && user.following_users_enabled?
|
||||
end
|
||||
|
||||
def following_users_enabled?
|
||||
return true unless ::Feature.enabled?(:disable_follow_users, self)
|
||||
|
||||
enabled_following
|
||||
end
|
||||
|
||||
def forkable_namespaces
|
||||
strong_memoize(:forkable_namespaces) do
|
||||
personal_namespace = Namespace.where(id: namespace_id)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Users
|
|||
attr_reader :user, :identity_params
|
||||
|
||||
ATTRS_REQUIRING_PASSWORD_CHECK = %w[email].freeze
|
||||
BATCH_SIZE = 100
|
||||
|
||||
def initialize(current_user, params = {})
|
||||
@current_user = current_user
|
||||
|
|
@ -34,7 +35,7 @@ module Users
|
|||
reset_unconfirmed_email
|
||||
|
||||
if @user.save(validate: validate) && update_status
|
||||
notify_success(user_exists)
|
||||
after_update(user_exists)
|
||||
else
|
||||
messages = @user.errors.full_messages + Array(@user.status&.errors&.full_messages)
|
||||
error(messages.uniq.join('. '))
|
||||
|
|
@ -80,8 +81,6 @@ module Users
|
|||
|
||||
def notify_success(user_exists)
|
||||
notify_new_user(@user, nil) unless user_exists
|
||||
|
||||
success
|
||||
end
|
||||
|
||||
def discard_read_only_attributes
|
||||
|
|
@ -118,6 +117,30 @@ module Users
|
|||
def provider_params
|
||||
identity_params.slice(*provider_attributes)
|
||||
end
|
||||
|
||||
def after_update(user_exists)
|
||||
notify_success(user_exists)
|
||||
remove_followers_and_followee! if ::Feature.enabled?(:disable_follow_users, user)
|
||||
|
||||
success
|
||||
end
|
||||
|
||||
def remove_followers_and_followee!
|
||||
return false unless user.user_preference.enabled_following_previously_changed?(from: true, to: false)
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
loop do
|
||||
inner_query = Users::UserFollowUser
|
||||
.where(follower_id: user.id).or(Users::UserFollowUser.where(followee_id: user.id))
|
||||
.select(:follower_id, :followee_id)
|
||||
.limit(BATCH_SIZE)
|
||||
|
||||
deleted_records = Users::UserFollowUser.where('(follower_id, followee_id) IN (?)', inner_query).delete_all
|
||||
|
||||
break if deleted_records == 0
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -170,5 +170,21 @@
|
|||
- if Feature.enabled?(:user_time_settings)
|
||||
.form-group
|
||||
= f.gitlab_ui_checkbox_component :time_format_in_24h, s_('Preferences|Display time in 24-hour format')
|
||||
- if Feature.enabled?(:disable_follow_users, @user)
|
||||
.row.js-preferences-form.js-search-settings-section
|
||||
.col-sm-12
|
||||
%hr
|
||||
.col-lg-4.profile-settings-sidebar#disabled_following
|
||||
%h4.gl-mt-0
|
||||
= s_('Preferences|Disable follow users feature')
|
||||
%p
|
||||
= s_('Preferences|Turns off the ability to follow or be followed by other users.')
|
||||
= succeed '.' do
|
||||
= link_to _('Learn more'), help_page_path('user/profile/index', anchor: 'follow-users'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.col-lg-8
|
||||
.form-group
|
||||
= f.gitlab_ui_checkbox_component :enabled_following,
|
||||
s_('Preferences|Disable follow users')
|
||||
|
||||
|
||||
#js-profile-preferences-app{ data: data_attributes }
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
= render Pajamas::ButtonComponent.new(href: [:admin, @user],
|
||||
icon: 'user',
|
||||
button_options: { class: 'gl-flex-grow-1 gl-mx-1 has-tooltip', title: s_('UserProfile|View user in admin area'), data: {toggle: 'tooltip', placement: 'bottom', container: 'body'}})
|
||||
- if current_user && current_user.id != @user.id
|
||||
- if current_user && current_user.following_users_allowed?(@user)
|
||||
- if current_user.following?(@user)
|
||||
= form_tag user_unfollow_path(@user, :json), class: link_classes + 'gl-display-inline-block' do
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, button_options: { class: 'gl-w-full', data: { track_action: 'click_button', track_label: 'unfollow_from_profile' } }) do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: disable_follow_users
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116023
|
||||
rollout_issue_url:
|
||||
milestone: '16.0'
|
||||
type: development
|
||||
group: group::authentication and authorization
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
migration_job_name: PopulateVulnerabilityDismissalFields
|
||||
description: This populates missing dismissal info for vulnerabilities.
|
||||
feature_category: vulnerability_management
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/405032
|
||||
milestone: 15.11
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddDisabledFollowingToUserPreferences < Gitlab::Database::Migration[2.1]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :user_preferences, :enabled_following, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ValidateFkProjectsCreatorId < Gitlab::Database::Migration[2.1]
|
||||
TABLE_NAME = :projects
|
||||
COLUMN_NAME = :creator_id
|
||||
FK_NAME = :fk_03ec10b0d3
|
||||
|
||||
def up
|
||||
validate_foreign_key TABLE_NAME, COLUMN_NAME, name: FK_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTempIndexToNullDismissedInfoVulnerabilities < Gitlab::Database::Migration[2.1]
|
||||
INDEX_NAME = 'tmp_index_vulnerability_dismissal_info'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
# Temporary index to be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/406653
|
||||
add_concurrent_index :vulnerabilities, :id,
|
||||
where: "state = 2 AND (dismissed_at IS NULL OR dismissed_by_id IS NULL)",
|
||||
name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :vulnerabilities, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexToGroupGroupLinks < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_group_group_links_on_shared_with_group_and_group_access'
|
||||
TABLE_NAME = :group_group_links
|
||||
|
||||
def up
|
||||
add_concurrent_index TABLE_NAME, [:shared_with_group_id, :group_access], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name TABLE_NAME, name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueuePopulateVulnerabilityDismissalFields < Gitlab::Database::Migration[2.1]
|
||||
MIGRATION = "PopulateVulnerabilityDismissalFields"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1_000
|
||||
MAX_BATCH_SIZE = 10_000
|
||||
SUB_BATCH_SIZE = 200
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:vulnerabilities,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
max_batch_size: MAX_BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :vulnerabilities, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
ea52a177f82cc872b1f38490ff17bf166a885a4df7cf2c6c99dc7b1cccd14d33
|
||||
|
|
@ -0,0 +1 @@
|
|||
d1e5678ea93b5b8f5d76b0332e79934badbaa84546754b66916bb2816cfaf307
|
||||
|
|
@ -0,0 +1 @@
|
|||
31e8f1f8d65ea821efb158bd59657776de54ecbd5e10d3a64c5afe37706c5388
|
||||
|
|
@ -0,0 +1 @@
|
|||
5a1245d37e10d03320a3cd8afda34226e54c6f6641c3abedfcb1333ea6ed69a0
|
||||
|
|
@ -0,0 +1 @@
|
|||
f9c342816a6c656b1c13b8e9d0a771c1ee6a9847c03a76577c662f9cf238ad03
|
||||
|
|
@ -23487,6 +23487,7 @@ CREATE TABLE user_preferences (
|
|||
achievements_enabled boolean DEFAULT true NOT NULL,
|
||||
pinned_nav_items jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
pass_user_identities_to_ci_jwt boolean DEFAULT false NOT NULL,
|
||||
enabled_following boolean DEFAULT true NOT NULL,
|
||||
CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)),
|
||||
CONSTRAINT check_d07ccd35f7 CHECK ((char_length(diffs_addition_color) <= 7))
|
||||
);
|
||||
|
|
@ -30780,6 +30781,8 @@ CREATE UNIQUE INDEX index_group_deploy_tokens_on_group_and_deploy_token_ids ON g
|
|||
|
||||
CREATE UNIQUE INDEX index_group_group_links_on_shared_group_and_shared_with_group ON group_group_links USING btree (shared_group_id, shared_with_group_id);
|
||||
|
||||
CREATE INDEX index_group_group_links_on_shared_with_group_and_group_access ON group_group_links USING btree (shared_with_group_id, group_access);
|
||||
|
||||
CREATE INDEX index_group_group_links_on_shared_with_group_and_shared_group ON group_group_links USING btree (shared_with_group_id, shared_group_id);
|
||||
|
||||
CREATE INDEX index_group_import_states_on_group_id ON group_import_states USING btree (group_id);
|
||||
|
|
@ -33010,6 +33013,8 @@ CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING
|
|||
|
||||
CREATE INDEX tmp_index_project_statistics_cont_registry_size ON project_statistics USING btree (project_id) WHERE (container_registry_size = 0);
|
||||
|
||||
CREATE INDEX tmp_index_vulnerability_dismissal_info ON vulnerabilities USING btree (id) WHERE ((state = 2) AND ((dismissed_at IS NULL) OR (dismissed_by_id IS NULL)));
|
||||
|
||||
CREATE INDEX tmp_index_vulnerability_overlong_title_html ON vulnerabilities USING btree (id) WHERE (length(title_html) > 800);
|
||||
|
||||
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name);
|
||||
|
|
@ -34470,7 +34475,7 @@ ALTER TABLE ONLY design_management_designs_versions
|
|||
ADD CONSTRAINT fk_03c671965c FOREIGN KEY (design_id) REFERENCES design_management_designs(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY projects
|
||||
ADD CONSTRAINT fk_03ec10b0d3 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL NOT VALID;
|
||||
ADD CONSTRAINT fk_03ec10b0d3 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY analytics_dashboards_pointers
|
||||
ADD CONSTRAINT fk_05d96922bd FOREIGN KEY (target_project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module API
|
|||
class User < UserBasic
|
||||
include UsersHelper
|
||||
include TimeZoneHelper
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
|
||||
expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :discord, :website_url, :organization, :job_title, :pronouns
|
||||
|
|
@ -12,18 +13,28 @@ module API
|
|||
expose :work_information do |user|
|
||||
work_information(user)
|
||||
end
|
||||
expose :followers, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user|
|
||||
expose :followers, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) && following_users_allowed(opts[:current_user], user) } do |user|
|
||||
user.followers.size
|
||||
end
|
||||
expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user|
|
||||
expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) && following_users_allowed(opts[:current_user], user) } do |user|
|
||||
user.followees.size
|
||||
end
|
||||
expose :is_followed, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) && opts[:current_user] } do |user, opts|
|
||||
expose :is_followed, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) && opts[:current_user] && following_users_allowed(opts[:current_user], user) } do |user, opts|
|
||||
user.followed_by?(opts[:current_user])
|
||||
end
|
||||
expose :local_time do |user|
|
||||
local_time(user.timezone)
|
||||
end
|
||||
|
||||
def following_users_allowed(current_user, user)
|
||||
strong_memoize(:following_users_allowed) do
|
||||
if current_user
|
||||
current_user.following_users_allowed?(user)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -195,12 +195,13 @@ module API
|
|||
not_found!('User') unless user
|
||||
|
||||
followee = current_user.follow(user)
|
||||
|
||||
not_modified! unless followee
|
||||
|
||||
if followee&.errors&.any?
|
||||
render_api_error!(followee.errors.full_messages.join(', '), 400)
|
||||
elsif followee&.persisted?
|
||||
present user, with: Entities::UserBasic
|
||||
else
|
||||
not_modified!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# Populates missing dismissal information for vulnerabilities.
|
||||
class PopulateVulnerabilityDismissalFields < BatchedMigrationJob
|
||||
feature_category :vulnerability_management
|
||||
scope_to ->(relation) { relation.where('state = 2 AND (dismissed_at IS NULL OR dismissed_by_id IS NULL)') }
|
||||
operation_name :populate_vulnerability_dismissal_fields
|
||||
|
||||
# rubocop:disable Style/Documentation
|
||||
class Vulnerability < ApplicationRecord
|
||||
self.table_name = 'vulnerabilities'
|
||||
|
||||
has_one :finding, class_name: 'Finding'
|
||||
|
||||
def copy_dismissal_information
|
||||
return unless finding&.dismissal_feedback
|
||||
|
||||
update_columns(
|
||||
dismissed_at: finding.dismissal_feedback.created_at,
|
||||
dismissed_by_id: finding.dismissal_feedback.author_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class Finding < ApplicationRecord
|
||||
self.table_name = 'vulnerability_occurrences'
|
||||
|
||||
validates :details, json_schema: { filename: "filename" }
|
||||
|
||||
def dismissal_feedback
|
||||
Feedback.dismissal.where(finding_uuid: uuid).first
|
||||
end
|
||||
end
|
||||
|
||||
class Feedback < ApplicationRecord
|
||||
DISMISSAL_TYPE = 0 # dismissal
|
||||
|
||||
self.table_name = 'vulnerability_feedback'
|
||||
|
||||
scope :dismissal, -> { where(feedback_type: DISMISSAL_TYPE) }
|
||||
end
|
||||
# rubocop:enable Style/Documentation
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
vulnerability_ids = sub_batch.pluck(:id)
|
||||
Vulnerability.includes(:finding).where(id: vulnerability_ids).each do |vulnerability|
|
||||
populate_for(vulnerability)
|
||||
end
|
||||
|
||||
log_info(vulnerability_ids)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def populate_for(vulnerability)
|
||||
log_warning(vulnerability) unless vulnerability.copy_dismissal_information
|
||||
rescue StandardError => error
|
||||
log_error(error, vulnerability)
|
||||
end
|
||||
|
||||
def log_info(vulnerability_ids)
|
||||
::Gitlab::BackgroundMigration::Logger.info(
|
||||
migrator: self.class.name,
|
||||
message: 'Dismissal information has been copied',
|
||||
count: vulnerability_ids.length
|
||||
)
|
||||
end
|
||||
|
||||
def log_warning(vulnerability)
|
||||
::Gitlab::BackgroundMigration::Logger.warn(
|
||||
migrator: self.class.name,
|
||||
message: 'Could not update vulnerability!',
|
||||
vulnerability_id: vulnerability.id
|
||||
)
|
||||
end
|
||||
|
||||
def log_error(error, vulnerability)
|
||||
::Gitlab::BackgroundMigration::Logger.error(
|
||||
migrator: self.class.name,
|
||||
message: error.message,
|
||||
vulnerability_id: vulnerability.id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2226,6 +2226,9 @@ msgstr ""
|
|||
msgid "Action"
|
||||
msgstr ""
|
||||
|
||||
msgid "Action not allowed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Action to take when receiving an alert. %{docsLink}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33326,6 +33329,12 @@ msgstr ""
|
|||
msgid "Preferences|Diff colors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences|Disable follow users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences|Disable follow users feature"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences|Display time in 24-hour format"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33404,6 +33413,9 @@ msgstr ""
|
|||
msgid "Preferences|Time preferences"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences|Turns off the ability to follow or be followed by other users."
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences|Use relative times"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rack/utils'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
# This cop prevents from using deprecated `track_redis_hll_event` method.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# track_redis_hll_event :show, name: 'p_analytics_valuestream'
|
||||
#
|
||||
# # good
|
||||
# track_event :show, name: 'g_analytics_valuestream', destinations: [:redis_hll]
|
||||
class DeprecateTrackRedisHLLEvent < RuboCop::Cop::Base
|
||||
MSG = '`track_redis_hll_event` is deprecated. Use `track_event` helper instead. ' \
|
||||
'See https://docs.gitlab.com/ee/development/service_ping/implement.html#add-new-events'
|
||||
|
||||
def_node_matcher :track_redis_hll_event_used?, <<~PATTERN
|
||||
(send _ :track_redis_hll_event ...)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless track_redis_hll_event_used?(node)
|
||||
|
||||
add_offense(node.loc.selector)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RedisTracking do
|
||||
include TrackingHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
controller(ApplicationController) do
|
||||
include RedisTracking
|
||||
|
||||
skip_before_action :authenticate_user!, only: :show
|
||||
track_redis_hll_event(:index, :show,
|
||||
name: 'g_compliance_approval_rules',
|
||||
if: [:custom_condition_one?, :custom_condition_two?]) { |controller| controller.get_custom_id }
|
||||
|
||||
def index
|
||||
render html: 'index'
|
||||
end
|
||||
|
||||
def new
|
||||
render html: 'new'
|
||||
end
|
||||
|
||||
def show
|
||||
render html: 'show'
|
||||
end
|
||||
|
||||
def get_custom_id
|
||||
'some_custom_id'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def custom_condition_one?
|
||||
true
|
||||
end
|
||||
|
||||
def custom_condition_two?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def expect_tracking
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
|
||||
.with('g_compliance_approval_rules', values: instance_of(String))
|
||||
end
|
||||
|
||||
def expect_no_tracking
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
|
||||
end
|
||||
|
||||
context 'when user is logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'tracks the event' do
|
||||
expect_tracking
|
||||
|
||||
get :index
|
||||
end
|
||||
|
||||
it 'tracks the event if DNT is not enabled' do
|
||||
stub_do_not_track('0')
|
||||
|
||||
expect_tracking
|
||||
|
||||
get :index
|
||||
end
|
||||
|
||||
it 'does not track the event if DNT is enabled' do
|
||||
stub_do_not_track('1')
|
||||
|
||||
expect_no_tracking
|
||||
|
||||
get :index
|
||||
end
|
||||
|
||||
it 'does not track the event if the format is not HTML' do
|
||||
expect_no_tracking
|
||||
|
||||
get :index, format: :json
|
||||
end
|
||||
|
||||
it 'does not track the event if a custom condition returns false' do
|
||||
expect(controller).to receive(:custom_condition_two?).and_return(false)
|
||||
|
||||
expect_no_tracking
|
||||
|
||||
get :index
|
||||
end
|
||||
|
||||
it 'does not track the event for untracked actions' do
|
||||
expect_no_tracking
|
||||
|
||||
get :new
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not logged in' do
|
||||
let(:visitor_id) { SecureRandom.uuid }
|
||||
|
||||
it 'tracks the event when there is a visitor id' do
|
||||
cookies[:visitor_id] = { value: visitor_id, expires: 24.months }
|
||||
|
||||
expect_tracking
|
||||
|
||||
get :show, params: { id: 1 }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not logged in and there is no visitor_id' do
|
||||
it 'does not track the event' do
|
||||
expect_no_tracking
|
||||
|
||||
get :index
|
||||
end
|
||||
|
||||
it 'tracks the event when there is custom id' do
|
||||
expect_tracking
|
||||
|
||||
get :show, params: { id: 1 }
|
||||
end
|
||||
|
||||
it 'does not track the event when there is no custom id' do
|
||||
expect(controller).to receive(:get_custom_id).and_return(nil)
|
||||
|
||||
expect_no_tracking
|
||||
|
||||
get :show, params: { id: 2 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -109,5 +109,33 @@ RSpec.describe Profiles::PreferencesController do
|
|||
expect(response.parsed_body['type']).to eq('alert')
|
||||
end
|
||||
end
|
||||
|
||||
context 'on disable_follow_users feature flag' do
|
||||
context 'with feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(disable_follow_users: false)
|
||||
end
|
||||
|
||||
it 'does not update enabled_following preference of user' do
|
||||
prefs = { enabled_following: false }
|
||||
|
||||
go params: prefs
|
||||
user.reload
|
||||
|
||||
expect(user.enabled_following).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag enabled' do
|
||||
it 'does not update enabled_following preference of user' do
|
||||
prefs = { enabled_following: false }
|
||||
|
||||
go params: prefs
|
||||
user.reload
|
||||
|
||||
expect(user.enabled_following).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ RSpec.describe 'Jobs (JavaScript fixtures)' do
|
|||
|
||||
shared_examples 'graphql queries' do |path, jobs_query|
|
||||
let_it_be(:variables) { {} }
|
||||
let_it_be(:success_path) { '' }
|
||||
|
||||
let_it_be(:query) do
|
||||
get_graphql_query_as_string("#{path}/#{jobs_query}")
|
||||
|
|
@ -57,9 +58,10 @@ RSpec.describe 'Jobs (JavaScript fixtures)' do
|
|||
|
||||
fixtures_path = 'graphql/jobs/'
|
||||
|
||||
it "#{fixtures_path}#{jobs_query}.json" do
|
||||
it "#{fixtures_path}#{jobs_query}.json", :aggregate_failures do
|
||||
post_graphql(query, current_user: user, variables: variables)
|
||||
|
||||
expect(graphql_data.dig(*success_path)).not_to be_nil
|
||||
expect_graphql_errors_to_be_empty
|
||||
end
|
||||
|
||||
|
|
@ -87,15 +89,18 @@ RSpec.describe 'Jobs (JavaScript fixtures)' do
|
|||
|
||||
it_behaves_like 'graphql queries', 'jobs/components/table/graphql/queries', 'get_jobs.query.graphql' do
|
||||
let(:variables) { { fullPath: 'frontend-fixtures/builds-project' } }
|
||||
let(:success_path) { %w[project jobs] }
|
||||
end
|
||||
|
||||
it_behaves_like 'graphql queries', 'pages/admin/jobs/components/table/graphql/queries', 'get_all_jobs.query.graphql' do
|
||||
let(:user) { create(:admin) }
|
||||
let(:success_path) { 'jobs' }
|
||||
end
|
||||
|
||||
it_behaves_like 'graphql queries', 'pages/admin/jobs/components/table/graphql/queries', 'get_cancelable_jobs_count.query.graphql' do
|
||||
let(:variables) { { statuses: %w[PENDING RUNNING] } }
|
||||
let(:user) { create(:admin) }
|
||||
let(:success_path) { %w[cancelable count] }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -30,21 +30,63 @@ RSpec.describe API::Entities::User do
|
|||
end
|
||||
|
||||
%i(followers following is_followed).each do |relationship|
|
||||
context 'when current user cannot read user profile' do
|
||||
let(:can_read_user_profile) { false }
|
||||
|
||||
shared_examples 'does not expose relationship' do
|
||||
it "does not expose #{relationship}" do
|
||||
expect(subject).not_to include(relationship)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current user can read user profile' do
|
||||
let(:can_read_user_profile) { true }
|
||||
|
||||
shared_examples 'exposes relationship' do
|
||||
it "exposes #{relationship}" do
|
||||
expect(subject).to include(relationship)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current user cannot read user profile' do
|
||||
let(:can_read_user_profile) { false }
|
||||
|
||||
it_behaves_like 'does not expose relationship'
|
||||
end
|
||||
|
||||
context 'when current user can read user profile' do
|
||||
let(:can_read_user_profile) { true }
|
||||
|
||||
it_behaves_like 'exposes relationship'
|
||||
end
|
||||
|
||||
context 'when current user can read user profile and disable_follow_users is switched off' do
|
||||
let(:can_read_user_profile) { true }
|
||||
|
||||
before do
|
||||
stub_feature_flags(disable_follow_users: false)
|
||||
user.enabled_following = false
|
||||
user.save!
|
||||
end
|
||||
|
||||
it_behaves_like 'exposes relationship'
|
||||
end
|
||||
|
||||
context 'when current user can read user profile, disable_follow_users is switched on and user disabled it for themself' do
|
||||
let(:can_read_user_profile) { true }
|
||||
|
||||
before do
|
||||
user.enabled_following = false
|
||||
user.save!
|
||||
end
|
||||
|
||||
it_behaves_like 'does not expose relationship'
|
||||
end
|
||||
|
||||
context 'when current user can read user profile, disable_follow_users is switched on and current user disabled it for themself' do
|
||||
let(:can_read_user_profile) { true }
|
||||
|
||||
before do
|
||||
current_user.enabled_following = false
|
||||
current_user.save!
|
||||
end
|
||||
|
||||
it_behaves_like 'does not expose relationship'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::PopulateVulnerabilityDismissalFields, schema: 20230412185837, feature_category: :vulnerability_management do # rubocop:disable Layout/LineLength
|
||||
let(:users) { table(:users) }
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:projects) { table(:projects) }
|
||||
let(:vulnerabilities) { table(:vulnerabilities) }
|
||||
let(:findings) { table(:vulnerability_occurrences) }
|
||||
let(:scanners) { table(:vulnerability_scanners) }
|
||||
let(:identifiers) { table(:vulnerability_identifiers) }
|
||||
let(:feedback) { table(:vulnerability_feedback) }
|
||||
|
||||
let(:user) { users.create!(name: 'test', email: 'test@example.com', projects_limit: 5) }
|
||||
let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
|
||||
let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo', project_namespace_id: namespace.id) }
|
||||
let(:vulnerability_1) do
|
||||
vulnerabilities.create!(title: 'title', state: 2, severity: 0,
|
||||
confidence: 5, report_type: 2, project_id: project.id, author_id: user.id
|
||||
)
|
||||
end
|
||||
|
||||
let(:vulnerability_2) do
|
||||
vulnerabilities.create!(title: 'title', state: 2, severity: 0,
|
||||
confidence: 5, report_type: 2, project_id: project.id, author_id: user.id
|
||||
)
|
||||
end
|
||||
|
||||
let(:scanner) { scanners.create!(project_id: project.id, external_id: 'foo', name: 'bar') }
|
||||
let(:identifier) do
|
||||
identifiers.create!(project_id: project.id, fingerprint: 'foo',
|
||||
external_type: 'bar', external_id: 'zoo', name: 'identifier'
|
||||
)
|
||||
end
|
||||
|
||||
let(:uuid) { SecureRandom.uuid }
|
||||
|
||||
before do
|
||||
feedback.create!(feedback_type: 0,
|
||||
category: 'sast',
|
||||
project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1fa8',
|
||||
project_id: project.id,
|
||||
author_id: user.id,
|
||||
created_at: Time.current,
|
||||
finding_uuid: uuid
|
||||
)
|
||||
|
||||
findings.create!(name: 'Finding',
|
||||
report_type: 'sast',
|
||||
project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1f98',
|
||||
location_fingerprint: 'bar',
|
||||
severity: 1,
|
||||
confidence: 1,
|
||||
metadata_version: 1,
|
||||
raw_metadata: '',
|
||||
details: {},
|
||||
uuid: uuid,
|
||||
project_id: project.id,
|
||||
vulnerability_id: vulnerability_1.id,
|
||||
scanner_id: scanner.id,
|
||||
primary_identifier_id: identifier.id
|
||||
)
|
||||
|
||||
allow(::Gitlab::BackgroundMigration::Logger).to receive_messages(info: true, warn: true, error: true)
|
||||
end
|
||||
|
||||
subject do
|
||||
described_class.new(
|
||||
start_id: vulnerability_1.id,
|
||||
end_id: vulnerability_2.id,
|
||||
batch_table: :vulnerabilities,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 200,
|
||||
pause_ms: 2.minutes,
|
||||
connection: ApplicationRecord.connection
|
||||
)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
it 'updates the missing dismissal information of the vulnerability' do
|
||||
expect { subject.perform }.to change { vulnerability_1.reload.dismissed_at }
|
||||
.from(nil)
|
||||
.and change { vulnerability_1.reload.dismissed_by_id }.from(nil).to(user.id)
|
||||
end
|
||||
|
||||
it 'writes log messages', :aggregate_failures do
|
||||
subject.perform
|
||||
|
||||
expect(::Gitlab::BackgroundMigration::Logger).to have_received(:info).with(migrator: described_class.name,
|
||||
message: 'Dismissal information has been copied',
|
||||
count: 2
|
||||
)
|
||||
expect(::Gitlab::BackgroundMigration::Logger).to have_received(:warn).with(migrator: described_class.name,
|
||||
message: 'Could not update vulnerability!',
|
||||
vulnerability_id: vulnerability_2.id
|
||||
)
|
||||
end
|
||||
|
||||
context 'when logger throws exception StandardError' do
|
||||
before do
|
||||
allow(::Gitlab::BackgroundMigration::Logger).to receive(:warn).and_raise(StandardError)
|
||||
end
|
||||
|
||||
it 'logs StandardError' do
|
||||
expect(::Gitlab::BackgroundMigration::Logger).to receive(:error).with({
|
||||
migrator: described_class.name, message: StandardError.to_s, vulnerability_id: vulnerability_2.id
|
||||
})
|
||||
|
||||
subject.perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueuePopulateVulnerabilityDismissalFields, feature_category: :vulnerability_management do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
describe '#up' do
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :vulnerabilities,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#down' do
|
||||
it 'deletes all batched migration records' do
|
||||
migrate!
|
||||
schema_migrate_down!
|
||||
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3840,6 +3840,54 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
|
||||
expect(user.following?(followee)).to be_falsey
|
||||
end
|
||||
|
||||
it 'does not follow if user disabled following' do
|
||||
user = create(:user)
|
||||
user.enabled_following = false
|
||||
|
||||
followee = create(:user)
|
||||
|
||||
expect(user.follow(followee)).to eq(false)
|
||||
|
||||
expect(user.following?(followee)).to be_falsey
|
||||
end
|
||||
|
||||
it 'does not follow if followee user disabled following' do
|
||||
user = create(:user)
|
||||
|
||||
followee = create(:user)
|
||||
followee.enabled_following = false
|
||||
|
||||
expect(user.follow(followee)).to eq(false)
|
||||
|
||||
expect(user.following?(followee)).to be_falsey
|
||||
end
|
||||
|
||||
context 'when disable_follow_users feature flag is off' do
|
||||
before do
|
||||
stub_feature_flags(disable_follow_users: false)
|
||||
end
|
||||
|
||||
it 'follows user even if user disabled following' do
|
||||
user = create(:user)
|
||||
user.enabled_following = false
|
||||
|
||||
followee = create(:user)
|
||||
|
||||
expect(user.follow(followee)).to be_truthy
|
||||
expect(user.following?(followee)).to be_truthy
|
||||
end
|
||||
|
||||
it 'follows user even if followee user disabled following' do
|
||||
user = create(:user)
|
||||
|
||||
followee = create(:user)
|
||||
followee.enabled_following = false
|
||||
|
||||
expect(user.follow(followee)).to be_truthy
|
||||
expect(user.following?(followee)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unfollow' do
|
||||
|
|
@ -3882,6 +3930,39 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#following_users_allowed?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:followee) { create(:user) }
|
||||
|
||||
where(:user_enabled_following, :followee_enabled_following, :feature_flag_status, :result) do
|
||||
true | true | false | true
|
||||
true | false | false | true
|
||||
true | true | true | true
|
||||
true | false | true | false
|
||||
false | true | false | true
|
||||
false | true | true | false
|
||||
false | false | false | true
|
||||
false | false | true | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
user.enabled_following = user_enabled_following
|
||||
followee.enabled_following = followee_enabled_following
|
||||
followee.save!
|
||||
stub_feature_flags(disable_follow_users: feature_flag_status)
|
||||
end
|
||||
|
||||
it { expect(user.following_users_allowed?(followee)).to eq result }
|
||||
end
|
||||
|
||||
it 'is false when user and followee is the same user' do
|
||||
expect(user.following_users_allowed?(user)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notification_email_or_default' do
|
||||
let(:email) { 'gonzo@muppets.com' }
|
||||
|
||||
|
|
|
|||
|
|
@ -1009,6 +1009,20 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
|
|||
expect(response).to have_gitlab_http_status(:not_modified)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on a user with disabled following' do
|
||||
before do
|
||||
user.enabled_following = false
|
||||
user.save!
|
||||
end
|
||||
|
||||
it 'does not change following' do
|
||||
post api("/users/#{followee.id}/follow", user)
|
||||
|
||||
expect(user.followees).to be_empty
|
||||
expect(response).to have_gitlab_http_status(:not_modified)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /users/:id/unfollow' do
|
||||
|
|
|
|||
|
|
@ -914,6 +914,35 @@ RSpec.describe UsersController, feature_category: :user_management do
|
|||
expect(user).not_to be_following(public_user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user or followee disabled following' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'alerts and not follow if user disabled following' do
|
||||
user.enabled_following = false
|
||||
|
||||
post user_follow_url(username: public_user.username)
|
||||
expect(response).to be_redirect
|
||||
|
||||
expected_message = format(_('Action not allowed.'))
|
||||
expect(flash[:alert]).to eq(expected_message)
|
||||
expect(user).not_to be_following(public_user)
|
||||
end
|
||||
|
||||
it 'alerts and not follow if followee disabled following' do
|
||||
public_user.enabled_following = false
|
||||
public_user.save!
|
||||
|
||||
post user_follow_url(username: public_user.username)
|
||||
expect(response).to be_redirect
|
||||
|
||||
expected_message = format(_('Action not allowed.'))
|
||||
expect(flash[:alert]).to eq(expected_message)
|
||||
expect(user).not_to be_following(public_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'token authentication' do
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop_spec_helper'
|
||||
require_relative '../../../../rubocop/cop/gitlab/deprecate_track_redis_hll_event'
|
||||
|
||||
RSpec.describe RuboCop::Cop::Gitlab::DeprecateTrackRedisHLLEvent do
|
||||
it 'does not flag the use of track_event' do
|
||||
expect_no_offenses('track_event :show, name: "p_analytics_insights"')
|
||||
end
|
||||
|
||||
it 'flags the use of track_redis_hll_event' do
|
||||
expect_offense(<<~SOURCE)
|
||||
track_redis_hll_event :show, name: 'p_analytics_valuestream'
|
||||
^^^^^^^^^^^^^^^^^^^^^ `track_redis_hll_event` is deprecated[...]
|
||||
SOURCE
|
||||
end
|
||||
end
|
||||
|
|
@ -185,6 +185,49 @@ RSpec.describe Users::UpdateService, feature_category: :user_profile do
|
|||
end.not_to raise_error
|
||||
end
|
||||
|
||||
describe 'updates the enabled_following' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
3.times do
|
||||
user.follow(create(:user))
|
||||
create(:user).follow(user)
|
||||
end
|
||||
user.reload
|
||||
end
|
||||
|
||||
it 'removes followers and followees' do
|
||||
expect do
|
||||
update_user(user, enabled_following: false)
|
||||
end.to change { user.followed_users.count }.from(3).to(0)
|
||||
.and change { user.following_users.count }.from(3).to(0)
|
||||
expect(user.enabled_following).to eq(false)
|
||||
end
|
||||
|
||||
it 'does not remove followers/followees if feature flag is off' do
|
||||
stub_feature_flags(disable_follow_users: false)
|
||||
|
||||
expect do
|
||||
update_user(user, enabled_following: false)
|
||||
end.to not_change { user.followed_users.count }
|
||||
.and not_change { user.following_users.count }
|
||||
end
|
||||
|
||||
context 'when there is more followers/followees then batch limit' do
|
||||
before do
|
||||
stub_env('BATCH_SIZE', 1)
|
||||
end
|
||||
|
||||
it 'removes followers and followees' do
|
||||
expect do
|
||||
update_user(user, enabled_following: false)
|
||||
end.to change { user.followed_users.count }.from(3).to(0)
|
||||
.and change { user.following_users.count }.from(3).to(0)
|
||||
expect(user.enabled_following).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_user(user, opts)
|
||||
described_class.new(user, opts.merge(user: user)).execute
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue