From 994b1729e413760c0134952ddcfb4562d75c2609 Mon Sep 17 00:00:00 2001
From: Zack Cuddy <zcuddy@gitlab.com>
Date: Thu, 16 May 2024 08:48:20 +0000
Subject: [PATCH] Admin Settings - Disable private profiles

This is part 1 of 2 towards
implementing a new application
setting that allows admins to disallow
users from creating private profiles.

The follow up MR will implement the
setting on the user profile UI.
---
 .../_private_profile_restrictions.html.haml   |   3 +
 .../_user_restrictions.html.haml              |   2 +-
 .../private_profile_restrictions.vue          | 118 ++++++++++
 .../user_restrictions/constants.js            |   7 +
 .../user_restrictions/index.js                |  24 ++
 .../application_settings/general/index.js     |   2 +
 .../admin/application_settings_controller.rb  |   6 +
 .../_private_profile_restrictions.html.haml   |   5 +
 .../private_profile_restrictions_spec.js      | 216 ++++++++++++++++++
 .../application_settings_controller_spec.rb   |  39 +++-
 .../general.html.haml_spec.rb                 |   8 +
 locale/gitlab.pot                             |  15 +-
 .../general.html.haml_spec.rb                 |   8 +
 13 files changed, 444 insertions(+), 9 deletions(-)
 create mode 100644 app/views/admin/application_settings/_private_profile_restrictions.html.haml
 create mode 100644 ee/app/assets/javascripts/admin/application_settings/user_restrictions/components/private_profile_restrictions.vue
 create mode 100644 ee/app/assets/javascripts/admin/application_settings/user_restrictions/constants.js
 create mode 100644 ee/app/assets/javascripts/admin/application_settings/user_restrictions/index.js
 create mode 100644 ee/app/views/admin/application_settings/_private_profile_restrictions.html.haml
 create mode 100644 ee/spec/frontend/admin/application_settings/user_restrictions/components/private_profile_restrictions_spec.js

diff --git a/app/views/admin/application_settings/_private_profile_restrictions.html.haml b/app/views/admin/application_settings/_private_profile_restrictions.html.haml
new file mode 100644
index 0000000000000..561fa85d534bd
--- /dev/null
+++ b/app/views/admin/application_settings/_private_profile_restrictions.html.haml
@@ -0,0 +1,3 @@
+- form = local_assigns.fetch(:form)
+
+= form.gitlab_ui_checkbox_component :user_defaults_to_private_profile, s_("AdminSettings|Make new users' profiles private by default")
diff --git a/app/views/admin/application_settings/_user_restrictions.html.haml b/app/views/admin/application_settings/_user_restrictions.html.haml
index 4bdc21a3695ea..3cf29c45cf5fa 100644
--- a/app/views/admin/application_settings/_user_restrictions.html.haml
+++ b/app/views/admin/application_settings/_user_restrictions.html.haml
@@ -6,6 +6,6 @@
   - if Feature.enabled?(:ui_for_organizations, current_user)
     = form.gitlab_ui_checkbox_component :can_create_organization, _("Allow users to create organizations")
   = form.gitlab_ui_checkbox_component :can_create_group, _("Allow new users to create top-level groups")
-  = form.gitlab_ui_checkbox_component :user_defaults_to_private_profile, _("Make new users' profiles private by default")
+  = render_if_exists 'admin/application_settings/private_profile_restrictions', form: form
   = render_if_exists 'admin/application_settings/allow_account_deletion', form: form
   = form.gitlab_ui_checkbox_component :allow_project_creation_for_guest_and_below, _("Allow users with up to Guest role to create groups and personal projects")
diff --git a/ee/app/assets/javascripts/admin/application_settings/user_restrictions/components/private_profile_restrictions.vue b/ee/app/assets/javascripts/admin/application_settings/user_restrictions/components/private_profile_restrictions.vue
new file mode 100644
index 0000000000000..e94420ebca9f0
--- /dev/null
+++ b/ee/app/assets/javascripts/admin/application_settings/user_restrictions/components/private_profile_restrictions.vue
@@ -0,0 +1,118 @@
+<script>
+import { GlFormCheckbox, GlIcon, GlPopover, GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { PRIVATE_PROFILES_DISABLED_ICON, PRIVATE_PROFILES_DISABLED_HELP_LINK } from '../constants';
+
+export default {
+  name: 'PrivateProfileRestrictions',
+  i18n: {
+    defaultToPrivateProfiles: s__("AdminSettings|Make new users' profiles private by default"),
+    allowPrivateProfiles: s__('AdminSettings|Allow users to make their profiles private'),
+    privateProfilesDisabledPopoverTitle: s__('AdminSettings|Setting locked'),
+    privateProfilesDisabledPopoverInfo: s__(
+      'AdminSettings|The option to make profiles private has been disabled. Profiles are required to be public in this instance, and cannot be set to private by default. %{linkStart}Learn more%{linkEnd}.',
+    ),
+  },
+  components: {
+    GlFormCheckbox,
+    GlIcon,
+    GlPopover,
+    GlLink,
+    GlSprintf,
+  },
+  mixins: [glFeatureFlagMixin()],
+  props: {
+    defaultToPrivateProfiles: {
+      type: Object,
+      required: true,
+    },
+    allowPrivateProfiles: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      defaultToPrivateProfilesValue: parseBoolean(this.defaultToPrivateProfiles.value),
+      allowPrivateProfilesValue: parseBoolean(this.allowPrivateProfiles.value),
+    };
+  },
+  computed: {
+    disablePrivateProfilesFeatureEnabled() {
+      return this.glFeatures.disablePrivateProfiles;
+    },
+    privateProfilesDisabled() {
+      return this.disablePrivateProfilesFeatureEnabled && !this.allowPrivateProfilesValue;
+    },
+  },
+  methods: {
+    allowPrivateProfilesChanged(val) {
+      if (!val) {
+        this.defaultToPrivateProfilesValue = false;
+      }
+    },
+  },
+  PRIVATE_PROFILES_DISABLED_ICON,
+  PRIVATE_PROFILES_DISABLED_HELP_LINK,
+};
+</script>
+
+<template>
+  <div>
+    <template v-if="disablePrivateProfilesFeatureEnabled">
+      <!-- This hidden field allows for unchecked checkboxes to be submitted to HTML form -->
+      <!-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#sect2 -->
+      <input
+        type="hidden"
+        :name="allowPrivateProfiles.name"
+        value="0"
+        :data-testid="`${allowPrivateProfiles.id}-hidden`"
+      />
+      <gl-form-checkbox
+        :id="allowPrivateProfiles.id"
+        v-model="allowPrivateProfilesValue"
+        :name="allowPrivateProfiles.name"
+        :data-testid="allowPrivateProfiles.id"
+        @input="allowPrivateProfilesChanged"
+      >
+        {{ $options.i18n.allowPrivateProfiles }}
+      </gl-form-checkbox>
+    </template>
+
+    <!-- This hidden field allows for unchecked checkboxes to be submitted to HTML form -->
+    <!-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#sect2 -->
+    <input
+      type="hidden"
+      :name="defaultToPrivateProfiles.name"
+      value="0"
+      :data-testid="`${defaultToPrivateProfiles.id}-hidden`"
+    />
+    <gl-form-checkbox
+      :id="defaultToPrivateProfiles.id"
+      v-model="defaultToPrivateProfilesValue"
+      :name="defaultToPrivateProfiles.name"
+      :disabled="privateProfilesDisabled"
+      :data-testid="defaultToPrivateProfiles.id"
+    >
+      {{ $options.i18n.defaultToPrivateProfiles }}
+
+      <template v-if="privateProfilesDisabled">
+        <gl-icon :id="$options.PRIVATE_PROFILES_DISABLED_ICON" name="lock" />
+        <gl-popover :target="$options.PRIVATE_PROFILES_DISABLED_ICON" placement="top">
+          <template #title> {{ $options.i18n.privateProfilesDisabledPopoverTitle }} </template>
+          <slot>
+            <gl-sprintf :message="$options.i18n.privateProfilesDisabledPopoverInfo">
+              <template #link="{ content }">
+                <gl-link :href="$options.PRIVATE_PROFILES_DISABLED_HELP_LINK">{{
+                  content
+                }}</gl-link>
+              </template>
+            </gl-sprintf>
+          </slot>
+        </gl-popover>
+      </template>
+    </gl-form-checkbox>
+  </div>
+</template>
diff --git a/ee/app/assets/javascripts/admin/application_settings/user_restrictions/constants.js b/ee/app/assets/javascripts/admin/application_settings/user_restrictions/constants.js
new file mode 100644
index 0000000000000..05fe86918a78c
--- /dev/null
+++ b/ee/app/assets/javascripts/admin/application_settings/user_restrictions/constants.js
@@ -0,0 +1,7 @@
+import { helpPagePath } from '~/helpers/help_page_helper';
+
+export const PRIVATE_PROFILES_DISABLED_ICON = 'private-profiles-disabled-icon';
+export const PRIVATE_PROFILES_DISABLED_HELP_LINK = helpPagePath(
+  'administration/settings/account_and_limit_settings',
+  { anchor: 'set-profiles-of-new-users-to-private-by-default' },
+);
diff --git a/ee/app/assets/javascripts/admin/application_settings/user_restrictions/index.js b/ee/app/assets/javascripts/admin/application_settings/user_restrictions/index.js
new file mode 100644
index 0000000000000..90c9166c813dd
--- /dev/null
+++ b/ee/app/assets/javascripts/admin/application_settings/user_restrictions/index.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import { parseRailsFormFields } from '~/lib/utils/forms';
+import PrivateProfileRestrictions from './components/private_profile_restrictions.vue';
+
+export const initPrivateProfileRestrictions = () => {
+  const el = document.getElementById('js-admin-settings-user-private-profile-restrictions');
+
+  if (!el) return false;
+
+  const { defaultToPrivateProfiles, allowPrivateProfiles } = parseRailsFormFields(el);
+
+  return new Vue({
+    el,
+    name: 'PrivateProfileRestrictionsRoot',
+    render(createElement) {
+      return createElement(PrivateProfileRestrictions, {
+        props: {
+          defaultToPrivateProfiles,
+          allowPrivateProfiles,
+        },
+      });
+    },
+  });
+};
diff --git a/ee/app/assets/javascripts/pages/admin/application_settings/general/index.js b/ee/app/assets/javascripts/pages/admin/application_settings/general/index.js
index 22b30eace6c55..a39d60946d651 100644
--- a/ee/app/assets/javascripts/pages/admin/application_settings/general/index.js
+++ b/ee/app/assets/javascripts/pages/admin/application_settings/general/index.js
@@ -1,4 +1,5 @@
 import '~/pages/admin/application_settings/general/index';
+import { initPrivateProfileRestrictions } from 'ee/admin/application_settings/user_restrictions';
 import initAddLicenseApp from 'ee/admin/application_settings/general/add_license';
 import { initScimTokenApp } from 'ee/saml_sso';
 import { initAdminDeletionProtectionSettings } from 'ee/admin/application_settings/deletion_protection';
@@ -11,4 +12,5 @@ initMaintenanceModeSettings();
 initServicePingSettingsClickTracking();
 initAddLicenseApp();
 initScimTokenApp();
+initPrivateProfileRestrictions();
 initInputCopyToggleVisibility();
diff --git a/ee/app/controllers/ee/admin/application_settings_controller.rb b/ee/app/controllers/ee/admin/application_settings_controller.rb
index 5091ca2eba821..77f80b7619afc 100644
--- a/ee/app/controllers/ee/admin/application_settings_controller.rb
+++ b/ee/app/controllers/ee/admin/application_settings_controller.rb
@@ -25,6 +25,8 @@ module ApplicationSettingsController
         before_action :verify_namespace_plan_check_enabled, only: [:namespace_storage]
         before_action :indexing_status, only: [:advanced_search]
 
+        before_action :push_disable_private_profiles_feature, only: [:general]
+
         feature_category :sm_provisioning, [:seat_link_payload]
         feature_category :source_code_management, [:templates]
         feature_category :global_search, [:advanced_search]
@@ -207,6 +209,10 @@ def analytics
           ::Feature.disabled?(:product_analytics_admin_settings)
       end
 
+      def push_disable_private_profiles_feature
+        push_licensed_feature(:disable_private_profiles) if ::Feature.enabled?(:disallow_private_profiles)
+      end
+
       private
 
       override :valid_setting_panels
diff --git a/ee/app/views/admin/application_settings/_private_profile_restrictions.html.haml b/ee/app/views/admin/application_settings/_private_profile_restrictions.html.haml
new file mode 100644
index 0000000000000..36976df68bdbc
--- /dev/null
+++ b/ee/app/views/admin/application_settings/_private_profile_restrictions.html.haml
@@ -0,0 +1,5 @@
+- form = local_assigns.fetch(:form)
+
+#js-admin-settings-user-private-profile-restrictions
+  = form.hidden_field :make_profile_private, data: { js_name: 'allowPrivateProfiles' }
+  = form.hidden_field :user_defaults_to_private_profile, data: { js_name: 'defaultToPrivateProfiles' }
diff --git a/ee/spec/frontend/admin/application_settings/user_restrictions/components/private_profile_restrictions_spec.js b/ee/spec/frontend/admin/application_settings/user_restrictions/components/private_profile_restrictions_spec.js
new file mode 100644
index 0000000000000..40c4ac016d9a0
--- /dev/null
+++ b/ee/spec/frontend/admin/application_settings/user_restrictions/components/private_profile_restrictions_spec.js
@@ -0,0 +1,216 @@
+import { GlIcon, GlPopover, GlLink, GlSprintf } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import PrivateProfileRestrictions from 'ee/admin/application_settings/user_restrictions/components/private_profile_restrictions.vue';
+import {
+  PRIVATE_PROFILES_DISABLED_ICON,
+  PRIVATE_PROFILES_DISABLED_HELP_LINK,
+} from 'ee/admin/application_settings/user_restrictions/constants';
+
+describe('PrivateProfileRestrictions', () => {
+  let wrapper;
+
+  const defaultProps = {
+    defaultToPrivateProfiles: {
+      id: 'defaultToPrivateProfiles',
+      name: 'defaultToPrivateProfiles',
+      value: 'false',
+    },
+    allowPrivateProfiles: {
+      id: 'allowPrivateProfiles',
+      name: 'allowPrivateProfiles',
+      value: 'true',
+    },
+  };
+
+  const createComponent = ({ props = {}, features = {} } = {}) => {
+    wrapper = shallowMountExtended(PrivateProfileRestrictions, {
+      propsData: {
+        ...defaultProps,
+        ...props,
+      },
+      provide: {
+        glFeatures: {
+          disablePrivateProfiles: true,
+          ...features,
+        },
+      },
+      stubs: {
+        GlSprintf,
+      },
+    });
+  };
+
+  const findDefaultToPrivateProfilesHiddenField = () =>
+    wrapper.findByTestId(`${defaultProps.defaultToPrivateProfiles.id}-hidden`);
+  const findDefaultToPrivateProfilesCheckbox = () =>
+    wrapper.findByTestId(defaultProps.defaultToPrivateProfiles.id);
+  const findAllowPrivateProfilesHiddenField = () =>
+    wrapper.findByTestId(`${defaultProps.allowPrivateProfiles.id}-hidden`);
+  const findAllowPrivateProfilesCheckbox = () =>
+    wrapper.findByTestId(defaultProps.allowPrivateProfiles.id);
+
+  const findDisabledLockIcon = () => wrapper.findComponent(GlIcon);
+  const findDisabledPopover = () => wrapper.findComponent(GlPopover);
+  const findDisabledHelpLink = () => wrapper.findComponent(GlLink);
+
+  describe('template', () => {
+    describe('when feature is enabled', () => {
+      describe('with allowPrivateProfiles set to true', () => {
+        beforeEach(() => {
+          createComponent({
+            props: {
+              allowPrivateProfiles: {
+                ...defaultProps.allowPrivateProfiles,
+                value: 'true',
+              },
+            },
+            features: {
+              disablePrivateProfiles: true,
+            },
+          });
+        });
+
+        it('renders all fields', () => {
+          expect(findDefaultToPrivateProfilesHiddenField().exists()).toBe(true);
+          expect(findDefaultToPrivateProfilesCheckbox().exists()).toBe(true);
+          expect(findAllowPrivateProfilesHiddenField().exists()).toBe(true);
+          expect(findAllowPrivateProfilesCheckbox().exists()).toBe(true);
+        });
+
+        it('does not disable the defaultToPrivateProfiles checkbox', () => {
+          expect(findDefaultToPrivateProfilesCheckbox().attributes('disabled')).toBeUndefined();
+        });
+
+        it('does not render disabled icon, popover, or help text', () => {
+          expect(findDisabledLockIcon().exists()).toBe(false);
+          expect(findDisabledPopover().exists()).toBe(false);
+          expect(findDisabledHelpLink().exists()).toBe(false);
+        });
+      });
+
+      describe('with allowPrivateProfiles set to false', () => {
+        beforeEach(() => {
+          createComponent({
+            props: {
+              allowPrivateProfiles: {
+                ...defaultProps.allowPrivateProfiles,
+                value: 'false',
+              },
+            },
+            features: {
+              disablePrivateProfiles: true,
+            },
+          });
+        });
+
+        it('renders all fields', () => {
+          expect(findDefaultToPrivateProfilesHiddenField().exists()).toBe(true);
+          expect(findDefaultToPrivateProfilesCheckbox().exists()).toBe(true);
+          expect(findAllowPrivateProfilesHiddenField().exists()).toBe(true);
+          expect(findAllowPrivateProfilesCheckbox().exists()).toBe(true);
+        });
+
+        it('does disable the defaultToPrivateProfiles checkbox', () => {
+          expect(findDefaultToPrivateProfilesCheckbox().attributes('disabled')).toBe('true');
+        });
+
+        it('does render disabled icon, popover, or help text', () => {
+          expect(findDisabledLockIcon().attributes('id')).toBe(PRIVATE_PROFILES_DISABLED_ICON);
+          expect(findDisabledPopover().text()).toContain(
+            'The option to make profiles private has been disabled.',
+          );
+          expect(findDisabledHelpLink().attributes('href')).toBe(
+            PRIVATE_PROFILES_DISABLED_HELP_LINK,
+          );
+        });
+      });
+    });
+
+    describe('when feature is disabled', () => {
+      beforeEach(() => {
+        createComponent({
+          props: {
+            allowPrivateProfiles: {
+              ...defaultProps.allowPrivateProfiles,
+              value: 'false',
+            },
+          },
+          features: {
+            disablePrivateProfiles: false,
+          },
+        });
+      });
+
+      it('does not render allowPrivateProfiles checkbox', () => {
+        expect(findDefaultToPrivateProfilesHiddenField().exists()).toBe(true);
+        expect(findDefaultToPrivateProfilesCheckbox().exists()).toBe(true);
+        expect(findAllowPrivateProfilesHiddenField().exists()).toBe(false);
+        expect(findAllowPrivateProfilesCheckbox().exists()).toBe(false);
+      });
+
+      it('does not disable the defaultToPrivateProfiles checkbox', () => {
+        expect(findDefaultToPrivateProfilesCheckbox().attributes('disabled')).toBeUndefined();
+      });
+
+      it('does not render disabled icon, popover, or help text', () => {
+        expect(findDisabledLockIcon().exists()).toBe(false);
+        expect(findDisabledPopover().exists()).toBe(false);
+        expect(findDisabledHelpLink().exists()).toBe(false);
+      });
+    });
+  });
+
+  describe('onMount', () => {
+    beforeEach(() => {
+      createComponent();
+    });
+
+    it('properly assigns HAML Boolean-String to model', () => {
+      expect(findDefaultToPrivateProfilesCheckbox().attributes('checked')).toBeUndefined();
+      expect(findAllowPrivateProfilesCheckbox().attributes('checked')).toBe('true');
+    });
+  });
+
+  describe('events', () => {
+    describe('when feature is enabled and both fields are checked', () => {
+      beforeEach(() => {
+        createComponent({
+          props: {
+            defaultToPrivateProfiles: {
+              ...defaultProps.defaultToPrivateProfiles,
+              value: 'true',
+            },
+            allowPrivateProfiles: {
+              ...defaultProps.allowPrivateProfiles,
+              value: 'true',
+            },
+          },
+          features: {
+            disablePrivateProfiles: true,
+          },
+        });
+      });
+
+      it('by default, field is not disabled and no disabled elements exist', () => {
+        expect(findDefaultToPrivateProfilesCheckbox().attributes('disabled')).toBeUndefined();
+        expect(findDisabledLockIcon().exists()).toBe(false);
+        expect(findDisabledPopover().exists()).toBe(false);
+        expect(findDisabledHelpLink().exists()).toBe(false);
+      });
+
+      it('when allowPrivateProfiles is updated to false, defaultToPrivateProfiles is also disabled and set to false', async () => {
+        expect(findDefaultToPrivateProfilesCheckbox().attributes('checked')).toBe('true');
+
+        findAllowPrivateProfilesCheckbox().vm.$emit('input', false);
+        await nextTick();
+
+        expect(findDefaultToPrivateProfilesCheckbox().attributes('checked')).toBeUndefined();
+        expect(findDefaultToPrivateProfilesCheckbox().attributes('disabled')).toBe('true');
+        expect(findDisabledLockIcon().exists()).toBe(true);
+        expect(findDisabledPopover().exists()).toBe(true);
+        expect(findDisabledHelpLink().exists()).toBe(true);
+      });
+    });
+  });
+});
diff --git a/ee/spec/requests/admin/application_settings_controller_spec.rb b/ee/spec/requests/admin/application_settings_controller_spec.rb
index 0da07b4142137..69535db0330f7 100644
--- a/ee/spec/requests/admin/application_settings_controller_spec.rb
+++ b/ee/spec/requests/admin/application_settings_controller_spec.rb
@@ -2,14 +2,17 @@
 
 require 'spec_helper'
 
-RSpec.describe Admin::ApplicationSettingsController, feature_category: :shared do
+RSpec.describe Admin::ApplicationSettingsController, :enable_admin_mode, feature_category: :shared do
+  include StubENV
+
   let(:admin) { create(:admin) }
 
-  describe 'PUT update_microsoft_application', :enable_admin_mode, feature_category: :system_access do
-    before do
-      sign_in(admin)
-    end
+  before do
+    sign_in(admin)
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+  end
 
+  describe 'PUT update_microsoft_application', feature_category: :system_access do
     it_behaves_like 'Microsoft application controller actions' do
       let(:path) { update_microsoft_application_admin_application_settings_path }
 
@@ -18,4 +21,30 @@
       end
     end
   end
+
+  describe 'GET #general', feature_category: :user_management do
+    context 'with feature flag :disallow_private_profiles disabled' do
+      before do
+        stub_feature_flags(disallow_private_profiles: false)
+      end
+
+      it 'does not push :disable_private_profiles license feature' do
+        expect_next_instance_of(described_class) do |instance|
+          expect(instance).to receive(:push_licensed_feature).with(:password_complexity)
+          expect(instance).not_to receive(:push_licensed_feature).with(:disable_private_profiles)
+        end
+
+        get general_admin_application_settings_path
+      end
+    end
+
+    it 'does push :disable_private_profiles license feature' do
+      expect_next_instance_of(described_class) do |instance|
+        expect(instance).to receive(:push_licensed_feature).with(:password_complexity)
+        expect(instance).to receive(:push_licensed_feature).with(:disable_private_profiles)
+      end
+
+      get general_admin_application_settings_path
+    end
+  end
 end
diff --git a/ee/spec/views/admin/application_settings/general.html.haml_spec.rb b/ee/spec/views/admin/application_settings/general.html.haml_spec.rb
index a35ca74b60955..3c482df1dbcc0 100644
--- a/ee/spec/views/admin/application_settings/general.html.haml_spec.rb
+++ b/ee/spec/views/admin/application_settings/general.html.haml_spec.rb
@@ -230,4 +230,12 @@
       end
     end
   end
+
+  describe 'private profile restrictions', feature_category: :user_management do
+    it 'renders correct ee partial' do
+      render
+
+      expect(rendered).to render_template('admin/application_settings/_private_profile_restrictions')
+    end
+  end
 end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7d710976ec5b4..fda4e96c9c232 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3629,6 +3629,9 @@ msgstr ""
 msgid "AdminSettings|Allow runner registration token"
 msgstr ""
 
+msgid "AdminSettings|Allow users to make their profiles private"
+msgstr ""
+
 msgid "AdminSettings|Auto DevOps domain"
 msgstr ""
 
@@ -3800,6 +3803,9 @@ msgstr ""
 msgid "AdminSettings|Limit the amount of namespace and project data to index"
 msgstr ""
 
+msgid "AdminSettings|Make new users' profiles private by default"
+msgstr ""
+
 msgid "AdminSettings|Maximum downstream pipeline trigger rate"
 msgstr ""
 
@@ -3932,6 +3938,9 @@ msgstr ""
 msgid "AdminSettings|Set visibility of project contents and configure Git access protocols."
 msgstr ""
 
+msgid "AdminSettings|Setting locked"
+msgstr ""
+
 msgid "AdminSettings|Setting must be greater than 0."
 msgstr ""
 
@@ -3962,6 +3971,9 @@ msgstr ""
 msgid "AdminSettings|The maximum number of included files per pipeline."
 msgstr ""
 
+msgid "AdminSettings|The option to make profiles private has been disabled. Profiles are required to be public in this instance, and cannot be set to private by default. %{linkStart}Learn more%{linkEnd}."
+msgstr ""
+
 msgid "AdminSettings|The selected level must be different from the selected default group and project visibility."
 msgstr ""
 
@@ -30965,9 +30977,6 @@ msgstr ""
 msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
 msgstr ""
 
-msgid "Make new users' profiles private by default"
-msgstr ""
-
 msgid "Make sure you choose a strong, unique password."
 msgstr ""
 
diff --git a/spec/views/admin/application_settings/general.html.haml_spec.rb b/spec/views/admin/application_settings/general.html.haml_spec.rb
index 63f66d269603b..79c42dd1b946f 100644
--- a/spec/views/admin/application_settings/general.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/general.html.haml_spec.rb
@@ -140,4 +140,12 @@
       it_behaves_like 'does not render the form'
     end
   end
+
+  describe 'private profile restrictions', feature_category: :user_management do
+    it 'renders correct ce partial' do
+      render
+
+      expect(rendered).to render_template('admin/application_settings/_private_profile_restrictions')
+    end
+  end
 end
-- 
GitLab