Commit 1cbe941e authored by Doug Stull's avatar Doug Stull Committed by Mayra Cabrera

Organize callouts code under users

parent c1f3d5e8
......@@ -543,7 +543,7 @@ Rails/LexicallyScopedActionFilter:
Rails/LinkToBlank:
Exclude:
- 'app/helpers/projects_helper.rb'
- 'ee/app/helpers/ee/user_callouts_helper.rb'
- 'ee/app/helpers/ee/users/callouts_helper.rb'
# Offense count: 1
# Cop supports --auto-correct.
......
......@@ -16,7 +16,7 @@ Cop/UserAdmin:
- app/helpers/nav_helper.rb
- app/helpers/projects_helper.rb
- app/helpers/search_helper.rb
- app/helpers/user_callouts_helper.rb
- app/helpers/users/callouts_helper.rb
- app/helpers/users_helper.rb
- app/helpers/visibility_level_helper.rb
- app/models/concerns/protected_ref_access.rb
......@@ -38,7 +38,7 @@ Cop/UserAdmin:
- ee/app/helpers/ee/dashboard_helper.rb
- ee/app/helpers/ee/import_helper.rb
- ee/app/helpers/ee/subscribable_banner_helper.rb
- ee/app/helpers/ee/user_callouts_helper.rb
- ee/app/helpers/ee/users/callouts_helper.rb
- ee/app/helpers/license_monitoring_helper.rb
- ee/app/helpers/push_rules_helper.rb
- ee/app/models/concerns/ee/protected_ref_access.rb
......
......@@ -32,7 +32,6 @@ Gitlab/NamespacedClass:
- app/controllers/sessions_controller.rb
- app/controllers/snippets_controller.rb
- app/controllers/uploads_controller.rb
- app/controllers/user_callouts_controller.rb
- app/controllers/users_controller.rb
- app/controllers/whats_new_controller.rb
- app/finders/abuse_reports_finder.rb
......@@ -351,7 +350,6 @@ Gitlab/NamespacedClass:
- app/models/upload.rb
- app/models/user.rb
- app/models/user_agent_detail.rb
- app/models/user_callout.rb
- app/models/user_canonical_email.rb
- app/models/user_custom_attribute.rb
- app/models/user_detail.rb
......
# frozen_string_literal: true
class UserCalloutsController < ApplicationController
feature_category :navigation
def create
if callout.persisted?
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :bad_request }
end
end
end
private
def callout
Users::DismissUserCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: feature_name }
).execute
end
def feature_name
params.require(:feature_name)
end
end
# frozen_string_literal: true
module Users
class CalloutsController < ApplicationController
feature_category :navigation
def create
if callout.persisted?
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :bad_request }
end
end
end
private
def callout
Users::DismissCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: feature_name }
).execute
end
def feature_name
params.require(:feature_name)
end
end
end
# frozen_string_literal: true
module Users
class GroupCalloutsController < UserCalloutsController
class GroupCalloutsController < Users::CalloutsController
private
def callout
......
......@@ -15,7 +15,7 @@ module Mutations
description: 'User callout dismissed.'
def resolve(feature_name:)
callout = Users::DismissUserCalloutService.new(
callout = Users::DismissCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: feature_name }
).execute
errors = errors_on_object(callout)
......
......@@ -5,7 +5,7 @@ module Types
graphql_name 'UserCalloutFeatureNameEnum'
description 'Name of the feature that the callout is for.'
::UserCallout.feature_names.keys.each do |feature_name|
::Users::Callout.feature_names.keys.each do |feature_name|
value feature_name.upcase, value: feature_name, description: "Callout feature name for #{feature_name}."
end
end
......
......@@ -182,7 +182,7 @@ module MergeRequestsHelper
project_path: project_path(merge_request.project),
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
is_fluid_layout: fluid_layout.to_s,
dismiss_endpoint: user_callouts_path,
dismiss_endpoint: callouts_path,
show_suggest_popover: show_suggest_popover?.to_s,
show_whitespace_default: @show_whitespace_default.to_s,
file_by_file_default: @file_by_file_default.to_s,
......
# frozen_string_literal: true
module UserCalloutsHelper
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'
GCP_SIGNUP_OFFER = 'gcp_signup_offer'
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version'
REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout'
UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout'
INVITE_MEMBERS_BANNER = 'invite_members_banner'
SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout'
def show_gke_cluster_integration_callout?(project)
active_nav_link?(controller: sidebar_operations_paths) &&
can?(current_user, :create_cluster, project) &&
!user_dismissed?(GKE_CLUSTER_INTEGRATION)
end
def show_gcp_signup_offer?
!user_dismissed?(GCP_SIGNUP_OFFER)
end
def render_flash_user_callout(flash_type, message, feature_name)
render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name
end
def render_dashboard_ultimate_trial(user)
end
def render_two_factor_auth_recovery_settings_check
end
def show_suggest_popover?
!user_dismissed?(SUGGEST_POPOVER_DISMISSED)
end
def show_customize_homepage_banner?
current_user.default_dashboard? && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
end
def show_feature_flags_new_version?
!user_dismissed?(FEATURE_FLAGS_NEW_VERSION)
end
def show_unfinished_tag_cleanup_callout?
!user_dismissed?(UNFINISHED_TAG_CLEANUP_CALLOUT)
end
def show_registration_enabled_user_callout?
!Gitlab.com? &&
current_user&.admin? &&
signup_enabled? &&
!user_dismissed?(REGISTRATION_ENABLED_CALLOUT)
end
def dismiss_two_factor_auth_recovery_settings_check
end
def show_invite_banner?(group)
Ability.allowed?(current_user, :admin_group, group) &&
!just_created? &&
!user_dismissed_for_group(INVITE_MEMBERS_BANNER, group) &&
!multiple_members?(group)
end
def show_security_newsletter_user_callout?
current_user&.admin? &&
!user_dismissed?(SECURITY_NEWSLETTER_CALLOUT)
end
private
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
return false unless current_user
current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
end
def user_dismissed_for_group(feature_name, group, ignore_dismissal_earlier_than = nil)
return false unless current_user
current_user.dismissed_callout_for_group?(feature_name: feature_name,
group: group,
ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
end
def just_created?
flash[:notice]&.include?('successfully created')
end
def multiple_members?(group)
group.member_count > 1 || group.members_with_parents.count > 1
end
end
UserCalloutsHelper.prepend_mod
# frozen_string_literal: true
module Users
module CalloutsHelper
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'
GCP_SIGNUP_OFFER = 'gcp_signup_offer'
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version'
REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout'
UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout'
SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout'
def show_gke_cluster_integration_callout?(project)
active_nav_link?(controller: sidebar_operations_paths) &&
can?(current_user, :create_cluster, project) &&
!user_dismissed?(GKE_CLUSTER_INTEGRATION)
end
def show_gcp_signup_offer?
!user_dismissed?(GCP_SIGNUP_OFFER)
end
def render_flash_user_callout(flash_type, message, feature_name)
render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name
end
def render_dashboard_ultimate_trial(user)
end
def render_two_factor_auth_recovery_settings_check
end
def show_suggest_popover?
!user_dismissed?(SUGGEST_POPOVER_DISMISSED)
end
def show_customize_homepage_banner?
current_user.default_dashboard? && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
end
def show_feature_flags_new_version?
!user_dismissed?(FEATURE_FLAGS_NEW_VERSION)
end
def show_unfinished_tag_cleanup_callout?
!user_dismissed?(UNFINISHED_TAG_CLEANUP_CALLOUT)
end
def show_registration_enabled_user_callout?
!Gitlab.com? &&
current_user&.admin? &&
signup_enabled? &&
!user_dismissed?(REGISTRATION_ENABLED_CALLOUT)
end
def dismiss_two_factor_auth_recovery_settings_check
end
def show_security_newsletter_user_callout?
current_user&.admin? &&
!user_dismissed?(SECURITY_NEWSLETTER_CALLOUT)
end
private
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
return false unless current_user
current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
end
end
end
Users::CalloutsHelper.prepend_mod
# frozen_string_literal: true
module Users
module GroupCalloutsHelper
INVITE_MEMBERS_BANNER = 'invite_members_banner'
def show_invite_banner?(group)
Ability.allowed?(current_user, :admin_group, group) &&
!just_created? &&
!user_dismissed_for_group(INVITE_MEMBERS_BANNER, group) &&
!multiple_members?(group)
end
private
def user_dismissed_for_group(feature_name, group, ignore_dismissal_earlier_than = nil)
return false unless current_user
current_user.dismissed_callout_for_group?(feature_name: feature_name,
group: group,
ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
end
def just_created?
flash[:notice]&.include?('successfully created')
end
def multiple_members?(group)
group.member_count > 1 || group.members_with_parents.count > 1
end
end
end
# frozen_string_literal: true
module Calloutable
extend ActiveSupport::Concern
included do
belongs_to :user
validates :user, presence: true
end
def dismissed_after?(dismissed_after)
dismissed_at > dismissed_after
end
end
......@@ -206,7 +206,7 @@ class User < ApplicationRecord
has_many :bulk_imports
has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
has_many :callouts, class_name: 'Users::Callout'
has_many :group_callouts, class_name: 'Users::GroupCallout'
has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
......@@ -1949,7 +1949,7 @@ class User < ApplicationRecord
end
def find_or_initialize_callout(feature_name)
callouts.find_or_initialize_by(feature_name: ::UserCallout.feature_names[feature_name])
callouts.find_or_initialize_by(feature_name: ::Users::Callout.feature_names[feature_name])
end
def find_or_initialize_group_callout(feature_name, group_id)
......
# frozen_string_literal: true
class UserCallout < ApplicationRecord
include Calloutable
enum feature_name: {
gke_cluster_integration: 1,
gcp_signup_offer: 2,
cluster_security_warning: 3,
ultimate_trial: 4, # EE-only
geo_enable_hashed_storage: 5, # EE-only
geo_migrate_hashed_storage: 6, # EE-only
canary_deployment: 7, # EE-only
gold_trial_billings: 8, # EE-only
suggest_popover_dismissed: 9,
tabs_position_highlight: 10,
threat_monitoring_info: 11, # EE-only
two_factor_auth_recovery_settings_check: 12, # EE-only
web_ide_alert_dismissed: 16, # no longer in use
active_user_count_threshold: 18, # EE-only
buy_pipeline_minutes_notification_dot: 19, # EE-only
personal_access_token_expiry: 21, # EE-only
suggest_pipeline: 22,
customize_homepage: 23,
feature_flags_new_version: 24,
registration_enabled_callout: 25,
new_user_signups_cap_reached: 26, # EE-only
unfinished_tag_cleanup_callout: 27,
eoa_bronze_plan_banner: 28, # EE-only
pipeline_needs_banner: 29,
pipeline_needs_hover_tip: 30,
web_ide_ci_environments_guidance: 31,
security_configuration_upgrade_banner: 32,
cloud_licensing_subscription_activation_banner: 33, # EE-only
trial_status_reminder_d14: 34, # EE-only
trial_status_reminder_d3: 35, # EE-only
security_configuration_devops_alert: 36, # EE-only
profile_personal_access_token_expiry: 37, # EE-only
terraform_notification_dismissed: 38,
security_newsletter_callout: 39,
verification_reminder: 40 # EE-only
}
validates :feature_name,
presence: true,
uniqueness: { scope: :user_id },
inclusion: { in: UserCallout.feature_names.keys }
end
# frozen_string_literal: true
module Users
class Callout < ApplicationRecord
include Users::Calloutable
self.table_name = 'user_callouts'
enum feature_name: {
gke_cluster_integration: 1,
gcp_signup_offer: 2,
cluster_security_warning: 3,
ultimate_trial: 4, # EE-only
geo_enable_hashed_storage: 5, # EE-only
geo_migrate_hashed_storage: 6, # EE-only
canary_deployment: 7, # EE-only
gold_trial_billings: 8, # EE-only
suggest_popover_dismissed: 9,
tabs_position_highlight: 10,
threat_monitoring_info: 11, # EE-only
two_factor_auth_recovery_settings_check: 12, # EE-only
web_ide_alert_dismissed: 16, # no longer in use
active_user_count_threshold: 18, # EE-only
buy_pipeline_minutes_notification_dot: 19, # EE-only
personal_access_token_expiry: 21, # EE-only
suggest_pipeline: 22,
customize_homepage: 23,
feature_flags_new_version: 24,
registration_enabled_callout: 25,
new_user_signups_cap_reached: 26, # EE-only
unfinished_tag_cleanup_callout: 27,
eoa_bronze_plan_banner: 28, # EE-only
pipeline_needs_banner: 29,
pipeline_needs_hover_tip: 30,
web_ide_ci_environments_guidance: 31,
security_configuration_upgrade_banner: 32,
cloud_licensing_subscription_activation_banner: 33, # EE-only
trial_status_reminder_d14: 34, # EE-only
trial_status_reminder_d3: 35, # EE-only
security_configuration_devops_alert: 36, # EE-only
profile_personal_access_token_expiry: 37, # EE-only
terraform_notification_dismissed: 38,
security_newsletter_callout: 39,
verification_reminder: 40 # EE-only
}
validates :feature_name,
presence: true,
uniqueness: { scope: :user_id },
inclusion: { in: Users::Callout.feature_names.keys }
end
end
# frozen_string_literal: true
module Users
module Calloutable
extend ActiveSupport::Concern
included do
belongs_to :user
validates :user, presence: true
end
def dismissed_after?(dismissed_after)
dismissed_at > dismissed_after
end
end
end
......@@ -2,7 +2,7 @@
module Users
class GroupCallout < ApplicationRecord
include Calloutable
include Users::Calloutable
self.table_name = 'user_group_callouts'
......
......@@ -73,7 +73,7 @@ class MergeRequestWidgetEntity < Grape::Entity
end
expose :user_callouts_path do |_merge_request|
user_callouts_path
callouts_path
end
expose :suggest_pipeline_feature_id do |_merge_request|
......
# frozen_string_literal: true
module Users
class DismissUserCalloutService < BaseContainerService
class DismissCalloutService < BaseContainerService
def execute
callout.tap do |record|
record.update(dismissed_at: Time.current) if record.valid?
......
# frozen_string_literal: true
module Users
class DismissGroupCalloutService < DismissUserCalloutService
class DismissGroupCalloutService < DismissCalloutService
private
def callout
......
......@@ -5,7 +5,7 @@
variant: :tip,
alert_class: 'js-security-newsletter-callout',
is_contained: true,
alert_data: { feature_id: UserCalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: user_callouts_path, defer_links: 'true' },
alert_data: { feature_id: Users::CalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' },
close_button_data: { testid: 'close-security-newsletter-callout' } do
.gl-alert-body
= s_('AdminArea|Sign up for the GitLab Security Newsletter to get notified for security updates.')
......
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
.gcp-signup-offer.gl-alert.gl-alert-info.gl-my-3{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
.gcp-signup-offer.gl-alert.gl-alert-info.gl-my-3{ role: 'alert', data: { feature_id: Users::CalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: callouts_path } }
.gl-alert-container
%button.js-close.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', size: 16, css_class: 'gl-icon')
......
......@@ -18,6 +18,6 @@
"gid_prefix": container_repository_gid_prefix,
connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
user_callouts_path: callouts_path,
user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } }
......@@ -15,7 +15,7 @@
track_label: 'invite_members_banner',
invite_members_path: group_group_members_path(@group),
callouts_path: group_callouts_path,
callouts_feature_id: UserCalloutsHelper::INVITE_MEMBERS_BANNER,
callouts_feature_id: Users::GroupCalloutsHelper::INVITE_MEMBERS_BANNER,
group_id: @group.id } }
= render 'groups/invite_members_modal', group: @group
......
......@@ -4,7 +4,7 @@
title: _('Open registration is enabled on your instance.'),
variant: :warning,
alert_class: 'js-registration-enabled-callout',
alert_data: { feature_id: UserCalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: user_callouts_path },
alert_data: { feature_id: Users::CalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: callouts_path },
close_button_data: { testid: 'close-registration-enabled-callout' } do
.gl-alert-body
= html_escape(_('%{anchorOpen}Learn more%{anchorClose} about how you can customize / disable registration on your instance.')) % { anchorOpen: "<a href=\"#{help_page_path('user/admin_area/settings/sign_up_restrictions')}\" class=\"gl-link\">".html_safe, anchorClose: '</a>'.html_safe }
......
......@@ -6,8 +6,8 @@
#js-new-feature-flag{ data: { endpoint: project_feature_flags_path(@project, format: :json),
feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
user_callouts_path: callouts_path,
user_callout_id: Users::CalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments/index.md', anchor: 'scope-environments-with-specs'),
......
......@@ -22,6 +22,6 @@
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
user_callouts_path: callouts_path,
user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } }
......@@ -3,8 +3,8 @@
.gl-display-none.gl-md-display-block{ class: "gl-pt-6! gl-pb-2! #{(container_class unless @no_container)} #{@content_class}" }
.js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
callouts_path: user_callouts_path,
callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE,
callouts_path: callouts_path,
callouts_feature_id: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE,
track_label: 'home_page' } }
= render template: 'dashboard/projects/index'
- callout_data = { uid: "callout_feature_#{feature_name}_dismissed", feature_id: feature_name, dismiss_endpoint: user_callouts_path }
- callout_data = { uid: "callout_feature_#{feature_name}_dismissed", feature_id: feature_name, dismiss_endpoint: callouts_path }
- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil)
.flash-container.flash-container-page.user-callout{ data: callout_data }
......
= render 'shared/global_alert',
variant: :warning,
alert_class: 'js-recovery-settings-callout',
alert_data: { feature_id: UserCalloutsHelper::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, dismiss_endpoint: user_callouts_path, defer_links: 'true' },
alert_data: { feature_id: Users::CalloutsHelper::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, dismiss_endpoint: callouts_path, defer_links: 'true' },
close_button_data: { testid: 'close-account-recovery-regular-check-callout' } do
.gl-alert-body
= s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.')
......
......@@ -145,7 +145,7 @@ Rails.application.routes.draw do
get 'acme-challenge/' => 'acme_challenges#show'
# UserCallouts
resources :user_callouts, only: [:create]
resources :user_callouts, controller: 'users/callouts', only: [:create] # remove after 14.6 2021-12-22 to handle mixed deployments
scope :ide, as: :ide, format: false do
get '/', to: 'ide#index'
......
......@@ -61,6 +61,7 @@ scope '-/users', module: :users do
post :decline, on: :member
end
resources :callouts, only: [:create]
resources :group_callouts, only: [:create]
end
......
# frozen_string_literal: true
module EE
module UserCalloutsHelper
extend ::Gitlab::Utils::Override
TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK = 'two_factor_auth_recovery_settings_check'
ACTIVE_USER_COUNT_THRESHOLD = 'active_user_count_threshold'
GEO_ENABLE_HASHED_STORAGE = 'geo_enable_hashed_storage'
GEO_MIGRATE_HASHED_STORAGE = 'geo_migrate_hashed_storage'
ULTIMATE_TRIAL = 'ultimate_trial'
NEW_USER_SIGNUPS_CAP_REACHED = 'new_user_signups_cap_reached'
PERSONAL_ACCESS_TOKEN_EXPIRY = 'personal_access_token_expiry'
EOA_BRONZE_PLAN_BANNER = 'eoa_bronze_plan_banner'
EOA_BRONZE_PLAN_END_DATE = '2022-01-26'
CL_SUBSCRIPTION_ACTIVATION = 'cloud_licensing_subscription_activation_banner'
PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY = 'profile_personal_access_token_expiry'
def render_enable_hashed_storage_warning
return unless show_enable_hashed_storage_warning?
message = enable_hashed_storage_warning_message
render_flash_user_callout(:warning, message, GEO_ENABLE_HASHED_STORAGE)
end
def render_migrate_hashed_storage_warning
return unless show_migrate_hashed_storage_warning?
message = migrate_hashed_storage_warning_message
render_flash_user_callout(:warning, message, GEO_MIGRATE_HASHED_STORAGE)
end
def show_enable_hashed_storage_warning?
return if hashed_storage_enabled?
!user_dismissed?(GEO_ENABLE_HASHED_STORAGE)
end
def show_migrate_hashed_storage_warning?
return unless hashed_storage_enabled?
return if user_dismissed?(GEO_MIGRATE_HASHED_STORAGE)
any_project_not_in_hashed_storage?
end
override :render_dashboard_ultimate_trial
def render_dashboard_ultimate_trial(user)
return unless show_ultimate_trial?(user, ULTIMATE_TRIAL) &&
user_default_dashboard?(user) &&
!user.owns_paid_namespace? &&
user.owns_group_without_trial?
render 'shared/ultimate_trial_callout_content'
end
def render_two_factor_auth_recovery_settings_check
return unless current_user &&
::Gitlab.com? &&
current_user.two_factor_otp_enabled? &&
!user_dismissed?(TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, 3.months.ago)
render 'shared/two_factor_auth_recovery_settings_check'
end
def show_token_expiry_notification?
return false unless current_user
!token_expiration_enforced? &&
current_user.active? &&
!user_dismissed?(PERSONAL_ACCESS_TOKEN_EXPIRY, 1.week.ago)
end
def show_profile_token_expiry_notification?
!token_expiration_enforced? && !user_dismissed?(PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY, 1.day.ago)
end
def show_new_user_signups_cap_reached?
return false unless current_user&.admin?
return false if user_dismissed?(NEW_USER_SIGNUPS_CAP_REACHED)
new_user_signups_cap = ::Gitlab::CurrentSettings.current_application_settings.new_user_signups_cap
return false if new_user_signups_cap.nil?
new_user_signups_cap.to_i <= ::User.billable.count
end
def show_eoa_bronze_plan_banner?(namespace)
return false unless ::Feature.enabled?(:show_billing_eoa_banner)
return false unless Date.current < eoa_bronze_plan_end_date
return false unless namespace.bronze_plan?
return false if user_dismissed?(EOA_BRONZE_PLAN_BANNER)
(namespace.group_namespace? && namespace.has_owner?(current_user.id)) || !namespace.group_namespace?
end
override :dismiss_two_factor_auth_recovery_settings_check
def dismiss_two_factor_auth_recovery_settings_check
::Users::DismissUserCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK }
).execute
end
def show_verification_reminder?
return false unless ::Gitlab.dev_env_or_com?
return false unless ::Feature.enabled?(:verification_reminder, default_enabled: :yaml)
return false unless current_user
return false if current_user.has_valid_credit_card?
failed_pipeline = current_user.pipelines.user_not_verified.last
failed_pipeline.present? && !user_dismissed?('verification_reminder', failed_pipeline.created_at)
end
private
def eoa_bronze_plan_end_date
Date.parse(EOA_BRONZE_PLAN_END_DATE)
end
def hashed_storage_enabled?
::Gitlab::CurrentSettings.current_application_settings.hashed_storage_enabled
end
def any_project_not_in_hashed_storage?
::Project.with_unmigrated_storage.exists?
end
def enable_hashed_storage_warning_message
message = _('Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}')
add_migrate_to_hashed_storage_link(message)
end
def migrate_hashed_storage_warning_message
message = _('Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}')
add_migrate_to_hashed_storage_link(message)
end
def add_migrate_to_hashed_storage_link(message)
migrate_link = link_to(_('For more info, read the documentation.'), help_page_path('administration/raketasks/storage.md', anchor: 'migrate-to-hashed-storage'), target: '_blank')
linked_message = message % { migrate_link: migrate_link }
linked_message.html_safe
end
def show_ultimate_trial?(user, callout = ULTIMATE_TRIAL)
return false unless user
return false unless show_ultimate_trial_suitable_env?
return false if user_dismissed?(callout)
true
end
def show_ultimate_trial_suitable_env?
::Gitlab.com? && !::Gitlab::Database.read_only?
end
def token_expiration_enforced?
::PersonalAccessToken.expiration_enforced?
end
def current_settings
end
end
end
# frozen_string_literal: true
module EE
module Users
module CalloutsHelper
extend ::Gitlab::Utils::Override
TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK = 'two_factor_auth_recovery_settings_check'
ACTIVE_USER_COUNT_THRESHOLD = 'active_user_count_threshold'
GEO_ENABLE_HASHED_STORAGE = 'geo_enable_hashed_storage'
GEO_MIGRATE_HASHED_STORAGE = 'geo_migrate_hashed_storage'
ULTIMATE_TRIAL = 'ultimate_trial'
NEW_USER_SIGNUPS_CAP_REACHED = 'new_user_signups_cap_reached'
PERSONAL_ACCESS_TOKEN_EXPIRY = 'personal_access_token_expiry'
EOA_BRONZE_PLAN_BANNER = 'eoa_bronze_plan_banner'
EOA_BRONZE_PLAN_END_DATE = '2022-01-26'
CL_SUBSCRIPTION_ACTIVATION = 'cloud_licensing_subscription_activation_banner'
PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY = 'profile_personal_access_token_expiry'
def render_enable_hashed_storage_warning
return unless show_enable_hashed_storage_warning?
message = enable_hashed_storage_warning_message
render_flash_user_callout(:warning, message, GEO_ENABLE_HASHED_STORAGE)
end
def render_migrate_hashed_storage_warning
return unless show_migrate_hashed_storage_warning?
message = migrate_hashed_storage_warning_message
render_flash_user_callout(:warning, message, GEO_MIGRATE_HASHED_STORAGE)
end
def show_enable_hashed_storage_warning?
return if hashed_storage_enabled?
!user_dismissed?(GEO_ENABLE_HASHED_STORAGE)
end
def show_migrate_hashed_storage_warning?
return unless hashed_storage_enabled?
return if user_dismissed?(GEO_MIGRATE_HASHED_STORAGE)
any_project_not_in_hashed_storage?
end
override :render_dashboard_ultimate_trial
def render_dashboard_ultimate_trial(user)
return unless show_ultimate_trial?(user, ULTIMATE_TRIAL) &&
user_default_dashboard?(user) &&
!user.owns_paid_namespace? &&
user.owns_group_without_trial?
render 'shared/ultimate_trial_callout_content'
end
def render_two_factor_auth_recovery_settings_check
return unless current_user &&
::Gitlab.com? &&
current_user.two_factor_otp_enabled? &&
!user_dismissed?(TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, 3.months.ago)
render 'shared/two_factor_auth_recovery_settings_check'
end
def show_token_expiry_notification?
return false unless current_user
!token_expiration_enforced? &&
current_user.active? &&
!user_dismissed?(PERSONAL_ACCESS_TOKEN_EXPIRY, 1.week.ago)
end
def show_profile_token_expiry_notification?
!token_expiration_enforced? && !user_dismissed?(PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY, 1.day.ago)
end
def show_new_user_signups_cap_reached?
return false unless current_user&.admin?
return false if user_dismissed?(NEW_USER_SIGNUPS_CAP_REACHED)
new_user_signups_cap = ::Gitlab::CurrentSettings.current_application_settings.new_user_signups_cap
return false if new_user_signups_cap.nil?
new_user_signups_cap.to_i <= ::User.billable.count
end
def show_eoa_bronze_plan_banner?(namespace)
return false unless ::Feature.enabled?(:show_billing_eoa_banner)
return false unless Date.current < eoa_bronze_plan_end_date
return false unless namespace.bronze_plan?
return false if user_dismissed?(EOA_BRONZE_PLAN_BANNER)
(namespace.group_namespace? && namespace.has_owner?(current_user.id)) || !namespace.group_namespace?
end
override :dismiss_two_factor_auth_recovery_settings_check
def dismiss_two_factor_auth_recovery_settings_check
::Users::DismissCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK }
).execute
end
def show_verification_reminder?
return false unless ::Gitlab.dev_env_or_com?
return false unless ::Feature.enabled?(:verification_reminder, default_enabled: :yaml)
return false unless current_user
return false if current_user.has_valid_credit_card?
failed_pipeline = current_user.pipelines.user_not_verified.last
failed_pipeline.present? && !user_dismissed?('verification_reminder', failed_pipeline.created_at)
end
private
def eoa_bronze_plan_end_date
Date.parse(EOA_BRONZE_PLAN_END_DATE)
end
def hashed_storage_enabled?
::Gitlab::CurrentSettings.current_application_settings.hashed_storage_enabled
end
def any_project_not_in_hashed_storage?
::Project.with_unmigrated_storage.exists?
end
def enable_hashed_storage_warning_message
message = _('Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}')
add_migrate_to_hashed_storage_link(message)
end
def migrate_hashed_storage_warning_message
message = _('Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}')
add_migrate_to_hashed_storage_link(message)
end
def add_migrate_to_hashed_storage_link(message)
migrate_link = link_to(_('For more info, read the documentation.'), help_page_path('administration/raketasks/storage.md', anchor: 'migrate-to-hashed-storage'), target: '_blank')
linked_message = message % { migrate_link: migrate_link }
linked_message.html_safe
end
def show_ultimate_trial?(user, callout = ULTIMATE_TRIAL)
return false unless user
return false unless show_ultimate_trial_suitable_env?
return false if user_dismissed?(callout)
true
end
def show_ultimate_trial_suitable_env?
::Gitlab.com? && !::Gitlab::Database.read_only?
end
def token_expiration_enforced?
::PersonalAccessToken.expiration_enforced?
end
def current_settings
end
end
end
end
......@@ -64,7 +64,7 @@ module LicenseHelper
license_remove_path: admin_license_path,
subscription_sync_path: sync_seat_link_admin_license_path,
congratulation_svg_path: image_path('illustrations/illustration-congratulation-purchase.svg'),
subscription_activation_banner_callout_name: ::EE::UserCalloutsHelper::CL_SUBSCRIPTION_ACTIVATION,
subscription_activation_banner_callout_name: ::EE::Users::CalloutsHelper::CL_SUBSCRIPTION_ACTIVATION,
license_usage_file_path: admin_license_usage_export_path(format: :csv)
}
end
......
......@@ -6,7 +6,7 @@ module LicenseMonitoringHelper
def show_active_user_count_threshold_banner?
return if ::Gitlab.com?
return unless admin_section?
return if user_dismissed?(UserCalloutsHelper::ACTIVE_USER_COUNT_THRESHOLD)
return if user_dismissed?(Users::CalloutsHelper::ACTIVE_USER_COUNT_THRESHOLD)
return if license_not_available_or_trial?
current_user&.admin? && current_license.active_user_count_threshold_reached?
......
......@@ -20,7 +20,7 @@ module TrialStatusWidgetHelper
start_initially_shown: force_popover_to_be_shown?(group),
target_id: base_attrs[:container_id],
trial_end_date: group.trial_ends_on,
user_callouts_path: user_callouts_path,
user_callouts_path: callouts_path,
user_callouts_feature_id: current_user_callout_feature_id(group.trial_days_remaining)
)
end
......
......@@ -18,7 +18,7 @@
- link_class << ' js-follow-link'
%li.js-buy-pipeline-minutes-notification-callout{ data: { feature_id: ::Ci::RunnersHelper::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT,
dismiss_endpoint: user_callouts_path } }
dismiss_endpoint: callouts_path } }
= link_to path, class: link_class, data: data_attributes do
= yield :buy_pipeline_with_subtext
- elsif show_buy_pipeline_with_subtext?(project, namespace)
......
- return unless show_active_user_count_threshold_banner?
.container-fluid.container-limited.pt-3
.gl-alert.gl-alert-info.gitlab-ee-license-banner.js-admin-licensed-user-count-threshold{ role: 'alert', data: { feature_id: UserCalloutsHelper::ACTIVE_USER_COUNT_THRESHOLD, dismiss_endpoint: user_callouts_path } }
.gl-alert.gl-alert-info.gitlab-ee-license-banner.js-admin-licensed-user-count-threshold{ role: 'alert', data: { feature_id: Users::CalloutsHelper::ACTIVE_USER_COUNT_THRESHOLD, dismiss_endpoint: callouts_path } }
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon')
%button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss'), data: { testid: 'gitlab-ee-license-banner-dismiss' } }
= sprite_icon('close', css_class: 'gl-icon')
......
......@@ -4,7 +4,7 @@
- link = link_to _('Generate new token'), profile_personal_access_tokens_path
.gl-alert.gl-alert-danger.js-token-expiry-callout{ role: 'alert', data: { feature_id: "personal_access_token_expiry", dismiss_endpoint: user_callouts_path, defer_links: "true" } }
.gl-alert.gl-alert-danger.js-token-expiry-callout{ role: 'alert', data: { feature_id: "personal_access_token_expiry", dismiss_endpoint: callouts_path, defer_links: "true" } }
%button.js-close.gl-alert-dismiss.gl-cursor-pointer{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', css_class: 'gl-icon')
.gl-alert-body
......
......@@ -3,7 +3,7 @@
- expired_tokens = active_tokens.select(&:expired_but_not_enforced?)
- return unless expired_tokens.present?
.gl-alert.gl-alert-danger.js-token-expiry-callout.gl-mb-3{ role: 'alert', data: { feature_id: "profile_personal_access_token_expiry", dismiss_endpoint: user_callouts_path, defer_links: "true" } }
.gl-alert.gl-alert-danger.js-token-expiry-callout.gl-mb-3{ role: 'alert', data: { feature_id: "profile_personal_access_token_expiry", dismiss_endpoint: callouts_path, defer_links: "true" } }
.gl-alert-container
= sprite_icon('error', css_class: 'gl-icon s16 gl-alert-icon')
%button.js-close.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon{ type: 'button', 'aria-label' => _('Dismiss') }
......
......@@ -10,7 +10,7 @@
title: s_('Admin|Your instance has reached its user cap'),
is_contained: true,
alert_class: 'js-new-user-signups-cap-reached',
alert_data: { feature_id: ::EE::UserCalloutsHelper::NEW_USER_SIGNUPS_CAP_REACHED, dismiss_endpoint: user_callouts_path, defer_links: "true" } do
alert_data: { feature_id: ::EE::Users::CalloutsHelper::NEW_USER_SIGNUPS_CAP_REACHED, dismiss_endpoint: callouts_path, defer_links: "true" } do
.gl-alert-body
= s_('Admin|Additional users must be reviewed and approved by a system administrator. Learn more about %{help_link_start}usage caps%{help_link_end}.').html_safe % { help_link_start: help_link_start, help_link_end: help_link_end }
- if User.blocked_pending_approval.count > 0
......
- is_dismissable = local_assigns.fetch(:is_dismissable, true)
- callout = local_assigns.fetch(:callout, UserCalloutsHelper::ULTIMATE_TRIAL)
- callout = local_assigns.fetch(:callout, Users::CalloutsHelper::ULTIMATE_TRIAL)
- button_css_class = is_dismissable ? 'mr-3' : ''
.pt-1.d-none.d-md-block{ class: container_class }
.user-callout.promotion-callout.thin-callout.js-gold-trial-callout{ data: { uid: 'trial_callout_dismissed', feature_id: callout, dismiss_endpoint: user_callouts_path } }
.user-callout.promotion-callout.thin-callout.js-gold-trial-callout{ data: { uid: 'trial_callout_dismissed', feature_id: callout, dismiss_endpoint: callouts_path } }
.bordered-box.justify-content-left.align-items-center
.svg-container
= image_tag 'illustrations/golden_tanuki.svg', class: 'svg'
......
- if show_eoa_bronze_plan_banner?(namespace)
.container-fluid.container-limited.pt-3
.gl-alert.gl-alert-info.gl-mt-5.js-eoa-bronze-plan-banner{ role: 'alert', data: { feature_id: ::EE::UserCalloutsHelper::EOA_BRONZE_PLAN_BANNER, dismiss_endpoint: user_callouts_path } }
.gl-alert.gl-alert-info.gl-mt-5.js-eoa-bronze-plan-banner{ role: 'alert', data: { feature_id: ::EE::Users::CalloutsHelper::EOA_BRONZE_PLAN_BANNER, dismiss_endpoint: callouts_path } }
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
%button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', css_class: 'gl-icon')
......
......@@ -37,7 +37,7 @@ RSpec.describe 'Billing plan pages', :feature, :js do
shared_examples 'does not display EoA banner' do
it 'does not display the banner', :js do
travel_to(Date.parse(EE::UserCalloutsHelper::EOA_BRONZE_PLAN_END_DATE) - 1.day) do
travel_to(Date.parse(EE::Users::CalloutsHelper::EOA_BRONZE_PLAN_END_DATE) - 1.day) do
visit page_path
expect(page).not_to have_content("End of availability for the Bronze Plan")
......@@ -250,7 +250,7 @@ RSpec.describe 'Billing plan pages', :feature, :js do
let!(:subscription) { create(:gitlab_subscription, namespace: namespace, hosted_plan: plan, seats: 15) }
it 'shows the EoA bronze banner that can be dismissed permanently', :js do
travel_to(Date.parse(EE::UserCalloutsHelper::EOA_BRONZE_PLAN_END_DATE) - 1.day) do
travel_to(Date.parse(EE::Users::CalloutsHelper::EOA_BRONZE_PLAN_END_DATE) - 1.day) do
visit page_path
page.within(".js-eoa-bronze-plan-banner") do
......
......@@ -131,7 +131,7 @@ RSpec.describe EE::Ci::RunnersHelper do
context 'when the notification dot has been acknowledged' do
before do
create(:user_callout, user: user, feature_name: described_class::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT)
create(:callout, user: user, feature_name: described_class::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT)
expect(helper).not_to receive(:show_out_of_pipeline_minutes_notification?)
end
......@@ -164,7 +164,7 @@ RSpec.describe EE::Ci::RunnersHelper do
context 'when the notification dot has been acknowledged' do
before do
create(:user_callout, user: user, feature_name: described_class::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT)
create(:callout, user: user, feature_name: described_class::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT)
expect(helper).to receive(:show_out_of_pipeline_minutes_notification?).and_return(true)
end
......
......@@ -2,7 +2,7 @@
require "spec_helper"
RSpec.describe EE::UserCalloutsHelper do
RSpec.describe EE::Users::CalloutsHelper do
include Devise::Test::ControllerHelpers
using RSpec::Parameterized::TableSyntax
......@@ -14,7 +14,7 @@ RSpec.describe EE::UserCalloutsHelper do
expect(helper).to receive(:render_flash_user_callout)
.with(:warning,
/Please enable and migrate to hashed/,
EE::UserCalloutsHelper::GEO_ENABLE_HASHED_STORAGE)
described_class::GEO_ENABLE_HASHED_STORAGE)
helper.render_enable_hashed_storage_warning
end
......@@ -39,7 +39,7 @@ RSpec.describe EE::UserCalloutsHelper do
expect(helper).to receive(:render_flash_user_callout)
.with(:warning,
/Please migrate all existing projects/,
EE::UserCalloutsHelper::GEO_MIGRATE_HASHED_STORAGE)
described_class::GEO_MIGRATE_HASHED_STORAGE)
helper.render_migrate_hashed_storage_warning
end
......@@ -73,7 +73,7 @@ RSpec.describe EE::UserCalloutsHelper do
context 'when the enable warning was dismissed' do
before do
create(:user_callout, user: user, feature_name: described_class::GEO_ENABLE_HASHED_STORAGE)
create(:callout, user: user, feature_name: described_class::GEO_ENABLE_HASHED_STORAGE)
end
it { is_expected.to be_falsy }
......@@ -124,7 +124,7 @@ RSpec.describe EE::UserCalloutsHelper do
context 'when the enable warning was dismissed' do
before do
create(:user_callout, user: user, feature_name: described_class::GEO_MIGRATE_HASHED_STORAGE)
create(:callout, user: user, feature_name: described_class::GEO_MIGRATE_HASHED_STORAGE)
end
it { is_expected.to be_falsy }
......@@ -400,12 +400,12 @@ RSpec.describe EE::UserCalloutsHelper do
end
it 'dismisses `TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK` callout' do
expect(::Users::DismissUserCalloutService)
expect(::Users::DismissCalloutService)
.to receive(:new)
.with(
container: nil,
current_user: user,
params: { feature_name: UserCalloutsHelper::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK }
params: { feature_name: described_class::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK }
)
.and_call_original
......@@ -446,7 +446,7 @@ RSpec.describe EE::UserCalloutsHelper do
before do
allow(Gitlab).to receive(:dev_env_or_com?).and_return(true)
allow(helper).to receive(:current_user).and_return(user)
create(:user_callout, user: user, feature_name: :verification_reminder, dismissed_at: Time.current)
create(:callout, user: user, feature_name: :verification_reminder, dismissed_at: Time.current)
create(:ci_pipeline, user: user, failure_reason: :user_not_verified, created_at: pipeline_created_at)
end
......
......@@ -101,7 +101,7 @@ RSpec.describe LicenseHelper do
license_upload_path: new_admin_license_path,
license_remove_path: admin_license_path,
congratulation_svg_path: helper.image_path('illustrations/illustration-congratulation-purchase.svg'),
subscription_activation_banner_callout_name: ::EE::UserCalloutsHelper::CL_SUBSCRIPTION_ACTIVATION,
subscription_activation_banner_callout_name: ::EE::Users::CalloutsHelper::CL_SUBSCRIPTION_ACTIVATION,
license_usage_file_path: admin_license_usage_export_path(format: :csv) })
end
end
......@@ -118,7 +118,7 @@ RSpec.describe LicenseHelper do
license_upload_path: new_admin_license_path,
license_remove_path: admin_license_path,
congratulation_svg_path: helper.image_path('illustrations/illustration-congratulation-purchase.svg'),
subscription_activation_banner_callout_name: ::EE::UserCalloutsHelper::CL_SUBSCRIPTION_ACTIVATION,
subscription_activation_banner_callout_name: ::EE::Users::CalloutsHelper::CL_SUBSCRIPTION_ACTIVATION,
license_usage_file_path: admin_license_usage_export_path(format: :csv) })
end
end
......
......@@ -36,7 +36,7 @@ RSpec.describe LicenseMonitoringHelper do
context 'when callout dismissed' do
before do
allow(helper).to receive(:user_dismissed?).with(UserCalloutsHelper::ACTIVE_USER_COUNT_THRESHOLD).and_return(true)
allow(helper).to receive(:user_dismissed?).with(Users::CalloutsHelper::ACTIVE_USER_COUNT_THRESHOLD).and_return(true)
end
it { is_expected.to be_falsey }
......
......@@ -62,7 +62,7 @@ RSpec.describe TrialStatusWidgetHelper, :saas do
target_id: shared_expected_attrs[:container_id],
start_initially_shown: start_initially_shown,
trial_end_date: trial_end_date,
user_callouts_path: user_callouts_path,
user_callouts_path: callouts_path,
user_callouts_feature_id: user_callouts_feature_id
)
)
......
......@@ -41,7 +41,7 @@ RSpec.describe 'layouts/header/_current_user_dropdown' do
expect(subject).to have_content('One of your groups is running out')
expect(subject).to have_selector('.js-follow-link')
expect(subject).to have_selector("[data-feature-id='#{::Ci::RunnersHelper::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT}']")
expect(subject).to have_selector("[data-dismiss-endpoint='#{user_callouts_path}']")
expect(subject).to have_selector("[data-dismiss-endpoint='#{callouts_path}']")
end
end
......@@ -53,7 +53,7 @@ RSpec.describe 'layouts/header/_current_user_dropdown' do
expect(subject).to have_content('One of your groups is running out')
expect(subject).not_to have_selector('.js-follow-link')
expect(subject).not_to have_selector("[data-feature-id='#{::Ci::RunnersHelper::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT}']")
expect(subject).not_to have_selector("[data-dismiss-endpoint='#{user_callouts_path}']")
expect(subject).not_to have_selector("[data-dismiss-endpoint='#{callouts_path}']")
end
end
......
......@@ -69,7 +69,7 @@ RSpec.describe 'shared/billings/_eoa_bronze_plan_banner.html.haml' do
shared_examples 'when user dismissed the banner' do
before do
allow(namespace).to receive(:actual_plan_name).and_return(::Plan::BRONZE)
allow(view).to receive(:user_dismissed?).with(::EE::UserCalloutsHelper::EOA_BRONZE_PLAN_BANNER).and_return(true)
allow(view).to receive(:user_dismissed?).with(::EE::Users::CalloutsHelper::EOA_BRONZE_PLAN_BANNER).and_return(true)
end
it 'does not display the banner' do
......@@ -83,7 +83,7 @@ RSpec.describe 'shared/billings/_eoa_bronze_plan_banner.html.haml' do
before do
allow(view).to receive(:eoa_bronze_plan_end_date).and_return(eoa_bronze_plan_end_date)
allow(view).to receive(:user_dismissed?).with(::EE::UserCalloutsHelper::EOA_BRONZE_PLAN_BANNER).and_return(false)
allow(view).to receive(:user_dismissed?).with(::EE::Users::CalloutsHelper::EOA_BRONZE_PLAN_BANNER).and_return(false)
end
context 'with group namespace' do
......
......@@ -57,9 +57,9 @@ module Sidebars
data: { trigger: 'manual',
container: 'body',
placement: 'right',
highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: user_callouts_path,
highlight: Users::CalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: Users::Callout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: callouts_path,
auto_devops_help_path: help_page_path('topics/autodevops/index.md') } }
end
......
......@@ -142,8 +142,8 @@ RSpec.describe RootController do
context 'without customize homepage banner' do
before do
Users::DismissUserCalloutService.new(
container: nil, current_user: user, params: { feature_name: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE }
Users::DismissCalloutService.new(
container: nil, current_user: user, params: { feature_name: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE }
).execute
end
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe UserCalloutsController do
RSpec.describe Users::CalloutsController do
let_it_be(:user) { create(:user) }
before do
......@@ -15,11 +15,11 @@ RSpec.describe UserCalloutsController do
subject { post :create, params: params, format: :json }
context 'with valid feature name' do
let(:feature_name) { UserCallout.feature_names.each_key.first }
let(:feature_name) { Users::Callout.feature_names.each_key.first }
context 'when callout entry does not exist' do
it 'creates a callout entry with dismissed state' do
expect { subject }.to change { UserCallout.count }.by(1)
expect { subject }.to change { Users::Callout.count }.by(1)
end
it 'returns success' do
......@@ -30,10 +30,10 @@ RSpec.describe UserCalloutsController do
end
context 'when callout entry already exists' do
let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.each_key.first, user: user) }
let!(:callout) { create(:callout, feature_name: Users::Callout.feature_names.each_key.first, user: user) }
it 'returns success', :aggregate_failures do
expect { subject }.not_to change { UserCallout.count }
expect { subject }.not_to change { Users::Callout.count }
expect(response).to have_gitlab_http_status(:ok)
end
end
......
......@@ -170,7 +170,7 @@ RSpec.describe 'Database schema' do
'PrometheusMetric' => %w[group],
'ResourceLabelEvent' => %w[action],
'User' => %w[layout dashboard project_view],
'UserCallout' => %w[feature_name],
'Users::Callout' => %w[feature_name],
'PrometheusAlert' => %w[operator]
}.freeze
......
# frozen_string_literal: true
FactoryBot.define do
factory :user_callout do
factory :callout, class: 'Users::Callout' do
feature_name { :gke_cluster_integration }
user
......
......@@ -13,7 +13,7 @@ RSpec.describe Mutations::UserCallouts::Create do
let(:feature_name) { 'not_supported' }
it 'does not create a user callout' do
expect { resolve }.not_to change(UserCallout, :count).from(0)
expect { resolve }.not_to change(Users::Callout, :count).from(0)
end
it 'returns error about feature name not being supported' do
......@@ -22,10 +22,10 @@ RSpec.describe Mutations::UserCallouts::Create do
end
context 'when feature name is supported' do
let(:feature_name) { UserCallout.feature_names.each_key.first.to_s }
let(:feature_name) { Users::Callout.feature_names.each_key.first.to_s }
it 'creates a user callout' do
expect { resolve }.to change(UserCallout, :count).from(0).to(1)
expect { resolve }.to change(Users::Callout, :count).from(0).to(1)
end
it 'sets dismissed_at for the user callout' do
......
......@@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['UserCalloutFeatureNameEnum'] do
specify { expect(described_class.graphql_name).to eq('UserCalloutFeatureNameEnum') }
it 'exposes all the existing user callout feature names' do
expect(described_class.values.keys).to match_array(::UserCallout.feature_names.keys.map(&:upcase))
expect(described_class.values.keys).to match_array(::Users::Callout.feature_names.keys.map(&:upcase))
end
end
......@@ -61,7 +61,7 @@ RSpec.describe IdeHelper do
context 'and the callout has been dismissed' do
it 'disables environment guidance' do
callout = create(:user_callout, feature_name: :web_ide_ci_environments_guidance, user: project.creator)
callout = create(:callout, feature_name: :web_ide_ci_environments_guidance, user: project.creator)
callout.update!(dismissed_at: Time.now - 1.week)
allow(helper).to receive(:current_user).and_return(User.find(project.creator.id))
expect(helper.ide_data).to include('enable-environments-guidance' => 'false')
......
......@@ -2,7 +2,7 @@
require "spec_helper"
RSpec.describe UserCalloutsHelper do
RSpec.describe Users::CalloutsHelper do
let_it_be(:user, refind: true) { create(:user) }
before do
......@@ -115,7 +115,7 @@ RSpec.describe UserCalloutsHelper do
context 'when the feature flags new version has been dismissed' do
before do
create(:user_callout, user: user, feature_name: described_class::FEATURE_FLAGS_NEW_VERSION)
create(:callout, user: user, feature_name: described_class::FEATURE_FLAGS_NEW_VERSION)
end
it { is_expected.to be_falsy }
......@@ -203,83 +203,6 @@ RSpec.describe UserCalloutsHelper do
end
end
describe '.show_invite_banner?' do
let_it_be(:group) { create(:group) }
subject { helper.show_invite_banner?(group) }
context 'when user has the admin ability for the group' do
before do
group.add_owner(user)
end
context 'when the invite_members_banner has not been dismissed' do
it { is_expected.to eq(true) }
context 'when the group was just created' do
before do
flash[:notice] = "Group #{group.name} was successfully created"
end
it { is_expected.to eq(false) }
end
context 'with concerning multiple members' do
let_it_be(:user_2) { create(:user) }
context 'on current group' do
before do
group.add_guest(user_2)
end
it { is_expected.to eq(false) }
end
context 'on current group that is a subgroup' do
let_it_be(:subgroup) { create(:group, parent: group) }
subject { helper.show_invite_banner?(subgroup) }
context 'with only one user on parent and this group' do
it { is_expected.to eq(true) }
end
context 'when another user is on this group' do
before do
subgroup.add_guest(user_2)
end
it { is_expected.to eq(false) }
end
context 'when another user is on the parent group' do
before do
group.add_guest(user_2)
end
it { is_expected.to eq(false) }
end
end
end
end
context 'when the invite_members_banner has been dismissed' do
before do
create(:group_callout,
user: user,
group: group,
feature_name: described_class::INVITE_MEMBERS_BANNER)
end
it { is_expected.to eq(false) }
end
end
context 'when user does not have admin ability for the group' do
it { is_expected.to eq(false) }
end
end
describe '.show_security_newsletter_user_callout?' do
let_it_be(:admin) { create(:user, :admin) }
......
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Users::GroupCalloutsHelper do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:group) { create(:group) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
describe '.show_invite_banner?' do
subject { helper.show_invite_banner?(group) }
context 'when user has the admin ability for the group' do
before do
group.add_owner(user)
end
context 'when the invite_members_banner has not been dismissed' do
it { is_expected.to eq(true) }
context 'when the group was just created' do
before do
flash[:notice] = "Group #{group.name} was successfully created"
end
it { is_expected.to eq(false) }
end
context 'with concerning multiple members' do
let_it_be(:user_2) { create(:user) }
context 'on current group' do
before do
group.add_guest(user_2)
end
it { is_expected.to eq(false) }
end
context 'on current group that is a subgroup' do
let_it_be(:subgroup) { create(:group, parent: group) }
subject { helper.show_invite_banner?(subgroup) }
context 'with only one user on parent and this group' do
it { is_expected.to eq(true) }
end
context 'when another user is on this group' do
before do
subgroup.add_guest(user_2)
end
it { is_expected.to eq(false) }
end
context 'when another user is on the parent group' do
before do
group.add_guest(user_2)
end
it { is_expected.to eq(false) }
end
end
end
end
context 'when the invite_members_banner has been dismissed' do
before do
create(:group_callout,
user: user,
group: group,
feature_name: described_class::INVITE_MEMBERS_BANNER)
end
it { is_expected.to eq(false) }
end
end
context 'when user does not have admin ability for the group' do
it { is_expected.to eq(false) }
end
end
end
......@@ -124,7 +124,7 @@ RSpec.describe User do
it { is_expected.to have_many(:created_custom_emoji).inverse_of(:creator) }
it { is_expected.to have_many(:in_product_marketing_emails) }
it { is_expected.to have_many(:timelogs) }
it { is_expected.to have_many(:callouts).class_name('UserCallout') }
it { is_expected.to have_many(:callouts).class_name('Users::Callout') }
it { is_expected.to have_many(:group_callouts).class_name('Users::GroupCallout') }
describe '#user_detail' do
......@@ -5593,7 +5593,7 @@ RSpec.describe User do
describe '#dismissed_callout?' do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:feature_name) { UserCallout.feature_names.each_key.first }
let_it_be(:feature_name) { Users::Callout.feature_names.each_key.first }
context 'when no callout dismissal record exists' do
it 'returns false when no ignore_dismissal_earlier_than provided' do
......@@ -5603,7 +5603,7 @@ RSpec.describe User do
context 'when dismissed callout exists' do
before_all do
create(:user_callout, user: user, feature_name: feature_name, dismissed_at: 4.months.ago)
create(:callout, user: user, feature_name: feature_name, dismissed_at: 4.months.ago)
end
it 'returns true when no ignore_dismissal_earlier_than provided' do
......@@ -5622,12 +5622,12 @@ RSpec.describe User do
describe '#find_or_initialize_callout' do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:feature_name) { UserCallout.feature_names.each_key.first }
let_it_be(:feature_name) { Users::Callout.feature_names.each_key.first }
subject(:find_or_initialize_callout) { user.find_or_initialize_callout(feature_name) }
context 'when callout exists' do
let!(:callout) { create(:user_callout, user: user, feature_name: feature_name) }
let!(:callout) { create(:callout, user: user, feature_name: feature_name) }
it 'returns existing callout' do
expect(find_or_initialize_callout).to eq(callout)
......@@ -5637,7 +5637,7 @@ RSpec.describe User do
context 'when callout does not exist' do
context 'when feature name is valid' do
it 'initializes a new callout' do
expect(find_or_initialize_callout).to be_a_new(UserCallout)
expect(find_or_initialize_callout).to be_a_new(Users::Callout)
end
it 'is valid' do
......@@ -5649,7 +5649,7 @@ RSpec.describe User do
let(:feature_name) { 'notvalid' }
it 'initializes a new callout' do
expect(find_or_initialize_callout).to be_a_new(UserCallout)
expect(find_or_initialize_callout).to be_a_new(Users::Callout)
end
it 'is not valid' do
......
......@@ -2,8 +2,8 @@
require 'spec_helper'
RSpec.describe UserCallout do
let_it_be(:callout) { create(:user_callout) }
RSpec.describe Users::Callout do
let_it_be(:callout) { create(:callout) }
it_behaves_like 'having unique enum values'
......
......@@ -2,8 +2,8 @@
require 'spec_helper'
RSpec.describe Calloutable do
subject { build(:user_callout) }
RSpec.describe Users::Calloutable do
subject { build(:callout) }
describe "Associations" do
it { is_expected.to belong_to(:user) }
......@@ -14,9 +14,9 @@ RSpec.describe Calloutable do
end
describe '#dismissed_after?' do
let(:some_feature_name) { UserCallout.feature_names.keys.second }
let(:callout_dismissed_month_ago) { create(:user_callout, feature_name: some_feature_name, dismissed_at: 1.month.ago )}
let(:callout_dismissed_day_ago) { create(:user_callout, feature_name: some_feature_name, dismissed_at: 1.day.ago )}
let(:some_feature_name) { Users::Callout.feature_names.keys.second }
let(:callout_dismissed_month_ago) { create(:callout, feature_name: some_feature_name, dismissed_at: 1.month.ago )}
let(:callout_dismissed_day_ago) { create(:callout, feature_name: some_feature_name, dismissed_at: 1.day.ago )}
it 'returns whether a callout dismissed after specified date' do
expect(callout_dismissed_month_ago.dismissed_after?(15.days.ago)).to eq(false)
......
......@@ -7,7 +7,7 @@ RSpec.describe 'Create a user callout' do
let_it_be(:current_user) { create(:user) }
let(:feature_name) { ::UserCallout.feature_names.each_key.first }
let(:feature_name) { ::Users::Callout.feature_names.each_key.first }
let(:input) do
{
......
......@@ -252,7 +252,7 @@ RSpec.describe MergeRequestWidgetEntity do
subject { described_class.new(resource, request: request).as_json }
it 'provides a valid path value for user callout path' do
expect(subject[:user_callouts_path]).to eq '/-/user_callouts'
expect(subject[:user_callouts_path]).to eq '/-/users/callouts'
end
it 'provides a valid value for suggest pipeline feature id' do
......@@ -362,7 +362,7 @@ RSpec.describe MergeRequestWidgetEntity do
context 'when suggest pipeline has been dismissed' do
before do
create(:user_callout, user: user, feature_name: described_class::SUGGEST_PIPELINE)
create(:callout, user: user, feature_name: described_class::SUGGEST_PIPELINE)
end
it 'is true' do
......
......@@ -2,12 +2,12 @@
require 'spec_helper'
RSpec.describe Users::DismissUserCalloutService do
RSpec.describe Users::DismissCalloutService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let(:params) { { feature_name: feature_name } }
let(:feature_name) { UserCallout.feature_names.each_key.first }
let(:feature_name) { Users::Callout.feature_names.each_key.first }
subject(:execute) do
described_class.new(
......@@ -15,6 +15,6 @@ RSpec.describe Users::DismissUserCalloutService do
).execute
end
it_behaves_like 'dismissing user callout', UserCallout
it_behaves_like 'dismissing user callout', Users::Callout
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment