summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_table_button.vue92
-rw-r--r--app/assets/stylesheets/components/content_editor.scss4
-rw-r--r--app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb2
-rw-r--r--app/controllers/concerns/metrics_dashboard.rb2
-rw-r--r--app/controllers/projects/metrics_dashboard_controller.rb2
-rw-r--r--app/controllers/projects/prometheus/metrics_controller.rb5
-rw-r--r--app/helpers/application_settings_helper.rb3
-rw-r--r--app/helpers/profiles_helper.rb5
-rw-r--r--app/models/application_setting_implementation.rb3
-rw-r--r--app/models/concerns/recoverable_by_any_email.rb45
-rw-r--r--app/models/namespaces/traversal/linear_scopes.rb9
-rw-r--r--app/models/user.rb1
-rw-r--r--app/views/admin/application_settings/_user_restrictions.html.haml1
-rw-r--r--app/views/profiles/accounts/show.html.haml65
14 files changed, 164 insertions, 75 deletions
diff --git a/app/assets/javascripts/content_editor/components/toolbar_table_button.vue b/app/assets/javascripts/content_editor/components/toolbar_table_button.vue
index bf2740f9864..a4440659d61 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_table_button.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_table_button.vue
@@ -1,11 +1,5 @@
<script>
-import {
- GlDropdown,
- GlDropdownDivider,
- GlDropdownForm,
- GlButton,
- GlTooltipDirective as GlTooltip,
-} from '@gitlab/ui';
+import { GlDisclosureDropdown, GlButton, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import { clamp } from '../services/utils';
@@ -19,9 +13,7 @@ const MAX_COLS = 10;
export default {
components: {
GlButton,
- GlDropdown,
- GlDropdownDivider,
- GlDropdownForm,
+ GlDisclosureDropdown,
},
directives: {
GlTooltip,
@@ -61,45 +53,75 @@ export default {
.run();
this.resetState();
+ this.$refs.dropdown.close();
this.$emit('execute', { contentType: 'table' });
},
getButtonLabel(rows, cols) {
- return sprintf(__('Insert a %{rows}x%{cols} table.'), { rows, cols });
+ return sprintf(__('Insert a %{rows}×%{cols} table'), { rows, cols });
},
+ onKeydown(key) {
+ const delta = {
+ ArrowUp: { rows: -1, cols: 0 },
+ ArrowDown: { rows: 1, cols: 0 },
+ ArrowLeft: { rows: 0, cols: -1 },
+ ArrowRight: { rows: 0, cols: 1 },
+ }[key] || { rows: 0, cols: 0 };
+
+ const rows = clamp(this.rows + delta.rows, 1, this.maxRows);
+ const cols = clamp(this.cols + delta.cols, 1, this.maxCols);
+
+ this.setRowsAndCols(rows, cols);
+ },
+ setFocus(row, col) {
+ this.$refs[`table-${row}-${col}`][0].$el.focus();
+ },
+ },
+ MAX_COLS,
+ MAX_ROWS,
+ popperOptions: {
+ strategy: 'fixed',
},
};
</script>
<template>
- <gl-dropdown
+ <gl-disclosure-dropdown
+ ref="dropdown"
v-gl-tooltip
size="small"
category="tertiary"
icon="table"
- :title="__('Insert table')"
- :text="__('Insert table')"
- class="content-editor-dropdown"
- right
+ :aria-label="__('Insert table')"
+ :toggle-text="__('Insert table')"
+ :popper-opts="$options.popperOptions"
+ class="content-editor-table-dropdown"
text-sr-only
- lazy
+ :fluid-width="true"
+ @shown="setFocus(1, 1)"
>
- <gl-dropdown-form class="gl-px-3! gl-pb-2!">
- <div v-for="r of list(maxRows)" :key="r" class="gl-display-flex">
- <gl-button
- v-for="c of list(maxCols)"
- :key="c"
- :data-testid="`table-${r}-${c}`"
- :class="{ 'active gl-bg-blue-50!': r <= rows && c <= cols }"
- :aria-label="getButtonLabel(r, c)"
- class="table-creator-grid-item gl-display-inline gl-rounded-0! gl-w-6! gl-h-6! gl-p-0!"
- @mouseover="setRowsAndCols(r, c)"
- @click="insertTable()"
- />
- </div>
- <gl-dropdown-divider class="gl-my-3! gl-mx-n3!" />
- <div class="gl-px-1">
- {{ getButtonLabel(rows, cols) }}
+ <div
+ class="gl-p-3 gl-pt-2"
+ role="grid"
+ :aria-colcount="$options.MAX_COLS"
+ :aria-rowcount="$options.MAX_ROWS"
+ >
+ <div v-for="r of list(maxRows)" :key="r" class="gl-display-flex" role="row">
+ <div v-for="c of list(maxCols)" :key="c" role="gridcell">
+ <gl-button
+ :ref="`table-${r}-${c}`"
+ :class="{ 'active gl-bg-blue-50!': r <= rows && c <= cols }"
+ :aria-label="getButtonLabel(r, c)"
+ class="table-creator-grid-item gl-display-inline gl-rounded-0! gl-w-6! gl-h-6! gl-p-0!"
+ @mouseover="setRowsAndCols(r, c)"
+ @focus="setRowsAndCols(r, c)"
+ @click="insertTable()"
+ @keydown="onKeydown($event.key)"
+ />
+ </div>
</div>
- </gl-dropdown-form>
- </gl-dropdown>
+ </div>
+ <div class="gl-border-t gl-px-4 gl-pt-3 gl-pb-2">
+ {{ getButtonLabel(rows, cols) }}
+ </div>
+ </gl-disclosure-dropdown>
</template>
diff --git a/app/assets/stylesheets/components/content_editor.scss b/app/assets/stylesheets/components/content_editor.scss
index 4e3fb819f4c..c50fcd9218b 100644
--- a/app/assets/stylesheets/components/content_editor.scss
+++ b/app/assets/stylesheets/components/content_editor.scss
@@ -155,7 +155,9 @@
}
}
-
+.content-editor-table-dropdown .gl-new-dropdown-panel {
+ min-width: auto;
+}
.bubble-menu-form {
width: 320px;
diff --git a/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb b/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb
index ea9fd2de961..6a24a7308b7 100644
--- a/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb
+++ b/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb
@@ -9,6 +9,8 @@ module Metrics::Dashboard::PrometheusApiProxy
end
def prometheus_proxy
+ return not_found if Feature.enabled?(:remove_monitor_metrics)
+
variable_substitution_result =
proxy_variable_substitution_service.new(proxyable, permit_params).execute
diff --git a/app/controllers/concerns/metrics_dashboard.rb b/app/controllers/concerns/metrics_dashboard.rb
index 7e202235cfa..7a84c597424 100644
--- a/app/controllers/concerns/metrics_dashboard.rb
+++ b/app/controllers/concerns/metrics_dashboard.rb
@@ -10,6 +10,8 @@ module MetricsDashboard
extend ActiveSupport::Concern
def metrics_dashboard
+ return not_found if Feature.enabled?(:remove_monitor_metrics)
+
result = dashboard_finder.find(
project_for_dashboard,
current_user,
diff --git a/app/controllers/projects/metrics_dashboard_controller.rb b/app/controllers/projects/metrics_dashboard_controller.rb
index 510c882d537..c95594d87c0 100644
--- a/app/controllers/projects/metrics_dashboard_controller.rb
+++ b/app/controllers/projects/metrics_dashboard_controller.rb
@@ -17,6 +17,8 @@ module Projects
urgency :low
def show
+ return not_found if Feature.enabled?(:remove_monitor_metrics)
+
if environment
render 'projects/environments/metrics'
elsif default_environment
diff --git a/app/controllers/projects/prometheus/metrics_controller.rb b/app/controllers/projects/prometheus/metrics_controller.rb
index c20c80ba334..396841e667d 100644
--- a/app/controllers/projects/prometheus/metrics_controller.rb
+++ b/app/controllers/projects/prometheus/metrics_controller.rb
@@ -3,6 +3,7 @@
module Projects
module Prometheus
class MetricsController < Projects::ApplicationController
+ before_action :check_feature_availability!
before_action :authorize_admin_project!
before_action :require_prometheus_metrics!
@@ -127,6 +128,10 @@ module Projects
def metrics_params
params.require(:prometheus_metric).permit(:title, :query, :y_label, :unit, :legend, :group)
end
+
+ def check_feature_availability!
+ render_404 if Feature.enabled?(:remove_monitor_metrics)
+ end
end
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index dab682d88e0..82ebf53334e 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -491,7 +491,8 @@ module ApplicationSettingsHelper
:deactivation_email_additional_text,
:projects_api_rate_limit_unauthenticated,
:gitlab_dedicated_instance,
- :ci_max_includes
+ :ci_max_includes,
+ :allow_account_deletion
].tap do |settings|
next if Gitlab.com?
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index 979b979fba7..26463003f8d 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -68,6 +68,11 @@ module ProfilesHelper
def ssh_key_expiration_policy_enabled?
false
end
+
+ # Overridden in EE::ProfilesHelper#prevent_delete_account?
+ def prevent_delete_account?
+ false
+ end
end
ProfilesHelper.prepend_mod
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 845d402f550..47ba96e238e 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -253,7 +253,8 @@ module ApplicationSettingImplementation
user_defaults_to_private_profile: false,
projects_api_rate_limit_unauthenticated: 400,
gitlab_dedicated_instance: false,
- ci_max_includes: 150
+ ci_max_includes: 150,
+ allow_account_deletion: true
}.tap do |hsh|
hsh.merge!(non_production_defaults) unless Rails.env.production?
end
diff --git a/app/models/concerns/recoverable_by_any_email.rb b/app/models/concerns/recoverable_by_any_email.rb
new file mode 100644
index 00000000000..aaea7707d51
--- /dev/null
+++ b/app/models/concerns/recoverable_by_any_email.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+# Concern that overrides the Devise methods
+# to send reset password instructions to any verified user email
+module RecoverableByAnyEmail
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def send_reset_password_instructions(attributes = {})
+ return super unless Feature.enabled?(:password_reset_any_verified_email)
+
+ email = attributes.delete(:email)
+ super unless email
+
+ recoverable = by_email_with_errors(email)
+ recoverable.send_reset_password_instructions(to: email) if recoverable&.persisted?
+ recoverable
+ end
+
+ private
+
+ def by_email_with_errors(email)
+ record = find_by_any_email(email, confirmed: true) || new
+ record.errors.add(:email, :invalid) unless record.persisted?
+ record
+ end
+ end
+
+ def send_reset_password_instructions(opts = {})
+ return super() unless Feature.enabled?(:password_reset_any_verified_email)
+
+ token = set_reset_password_token
+ send_reset_password_instructions_notification(token, opts)
+
+ token
+ end
+
+ private
+
+ def send_reset_password_instructions_notification(token, opts = {})
+ return super(token) unless Feature.enabled?(:password_reset_any_verified_email)
+
+ send_devise_notification(:reset_password_instructions, token, opts)
+ end
+end
diff --git a/app/models/namespaces/traversal/linear_scopes.rb b/app/models/namespaces/traversal/linear_scopes.rb
index 792964a6c7f..c50d3dd1de6 100644
--- a/app/models/namespaces/traversal/linear_scopes.rb
+++ b/app/models/namespaces/traversal/linear_scopes.rb
@@ -25,8 +25,6 @@ module Namespaces
end
def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
- return super unless use_traversal_ids_for_ancestor_scopes?
-
self_and_ancestors_from_inner_join(
include_self: include_self,
upto: upto, hierarchy_order:
@@ -35,8 +33,6 @@ module Namespaces
end
def self_and_ancestor_ids(include_self: true)
- return super unless use_traversal_ids_for_ancestor_scopes?
-
self_and_ancestors(include_self: include_self).as_ids
end
@@ -87,11 +83,6 @@ module Namespaces
use_traversal_ids?
end
- def use_traversal_ids_for_ancestor_scopes?
- Feature.enabled?(:use_traversal_ids_for_ancestor_scopes) &&
- use_traversal_ids?
- end
-
def use_traversal_ids_for_descendants_scopes?
Feature.enabled?(:use_traversal_ids_for_descendants_scopes) &&
use_traversal_ids?
diff --git a/app/models/user.rb b/app/models/user.rb
index dc70ff2e232..0c8ff873ba6 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -91,6 +91,7 @@ class User < ApplicationRecord
# Must be included after `devise`
include EncryptedUserPassword
+ include RecoverableByAnyEmail
include AdminChangedPasswordNotifier
diff --git a/app/views/admin/application_settings/_user_restrictions.html.haml b/app/views/admin/application_settings/_user_restrictions.html.haml
index c35056383fa..c21d1ec47e6 100644
--- a/app/views/admin/application_settings/_user_restrictions.html.haml
+++ b/app/views/admin/application_settings/_user_restrictions.html.haml
@@ -5,3 +5,4 @@
= render_if_exists 'admin/application_settings/updating_name_disabled_for_users', form: form
= 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/allow_account_deletion', form: form
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 0505a205333..ea8d6b7fda2 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -59,36 +59,45 @@
.col-lg-12
%hr
-.row.gl-mt-3.js-search-settings-section
- .col-lg-4.profile-settings-sidebar
- %h4.gl-mt-0.danger-title
- = s_('Profiles|Delete account')
- .col-lg-8
- - if current_user.can_be_removed? && can?(current_user, :destroy_user, current_user)
+- if prevent_delete_account?
+ .row.gl-mt-3.js-search-settings-section
+ .col-lg-4.profile-settings-sidebar
+ %h4.gl-mt-0.danger-title
+ = s_('Profiles|Delete account')
+ .col-lg-8
%p
- = s_('Profiles|Deleting an account has the following effects:')
- = render 'users/deletion_guidance', user: current_user
-
- -# Delete button here
- = render Pajamas::ButtonComponent.new(variant: :danger, button_options: { id: 'delete-account-button', disabled: true, data: { qa_selector: 'delete_account_button' }}) do
+ = s_('Profiles|Account deletion is not allowed by your administrator.')
+- else
+ .row.gl-mt-3.js-search-settings-section
+ .col-lg-4.profile-settings-sidebar
+ %h4.gl-mt-0.danger-title
= s_('Profiles|Delete account')
-
- #delete-account-modal{ data: { action_url: user_registration_path,
- confirm_with_password: ('true' if current_user.confirm_deletion_with_password?),
- username: current_user.username } }
- - else
- - if current_user.solo_owned_groups.present?
- %p
- = s_('Profiles|Your account is currently an owner in these groups:')
- %strong= current_user.solo_owned_groups.map(&:name).join(', ')
- %p
- = s_('Profiles|You must transfer ownership or delete these groups before you can delete your account.')
- - elsif !current_user.can_remove_self?
- %p
- = s_('Profiles|GitLab is unable to verify your identity automatically. For security purposes, you must set a password by %{openingTag}resetting your password%{closingTag} to delete your account.').html_safe % { openingTag: "<a href='#{reset_profile_password_path}' rel=\"nofollow\" data-method=\"put\">".html_safe, closingTag: '</a>'.html_safe}
+ .col-lg-8
+ - if current_user.can_be_removed? && can?(current_user, :destroy_user, current_user)
%p
- = s_('Profiles|If after setting a password, the option to delete your account is still not available, please %{link_start}submit a request%{link_end} to begin the account deletion process.').html_safe % { link_start: '<a href="https://support.gitlab.io/account-deletion/" rel="nofollow noreferrer noopener" target="_blank">'.html_safe, link_end: '</a>'.html_safe}
+ = s_('Profiles|Deleting an account has the following effects:')
+ = render 'users/deletion_guidance', user: current_user
+
+ -# Delete button here
+ = render Pajamas::ButtonComponent.new(variant: :danger, button_options: { id: 'delete-account-button', disabled: true, data: { qa_selector: 'delete_account_button' }}) do
+ = s_('Profiles|Delete account')
+
+ #delete-account-modal{ data: { action_url: user_registration_path,
+ confirm_with_password: ('true' if current_user.confirm_deletion_with_password?),
+ username: current_user.username } }
- else
- %p
- = s_("Profiles|You don't have access to delete this user.")
+ - if current_user.solo_owned_groups.present?
+ %p
+ = s_('Profiles|Your account is currently an owner in these groups:')
+ %strong= current_user.solo_owned_groups.map(&:name).join(', ')
+ %p
+ = s_('Profiles|You must transfer ownership or delete these groups before you can delete your account.')
+ - elsif !current_user.can_remove_self?
+ %p
+ = s_('Profiles|GitLab is unable to verify your identity automatically. For security purposes, you must set a password by %{openingTag}resetting your password%{closingTag} to delete your account.').html_safe % { openingTag: "<a href='#{reset_profile_password_path}' rel=\"nofollow\" data-method=\"put\">".html_safe, closingTag: '</a>'.html_safe}
+ %p
+ = s_('Profiles|If after setting a password, the option to delete your account is still not available, please %{link_start}submit a request%{link_end} to begin the account deletion process.').html_safe % { link_start: '<a href="https://support.gitlab.io/account-deletion/" rel="nofollow noreferrer noopener" target="_blank">'.html_safe, link_end: '</a>'.html_safe}
+ - else
+ %p
+ = s_("Profiles|You don't have access to delete this user.")
.gl-mb-3