diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 03:07:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 03:07:10 +0000 |
commit | 9bf8cb8d34039f3cef9e1b2f812ce634f2bebe69 (patch) | |
tree | c1e4d7a8dc008004b3e3861a4d8f6d9439ffabf8 | |
parent | e91080371b32e69d038b3a94261688c09dbcd641 (diff) | |
download | gitlab-ce-9bf8cb8d34039f3cef9e1b2f812ce634f2bebe69.tar.gz |
Add latest changes from gitlab-org/gitlab@master
44 files changed, 636 insertions, 181 deletions
diff --git a/.rubocop_todo/rspec/avoid_conditional_statements.yml b/.rubocop_todo/rspec/avoid_conditional_statements.yml index 43ffaaa452a..4817708667a 100644 --- a/.rubocop_todo/rspec/avoid_conditional_statements.yml +++ b/.rubocop_todo/rspec/avoid_conditional_statements.yml @@ -78,7 +78,6 @@ RSpec/AvoidConditionalStatements: - 'spec/features/projects_spec.rb' - 'spec/features/search/user_uses_header_search_field_spec.rb' - 'spec/features/snippets/explore_spec.rb' - - 'spec/features/tags/developer_creates_tag_spec.rb' - 'spec/features/usage_stats_consent_spec.rb' - 'spec/features/users/login_spec.rb' - 'spec/features/users/overview_spec.rb' diff --git a/Gemfile.checksum b/Gemfile.checksum index 4409e0357fb..edecf558466 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -518,7 +518,7 @@ {"name":"rexml","version":"3.2.5","platform":"ruby","checksum":"a33c3bf95fda7983ec7f05054f3a985af41dbc25a0339843bd2479e93cabb123"}, {"name":"rinku","version":"2.0.0","platform":"ruby","checksum":"3e695aaf9f24baba3af45823b5c427b58a624582132f18482320e2737f9f8a85"}, {"name":"rotp","version":"6.2.0","platform":"ruby","checksum":"239a2eefba6f1bd4157b2c735d0f975598e0ef94823eea2f35d103d2e5cc0787"}, -{"name":"rouge","version":"4.1.0","platform":"ruby","checksum":"0f6fc19a0d66db782f6fa67f56356af4ef001cd43bbd8ad5aa798a081de4dd10"}, +{"name":"rouge","version":"4.1.1","platform":"ruby","checksum":"41cc3ed28de7a9f5c0145bcdbeae8f5c16133065d570e21393aac935a235fd4b"}, {"name":"rqrcode","version":"0.7.0","platform":"ruby","checksum":"8b3a5cba9cc199ba2d781a7c767cb55679f29a3621aa0506a799cec3760d16a1"}, {"name":"rqrcode-rails3","version":"0.1.7","platform":"ruby","checksum":"6f0582f26485123e5ed6f2a8a2871f00d86d353e0f58c8429a5a13212bcf48c4"}, {"name":"rspec","version":"3.12.0","platform":"ruby","checksum":"ccc41799a43509dc0be84070e3f0410ac95cbd480ae7b6c245543eb64162399c"}, diff --git a/Gemfile.lock b/Gemfile.lock index 3d28928425f..b0bc178f8b5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1270,7 +1270,7 @@ GEM rexml (3.2.5) rinku (2.0.0) rotp (6.2.0) - rouge (4.1.0) + rouge (4.1.1) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) 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 diff --git a/config/feature_flags/development/password_reset_any_verified_email.yml b/config/feature_flags/development/password_reset_any_verified_email.yml new file mode 100644 index 00000000000..9438c6ef414 --- /dev/null +++ b/config/feature_flags/development/password_reset_any_verified_email.yml @@ -0,0 +1,8 @@ +--- +name: password_reset_any_verified_email +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119231 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/410038 +milestone: '16.0' +type: development +group: group::authentication and authorization +default_enabled: false diff --git a/config/feature_flags/development/use_traversal_ids_for_ancestor_scopes.yml b/config/feature_flags/development/use_traversal_ids_for_ancestor_scopes.yml deleted file mode 100644 index 0ac765b6ab3..00000000000 --- a/config/feature_flags/development/use_traversal_ids_for_ancestor_scopes.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: use_traversal_ids_for_ancestor_scopes -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67652 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340159 -milestone: '14.3' -type: development -group: group::tenant scale -default_enabled: true diff --git a/db/migrate/20230428070443_add_allow_account_deletion_to_application_settings.rb b/db/migrate/20230428070443_add_allow_account_deletion_to_application_settings.rb new file mode 100644 index 00000000000..1731d91eb5c --- /dev/null +++ b/db/migrate/20230428070443_add_allow_account_deletion_to_application_settings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddAllowAccountDeletionToApplicationSettings < Gitlab::Database::Migration[2.1] + def change + add_column :application_settings, :allow_account_deletion, :boolean, default: true, null: false + end +end diff --git a/db/schema_migrations/20230428070443 b/db/schema_migrations/20230428070443 new file mode 100644 index 00000000000..c1798ec1bf4 --- /dev/null +++ b/db/schema_migrations/20230428070443 @@ -0,0 +1 @@ +8277328b39ff873c549453bbdc8b0ae67e49cc23fd6e8166aea68c1d61fc7116
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index e8fdacd0ee9..ed00829cb6e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11832,6 +11832,7 @@ CREATE TABLE application_settings ( remember_me_enabled boolean DEFAULT true NOT NULL, encrypted_anthropic_api_key bytea, encrypted_anthropic_api_key_iv bytea, + allow_account_deletion boolean DEFAULT true NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index b2b1ede12d4..b7b728649a7 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -137,11 +137,12 @@ Use one of the following tabs to filter the list of repositories: - **Collaborated**: Filter the list to the repositories that you have contributed to. - **Organization**: Filter the list to the repositories that belong to an organization you are a member of. -When the **Organization** tab is selected, you can further narrow down your search by selecting an available GitHub organization from a dropdown. +When the **Organization** tab is selected, you can further narrow down your search by selecting an available GitHub organization from a dropdown list. ### Select additional items to import -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/373705) in GitLab 15.5. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/373705) in GitLab 15.5. +> - Importing collaborators as an additional item was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/398154) in GitLab 16.0. To make imports as fast as possible, the following items aren't imported from GitHub by default: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 673b6e60a01..27dc7af7e5c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5370,6 +5370,9 @@ msgstr "" msgid "ApplicationSettings|Allowed domains for sign-ups" msgstr "" +msgid "ApplicationSettings|Allows users to delete their own accounts" +msgstr "" + msgid "ApplicationSettings|Any user that visits %{host} and creates an account must be explicitly approved by an administrator before they can sign in. Only effective if sign-ups are enabled." msgstr "" @@ -23791,7 +23794,7 @@ msgstr "" msgid "Input the remote repository URL" msgstr "" -msgid "Insert a %{rows}x%{cols} table." +msgid "Insert a %{rows}×%{cols} table" msgstr "" msgid "Insert a quote" @@ -34378,6 +34381,12 @@ msgstr "" msgid "Profiles|Account could not be deleted. GitLab was unable to verify your identity." msgstr "" +msgid "Profiles|Account deletion is not allowed by your administrator." +msgstr "" + +msgid "Profiles|Account deletion is not allowed." +msgstr "" + msgid "Profiles|Account scheduled for removal." msgstr "" diff --git a/spec/controllers/concerns/metrics_dashboard_spec.rb b/spec/controllers/concerns/metrics_dashboard_spec.rb index d68a9d70ec6..4a9c7c493a7 100644 --- a/spec/controllers/concerns/metrics_dashboard_spec.rb +++ b/spec/controllers/concerns/metrics_dashboard_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MetricsDashboard do +RSpec.describe MetricsDashboard, feature_category: :metrics do include MetricsDashboardHelpers describe 'GET #metrics_dashboard' do @@ -11,6 +11,7 @@ RSpec.describe MetricsDashboard do let_it_be(:environment) { create(:environment, project: project) } before do + stub_feature_flags(remove_monitor_metrics: false) sign_in(user) project.add_maintainer(user) end @@ -179,5 +180,16 @@ RSpec.describe MetricsDashboard do end end end + + context 'when metrics dashboard feature is unavailable' do + it 'returns 404 not found' do + stub_feature_flags(remove_monitor_metrics: true) + + routes.draw { get "metrics_dashboard" => "anonymous#metrics_dashboard" } + response = get :metrics_dashboard, format: :json + + expect(response).to have_gitlab_http_status(:not_found) + end + end end end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index f097d08fe1b..22804339fef 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -922,6 +922,18 @@ RSpec.describe Projects::EnvironmentsController, feature_category: :continuous_d get :metrics, params: environment_params end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 404 not found' do + get :metrics_dashboard, params: environment_params(dashboard_params) + + expect(response).to have_gitlab_http_status(:not_found) + end + end end end diff --git a/spec/controllers/projects/grafana_api_controller_spec.rb b/spec/controllers/projects/grafana_api_controller_spec.rb index fa20fc5037f..9bc4a83030e 100644 --- a/spec/controllers/projects/grafana_api_controller_spec.rb +++ b/spec/controllers/projects/grafana_api_controller_spec.rb @@ -250,6 +250,19 @@ RSpec.describe Projects::GrafanaApiController, feature_category: :metrics do it_behaves_like 'error response', :bad_request end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 404 Not found' do + get :metrics_dashboard, params: params + + expect(response).to have_gitlab_http_status(:not_found) + expect(response.body).to be_empty + end + end end end end diff --git a/spec/controllers/projects/prometheus/alerts_controller_spec.rb b/spec/controllers/projects/prometheus/alerts_controller_spec.rb index 91d3ba7e106..44292b9ce19 100644 --- a/spec/controllers/projects/prometheus/alerts_controller_spec.rb +++ b/spec/controllers/projects/prometheus/alerts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Prometheus::AlertsController do +RSpec.describe Projects::Prometheus::AlertsController, feature_category: :incident_management do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:environment) { create(:environment, project: project) } @@ -115,11 +115,15 @@ RSpec.describe Projects::Prometheus::AlertsController do end end - describe 'GET #metrics_dashboard' do + describe 'GET #metrics_dashboard', feature_category: :metrics do let!(:alert) do create(:prometheus_alert, project: project, environment: environment, prometheus_metric: metric) end + before do + stub_feature_flags(remove_monitor_metrics: false) + end + it 'returns a json object with the correct keys' do get :metrics_dashboard, params: request_params(id: metric.id, environment_id: alert.environment.id), format: :json @@ -148,6 +152,14 @@ RSpec.describe Projects::Prometheus::AlertsController do expect(response).to have_gitlab_http_status(:not_found) end + + it 'returns 404 when metrics dashboard feature is unavailable' do + stub_feature_flags(remove_monitor_metrics: true) + + get :metrics_dashboard, params: request_params(id: 0), format: :json + + expect(response).to have_gitlab_http_status(:not_found) + end end def project_params(opts = {}) diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb index 327651b2058..8f8edebbc30 100644 --- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb +++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb @@ -2,13 +2,14 @@ require 'spec_helper' -RSpec.describe Projects::Prometheus::MetricsController do +RSpec.describe Projects::Prometheus::MetricsController, feature_category: :metrics do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :with_prometheus_integration) } let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } before do + stub_feature_flags(remove_monitor_metrics: false) project.add_maintainer(user) sign_in(user) end @@ -79,6 +80,18 @@ RSpec.describe Projects::Prometheus::MetricsController do expect(response).to have_gitlab_http_status(:not_found) end end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'renders 404' do + get :active_common, params: project_params(format: :json) + + expect(response).to have_gitlab_http_status(:not_found) + end + end end describe 'POST #validate_query' do diff --git a/spec/features/tags/developer_creates_tag_spec.rb b/spec/features/tags/developer_creates_tag_spec.rb index cb59ee17514..be9f19fe84a 100644 --- a/spec/features/tags/developer_creates_tag_spec.rb +++ b/spec/features/tags/developer_creates_tag_spec.rb @@ -20,7 +20,10 @@ RSpec.describe 'Developer creates tag', :js, feature_category: :source_code_mana end it 'with an invalid name displays an error' do - create_tag_in_form(tag: 'v 1.0', ref: 'master') + fill_in 'tag_name', with: 'v 1.0' + select_ref(ref: 'master') + + click_button 'Create tag' expect(page).to have_content 'Tag name invalid' end @@ -39,13 +42,20 @@ RSpec.describe 'Developer creates tag', :js, feature_category: :source_code_mana end it 'that already exists displays an error' do - create_tag_in_form(tag: 'v1.1.0', ref: 'master') + fill_in 'tag_name', with: 'v1.1.0' + select_ref(ref: 'master') + + click_button 'Create tag' expect(page).to have_content 'Tag v1.1.0 already exists' end it 'with multiline message displays the message in a <pre> block' do - create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world") + fill_in 'tag_name', with: 'v3.0' + select_ref(ref: 'master') + fill_in 'message', with: "Awesome tag message\n\n- hello\n- world" + + click_button 'Create tag' expect(page).to have_current_path( project_tag_path(project, 'v3.0'), ignore_query: true) @@ -67,14 +77,6 @@ RSpec.describe 'Developer creates tag', :js, feature_category: :source_code_mana end end - def create_tag_in_form(tag:, ref:, message: nil, desc: nil) - fill_in 'tag_name', with: tag - select_ref(ref: ref) - fill_in 'message', with: message unless message.nil? - fill_in 'release_description', with: desc unless desc.nil? - click_button 'Create tag' - end - def select_ref(ref:) ref_selector = '.ref-selector' find(ref_selector).click diff --git a/spec/features/users/password_spec.rb b/spec/features/users/password_spec.rb index ccd383c8a15..59f49c791b6 100644 --- a/spec/features/users/password_spec.rb +++ b/spec/features/users/password_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'User password', feature_category: :system_access do + include EmailHelpers + describe 'send password reset' do context 'when recaptcha is enabled' do before do @@ -26,5 +28,57 @@ RSpec.describe 'User password', feature_category: :system_access do expect(page).not_to have_css('.g-recaptcha') end end + + context 'when user has multiple emails' do + let_it_be(:user) { create(:user, email: 'primary@example.com') } + let_it_be(:verified_email) { create(:email, :confirmed, user: user, email: 'second@example.com') } + let_it_be(:unverified_email) { create(:email, user: user, email: 'unverified@example.com') } + + let(:ff_enabled) { true } + + before do + stub_feature_flags(password_reset_any_verified_email: ff_enabled) + + perform_enqueued_jobs do + visit new_user_password_path + fill_in 'user_email', with: email + click_button 'Reset password' + end + end + + context 'when user enters the primary email' do + let(:email) { user.email } + + it 'send the email to the correct email address' do + expect(ActionMailer::Base.deliveries.first.to).to include(email) + end + end + + context 'when user enters a secondary verified email' do + let(:email) { verified_email.email } + + context 'when password_reset_any_verified_email FF is enabled' do + it 'send the email to the correct email address' do + expect(ActionMailer::Base.deliveries.first.to).to include(email) + end + end + + context 'when password_reset_any_verified_email FF is not enabled' do + let(:ff_enabled) { false } + + it 'does not send an email' do + expect(ActionMailer::Base.deliveries.count).to eq(0) + end + end + end + + context 'when user enters an unverified email' do + let(:email) { unverified_email.email } + + it 'does not send an email' do + expect(ActionMailer::Base.deliveries.count).to eq(0) + end + end + end end end diff --git a/spec/frontend/content_editor/components/toolbar_table_button_spec.js b/spec/frontend/content_editor/components/toolbar_table_button_spec.js index 35741971488..be6e47e067f 100644 --- a/spec/frontend/content_editor/components/toolbar_table_button_spec.js +++ b/spec/frontend/content_editor/components/toolbar_table_button_spec.js @@ -1,4 +1,4 @@ -import { GlDropdown, GlButton } from '@gitlab/ui'; +import { GlDisclosureDropdown, GlButton } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import ToolbarTableButton from '~/content_editor/components/toolbar_table_button.vue'; import { stubComponent } from 'helpers/stub_component'; @@ -14,12 +14,13 @@ describe('content_editor/components/toolbar_table_button', () => { tiptapEditor: editor, }, stubs: { - GlDropdown: stubComponent(GlDropdown), + GlDisclosureDropdown: stubComponent(GlDisclosureDropdown), }, }); }; - const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); + const findButton = (row, col) => wrapper.findComponent({ ref: `table-${row}-${col}` }); const getNumButtons = () => findDropdown().findAllComponents(GlButton).length; beforeEach(() => { @@ -32,32 +33,44 @@ describe('content_editor/components/toolbar_table_button', () => { editor.destroy(); }); - it('renders a grid of 5x5 buttons to create a table', () => { - expect(getNumButtons()).toBe(25); // 5x5 - }); - describe.each` row | col | numButtons | tableSize - ${3} | ${4} | ${25} | ${'3x4'} - ${4} | ${4} | ${25} | ${'4x4'} - ${4} | ${5} | ${30} | ${'4x5'} - ${5} | ${4} | ${30} | ${'5x4'} - ${5} | ${5} | ${36} | ${'5x5'} + ${3} | ${4} | ${25} | ${'3×4'} + ${4} | ${4} | ${25} | ${'4×4'} + ${4} | ${5} | ${30} | ${'4×5'} + ${5} | ${4} | ${30} | ${'5×4'} + ${5} | ${5} | ${36} | ${'5×5'} `('button($row, $col) in the table creator grid', ({ row, col, numButtons, tableSize }) => { - describe('on mouse over', () => { + describe('a11y tests', () => { + it('is in its own gridcell', () => { + expect(findButton(row, col).element.parentElement.getAttribute('role')).toBe('gridcell'); + }); + + it('has an aria-label', () => { + expect(findButton(row, col).attributes('aria-label')).toBe(`Insert a ${tableSize} table`); + }); + }); + + describe.each` + event | triggerEvent + ${'mouseover'} | ${(button) => button.trigger('mouseover')} + ${'focus'} | ${(button) => button.element.dispatchEvent(new FocusEvent('focus'))} + `('on $event', ({ triggerEvent }) => { beforeEach(async () => { - const button = wrapper.findByTestId(`table-${row}-${col}`); - await button.trigger('mouseover'); + const button = wrapper.findComponent({ ref: `table-${row}-${col}` }); + await triggerEvent(button); }); it('marks all rows and cols before it as active', () => { const prevRow = Math.max(1, row - 1); const prevCol = Math.max(1, col - 1); - expect(wrapper.findByTestId(`table-${prevRow}-${prevCol}`).element).toHaveClass('active'); + expect(wrapper.findComponent({ ref: `table-${prevRow}-${prevCol}` }).element).toHaveClass( + 'active', + ); }); it('shows a help text indicating the size of the table being inserted', () => { - expect(findDropdown().element).toHaveText(`Insert a ${tableSize} table.`); + expect(findDropdown().element).toHaveText(`Insert a ${tableSize} table`); }); it('adds another row and col of buttons to create a bigger table', () => { @@ -71,7 +84,7 @@ describe('content_editor/components/toolbar_table_button', () => { beforeEach(async () => { commands = mockChainedCommands(editor, ['focus', 'insertTable', 'run']); - const button = wrapper.findByTestId(`table-${row}-${col}`); + const button = wrapper.findComponent({ ref: `table-${row}-${col}` }); await button.trigger('mouseover'); await button.trigger('click'); }); @@ -95,8 +108,8 @@ describe('content_editor/components/toolbar_table_button', () => { expect(getNumButtons()).toBe(i * i); // eslint-disable-next-line no-await-in-loop - await wrapper.findByTestId(`table-${i}-${i}`).trigger('mouseover'); - expect(findDropdown().element).toHaveText(`Insert a ${i}x${i} table.`); + await wrapper.findComponent({ ref: `table-${i}-${i}` }).trigger('mouseover'); + expect(findDropdown().element).toHaveText(`Insert a ${i}×${i} table`); } expect(getNumButtons()).toBe(100); // 10x10 (and not 11x11) @@ -105,10 +118,50 @@ describe('content_editor/components/toolbar_table_button', () => { describe('a11y tests', () => { it('sets text, title, and text-sr-only properties to the table button dropdown', () => { expect(findDropdown().props()).toMatchObject({ - text: 'Insert table', + toggleText: 'Insert table', textSrOnly: true, }); - expect(findDropdown().attributes('title')).toBe('Insert table'); + expect(findDropdown().attributes('aria-label')).toBe('Insert table'); + }); + + it('renders a role=grid of 5x5 gridcells to create a table', () => { + expect(getNumButtons()).toBe(25); // 5x5 + expect(wrapper.find('[role="grid"]').exists()).toBe(true); + wrapper.findAll('[role="row"]').wrappers.forEach((row) => { + expect(row.findAll('[role="gridcell"]')).toHaveLength(5); + }); + }); + + it('sets aria-rowcount and aria-colcount on the dropdown contents', () => { + expect(wrapper.find('[role="grid"]').attributes()).toMatchObject({ + 'aria-rowcount': '10', + 'aria-colcount': '10', + }); + }); + + it('allows navigating the grid with the arrow keys', async () => { + const dispatchKeyboardEvent = (button, key) => + button.element.dispatchEvent(new KeyboardEvent('keydown', { key })); + + let button = findButton(3, 4); + await button.trigger('mouseover'); + expect(button.element).toHaveClass('active'); + + button = findButton(3, 5); + await dispatchKeyboardEvent(button, 'ArrowRight'); + expect(button.element).toHaveClass('active'); + + button = findButton(4, 5); + await dispatchKeyboardEvent(button, 'ArrowDown'); + expect(button.element).toHaveClass('active'); + + button = findButton(4, 4); + await dispatchKeyboardEvent(button, 'ArrowLeft'); + expect(button.element).toHaveClass('active'); + + button = findButton(3, 4); + await dispatchKeyboardEvent(button, 'ArrowUp'); + expect(button.element).toHaveClass('active'); }); }); }); diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb index ebe86ccb08d..4c43b1ec4cf 100644 --- a/spec/helpers/profiles_helper_spec.rb +++ b/spec/helpers/profiles_helper_spec.rb @@ -118,6 +118,12 @@ RSpec.describe ProfilesHelper do end end + describe '#prevent_delete_account?' do + it 'returns false' do + expect(helper.prevent_delete_account?).to eq false + end + end + def stub_auth0_omniauth_provider provider = OpenStruct.new( 'name' => example_omniauth_provider, diff --git a/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb b/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb index 7c7ca8207ff..8c6dcbf4b96 100644 --- a/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb +++ b/spec/lib/generators/gitlab/partitioning/foreign_keys_generator_spec.rb @@ -60,7 +60,7 @@ feature_category: :continuous_integration do RemoveFkToTestTmpBuildsTestTmpMetadataOnBuildsId ]) - schema_migrate_up! + schema_migrate_up!(only_databases: [:main]) fks = Gitlab::Database::PostgresForeignKey .by_referenced_table_identifier('public._test_tmp_builds') diff --git a/spec/mailers/devise_mailer_spec.rb b/spec/mailers/devise_mailer_spec.rb index 6eb0e817803..171251f51ef 100644 --- a/spec/mailers/devise_mailer_spec.rb +++ b/spec/mailers/devise_mailer_spec.rb @@ -102,9 +102,12 @@ RSpec.describe DeviseMailer do end describe '#reset_password_instructions' do - subject { described_class.reset_password_instructions(user, 'faketoken') } - let_it_be(:user) { create(:user) } + let(:params) { {} } + + subject do + described_class.reset_password_instructions(user, 'faketoken', params) + end it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' @@ -135,6 +138,15 @@ RSpec.describe DeviseMailer do it 'has the mailgun suppression bypass header' do is_expected.to have_header 'X-Mailgun-Suppressions-Bypass', 'true' end + + context 'with email in params' do + let(:email) { 'example@example.com' } + let(:params) { { to: email } } + + it 'is sent to the specified email' do + is_expected.to deliver_to email + end + end end describe '#email_changed' do diff --git a/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb b/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb index 1834e8c6e0e..c4f091d0d80 100644 --- a/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb +++ b/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' require_migration! RSpec.describe FinalizeIssuesIidScopingToNamespace, :migration, feature_category: :team_planning do - let(:batched_migrations) { table(:batched_background_migrations) } + let(:batched_migrations) { table(:batched_background_migrations, database: :main) } let!(:migration) { described_class::MIGRATION } diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index d202fef0ed0..b0ff070e4a6 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -317,8 +317,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do before do stub_feature_flags( use_traversal_ids: false, - use_traversal_ids_for_ancestors: false, - use_traversal_ids_for_ancestor_scopes: false + use_traversal_ids_for_ancestors: false ) end diff --git a/spec/models/concerns/recoverable_by_any_email_spec.rb b/spec/models/concerns/recoverable_by_any_email_spec.rb new file mode 100644 index 00000000000..11dd89d97c9 --- /dev/null +++ b/spec/models/concerns/recoverable_by_any_email_spec.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RecoverableByAnyEmail, feature_category: :system_access do + describe '.send_reset_password_instructions' do + let_it_be(:user) { create(:user, email: 'test@example.com') } + let_it_be(:verified_email) { create(:email, :confirmed, user: user) } + let_it_be(:unverified_email) { create(:email, user: user) } + + let(:ff_enabled) { true } + + before do + stub_feature_flags(password_reset_any_verified_email: ff_enabled) + end + + subject(:send_reset_password_instructions) do + User.send_reset_password_instructions(email: email) + end + + shared_examples 'sends the password reset email' do + it 'finds the user' do + expect(send_reset_password_instructions).to eq(user) + end + + it 'sends the email' do + expect { send_reset_password_instructions }.to have_enqueued_mail(DeviseMailer, :reset_password_instructions) + end + end + + shared_examples 'does not send the password reset email' do + it 'does not find the user' do + expect(subject.id).to be_nil + expect(subject.errors).not_to be_empty + end + + it 'does not send any email' do + subject + + expect { subject }.not_to have_enqueued_mail(DeviseMailer, :reset_password_instructions) + end + end + + context 'with user primary email' do + let(:email) { user.email } + + it_behaves_like 'sends the password reset email' + end + + context 'with user verified email' do + let(:email) { verified_email.email } + + context 'when password_reset_any_verified_email FF is enabled' do + it_behaves_like 'sends the password reset email' + end + + context 'when password_reset_any_verified_email FF is not enabled' do + let(:ff_enabled) { false } + + it_behaves_like 'does not send the password reset email' + end + end + + context 'with user unverified email' do + let(:email) { unverified_email.email } + + it_behaves_like 'does not send the password reset email' + end + end + + describe '#send_reset_password_instructions' do + let_it_be(:user) { create(:user) } + let_it_be(:opts) { { email: 'random@email.com' } } + let_it_be(:token) { 'passwordresettoken' } + + before do + stub_feature_flags(password_reset_any_verified_email: ff_enabled) + + allow(user).to receive(:set_reset_password_token).and_return(token) + end + + subject { user.send_reset_password_instructions(opts) } + + context 'when password_reset_any_verified_email FF is not enabled' do + let(:ff_enabled) { false } + + # original Devise behavior + it 'calls send_reset_password_instructions_notification just with token' do + expect(user).to receive(:send_reset_password_instructions_notification).with(token) + + subject + end + end + + context 'when password_reset_any_verified_email FF is enabled' do + let(:ff_enabled) { true } + + it 'sends the email' do + expect { subject }.to have_enqueued_mail(DeviseMailer, :reset_password_instructions) + end + + it 'calls send_reset_password_instructions_notification with correct arguments' do + expect(user).to receive(:send_reset_password_instructions_notification).with(token, opts) + + subject + end + + it 'returns the generated token' do + expect(subject).to eq(token) + end + end + end +end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 3f66cbaf2b7..9a898159088 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -76,6 +76,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['slack_app_verification_token']).to be_nil expect(json_response['valid_runner_registrars']).to match_array(%w(project group)) expect(json_response['ci_max_includes']).to eq(150) + expect(json_response['allow_account_deletion']).to eq(true) end end @@ -188,7 +189,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu slack_app_secret: 'SLACK_APP_SECRET', slack_app_signing_secret: 'SLACK_APP_SIGNING_SECRET', slack_app_verification_token: 'SLACK_APP_VERIFICATION_TOKEN', - valid_runner_registrars: ['group'] + valid_runner_registrars: ['group'], + allow_account_deletion: false } expect(response).to have_gitlab_http_status(:ok) @@ -265,6 +267,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['slack_app_signing_secret']).to eq('SLACK_APP_SIGNING_SECRET') expect(json_response['slack_app_verification_token']).to eq('SLACK_APP_VERIFICATION_TOKEN') expect(json_response['valid_runner_registrars']).to eq(['group']) + expect(json_response['allow_account_deletion']).to be(false) end end diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb index d0181275927..7e94bc6134d 100644 --- a/spec/requests/projects/metrics_dashboard_spec.rb +++ b/spec/requests/projects/metrics_dashboard_spec.rb @@ -9,12 +9,28 @@ RSpec.describe 'Projects::MetricsDashboardController', feature_category: :metric let_it_be(:user) { project.first_owner } before do + stub_feature_flags(remove_monitor_metrics: false) project.add_developer(user) login_as(user) stub_feature_flags(remove_monitor_metrics: false) end + shared_examples 'metrics dashboard is unavailable' do + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 404 not found' do + send_request + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + describe 'GET /:namespace/:project/-/metrics' do + include_examples 'metrics dashboard is unavailable' + it "redirects to default environment's metrics dashboard" do send_request expect(response).to redirect_to(dashboard_route(environment: environment)) @@ -70,6 +86,8 @@ RSpec.describe 'Projects::MetricsDashboardController', feature_category: :metric end describe 'GET /:namespace/:project/-/metrics?environment=:environment.id' do + include_examples 'metrics dashboard is unavailable' + it 'returns 200' do send_request(environment: environment2.id) expect(response).to have_gitlab_http_status(:ok) @@ -91,6 +109,8 @@ RSpec.describe 'Projects::MetricsDashboardController', feature_category: :metric describe 'GET /:namespace/:project/-/metrics/:dashboard_path' do let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' } + include_examples 'metrics dashboard is unavailable' + it 'returns 200' do send_request(dashboard_path: dashboard_path, environment: environment.id) expect(response).to have_gitlab_http_status(:ok) @@ -105,6 +125,8 @@ RSpec.describe 'Projects::MetricsDashboardController', feature_category: :metric describe 'GET :/namespace/:project/-/metrics/:dashboard_path?environment=:environment.id' do let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' } + include_examples 'metrics dashboard is unavailable' + it 'returns 200' do send_request(dahboard_path: dashboard_path, environment: environment.id) expect(response).to have_gitlab_http_status(:ok) @@ -124,6 +146,8 @@ RSpec.describe 'Projects::MetricsDashboardController', feature_category: :metric end describe 'GET :/namespace/:project/-/metrics/:page' do + include_examples 'metrics dashboard is unavailable' + it 'returns 200 with path param page' do send_request(page: 'panel/new', environment: environment.id) diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index 1b8c3388051..0084835ff8d 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -130,19 +130,45 @@ module MigrationsHelpers end end - def schema_migrate_down! + # TODO: use Gitlab::Database::EachDatabase class (https://gitlab.com/gitlab-org/gitlab/-/issues/410154) + def migrate_databases!(only_databases: nil, version: nil) + only_databases ||= if Gitlab::Database.database_mode == Gitlab::Database::MODE_SINGLE_DATABASE + [:main] + else + %i[main ci] + end + + # unique in the context of database, host, port + configurations = Gitlab::Database.database_base_models.each_with_object({}) do |(_name, model), h| + config = model.connection_db_config + + h[config.configuration_hash.slice(:database, :host, :port)] ||= config + end + + with_reestablished_active_record_base do + configurations.each_value do |configuration| + next unless only_databases.include? configuration.name.to_sym + + ActiveRecord::Base.establish_connection(configuration) # rubocop:disable Database/EstablishConnection + + migration_context.migrate(version) # rubocop:disable Database/MultipleDatabases + end + end + end + + def schema_migrate_down!(only_databases: nil) disable_migrations_output do - migration_context.down(migration_schema_version) + migrate_databases!(only_databases: only_databases, version: migration_schema_version) end reset_column_in_all_models end - def schema_migrate_up! + def schema_migrate_up!(only_databases: nil) reset_column_in_all_models disable_migrations_output do - migration_context.up + migrate_databases!(only_databases: only_databases) end reset_column_in_all_models diff --git a/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb index 19b1cee44ee..9cdde13b36b 100644 --- a/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb +++ b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb @@ -21,6 +21,8 @@ RSpec.shared_examples_for 'metrics dashboard prometheus api proxy' do end before do + stub_feature_flags(remove_monitor_metrics: false) + allow_next_instance_of(Prometheus::ProxyService, *service_params) do |proxy_service| allow(proxy_service).to receive(:execute).and_return(service_result) end @@ -106,6 +108,19 @@ RSpec.shared_examples_for 'metrics dashboard prometheus api proxy' do end end end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 404 not found' do + get :prometheus_proxy, params: prometheus_proxy_params + + expect(response).to have_gitlab_http_status(:not_found) + expect(response.body).to be_empty + end + end end context 'with inappropriate requests' do diff --git a/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb index cb8f6721d66..5b63ef10c85 100644 --- a/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb +++ b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb @@ -17,6 +17,10 @@ RSpec.shared_examples_for 'GET #metrics_dashboard for dashboard' do |dashboard_n let(:expected_keys) { %w(dashboard status metrics_data) } let(:status_code) { :ok } + before do + stub_feature_flags(remove_monitor_metrics: false) + end + it_behaves_like 'GET #metrics_dashboard correctly formatted response' it 'returns correct dashboard' do @@ -24,4 +28,17 @@ RSpec.shared_examples_for 'GET #metrics_dashboard for dashboard' do |dashboard_n expect(json_response['dashboard']['dashboard']).to eq(dashboard_name) end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 404 not found' do + get :metrics_dashboard, params: metrics_dashboard_req_params, format: :json + + expect(response).to have_gitlab_http_status(:not_found) + expect(response.body).to be_empty + end + end end diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb index 4afed5139d8..0c4e5ce51fc 100644 --- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb @@ -139,29 +139,10 @@ RSpec.shared_examples 'namespace traversal scopes' do end describe '.self_and_ancestors' do - context "use_traversal_ids_ancestor_scopes feature flag is true" do - before do - stub_feature_flags(use_traversal_ids: true) - stub_feature_flags(use_traversal_ids_for_ancestor_scopes: true) - end - - it_behaves_like '.self_and_ancestors' - - it 'not make recursive queries' do - expect { described_class.where(id: [nested_group_1]).self_and_ancestors.load }.not_to make_queries_matching(/WITH RECURSIVE/) - end - end - - context "use_traversal_ids_ancestor_scopes feature flag is false" do - before do - stub_feature_flags(use_traversal_ids_for_ancestor_scopes: false) - end + it_behaves_like '.self_and_ancestors' - it_behaves_like '.self_and_ancestors' - - it 'makes recursive queries' do - expect { described_class.where(id: [nested_group_1]).self_and_ancestors.load }.to make_queries_matching(/WITH RECURSIVE/) - end + it 'not make recursive queries' do + expect { described_class.where(id: [nested_group_1]).self_and_ancestors.load }.not_to make_queries_matching(/WITH RECURSIVE/) end end @@ -197,29 +178,10 @@ RSpec.shared_examples 'namespace traversal scopes' do end describe '.self_and_ancestor_ids' do - context "use_traversal_ids_ancestor_scopes feature flag is true" do - before do - stub_feature_flags(use_traversal_ids: true) - stub_feature_flags(use_traversal_ids_for_ancestor_scopes: true) - end - - it_behaves_like '.self_and_ancestor_ids' - - it 'makes recursive queries' do - expect { described_class.where(id: [nested_group_1]).self_and_ancestor_ids.load }.not_to make_queries_matching(/WITH RECURSIVE/) - end - end - - context "use_traversal_ids_ancestor_scopes feature flag is false" do - before do - stub_feature_flags(use_traversal_ids_for_ancestor_scopes: false) - end + it_behaves_like '.self_and_ancestor_ids' - it_behaves_like '.self_and_ancestor_ids' - - it 'makes recursive queries' do - expect { described_class.where(id: [nested_group_1]).self_and_ancestor_ids.load }.to make_queries_matching(/WITH RECURSIVE/) - end + it 'not make recursive queries' do + expect { described_class.where(id: [nested_group_1]).self_and_ancestor_ids.load }.not_to make_queries_matching(/WITH RECURSIVE/) end end |