Commit 517a4219 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '340653-organize-callouts-code-under-users' into 'master'

Organize callouts code under users

See merge request gitlab-org/gitlab!75941
parents 44cdc72e 1cbe941e
...@@ -543,7 +543,7 @@ Rails/LexicallyScopedActionFilter: ...@@ -543,7 +543,7 @@ Rails/LexicallyScopedActionFilter:
Rails/LinkToBlank: Rails/LinkToBlank:
Exclude: Exclude:
- 'app/helpers/projects_helper.rb' - 'app/helpers/projects_helper.rb'
- 'ee/app/helpers/ee/user_callouts_helper.rb' - 'ee/app/helpers/ee/users/callouts_helper.rb'
# Offense count: 1 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
......
...@@ -16,7 +16,7 @@ Cop/UserAdmin: ...@@ -16,7 +16,7 @@ Cop/UserAdmin:
- app/helpers/nav_helper.rb - app/helpers/nav_helper.rb
- app/helpers/projects_helper.rb - app/helpers/projects_helper.rb
- app/helpers/search_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/users_helper.rb
- app/helpers/visibility_level_helper.rb - app/helpers/visibility_level_helper.rb
- app/models/concerns/protected_ref_access.rb - app/models/concerns/protected_ref_access.rb
...@@ -38,7 +38,7 @@ Cop/UserAdmin: ...@@ -38,7 +38,7 @@ Cop/UserAdmin:
- ee/app/helpers/ee/dashboard_helper.rb - ee/app/helpers/ee/dashboard_helper.rb
- ee/app/helpers/ee/import_helper.rb - ee/app/helpers/ee/import_helper.rb
- ee/app/helpers/ee/subscribable_banner_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/license_monitoring_helper.rb
- ee/app/helpers/push_rules_helper.rb - ee/app/helpers/push_rules_helper.rb
- ee/app/models/concerns/ee/protected_ref_access.rb - ee/app/models/concerns/ee/protected_ref_access.rb
......
...@@ -32,7 +32,6 @@ Gitlab/NamespacedClass: ...@@ -32,7 +32,6 @@ Gitlab/NamespacedClass:
- app/controllers/sessions_controller.rb - app/controllers/sessions_controller.rb
- app/controllers/snippets_controller.rb - app/controllers/snippets_controller.rb
- app/controllers/uploads_controller.rb - app/controllers/uploads_controller.rb
- app/controllers/user_callouts_controller.rb
- app/controllers/users_controller.rb - app/controllers/users_controller.rb
- app/controllers/whats_new_controller.rb - app/controllers/whats_new_controller.rb
- app/finders/abuse_reports_finder.rb - app/finders/abuse_reports_finder.rb
...@@ -351,7 +350,6 @@ Gitlab/NamespacedClass: ...@@ -351,7 +350,6 @@ Gitlab/NamespacedClass:
- app/models/upload.rb - app/models/upload.rb
- app/models/user.rb - app/models/user.rb
- app/models/user_agent_detail.rb - app/models/user_agent_detail.rb
- app/models/user_callout.rb
- app/models/user_canonical_email.rb - app/models/user_canonical_email.rb
- app/models/user_custom_attribute.rb - app/models/user_custom_attribute.rb
- app/models/user_detail.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 # frozen_string_literal: true
module Users module Users
class GroupCalloutsController < UserCalloutsController class GroupCalloutsController < Users::CalloutsController
private private
def callout def callout
......
...@@ -15,7 +15,7 @@ module Mutations ...@@ -15,7 +15,7 @@ module Mutations
description: 'User callout dismissed.' description: 'User callout dismissed.'
def resolve(feature_name:) def resolve(feature_name:)
callout = Users::DismissUserCalloutService.new( callout = Users::DismissCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: feature_name } container: nil, current_user: current_user, params: { feature_name: feature_name }
).execute ).execute
errors = errors_on_object(callout) errors = errors_on_object(callout)
......
...@@ -5,7 +5,7 @@ module Types ...@@ -5,7 +5,7 @@ module Types
graphql_name 'UserCalloutFeatureNameEnum' graphql_name 'UserCalloutFeatureNameEnum'
description 'Name of the feature that the callout is for.' 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}." value feature_name.upcase, value: feature_name, description: "Callout feature name for #{feature_name}."
end end
end end
......
...@@ -182,7 +182,7 @@ module MergeRequestsHelper ...@@ -182,7 +182,7 @@ module MergeRequestsHelper
project_path: project_path(merge_request.project), project_path: project_path(merge_request.project),
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'), changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
is_fluid_layout: fluid_layout.to_s, 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_suggest_popover: show_suggest_popover?.to_s,
show_whitespace_default: @show_whitespace_default.to_s, show_whitespace_default: @show_whitespace_default.to_s,
file_by_file_default: @file_by_file_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
...@@ -204,7 +204,7 @@ class User < ApplicationRecord ...@@ -204,7 +204,7 @@ class User < ApplicationRecord
has_many :bulk_imports has_many :bulk_imports
has_many :custom_attributes, class_name: 'UserCustomAttribute' 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 :group_callouts, class_name: 'Users::GroupCallout'
has_many :term_agreements has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term' belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
...@@ -1947,7 +1947,7 @@ class User < ApplicationRecord ...@@ -1947,7 +1947,7 @@ class User < ApplicationRecord
end end
def find_or_initialize_callout(feature_name) 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 end
def find_or_initialize_group_callout(feature_name, group_id) 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 @@ ...@@ -2,7 +2,7 @@
module Users module Users
class GroupCallout < ApplicationRecord class GroupCallout < ApplicationRecord
include Calloutable include Users::Calloutable
self.table_name = 'user_group_callouts' self.table_name = 'user_group_callouts'
......
...@@ -73,7 +73,7 @@ class MergeRequestWidgetEntity < Grape::Entity ...@@ -73,7 +73,7 @@ class MergeRequestWidgetEntity < Grape::Entity
end end
expose :user_callouts_path do |_merge_request| expose :user_callouts_path do |_merge_request|
user_callouts_path callouts_path
end end
expose :suggest_pipeline_feature_id do |_merge_request| expose :suggest_pipeline_feature_id do |_merge_request|
......
# frozen_string_literal: true # frozen_string_literal: true
module Users module Users
class DismissUserCalloutService < BaseContainerService class DismissCalloutService < BaseContainerService
def execute def execute
callout.tap do |record| callout.tap do |record|
record.update(dismissed_at: Time.current) if record.valid? record.update(dismissed_at: Time.current) if record.valid?
......
# frozen_string_literal: true # frozen_string_literal: true
module Users module Users
class DismissGroupCalloutService < DismissUserCalloutService class DismissGroupCalloutService < DismissCalloutService
private private
def callout def callout
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
variant: :tip, variant: :tip,
alert_class: 'js-security-newsletter-callout', alert_class: 'js-security-newsletter-callout',
is_contained: true, 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 close_button_data: { testid: 'close-security-newsletter-callout' } do
.gl-alert-body .gl-alert-body
= s_('AdminArea|Sign up for the GitLab Security Newsletter to get notified for security updates.') = 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') - 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 .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') } %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') = sprite_icon('close', size: 16, css_class: 'gl-icon')
......
...@@ -18,6 +18,6 @@ ...@@ -18,6 +18,6 @@
"gid_prefix": container_repository_gid_prefix, "gid_prefix": container_repository_gid_prefix,
connection_error: (!!@connection_error).to_s, connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s, invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path, user_callouts_path: callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } } show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } }
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
track_label: 'invite_members_banner', track_label: 'invite_members_banner',
invite_members_path: group_group_members_path(@group), invite_members_path: group_group_members_path(@group),
callouts_path: group_callouts_path, callouts_path: group_callouts_path,
callouts_feature_id: UserCalloutsHelper::INVITE_MEMBERS_BANNER, callouts_feature_id: Users::GroupCalloutsHelper::INVITE_MEMBERS_BANNER,
group_id: @group.id } } group_id: @group.id } }
= render 'groups/invite_members_modal', group: @group = render 'groups/invite_members_modal', group: @group
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
title: _('Open registration is enabled on your instance.'), title: _('Open registration is enabled on your instance.'),
variant: :warning, variant: :warning,
alert_class: 'js-registration-enabled-callout', 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 close_button_data: { testid: 'close-registration-enabled-callout' } do
.gl-alert-body .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 } = 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 @@ ...@@ -6,8 +6,8 @@
#js-new-feature-flag{ data: { endpoint: project_feature_flags_path(@project, format: :json), #js-new-feature-flag{ data: { endpoint: project_feature_flags_path(@project, format: :json),
feature_flags_path: project_feature_flags_path(@project), feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json), environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path, user_callouts_path: callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION, user_callout_id: Users::CalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s, 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'), 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'), environments_scope_docs_path: help_page_path('ci/environments/index.md', anchor: 'scope-environments-with-specs'),
......
...@@ -22,6 +22,6 @@ ...@@ -22,6 +22,6 @@
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project), "cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
connection_error: (!!@connection_error).to_s, connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s, invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path, user_callouts_path: callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } } show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } }
...@@ -3,8 +3,8 @@ ...@@ -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}" } .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'), .js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'), preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
callouts_path: user_callouts_path, callouts_path: callouts_path,
callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE, callouts_feature_id: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE,
track_label: 'home_page' } } track_label: 'home_page' } }
= render template: 'dashboard/projects/index' = 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) - extra_flash_class = local_assigns.fetch(:extra_flash_class, nil)
.flash-container.flash-container-page.user-callout{ data: callout_data } .flash-container.flash-container-page.user-callout{ data: callout_data }
......
= render 'shared/global_alert', = render 'shared/global_alert',
variant: :warning, variant: :warning,
alert_class: 'js-recovery-settings-callout', 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 close_button_data: { testid: 'close-account-recovery-regular-check-callout' } do
.gl-alert-body .gl-alert-body
= s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.') = s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.')
......
...@@ -145,7 +145,7 @@ Rails.application.routes.draw do ...@@ -145,7 +145,7 @@ Rails.application.routes.draw do
get 'acme-challenge/' => 'acme_challenges#show' get 'acme-challenge/' => 'acme_challenges#show'
# UserCallouts # 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 scope :ide, as: :ide, format: false do
get '/', to: 'ide#index' get '/', to: 'ide#index'
......
...@@ -61,6 +61,7 @@ scope '-/users', module: :users do ...@@ -61,6 +61,7 @@ scope '-/users', module: :users do
post :decline, on: :member post :decline, on: :member
end end
resources :callouts, only: [:create]
resources :group_callouts, only: [:create] resources :group_callouts, only: [:create]
end 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 ...@@ -64,7 +64,7 @@ module LicenseHelper
license_remove_path: admin_license_path, license_remove_path: admin_license_path,
subscription_sync_path: sync_seat_link_admin_license_path, subscription_sync_path: sync_seat_link_admin_license_path,
congratulation_svg_path: image_path('illustrations/illustration-congratulation-purchase.svg'), 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) license_usage_file_path: admin_license_usage_export_path(format: :csv)
} }
end end
......
...@@ -6,7 +6,7 @@ module LicenseMonitoringHelper ...@@ -6,7 +6,7 @@ module LicenseMonitoringHelper
def show_active_user_count_threshold_banner? def show_active_user_count_threshold_banner?
return if ::Gitlab.com? return if ::Gitlab.com?
return unless admin_section? 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? return if license_not_available_or_trial?
current_user&.admin? && current_license.active_user_count_threshold_reached? current_user&.admin? && current_license.active_user_count_threshold_reached?
......
...@@ -20,7 +20,7 @@ module TrialStatusWidgetHelper ...@@ -20,7 +20,7 @@ module TrialStatusWidgetHelper
start_initially_shown: force_popover_to_be_shown?(group), start_initially_shown: force_popover_to_be_shown?(group),
target_id: base_attrs[:container_id], target_id: base_attrs[:container_id],
trial_end_date: group.trial_ends_on, 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) user_callouts_feature_id: current_user_callout_feature_id(group.trial_days_remaining)
) )
end end
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
- link_class << ' js-follow-link' - link_class << ' js-follow-link'
%li.js-buy-pipeline-minutes-notification-callout{ data: { feature_id: ::Ci::RunnersHelper::BUY_PIPELINE_MINUTES_NOTIFICATION_DOT, %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 = link_to path, class: link_class, data: data_attributes do
= yield :buy_pipeline_with_subtext = yield :buy_pipeline_with_subtext
- elsif show_buy_pipeline_with_subtext?(project, namespace) - elsif show_buy_pipeline_with_subtext?(project, namespace)
......
- return unless show_active_user_count_threshold_banner? - return unless show_active_user_count_threshold_banner?
.container-fluid.container-limited.pt-3 .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') = 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' } } %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') = sprite_icon('close', css_class: 'gl-icon')
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- link = link_to _('Generate new token'), profile_personal_access_tokens_path - 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') } %button.js-close.gl-alert-dismiss.gl-cursor-pointer{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', css_class: 'gl-icon') = sprite_icon('close', css_class: 'gl-icon')
.gl-alert-body .gl-alert-body
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- expired_tokens = active_tokens.select(&:expired_but_not_enforced?) - expired_tokens = active_tokens.select(&:expired_but_not_enforced?)
- return unless expired_tokens.present? - 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 .gl-alert-container
= sprite_icon('error', css_class: 'gl-icon s16 gl-alert-icon') = 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') } %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 @@ ...@@ -10,7 +10,7 @@
title: s_('Admin|Your instance has reached its user cap'), title: s_('Admin|Your instance has reached its user cap'),
is_contained: true, is_contained: true,
alert_class: 'js-new-user-signups-cap-reached', 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 .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 } = 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 - if User.blocked_pending_approval.count > 0
......
- is_dismissable = local_assigns.fetch(:is_dismissable, true) - 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' : '' - button_css_class = is_dismissable ? 'mr-3' : ''
.pt-1.d-none.d-md-block{ class: container_class } .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 .bordered-box.justify-content-left.align-items-center
.svg-container .svg-container
= image_tag 'illustrations/golden_tanuki.svg', class: 'svg' = image_tag 'illustrations/golden_tanuki.svg', class: 'svg'
......
- if show_eoa_bronze_plan_banner?(namespace) - if show_eoa_bronze_plan_banner?(namespace)
.container-fluid.container-limited.pt-3 .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') = 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') } %button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', css_class: 'gl-icon') = sprite_icon('close', css_class: 'gl-icon')
......
...@@ -37,7 +37,7 @@ RSpec.describe 'Billing plan pages', :feature, :js do ...@@ -37,7 +37,7 @@ RSpec.describe 'Billing plan pages', :feature, :js do
shared_examples 'does not display EoA banner' do shared_examples 'does not display EoA banner' do
it 'does not display the banner', :js 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 visit page_path
expect(page).not_to have_content("End of availability for the Bronze Plan") expect(page).not_to have_content("End of availability for the Bronze Plan")
...@@ -232,7 +232,7 @@ RSpec.describe 'Billing plan pages', :feature, :js do ...@@ -232,7 +232,7 @@ RSpec.describe 'Billing plan pages', :feature, :js do
let!(:subscription) { create(:gitlab_subscription, namespace: namespace, hosted_plan: plan, seats: 15) } 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 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 visit page_path
page.within(".js-eoa-bronze-plan-banner") do page.within(".js-eoa-bronze-plan-banner") do
......
...@@ -131,7 +131,7 @@ RSpec.describe EE::Ci::RunnersHelper do ...@@ -131,7 +131,7 @@ RSpec.describe EE::Ci::RunnersHelper do
context 'when the notification dot has been acknowledged' do context 'when the notification dot has been acknowledged' do
before 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?) expect(helper).not_to receive(:show_out_of_pipeline_minutes_notification?)
end end
...@@ -164,7 +164,7 @@ RSpec.describe EE::Ci::RunnersHelper do ...@@ -164,7 +164,7 @@ RSpec.describe EE::Ci::RunnersHelper do
context 'when the notification dot has been acknowledged' do context 'when the notification dot has been acknowledged' do
before 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) expect(helper).to receive(:show_out_of_pipeline_minutes_notification?).and_return(true)
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require "spec_helper" require "spec_helper"
RSpec.describe EE::UserCalloutsHelper do RSpec.describe EE::Users::CalloutsHelper do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
...@@ -14,7 +14,7 @@ RSpec.describe EE::UserCalloutsHelper do ...@@ -14,7 +14,7 @@ RSpec.describe EE::UserCalloutsHelper do
expect(helper).to receive(:render_flash_user_callout) expect(helper).to receive(:render_flash_user_callout)
.with(:warning, .with(:warning,
/Please enable and migrate to hashed/, /Please enable and migrate to hashed/,
EE::UserCalloutsHelper::GEO_ENABLE_HASHED_STORAGE) described_class::GEO_ENABLE_HASHED_STORAGE)
helper.render_enable_hashed_storage_warning helper.render_enable_hashed_storage_warning
end end
...@@ -39,7 +39,7 @@ RSpec.describe EE::UserCalloutsHelper do ...@@ -39,7 +39,7 @@ RSpec.describe EE::UserCalloutsHelper do
expect(helper).to receive(:render_flash_user_callout) expect(helper).to receive(:render_flash_user_callout)
.with(:warning, .with(:warning,
/Please migrate all existing projects/, /Please migrate all existing projects/,
EE::UserCalloutsHelper::GEO_MIGRATE_HASHED_STORAGE) described_class::GEO_MIGRATE_HASHED_STORAGE)
helper.render_migrate_hashed_storage_warning helper.render_migrate_hashed_storage_warning
end end
...@@ -73,7 +73,7 @@ RSpec.describe EE::UserCalloutsHelper do ...@@ -73,7 +73,7 @@ RSpec.describe EE::UserCalloutsHelper do
context 'when the enable warning was dismissed' do context 'when the enable warning was dismissed' do
before 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 end
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
...@@ -124,7 +124,7 @@ RSpec.describe EE::UserCalloutsHelper do ...@@ -124,7 +124,7 @@ RSpec.describe EE::UserCalloutsHelper do
context 'when the enable warning was dismissed' do context 'when the enable warning was dismissed' do
before 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 end
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
...@@ -400,12 +400,12 @@ RSpec.describe EE::UserCalloutsHelper do ...@@ -400,12 +400,12 @@ RSpec.describe EE::UserCalloutsHelper do
end end
it 'dismisses `TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK` callout' do it 'dismisses `TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK` callout' do
expect(::Users::DismissUserCalloutService) expect(::Users::DismissCalloutService)
.to receive(:new) .to receive(:new)
.with( .with(
container: nil, container: nil,
current_user: user, 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 .and_call_original
...@@ -446,7 +446,7 @@ RSpec.describe EE::UserCalloutsHelper do ...@@ -446,7 +446,7 @@ RSpec.describe EE::UserCalloutsHelper do
before do before do
allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) allow(Gitlab).to receive(:dev_env_or_com?).and_return(true)
allow(helper).to receive(:current_user).and_return(user) 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) create(:ci_pipeline, user: user, failure_reason: :user_not_verified, created_at: pipeline_created_at)
end end
......
...@@ -101,7 +101,7 @@ RSpec.describe LicenseHelper do ...@@ -101,7 +101,7 @@ RSpec.describe LicenseHelper do
license_upload_path: new_admin_license_path, license_upload_path: new_admin_license_path,
license_remove_path: admin_license_path, license_remove_path: admin_license_path,
congratulation_svg_path: helper.image_path('illustrations/illustration-congratulation-purchase.svg'), 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) }) license_usage_file_path: admin_license_usage_export_path(format: :csv) })
end end
end end
...@@ -118,7 +118,7 @@ RSpec.describe LicenseHelper do ...@@ -118,7 +118,7 @@ RSpec.describe LicenseHelper do
license_upload_path: new_admin_license_path, license_upload_path: new_admin_license_path,
license_remove_path: admin_license_path, license_remove_path: admin_license_path,
congratulation_svg_path: helper.image_path('illustrations/illustration-congratulation-purchase.svg'), 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) }) license_usage_file_path: admin_license_usage_export_path(format: :csv) })
end end
end end
......
...@@ -36,7 +36,7 @@ RSpec.describe LicenseMonitoringHelper do ...@@ -36,7 +36,7 @@ RSpec.describe LicenseMonitoringHelper do
context 'when callout dismissed' do context 'when callout dismissed' do
before 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 end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
......
...@@ -62,7 +62,7 @@ RSpec.describe TrialStatusWidgetHelper, :saas do ...@@ -62,7 +62,7 @@ RSpec.describe TrialStatusWidgetHelper, :saas do
target_id: shared_expected_attrs[:container_id], target_id: shared_expected_attrs[:container_id],
start_initially_shown: start_initially_shown, start_initially_shown: start_initially_shown,
trial_end_date: trial_end_date, 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 user_callouts_feature_id: user_callouts_feature_id
) )
) )
......
...@@ -41,7 +41,7 @@ RSpec.describe 'layouts/header/_current_user_dropdown' do ...@@ -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_content('One of your groups is running out')
expect(subject).to have_selector('.js-follow-link') 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-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
end end
...@@ -53,7 +53,7 @@ RSpec.describe 'layouts/header/_current_user_dropdown' do ...@@ -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).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('.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-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
end end
......
...@@ -69,7 +69,7 @@ RSpec.describe 'shared/billings/_eoa_bronze_plan_banner.html.haml' do ...@@ -69,7 +69,7 @@ RSpec.describe 'shared/billings/_eoa_bronze_plan_banner.html.haml' do
shared_examples 'when user dismissed the banner' do shared_examples 'when user dismissed the banner' do
before do before do
allow(namespace).to receive(:actual_plan_name).and_return(::Plan::BRONZE) 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 end
it 'does not display the banner' do it 'does not display the banner' do
...@@ -83,7 +83,7 @@ RSpec.describe 'shared/billings/_eoa_bronze_plan_banner.html.haml' do ...@@ -83,7 +83,7 @@ RSpec.describe 'shared/billings/_eoa_bronze_plan_banner.html.haml' do
before do before do
allow(view).to receive(:eoa_bronze_plan_end_date).and_return(eoa_bronze_plan_end_date) 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 end
context 'with group namespace' do context 'with group namespace' do
......
...@@ -57,9 +57,9 @@ module Sidebars ...@@ -57,9 +57,9 @@ module Sidebars
data: { trigger: 'manual', data: { trigger: 'manual',
container: 'body', container: 'body',
placement: 'right', placement: 'right',
highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION, highlight: Users::CalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION], highlight_priority: Users::Callout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: user_callouts_path, dismiss_endpoint: callouts_path,
auto_devops_help_path: help_page_path('topics/autodevops/index.md') } } auto_devops_help_path: help_page_path('topics/autodevops/index.md') } }
end end
......
...@@ -142,8 +142,8 @@ RSpec.describe RootController do ...@@ -142,8 +142,8 @@ RSpec.describe RootController do
context 'without customize homepage banner' do context 'without customize homepage banner' do
before do before do
Users::DismissUserCalloutService.new( Users::DismissCalloutService.new(
container: nil, current_user: user, params: { feature_name: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE } container: nil, current_user: user, params: { feature_name: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE }
).execute ).execute
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe UserCalloutsController do RSpec.describe Users::CalloutsController do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
before do before do
...@@ -15,11 +15,11 @@ RSpec.describe UserCalloutsController do ...@@ -15,11 +15,11 @@ RSpec.describe UserCalloutsController do
subject { post :create, params: params, format: :json } subject { post :create, params: params, format: :json }
context 'with valid feature name' do 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 context 'when callout entry does not exist' do
it 'creates a callout entry with dismissed state' 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 end
it 'returns success' do it 'returns success' do
...@@ -30,10 +30,10 @@ RSpec.describe UserCalloutsController do ...@@ -30,10 +30,10 @@ RSpec.describe UserCalloutsController do
end end
context 'when callout entry already exists' do 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 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) expect(response).to have_gitlab_http_status(:ok)
end end
end end
......
...@@ -169,7 +169,7 @@ RSpec.describe 'Database schema' do ...@@ -169,7 +169,7 @@ RSpec.describe 'Database schema' do
'PrometheusMetric' => %w[group], 'PrometheusMetric' => %w[group],
'ResourceLabelEvent' => %w[action], 'ResourceLabelEvent' => %w[action],
'User' => %w[layout dashboard project_view], 'User' => %w[layout dashboard project_view],
'UserCallout' => %w[feature_name], 'Users::Callout' => %w[feature_name],
'PrometheusAlert' => %w[operator] 'PrometheusAlert' => %w[operator]
}.freeze }.freeze
......
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :user_callout do factory :callout, class: 'Users::Callout' do
feature_name { :gke_cluster_integration } feature_name { :gke_cluster_integration }
user user
......
...@@ -13,7 +13,7 @@ RSpec.describe Mutations::UserCallouts::Create do ...@@ -13,7 +13,7 @@ RSpec.describe Mutations::UserCallouts::Create do
let(:feature_name) { 'not_supported' } let(:feature_name) { 'not_supported' }
it 'does not create a user callout' do 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 end
it 'returns error about feature name not being supported' do it 'returns error about feature name not being supported' do
...@@ -22,10 +22,10 @@ RSpec.describe Mutations::UserCallouts::Create do ...@@ -22,10 +22,10 @@ RSpec.describe Mutations::UserCallouts::Create do
end end
context 'when feature name is supported' do 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 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 end
it 'sets dismissed_at for the user callout' do it 'sets dismissed_at for the user callout' do
......
...@@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['UserCalloutFeatureNameEnum'] do ...@@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['UserCalloutFeatureNameEnum'] do
specify { expect(described_class.graphql_name).to eq('UserCalloutFeatureNameEnum') } specify { expect(described_class.graphql_name).to eq('UserCalloutFeatureNameEnum') }
it 'exposes all the existing user callout feature names' do 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
end end
...@@ -61,7 +61,7 @@ RSpec.describe IdeHelper do ...@@ -61,7 +61,7 @@ RSpec.describe IdeHelper do
context 'and the callout has been dismissed' do context 'and the callout has been dismissed' do
it 'disables environment guidance' 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) callout.update!(dismissed_at: Time.now - 1.week)
allow(helper).to receive(:current_user).and_return(User.find(project.creator.id)) allow(helper).to receive(:current_user).and_return(User.find(project.creator.id))
expect(helper.ide_data).to include('enable-environments-guidance' => 'false') expect(helper.ide_data).to include('enable-environments-guidance' => 'false')
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require "spec_helper" require "spec_helper"
RSpec.describe UserCalloutsHelper do RSpec.describe Users::CalloutsHelper do
let_it_be(:user, refind: true) { create(:user) } let_it_be(:user, refind: true) { create(:user) }
before do before do
...@@ -115,7 +115,7 @@ RSpec.describe UserCalloutsHelper do ...@@ -115,7 +115,7 @@ RSpec.describe UserCalloutsHelper do
context 'when the feature flags new version has been dismissed' do context 'when the feature flags new version has been dismissed' do
before 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 end
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
...@@ -203,83 +203,6 @@ RSpec.describe UserCalloutsHelper do ...@@ -203,83 +203,6 @@ RSpec.describe UserCalloutsHelper do
end end
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 describe '.show_security_newsletter_user_callout?' do
let_it_be(:admin) { create(:user, :admin) } 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 ...@@ -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(:created_custom_emoji).inverse_of(:creator) }
it { is_expected.to have_many(:in_product_marketing_emails) } it { is_expected.to have_many(:in_product_marketing_emails) }
it { is_expected.to have_many(:timelogs) } 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') } it { is_expected.to have_many(:group_callouts).class_name('Users::GroupCallout') }
describe '#user_detail' do describe '#user_detail' do
...@@ -5589,7 +5589,7 @@ RSpec.describe User do ...@@ -5589,7 +5589,7 @@ RSpec.describe User do
describe '#dismissed_callout?' do describe '#dismissed_callout?' do
let_it_be(:user, refind: true) { create(:user) } 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 context 'when no callout dismissal record exists' do
it 'returns false when no ignore_dismissal_earlier_than provided' do it 'returns false when no ignore_dismissal_earlier_than provided' do
...@@ -5599,7 +5599,7 @@ RSpec.describe User do ...@@ -5599,7 +5599,7 @@ RSpec.describe User do
context 'when dismissed callout exists' do context 'when dismissed callout exists' do
before_all 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 end
it 'returns true when no ignore_dismissal_earlier_than provided' do it 'returns true when no ignore_dismissal_earlier_than provided' do
...@@ -5618,12 +5618,12 @@ RSpec.describe User do ...@@ -5618,12 +5618,12 @@ RSpec.describe User do
describe '#find_or_initialize_callout' do describe '#find_or_initialize_callout' do
let_it_be(:user, refind: true) { create(:user) } 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) } subject(:find_or_initialize_callout) { user.find_or_initialize_callout(feature_name) }
context 'when callout exists' do 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 it 'returns existing callout' do
expect(find_or_initialize_callout).to eq(callout) expect(find_or_initialize_callout).to eq(callout)
...@@ -5633,7 +5633,7 @@ RSpec.describe User do ...@@ -5633,7 +5633,7 @@ RSpec.describe User do
context 'when callout does not exist' do context 'when callout does not exist' do
context 'when feature name is valid' do context 'when feature name is valid' do
it 'initializes a new callout' 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 end
it 'is valid' do it 'is valid' do
...@@ -5645,7 +5645,7 @@ RSpec.describe User do ...@@ -5645,7 +5645,7 @@ RSpec.describe User do
let(:feature_name) { 'notvalid' } let(:feature_name) { 'notvalid' }
it 'initializes a new callout' 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 end
it 'is not valid' do it 'is not valid' do
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe UserCallout do RSpec.describe Users::Callout do
let_it_be(:callout) { create(:user_callout) } let_it_be(:callout) { create(:callout) }
it_behaves_like 'having unique enum values' it_behaves_like 'having unique enum values'
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Calloutable do RSpec.describe Users::Calloutable do
subject { build(:user_callout) } subject { build(:callout) }
describe "Associations" do describe "Associations" do
it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:user) }
...@@ -14,9 +14,9 @@ RSpec.describe Calloutable do ...@@ -14,9 +14,9 @@ RSpec.describe Calloutable do
end end
describe '#dismissed_after?' do describe '#dismissed_after?' do
let(:some_feature_name) { UserCallout.feature_names.keys.second } let(:some_feature_name) { Users::Callout.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_month_ago) { create(: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(: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 it 'returns whether a callout dismissed after specified date' do
expect(callout_dismissed_month_ago.dismissed_after?(15.days.ago)).to eq(false) expect(callout_dismissed_month_ago.dismissed_after?(15.days.ago)).to eq(false)
......
...@@ -7,7 +7,7 @@ RSpec.describe 'Create a user callout' do ...@@ -7,7 +7,7 @@ RSpec.describe 'Create a user callout' do
let_it_be(:current_user) { create(:user) } 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 let(:input) do
{ {
......
...@@ -252,7 +252,7 @@ RSpec.describe MergeRequestWidgetEntity do ...@@ -252,7 +252,7 @@ RSpec.describe MergeRequestWidgetEntity do
subject { described_class.new(resource, request: request).as_json } subject { described_class.new(resource, request: request).as_json }
it 'provides a valid path value for user callout path' do 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 end
it 'provides a valid value for suggest pipeline feature id' do it 'provides a valid value for suggest pipeline feature id' do
...@@ -362,7 +362,7 @@ RSpec.describe MergeRequestWidgetEntity do ...@@ -362,7 +362,7 @@ RSpec.describe MergeRequestWidgetEntity do
context 'when suggest pipeline has been dismissed' do context 'when suggest pipeline has been dismissed' do
before 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 end
it 'is true' do it 'is true' do
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Users::DismissUserCalloutService do RSpec.describe Users::DismissCalloutService do
describe '#execute' do describe '#execute' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:params) { { feature_name: feature_name } } 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 subject(:execute) do
described_class.new( described_class.new(
...@@ -15,6 +15,6 @@ RSpec.describe Users::DismissUserCalloutService do ...@@ -15,6 +15,6 @@ RSpec.describe Users::DismissUserCalloutService do
).execute ).execute
end end
it_behaves_like 'dismissing user callout', UserCallout it_behaves_like 'dismissing user callout', Users::Callout
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment