diff options
Diffstat (limited to 'app')
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 |