Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-24 18:15:38 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-24 18:15:38 +0300
commitefbf661c4224d481c57d0346e26983a805e5ec93 (patch)
tree4736f287350884cb49d84a09c52c8c2e1b851080
parent4720346c2e10e1ff62a20b39dfc9866eb88858e6 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.eslintrc.yml5
-rw-r--r--.gitpod.yml1
-rw-r--r--.rubocop_todo/layout/array_alignment.yml1
-rw-r--r--.rubocop_todo/layout/first_array_element_indentation.yml1
-rw-r--r--.rubocop_todo/layout/line_length.yml2
-rw-r--r--.rubocop_todo/layout/space_in_lambda_literal.yml1
-rw-r--r--.rubocop_todo/rspec/described_class.yml1
-rw-r--r--.rubocop_todo/rspec/return_from_stub.yml1
-rw-r--r--.rubocop_todo/style/empty_method.yml1
-rw-r--r--.rubocop_todo/style/explicit_block_argument.yml1
-rw-r--r--.rubocop_todo/style/if_unless_modifier.yml1
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue14
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/date_option.vue17
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/message_form.vue8
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue12
-rw-r--r--app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue2
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/keybindings.js4
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js105
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js8
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js20
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js22
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js56
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js16
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js2
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js6
-rw-r--r--app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue4
-rw-r--r--app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue43
-rw-r--r--app/assets/javascripts/ci/runner/admin_new_runner/index.js8
-rw-r--r--app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue21
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue33
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue56
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_token.vue6
-rw-r--r--app/assets/javascripts/ci/runner/constants.js1
-rw-r--r--app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue41
-rw-r--r--app/assets/javascripts/ci/runner/group_new_runner/index.js3
-rw-r--r--app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue29
-rw-r--r--app/assets/javascripts/ci/runner/project_new_runner/index.js3
-rw-r--r--app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue35
-rw-r--r--app/assets/javascripts/design_management/components/toolbar/design_navigation.vue3
-rw-r--r--app/assets/javascripts/design_management/pages/design/index.vue2
-rw-r--r--app/assets/javascripts/diffs/components/app.vue2
-rw-r--r--app/assets/javascripts/docs/docs_bundle.js2
-rw-r--r--app/assets/javascripts/editor/schema/ci.json19
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/stages_dropdown.vue2
-rw-r--r--app/assets/javascripts/lib/mousetrap.js59
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_navigator.vue3
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js1
-rw-r--r--app/assets/javascripts/projects/commits/index.js1
-rw-r--r--app/assets/javascripts/ref/components/ref_selector.vue5
-rw-r--r--app/assets/javascripts/repository/components/blob_controls.vue1
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_finder/index.vue10
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue5
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue50
-rw-r--r--app/assets/javascripts/zen_mode.js2
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss1
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss1
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb24
-rw-r--r--app/controllers/groups/runners_controller.rb2
-rw-r--r--app/helpers/broadcast_messages_helper.rb1
-rw-r--r--app/models/application_setting.rb27
-rw-r--r--app/models/broadcast_message.rb11
-rw-r--r--app/services/packages/generic/create_package_file_service.rb6
-rw-r--r--app/services/spam/spam_verdict_service.rb51
-rw-r--r--app/views/admin/runners/new.html.haml2
-rw-r--r--app/views/admin/sessions/new.html.haml2
-rw-r--r--app/views/groups/runners/new.html.haml2
-rw-r--r--app/views/projects/runners/new.html.haml2
-rw-r--r--app/workers/authorized_project_update/project_recalculate_worker.rb2
-rw-r--r--app/workers/authorized_projects_worker.rb1
-rw-r--r--app/workers/concerns/waitable_worker.rb13
-rw-r--r--config/feature_flags/development/role_targeted_broadcast_messages.yml8
-rw-r--r--config/feature_flags/development/soft_email_confirmation.yml8
-rw-r--r--db/post_migrate/20230420120431_create_namespaces_by_top_level_namespace_index.rb15
-rw-r--r--db/schema_migrations/202304201204311
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/geo/replication/configuration.md4
-rw-r--r--doc/administration/gitaly/praefect.md2
-rw-r--r--doc/administration/integration/plantuml.md2
-rw-r--r--doc/administration/load_balancer.md8
-rw-r--r--doc/administration/operations/puma.md2
-rw-r--r--doc/administration/packages/container_registry.md4
-rw-r--r--doc/administration/pages/index.md2
-rw-r--r--doc/administration/reference_architectures/10k_users.md10
-rw-r--r--doc/administration/reference_architectures/25k_users.md10
-rw-r--r--doc/administration/reference_architectures/2k_users.md10
-rw-r--r--doc/administration/reference_architectures/3k_users.md10
-rw-r--r--doc/administration/reference_architectures/50k_users.md10
-rw-r--r--doc/administration/reference_architectures/5k_users.md10
-rw-r--r--doc/architecture/blueprints/ci_pipeline_components/index.md38
-rw-r--r--doc/ci/yaml/includes.md10
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/contributing/index.md6
-rw-r--r--doc/development/database/database_lab.md2
-rw-r--r--doc/development/fe_guide/source_editor.md2
-rw-r--r--doc/index.md2
-rw-r--r--doc/install/aws/manual_install_aws.md4
-rw-r--r--doc/install/azure/index.md2
-rw-r--r--doc/install/docker.md2
-rw-r--r--doc/install/google_cloud_platform/index.md2
-rw-r--r--doc/integration/index.md2
-rw-r--r--doc/integration/jira/dvcs/troubleshooting.md2
-rw-r--r--doc/integration/security_partners/index.md2
-rw-r--r--doc/topics/offline/quick_start_guide.md2
-rw-r--r--doc/update/index.md1
-rw-r--r--doc/user/admin_area/settings/external_authorization.md2
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md2
-rw-r--r--doc/user/application_security/sast/index.md2
-rw-r--r--doc/user/group/saml_sso/example_saml_config.md2
-rw-r--r--doc/user/project/clusters/add_eks_clusters.md2
-rw-r--r--doc/user/project/import/cvs.md1
-rw-r--r--doc/user/project/import/perforce.md2
-rw-r--r--doc/user/project/integrations/google_play.md2
-rw-r--r--doc/user/project/integrations/slack.md2
-rw-r--r--doc/user/project/repository/forking_workflow.md2
-rw-r--r--doc/user/project/repository/x509_signed_commits/index.md4
-rw-r--r--doc/user/workspace/quick_start/index.md18
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb4
-rw-r--r--lib/gitlab/spamcheck/client.rb19
-rw-r--r--lib/gitlab/spamcheck/result.rb34
-rw-r--r--locale/gitlab.pot15
-rw-r--r--package.json8
-rw-r--r--spec/controllers/confirmations_controller_spec.rb4
-rw-r--r--spec/features/admin/admin_appearance_spec.rb266
-rw-r--r--spec/features/admin/admin_mode/login_spec.rb342
-rw-r--r--spec/features/admin/users/user_spec.rb5
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb2
-rw-r--r--spec/features/nav/pinned_nav_items_spec.rb6
-rw-r--r--spec/finders/packages/group_packages_finder_spec.rb4
-rw-r--r--spec/finders/projects_finder_spec.rb2
-rw-r--r--spec/frontend/__mocks__/mousetrap/index.js6
-rw-r--r--spec/frontend/admin/broadcast_messages/components/message_form_spec.js12
-rw-r--r--spec/frontend/admin/broadcast_messages/components/messages_table_spec.js15
-rw-r--r--spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js26
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_compatibility_alert_spec.js32
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js66
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_token_spec.js22
-rw-r--r--spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js24
-rw-r--r--spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js21
-rw-r--r--spec/frontend/design_management/components/toolbar/design_navigation_spec.js3
-rw-r--r--spec/frontend/diffs/components/app_spec.js2
-rw-r--r--spec/frontend/jobs/components/job/stages_dropdown_spec.js2
-rw-r--r--spec/frontend/lib/mousetrap_spec.js113
-rw-r--r--spec/frontend/notes/components/discussion_navigator_spec.js3
-rw-r--r--spec/frontend/projects/settings/components/default_branch_selector_spec.js1
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js6
-rw-r--r--spec/frontend/shortcuts_spec.js79
-rw-r--r--spec/frontend/super_sidebar/components/super_sidebar_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/file_finder/index_spec.js2
-rw-r--r--spec/frontend/zen_mode_spec.js2
-rw-r--r--spec/helpers/broadcast_messages_helper_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/config/external/file/artifact_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/image_formatter_spec.rb10
-rw-r--r--spec/lib/gitlab/spamcheck/client_spec.rb35
-rw-r--r--spec/lib/gitlab/spamcheck/result_spec.rb43
-rw-r--r--spec/models/application_setting_spec.rb46
-rw-r--r--spec/models/broadcast_message_spec.rb52
-rw-r--r--spec/models/concerns/has_user_type_spec.rb2
-rw-r--r--spec/models/user_spec.rb3
-rw-r--r--spec/requests/api/users_spec.rb884
-rw-r--r--spec/requests/registrations_controller_spec.rb1
-rw-r--r--spec/rubocop/cop/background_migration/feature_category_spec.rb2
-rw-r--r--spec/rubocop/cop/gitlab/avoid_feature_get_spec.rb2
-rw-r--r--spec/rubocop/cop/gitlab/keys_first_and_values_first_spec.rb2
-rw-r--r--spec/rubocop/cop/gitlab/service_response_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/add_reference_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/background_migrations_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/batch_migrations_post_only_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/schedule_async_spec.rb1
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb137
-rw-r--r--spec/services/metrics/dashboard/panel_preview_service_spec.rb20
-rw-r--r--spec/services/packages/generic/create_package_file_service_spec.rb14
-rw-r--r--spec/services/packages/npm/generate_metadata_service_spec.rb2
-rw-r--r--spec/services/spam/spam_verdict_service_spec.rb224
-rw-r--r--spec/support/rspec_order_todo.yml3
-rw-r--r--spec/workers/concerns/waitable_worker_spec.rb53
-rw-r--r--yarn.lock34
186 files changed, 2123 insertions, 1816 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 0b242e4cb94..85c40e7ec05 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -117,6 +117,11 @@ rules:
message: 'Migrate to GlSkeletonLoader, or import GlDeprecatedSkeletonLoading.'
- selector: ImportSpecifier[imported.name='GlSafeHtmlDirective']
message: 'Use directive at ~/vue_shared/directives/safe_html.js instead.'
+ no-restricted-imports:
+ - error
+ - paths:
+ - name: mousetrap
+ message: "Import { Mousetrap } from ~/lib/mousetrap instead."
# See https://gitlab.com/gitlab-org/gitlab/-/issues/360551
vue/multi-word-component-names: off
unicorn/prefer-dom-node-dataset:
diff --git a/.gitpod.yml b/.gitpod.yml
index ef6e39881e8..fd57b15ce38 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -126,3 +126,4 @@ vscode:
- dbaeumer.vscode-eslint@2.2.6
- GitLab.gitlab-workflow@3.56.0
- DavidAnson.vscode-markdownlint@0.47.0
+ - esbenp.prettier-vscode
diff --git a/.rubocop_todo/layout/array_alignment.yml b/.rubocop_todo/layout/array_alignment.yml
index ef4e51b88da..893a9338c5b 100644
--- a/.rubocop_todo/layout/array_alignment.yml
+++ b/.rubocop_todo/layout/array_alignment.yml
@@ -4,7 +4,6 @@ Layout/ArrayAlignment:
Details: grace period
Exclude:
- 'app/controllers/admin/application_settings_controller.rb'
- - 'app/controllers/admin/broadcast_messages_controller.rb'
- 'app/controllers/admin/plan_limits_controller.rb'
- 'app/controllers/concerns/observability/content_security_policy.rb'
- 'app/controllers/profiles_controller.rb'
diff --git a/.rubocop_todo/layout/first_array_element_indentation.yml b/.rubocop_todo/layout/first_array_element_indentation.yml
index e06a9a9d824..f55a4e6b0dc 100644
--- a/.rubocop_todo/layout/first_array_element_indentation.yml
+++ b/.rubocop_todo/layout/first_array_element_indentation.yml
@@ -3,7 +3,6 @@
Layout/FirstArrayElementIndentation:
Details: grace period
Exclude:
- - 'app/controllers/admin/broadcast_messages_controller.rb'
- 'app/controllers/admin/plan_limits_controller.rb'
- 'app/finders/user_groups_counter.rb'
- 'app/helpers/search_helper.rb'
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index a0e12418711..a2cbf2b5b2d 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -3696,7 +3696,6 @@ Layout/LineLength:
- 'spec/helpers/blob_helper_spec.rb'
- 'spec/helpers/boards_helper_spec.rb'
- 'spec/helpers/branches_helper_spec.rb'
- - 'spec/helpers/broadcast_messages_helper_spec.rb'
- 'spec/helpers/button_helper_spec.rb'
- 'spec/helpers/ci/pipeline_editor_helper_spec.rb'
- 'spec/helpers/clusters_helper_spec.rb'
@@ -4304,7 +4303,6 @@ Layout/LineLength:
- 'spec/models/award_emoji_spec.rb'
- 'spec/models/blob_viewer/go_mod_spec.rb'
- 'spec/models/blob_viewer/metrics_dashboard_yml_spec.rb'
- - 'spec/models/broadcast_message_spec.rb'
- 'spec/models/bulk_import_spec.rb'
- 'spec/models/bulk_imports/entity_spec.rb'
- 'spec/models/bulk_imports/file_transfer/project_config_spec.rb'
diff --git a/.rubocop_todo/layout/space_in_lambda_literal.yml b/.rubocop_todo/layout/space_in_lambda_literal.yml
index 9bfa99f6591..431e6e9fa72 100644
--- a/.rubocop_todo/layout/space_in_lambda_literal.yml
+++ b/.rubocop_todo/layout/space_in_lambda_literal.yml
@@ -392,7 +392,6 @@ Layout/SpaceInLambdaLiteral:
- 'spec/lib/gitlab/sidekiq_signals_spec.rb'
- 'spec/lib/gitlab/utils/sanitize_node_link_spec.rb'
- 'spec/models/ability_spec.rb'
- - 'spec/models/broadcast_message_spec.rb'
- 'spec/models/concerns/participable_spec.rb'
- 'spec/models/merge_request_spec.rb'
- 'spec/support/shared_examples/lib/cache_helpers_shared_examples.rb'
diff --git a/.rubocop_todo/rspec/described_class.yml b/.rubocop_todo/rspec/described_class.yml
index aee75fe32d4..67f7c365248 100644
--- a/.rubocop_todo/rspec/described_class.yml
+++ b/.rubocop_todo/rspec/described_class.yml
@@ -106,7 +106,6 @@ RSpec/DescribedClass:
- 'spec/models/alert_management/alert_spec.rb'
- 'spec/models/application_record_spec.rb'
- 'spec/models/application_setting_spec.rb'
- - 'spec/models/broadcast_message_spec.rb'
- 'spec/models/chat_name_spec.rb'
- 'spec/models/ci/build_runner_session_spec.rb'
- 'spec/models/ci/build_spec.rb'
diff --git a/.rubocop_todo/rspec/return_from_stub.yml b/.rubocop_todo/rspec/return_from_stub.yml
index 29437bcfb2b..41428687eaf 100644
--- a/.rubocop_todo/rspec/return_from_stub.yml
+++ b/.rubocop_todo/rspec/return_from_stub.yml
@@ -110,7 +110,6 @@ RSpec/ReturnFromStub:
- 'spec/graphql/mutations/environments/canary_ingress/update_spec.rb'
- 'spec/graphql/types/project_type_spec.rb'
- 'spec/helpers/auth_helper_spec.rb'
- - 'spec/helpers/broadcast_messages_helper_spec.rb'
- 'spec/helpers/dashboard_helper_spec.rb'
- 'spec/helpers/diff_helper_spec.rb'
- 'spec/helpers/explore_helper_spec.rb'
diff --git a/.rubocop_todo/style/empty_method.yml b/.rubocop_todo/style/empty_method.yml
index f8a033317c1..adf3e8ee9b2 100644
--- a/.rubocop_todo/style/empty_method.yml
+++ b/.rubocop_todo/style/empty_method.yml
@@ -4,7 +4,6 @@ Style/EmptyMethod:
Exclude:
- 'app/controllers/admin/application_settings/appearances_controller.rb'
- 'app/controllers/admin/applications_controller.rb'
- - 'app/controllers/admin/broadcast_messages_controller.rb'
- 'app/controllers/admin/deploy_keys_controller.rb'
- 'app/controllers/admin/identities_controller.rb'
- 'app/controllers/admin/labels_controller.rb'
diff --git a/.rubocop_todo/style/explicit_block_argument.yml b/.rubocop_todo/style/explicit_block_argument.yml
index 591c228f18a..5027e699ae7 100644
--- a/.rubocop_todo/style/explicit_block_argument.yml
+++ b/.rubocop_todo/style/explicit_block_argument.yml
@@ -6,7 +6,6 @@ Style/ExplicitBlockArgument:
- 'app/controllers/admin/batched_jobs_controller.rb'
- 'app/controllers/application_controller.rb'
- 'app/models/application_record.rb'
- - 'app/models/broadcast_message.rb'
- 'app/models/ci/build.rb'
- 'app/models/ci/build_trace_chunks/redis.rb'
- 'app/models/ci/build_trace_chunks/redis_trace_chunks.rb'
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml
index 1cf44ae494b..efbfc8e03f5 100644
--- a/.rubocop_todo/style/if_unless_modifier.yml
+++ b/.rubocop_todo/style/if_unless_modifier.yml
@@ -112,7 +112,6 @@ Style/IfUnlessModifier:
- 'app/models/ability.rb'
- 'app/models/appearance.rb'
- 'app/models/application_setting_implementation.rb'
- - 'app/models/broadcast_message.rb'
- 'app/models/bulk_imports/entity.rb'
- 'app/models/ci/application_record.rb'
- 'app/models/ci/build.rb'
diff --git a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
index a5f8f369604..a9fb692b299 100644
--- a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
+++ b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
@@ -18,6 +18,7 @@ import {
removeIfPresent,
} from '../utils';
import Token from './token.vue';
+import DateOption from './date_option.vue';
export default {
components: {
@@ -73,6 +74,7 @@ export default {
operators: OPERATORS_IS,
token: Token,
unique: true,
+ optionComponent: DateOption,
},
{
formattedKey: __('Committed-after'),
@@ -86,6 +88,7 @@ export default {
operators: OPERATORS_IS,
token: Token,
unique: true,
+ optionComponent: DateOption,
},
],
};
@@ -317,16 +320,7 @@ export default {
:available-tokens="availableTokens"
@clear="handleSearchCommits"
@submit="handleSearchCommits"
- >
- <template #title="{ value }">
- <div>
- {{ value }}
- <span v-if="shouldShowInputDateFormat(value)" class="title-hint-text">
- &lt;{{ __('yyyy-mm-dd') }}&gt;
- </span>
- </div>
- </template>
- </gl-filtered-search>
+ />
<review-tab-container
:is-loading="isLoadingCommits"
diff --git a/app/assets/javascripts/add_context_commits_modal/components/date_option.vue b/app/assets/javascripts/add_context_commits_modal/components/date_option.vue
new file mode 100644
index 00000000000..1945e048029
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/components/date_option.vue
@@ -0,0 +1,17 @@
+<script>
+export default {
+ props: {
+ option: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <span
+ >{{ option.title }}
+ <span class="title-hint-text">&lt;{{ __('yyyy-mm-dd') }}&gt;</span>
+ </span>
+</template>
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
index 3168d693234..08aec58e89d 100644
--- a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
+++ b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
@@ -15,7 +15,6 @@ import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import { createAlert, VARIANT_DANGER } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { THEMES, TYPES, TYPE_BANNER } from '../constants';
@@ -42,7 +41,6 @@ export default {
directives: {
SafeHtml,
},
- mixins: [glFeatureFlagsMixin()],
inject: {
targetAccessLevelOptions: {
default: [[]],
@@ -226,11 +224,7 @@ export default {
</gl-form-group>
</template>
- <gl-form-group
- v-if="glFeatures.roleTargetedBroadcastMessages"
- :label="$options.i18n.targetRoles"
- data-testid="target-roles-checkboxes"
- >
+ <gl-form-group :label="$options.i18n.targetRoles" data-testid="target-roles-checkboxes">
<gl-form-checkbox-group v-model="targetAccessLevels" :options="targetAccessLevelOptions" />
<gl-form-text>
{{ $options.i18n.targetRolesDescription }}
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue b/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
index a523dd3b391..2e8ecbd3a5d 100644
--- a/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
+++ b/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
@@ -3,7 +3,6 @@ import { GlButton, GlTableLite } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { __ } from '~/locale';
import { formatDate } from '~/lib/utils/datetime/date_format_utility';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const DEFAULT_TD_CLASSES = 'gl-vertical-align-middle!';
@@ -16,7 +15,6 @@ export default {
directives: {
SafeHtml,
},
- mixins: [glFeatureFlagsMixin()],
i18n: {
edit: __('Edit'),
delete: __('Delete'),
@@ -27,13 +25,7 @@ export default {
required: true,
},
},
- computed: {
- fields() {
- if (this.glFeatures.roleTargetedBroadcastMessages) return this.$options.allFields;
- return this.$options.allFields.filter((f) => f.key !== 'target_roles');
- },
- },
- allFields: [
+ fields: [
{
key: 'status',
label: __('Status'),
@@ -89,7 +81,7 @@ export default {
<template>
<gl-table-lite
:items="messages"
- :fields="fields"
+ :fields="$options.fields"
:tbody-tr-attr="{ 'data-testid': 'message-row' }"
stacked="md"
>
diff --git a/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue b/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue
index 9cf41750efe..f23a6fbcaa0 100644
--- a/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue
+++ b/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue
@@ -1,6 +1,6 @@
<script>
import { GlSprintf, GlButton, GlAlert, GlCard } from '@gitlab/ui';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import { __ } from '~/locale';
import Tracking from '~/tracking';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
index d173703f172..cc8a9baf69e 100644
--- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js
+++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
@@ -698,8 +698,8 @@ export const keybindingGroups = [
/**
* Gets keyboard shortcuts associated with a command
*
- * @param {string} command The command object. All command
- * objects are available as imports from this file.
+ * @param {Object} command The command object. All command objects are
+ * available as imports from this file.
*
* @returns {string[]} An array of keyboard shortcut strings bound to the command
*
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 301dd1c5669..5c6e4665a5c 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import { flatten } from 'lodash';
-import Mousetrap from 'mousetrap';
import Vue from 'vue';
+import { Mousetrap, addStopCallback } from '~/lib/mousetrap';
import { getCookie, setCookie, parseBoolean } from '~/lib/utils/common_utils';
import findAndFollowLink from '~/lib/utils/navigation_utility';
@@ -28,20 +28,11 @@ import {
} from './keybindings';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
-const defaultStopCallback = Mousetrap.prototype.stopCallback;
-Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) {
- if (keysFor(TOGGLE_MARKDOWN_PREVIEW).indexOf(combo) !== -1) {
- return false;
- }
-
- return defaultStopCallback.call(this, e, element, combo);
-};
-
/**
* The key used to save and fetch the local Mousetrap instance
* attached to a `<textarea>` element using `jQuery.data`
*/
-const LOCAL_MOUSETRAP_DATA_KEY = 'local-mousetrap-instance';
+export const LOCAL_MOUSETRAP_DATA_KEY = 'local-mousetrap-instance';
/**
* Gets a mapping of toolbar button => keyboard shortcuts
@@ -76,54 +67,38 @@ export default class Shortcuts {
this.helpModalElement = null;
this.helpModalVueInstance = null;
- Mousetrap.bind(keysFor(TOGGLE_KEYBOARD_SHORTCUTS_DIALOG), this.onToggleHelp);
- Mousetrap.bind(keysFor(START_SEARCH), Shortcuts.focusSearch);
- Mousetrap.bind(keysFor(FOCUS_FILTER_BAR), this.focusFilter.bind(this));
- Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), Shortcuts.onTogglePerfBar);
- Mousetrap.bind(keysFor(HIDE_APPEARING_CONTENT), Shortcuts.hideAppearingContent);
- Mousetrap.bind(keysFor(TOGGLE_CANARY), Shortcuts.onToggleCanary);
-
- const findFileURL = document.body.dataset.findFile;
-
- Mousetrap.bind(keysFor(GO_TO_YOUR_TODO_LIST), () => findAndFollowLink('.shortcuts-todos'));
- Mousetrap.bind(keysFor(GO_TO_ACTIVITY_FEED), () =>
- findAndFollowLink('.dashboard-shortcuts-activity'),
- );
- Mousetrap.bind(keysFor(GO_TO_YOUR_ISSUES), () =>
- findAndFollowLink('.dashboard-shortcuts-issues'),
+ this.bindCommands([
+ [TOGGLE_KEYBOARD_SHORTCUTS_DIALOG, this.onToggleHelp],
+ [START_SEARCH, Shortcuts.focusSearch],
+ [FOCUS_FILTER_BAR, this.focusFilter.bind(this)],
+ [TOGGLE_PERFORMANCE_BAR, Shortcuts.onTogglePerfBar],
+ [HIDE_APPEARING_CONTENT, Shortcuts.hideAppearingContent],
+ [TOGGLE_CANARY, Shortcuts.onToggleCanary],
+
+ [GO_TO_YOUR_TODO_LIST, () => findAndFollowLink('.shortcuts-todos')],
+ [GO_TO_ACTIVITY_FEED, () => findAndFollowLink('.dashboard-shortcuts-activity')],
+ [GO_TO_YOUR_ISSUES, () => findAndFollowLink('.dashboard-shortcuts-issues')],
+ [GO_TO_YOUR_MERGE_REQUESTS, () => findAndFollowLink('.dashboard-shortcuts-merge_requests')],
+ [GO_TO_YOUR_REVIEW_REQUESTS, () => findAndFollowLink('.dashboard-shortcuts-review_requests')],
+ [GO_TO_YOUR_PROJECTS, () => findAndFollowLink('.dashboard-shortcuts-projects')],
+ [GO_TO_YOUR_GROUPS, () => findAndFollowLink('.dashboard-shortcuts-groups')],
+ [GO_TO_MILESTONE_LIST, () => findAndFollowLink('.dashboard-shortcuts-milestones')],
+ [GO_TO_YOUR_SNIPPETS, () => findAndFollowLink('.dashboard-shortcuts-snippets')],
+
+ [TOGGLE_MARKDOWN_PREVIEW, Shortcuts.toggleMarkdownPreview],
+ ]);
+
+ addStopCallback((e, element, combo) =>
+ keysFor(TOGGLE_MARKDOWN_PREVIEW).includes(combo) ? false : undefined,
);
- Mousetrap.bind(keysFor(GO_TO_YOUR_MERGE_REQUESTS), () =>
- findAndFollowLink('.dashboard-shortcuts-merge_requests'),
- );
- Mousetrap.bind(keysFor(GO_TO_YOUR_REVIEW_REQUESTS), () =>
- findAndFollowLink('.dashboard-shortcuts-review_requests'),
- );
- Mousetrap.bind(keysFor(GO_TO_YOUR_PROJECTS), () =>
- findAndFollowLink('.dashboard-shortcuts-projects'),
- );
- Mousetrap.bind(keysFor(GO_TO_YOUR_GROUPS), () =>
- findAndFollowLink('.dashboard-shortcuts-groups'),
- );
- Mousetrap.bind(keysFor(GO_TO_MILESTONE_LIST), () =>
- findAndFollowLink('.dashboard-shortcuts-milestones'),
- );
- Mousetrap.bind(keysFor(GO_TO_YOUR_SNIPPETS), () =>
- findAndFollowLink('.dashboard-shortcuts-snippets'),
- );
-
- Mousetrap.bind(keysFor(TOGGLE_MARKDOWN_PREVIEW), Shortcuts.toggleMarkdownPreview);
+ const findFileURL = document.body.dataset.findFile;
if (typeof findFileURL !== 'undefined' && findFileURL !== null) {
- Mousetrap.bind(keysFor(GO_TO_PROJECT_FIND_FILE), () => {
+ this.bindCommand(GO_TO_PROJECT_FIND_FILE, () => {
visitUrl(findFileURL);
});
}
- $(document).on('click.more_help', '.js-more-help-button', function clickMoreHelp(e) {
- $(this).remove();
- e.preventDefault();
- });
-
const shortcutsModalTriggerEvent = 'click.shortcutsModalTrigger';
// eslint-disable-next-line @gitlab/no-global-event-off
$(document)
@@ -135,6 +110,32 @@ export default class Shortcuts {
}
}
+ /**
+ * Bind the keyboard shortcut(s) defined by the given command to the given
+ * callback.
+ *
+ * @param {Object} command A command object.
+ * @param {Function} callback The callback to call when the command's key
+ * combo has been pressed.
+ * @returns {void}
+ */
+ // eslint-disable-next-line class-methods-use-this
+ bindCommand(command, callback) {
+ Mousetrap.bind(keysFor(command), callback);
+ }
+
+ /**
+ * Bind the keyboard shortcut(s) defined by the given commands to the given
+ * callbacks.
+ *
+ * @param {Array<[Object, Function]>} commandsAndCallbacks An array of
+ * command/callback pairs.
+ * @returns {void}
+ */
+ bindCommands(commandsAndCallbacks) {
+ commandsAndCallbacks.forEach((commandAndCallback) => this.bindCommand(...commandAndCallback));
+ }
+
onToggleHelp(e) {
if (e?.preventDefault) {
e.preventDefault();
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
index ab7fcbb35f1..65ae67d156f 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
@@ -1,5 +1,4 @@
-import Mousetrap from 'mousetrap';
-import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
+import { PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
import {
getLocationHash,
updateHistory,
@@ -11,7 +10,6 @@ import { updateRefPortionOfTitle } from '~/repository/utils/title';
import Shortcuts from './shortcuts';
const defaults = {
- skipResetBindings: false,
fileBlobPermalinkUrl: null,
fileBlobPermalinkUrlElement: null,
};
@@ -24,12 +22,12 @@ function eventHasModifierKeys(event) {
export default class ShortcutsBlob extends Shortcuts {
constructor(opts) {
const options = { ...defaults, ...opts };
- super(options.skipResetBindings);
+ super();
this.options = options;
this.shortcircuitPermalinkButton();
- Mousetrap.bind(keysFor(PROJECT_FILES_GO_TO_PERMALINK), this.moveToFilePermalink.bind(this));
+ this.bindCommand(PROJECT_FILES_GO_TO_PERMALINK, this.moveToFilePermalink.bind(this));
}
moveToFilePermalink() {
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
index 992e571e596..f26878cf161 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
@@ -1,4 +1,3 @@
-import Mousetrap from 'mousetrap';
import {
keysFor,
PROJECT_FILES_MOVE_SELECTION_UP,
@@ -6,15 +5,14 @@ import {
PROJECT_FILES_OPEN_SELECTION,
PROJECT_FILES_GO_BACK,
} from '~/behaviors/shortcuts/keybindings';
+import { addStopCallback } from '~/lib/mousetrap';
import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsFindFile extends ShortcutsNavigation {
constructor(projectFindFile) {
super();
- const oldStopCallback = Mousetrap.prototype.stopCallback;
-
- Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) {
+ addStopCallback((e, element, combo) => {
if (
element === projectFindFile.inputElement[0] &&
(keysFor(PROJECT_FILES_MOVE_SELECTION_UP).includes(combo) ||
@@ -27,12 +25,14 @@ export default class ShortcutsFindFile extends ShortcutsNavigation {
return false;
}
- return oldStopCallback.call(this, e, element, combo);
- };
+ return undefined;
+ });
- Mousetrap.bind(keysFor(PROJECT_FILES_MOVE_SELECTION_UP), projectFindFile.selectRowUp);
- Mousetrap.bind(keysFor(PROJECT_FILES_MOVE_SELECTION_DOWN), projectFindFile.selectRowDown);
- Mousetrap.bind(keysFor(PROJECT_FILES_GO_BACK), projectFindFile.goToTree);
- Mousetrap.bind(keysFor(PROJECT_FILES_OPEN_SELECTION), projectFindFile.goToBlob);
+ this.bindCommands([
+ [PROJECT_FILES_MOVE_SELECTION_UP, projectFindFile.selectRowUp],
+ [PROJECT_FILES_MOVE_SELECTION_DOWN, projectFindFile.selectRowDown],
+ [PROJECT_FILES_GO_BACK, projectFindFile.goToTree],
+ [PROJECT_FILES_OPEN_SELECTION, projectFindFile.goToBlob],
+ ]);
}
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index 64297da39cd..0c882ff9ea2 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -1,6 +1,5 @@
import $ from 'jquery';
import ClipboardJS from 'clipboard';
-import Mousetrap from 'mousetrap';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { isElementVisible } from '~/lib/utils/dom_utils';
import { DEBOUNCE_DROPDOWN_DELAY } from '~/sidebar/components/labels/labels_select_widget/constants';
@@ -9,7 +8,6 @@ import { s__ } from '~/locale';
import Sidebar from '~/right_sidebar';
import { CopyAsGFM } from '../markdown/copy_as_gfm';
import {
- keysFor,
ISSUE_MR_CHANGE_ASSIGNEE,
ISSUE_MR_CHANGE_MILESTONE,
ISSUABLE_CHANGE_LABEL,
@@ -32,18 +30,14 @@ export default class ShortcutsIssuable extends Shortcuts {
toast(s__('GlobalShortcuts|Unable to copy the source branch name at this time.'));
});
- Mousetrap.bind(keysFor(ISSUE_MR_CHANGE_ASSIGNEE), () =>
- ShortcutsIssuable.openSidebarDropdown('assignee'),
- );
- Mousetrap.bind(keysFor(ISSUE_MR_CHANGE_MILESTONE), () =>
- ShortcutsIssuable.openSidebarDropdown('milestone'),
- );
- Mousetrap.bind(keysFor(ISSUABLE_CHANGE_LABEL), () =>
- ShortcutsIssuable.openSidebarDropdown('labels'),
- );
- Mousetrap.bind(keysFor(ISSUABLE_COMMENT_OR_REPLY), ShortcutsIssuable.replyWithSelectedText);
- Mousetrap.bind(keysFor(ISSUABLE_EDIT_DESCRIPTION), ShortcutsIssuable.editIssue);
- Mousetrap.bind(keysFor(MR_COPY_SOURCE_BRANCH_NAME), () => this.copyBranchName());
+ this.bindCommands([
+ [ISSUE_MR_CHANGE_ASSIGNEE, () => ShortcutsIssuable.openSidebarDropdown('assignee')],
+ [ISSUE_MR_CHANGE_MILESTONE, () => ShortcutsIssuable.openSidebarDropdown('milestone')],
+ [ISSUABLE_CHANGE_LABEL, () => ShortcutsIssuable.openSidebarDropdown('labels')],
+ [ISSUABLE_COMMENT_OR_REPLY, ShortcutsIssuable.replyWithSelectedText],
+ [ISSUABLE_EDIT_DESCRIPTION, ShortcutsIssuable.editIssue],
+ [MR_COPY_SOURCE_BRANCH_NAME, () => this.copyBranchName()],
+ ]);
/**
* We're attaching a global focus event listener on document for
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
index 33a71c3a65b..bd08dc28f7a 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
@@ -1,8 +1,6 @@
-import Mousetrap from 'mousetrap';
import { visitUrl, constructWebIDEPath } from '~/lib/utils/url_utility';
import findAndFollowLink from '~/lib/utils/navigation_utility';
import {
- keysFor,
GO_TO_PROJECT_OVERVIEW,
GO_TO_PROJECT_ACTIVITY_FEED,
GO_TO_PROJECT_RELEASES,
@@ -28,40 +26,26 @@ export default class ShortcutsNavigation extends Shortcuts {
constructor() {
super();
- Mousetrap.bind(keysFor(GO_TO_PROJECT_OVERVIEW), () => findAndFollowLink('.shortcuts-project'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_ACTIVITY_FEED), () =>
- findAndFollowLink('.shortcuts-project-activity'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_RELEASES), () =>
- findAndFollowLink('.shortcuts-deployments-releases'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_FILES), () => findAndFollowLink('.shortcuts-tree'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_COMMITS), () => findAndFollowLink('.shortcuts-commits'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_JOBS), () => findAndFollowLink('.shortcuts-builds'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_REPO_GRAPH), () =>
- findAndFollowLink('.shortcuts-network'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_REPO_CHARTS), () =>
- findAndFollowLink('.shortcuts-repository-charts'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_ISSUES), () => findAndFollowLink('.shortcuts-issues'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_ISSUE_BOARDS), () =>
- findAndFollowLink('.shortcuts-issue-boards'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_MERGE_REQUESTS), () =>
- findAndFollowLink('.shortcuts-merge_requests'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_WIKI), () => findAndFollowLink('.shortcuts-wiki'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_SNIPPETS), () => findAndFollowLink('.shortcuts-snippets'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_KUBERNETES), () =>
- findAndFollowLink('.shortcuts-kubernetes'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_ENVIRONMENTS), () =>
- findAndFollowLink('.shortcuts-environments'),
- );
- Mousetrap.bind(keysFor(GO_TO_PROJECT_METRICS), () => findAndFollowLink('.shortcuts-metrics'));
- Mousetrap.bind(keysFor(GO_TO_PROJECT_WEBIDE), ShortcutsNavigation.navigateToWebIDE);
- Mousetrap.bind(keysFor(NEW_ISSUE), () => findAndFollowLink('.shortcuts-new-issue'));
+ this.bindCommands([
+ [GO_TO_PROJECT_OVERVIEW, () => findAndFollowLink('.shortcuts-project')],
+ [GO_TO_PROJECT_ACTIVITY_FEED, () => findAndFollowLink('.shortcuts-project-activity')],
+ [GO_TO_PROJECT_RELEASES, () => findAndFollowLink('.shortcuts-deployments-releases')],
+ [GO_TO_PROJECT_FILES, () => findAndFollowLink('.shortcuts-tree')],
+ [GO_TO_PROJECT_COMMITS, () => findAndFollowLink('.shortcuts-commits')],
+ [GO_TO_PROJECT_JOBS, () => findAndFollowLink('.shortcuts-builds')],
+ [GO_TO_PROJECT_REPO_GRAPH, () => findAndFollowLink('.shortcuts-network')],
+ [GO_TO_PROJECT_REPO_CHARTS, () => findAndFollowLink('.shortcuts-repository-charts')],
+ [GO_TO_PROJECT_ISSUES, () => findAndFollowLink('.shortcuts-issues')],
+ [GO_TO_PROJECT_ISSUE_BOARDS, () => findAndFollowLink('.shortcuts-issue-boards')],
+ [GO_TO_PROJECT_MERGE_REQUESTS, () => findAndFollowLink('.shortcuts-merge_requests')],
+ [GO_TO_PROJECT_WIKI, () => findAndFollowLink('.shortcuts-wiki')],
+ [GO_TO_PROJECT_SNIPPETS, () => findAndFollowLink('.shortcuts-snippets')],
+ [GO_TO_PROJECT_KUBERNETES, () => findAndFollowLink('.shortcuts-kubernetes')],
+ [GO_TO_PROJECT_ENVIRONMENTS, () => findAndFollowLink('.shortcuts-environments')],
+ [GO_TO_PROJECT_METRICS, () => findAndFollowLink('.shortcuts-metrics')],
+ [GO_TO_PROJECT_WEBIDE, ShortcutsNavigation.navigateToWebIDE],
+ [NEW_ISSUE, () => findAndFollowLink('.shortcuts-new-issue')],
+ ]);
}
static navigateToWebIDE() {
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
index c33c092b009..02c6af53fc2 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
@@ -1,6 +1,4 @@
-import Mousetrap from 'mousetrap';
import {
- keysFor,
REPO_GRAPH_SCROLL_BOTTOM,
REPO_GRAPH_SCROLL_DOWN,
REPO_GRAPH_SCROLL_LEFT,
@@ -14,11 +12,13 @@ export default class ShortcutsNetwork extends ShortcutsNavigation {
constructor(graph) {
super();
- Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_LEFT), graph.scrollLeft);
- Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_RIGHT), graph.scrollRight);
- Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_UP), graph.scrollUp);
- Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_DOWN), graph.scrollDown);
- Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_TOP), graph.scrollTop);
- Mousetrap.bind(keysFor(REPO_GRAPH_SCROLL_BOTTOM), graph.scrollBottom);
+ this.bindCommands([
+ [REPO_GRAPH_SCROLL_LEFT, graph.scrollLeft],
+ [REPO_GRAPH_SCROLL_RIGHT, graph.scrollRight],
+ [REPO_GRAPH_SCROLL_UP, graph.scrollUp],
+ [REPO_GRAPH_SCROLL_DOWN, graph.scrollDown],
+ [REPO_GRAPH_SCROLL_TOP, graph.scrollTop],
+ [REPO_GRAPH_SCROLL_BOTTOM, graph.scrollBottom],
+ ]);
}
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js
index 66aa1b752ae..3f3e0c51de5 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js
@@ -1,4 +1,4 @@
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause';
const shorcutsDisabledKey = 'shortcutsDisabled';
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
index b2801f9118d..62d612cfa6d 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
@@ -1,12 +1,12 @@
-import Mousetrap from 'mousetrap';
import findAndFollowLink from '~/lib/utils/navigation_utility';
-import { keysFor, EDIT_WIKI_PAGE } from './keybindings';
+import { EDIT_WIKI_PAGE } from './keybindings';
import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() {
super();
- Mousetrap.bind(keysFor(EDIT_WIKI_PAGE), ShortcutsWiki.editWiki);
+
+ this.bindCommand(EDIT_WIKI_PAGE, ShortcutsWiki.editWiki);
}
static editWiki() {
diff --git a/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue b/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue
index 9b57f135a40..429f8e78dbe 100644
--- a/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue
+++ b/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue
@@ -1,11 +1,10 @@
<script>
import { __ } from '~/locale';
import RefSelector from '~/ref/components/ref_selector.vue';
-import { BRANCH_REF_TYPE, REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants';
+import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants';
import { formatToShortName } from '../utils/format_refs';
export default {
- BRANCH_REF_TYPE,
ENABLED_TYPE_REFS: [REF_TYPE_BRANCHES, REF_TYPE_TAGS],
i18n: {
/**
@@ -49,7 +48,6 @@ export default {
<ref-selector
:value="refShortName"
:enabled-ref-types="$options.ENABLED_TYPE_REFS"
- :ref-type="$options.BRANCH_REF_TYPE"
:project-id="projectId"
:translations="$options.i18n"
:use-symbolic-ref-names="true"
diff --git a/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue b/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue
index 43d0dae6e78..fbfd03d2716 100644
--- a/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue
+++ b/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue
@@ -1,9 +1,9 @@
<script>
-import { GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { redirectTo, setUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
-import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
+
+import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import { DEFAULT_PLATFORM, PARAM_KEY_PLATFORM, INSTANCE_TYPE } from '../constants';
@@ -12,21 +12,10 @@ import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_lo
export default {
name: 'AdminNewRunnerApp',
components: {
- GlLink,
- GlSprintf,
- RunnerInstructionsModal,
+ RegistrationCompatibilityAlert,
RunnerPlatformsRadioGroup,
RunnerCreateForm,
},
- directives: {
- GlModal: GlModalDirective,
- },
- props: {
- legacyRegistrationToken: {
- type: String,
- required: true,
- },
- },
data() {
return {
platform: DEFAULT_PLATFORM,
@@ -47,7 +36,6 @@ export default {
createAlert({ message: error.message });
},
},
- modalId: 'runners-legacy-registration-instructions-modal',
INSTANCE_TYPE,
};
</script>
@@ -55,24 +43,15 @@ export default {
<template>
<div>
<h1 class="gl-font-size-h2">{{ s__('Runners|New instance runner') }}</h1>
+
+ <registration-compatibility-alert />
+
<p>
- <gl-sprintf
- :message="
- s__(
- 'Runners|Create an instance runner to generate a command that registers the runner with all its configurations. %{linkStart}Prefer to use a registration token to create a runner?%{linkEnd}',
- )
- "
- >
- <template #link="{ content }">
- <gl-link v-gl-modal="$options.modalId" data-testid="legacy-instructions-link">{{
- content
- }}</gl-link>
- <runner-instructions-modal
- :modal-id="$options.modalId"
- :registration-token="legacyRegistrationToken"
- />
- </template>
- </gl-sprintf>
+ {{
+ s__(
+ 'Runners|Create an instance runner to generate a command that registers the runner with all its configurations.',
+ )
+ }}
</p>
<hr aria-hidden="true" />
diff --git a/app/assets/javascripts/ci/runner/admin_new_runner/index.js b/app/assets/javascripts/ci/runner/admin_new_runner/index.js
index 502d9d33b4d..434c1197f71 100644
--- a/app/assets/javascripts/ci/runner/admin_new_runner/index.js
+++ b/app/assets/javascripts/ci/runner/admin_new_runner/index.js
@@ -12,8 +12,6 @@ export const initAdminNewRunner = (selector = '#js-admin-new-runner') => {
return null;
}
- const { legacyRegistrationToken } = el.dataset;
-
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
@@ -22,11 +20,7 @@ export const initAdminNewRunner = (selector = '#js-admin-new-runner') => {
el,
apolloProvider,
render(h) {
- return h(AdminNewRunnerApp, {
- props: {
- legacyRegistrationToken,
- },
- });
+ return h(AdminNewRunnerApp);
},
});
};
diff --git a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue
index d452adb34d9..24225acfd08 100644
--- a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue
@@ -193,16 +193,17 @@ export default {
nav-class="gl-border-none!"
/>
- <gl-button v-if="shouldShowCreateRunnerWorkflow" :href="newRunnerPath" variant="confirm">
- {{ s__('Runners|New instance runner') }}
- </gl-button>
- <registration-dropdown
- v-else
- class="gl-w-full gl-sm-w-auto gl-mr-auto"
- :registration-token="registrationToken"
- :type="$options.INSTANCE_TYPE"
- right
- />
+ <div class="gl-w-full gl-md-w-auto gl-display-flex">
+ <gl-button v-if="shouldShowCreateRunnerWorkflow" :href="newRunnerPath" variant="confirm">
+ {{ s__('Runners|New instance runner') }}
+ </gl-button>
+ <registration-dropdown
+ class="gl-ml-3"
+ :registration-token="registrationToken"
+ :type="$options.INSTANCE_TYPE"
+ right
+ />
+ </div>
</div>
<runner-filtered-search-bar
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue b/app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue
new file mode 100644
index 00000000000..0b6716c5082
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue
@@ -0,0 +1,33 @@
+<script>
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { CHANGELOG_URL } from '../../constants';
+
+export default {
+ name: 'RegistrationCompatibilityAlert',
+ components: {
+ GlAlert,
+ GlLink,
+ GlSprintf,
+ },
+ CHANGELOG_URL,
+ i18n: {
+ title: s__(
+ 'Runners|This registration process is only supported in GitLab Runner 15.10 or later',
+ ),
+ message: s__(
+ 'Runners|This registration process is not supported in GitLab Runner 15.9 or earlier and only available as an experimental feature in GitLab Runner 15.10 and 15.11. You should upgrade to %{linkStart}GitLab Runner 16.0%{linkEnd} or later to use a stable version of this registration process.',
+ ),
+ },
+};
+</script>
+
+<template>
+ <gl-alert class="gl-mb-4" variant="warning" :dismissible="false" :title="$options.i18n.title">
+ <gl-sprintf :message="$options.i18n.message">
+ <template #link="{ content }">
+ <gl-link :href="$options.CHANGELOG_URL" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue
index 212ad5fa5a0..c1e862f6fa8 100644
--- a/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue
@@ -1,6 +1,7 @@
<script>
-import { GlDropdown, GlDropdownForm, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
+import { GlDropdown, GlDropdownForm, GlDropdownItem, GlDropdownDivider, GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants';
import RegistrationToken from './registration_token.vue';
@@ -17,10 +18,12 @@ export default {
GlDropdownForm,
GlDropdownItem,
GlDropdownDivider,
+ GlIcon,
RegistrationToken,
RunnerInstructionsModal,
RegistrationTokenResetDropdownItem,
},
+ mixins: [glFeatureFlagMixin()],
props: {
registrationToken: {
type: String,
@@ -40,7 +43,18 @@ export default {
};
},
computed: {
+ isDeprecated() {
+ // Show a compact version when used as secondary option
+ // create_runner_workflow_for_admin or create_runner_workflow_for_namespace
+ return (
+ this.glFeatures?.createRunnerWorkflowForAdmin ||
+ this.glFeatures?.createRunnerWorkflowForNamespace
+ );
+ },
dropdownText() {
+ if (this.isDeprecated) {
+ return '';
+ }
switch (this.type) {
case INSTANCE_TYPE:
return s__('Runners|Register an instance runner');
@@ -52,6 +66,24 @@ export default {
return s__('Runners|Register a runner');
}
},
+ dropdownToggleClass() {
+ if (this.isDeprecated) {
+ return ['gl-px-3!'];
+ }
+ return [];
+ },
+ dropdownCategory() {
+ if (this.isDeprecated) {
+ return 'tertiary';
+ }
+ return 'primary';
+ },
+ dropdownVariant() {
+ if (this.isDeprecated) {
+ return 'default';
+ }
+ return 'confirm';
+ },
},
methods: {
onShowInstructionsClick() {
@@ -71,9 +103,25 @@ export default {
ref="runnerRegistrationDropdown"
menu-class="gl-w-auto!"
:text="dropdownText"
- variant="confirm"
+ :toggle-class="dropdownToggleClass"
+ :variant="dropdownVariant"
+ :category="dropdownCategory"
v-bind="$attrs"
>
+ <template v-if="isDeprecated" #button-content>
+ <gl-icon name="ellipsis_v" />
+ </template>
+ <gl-dropdown-form class="gl-p-4!">
+ <registration-token input-id="token-value" :value="currentRegistrationToken">
+ <template v-if="isDeprecated" #label-description>
+ <gl-icon name="warning" class="gl-text-orange-500" />
+ <span class="gl-text-secondary">
+ {{ s__('Runners|Support for registration tokens is deprecated') }}
+ </span>
+ </template>
+ </registration-token>
+ </gl-dropdown-form>
+ <gl-dropdown-divider />
<gl-dropdown-item @click.capture.native.stop="onShowInstructionsClick">
{{ $options.i18n.showInstallationInstructions }}
<runner-instructions-modal
@@ -83,10 +131,6 @@ export default {
/>
</gl-dropdown-item>
<gl-dropdown-divider />
- <gl-dropdown-form class="gl-p-4!">
- <registration-token input-id="token-value" :value="currentRegistrationToken" />
- </gl-dropdown-form>
- <gl-dropdown-divider />
<registration-token-reset-dropdown-item :type="type" @tokenReset="onTokenReset" />
</gl-dropdown>
</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_token.vue b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue
index 6b4e6a929b7..b196bccf66f 100644
--- a/app/assets/javascripts/ci/runner/components/registration/registration_token.vue
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue
@@ -45,5 +45,9 @@ export default {
:copy-button-title="$options.I18N_COPY_BUTTON_TITLE"
:form-input-group-props="formInputGroupProps"
@copy="onCopy"
- />
+ >
+ <template v-for="slot in Object.keys($scopedSlots)" #[slot]>
+ <slot :name="slot"></slot>
+ </template>
+ </input-copy-toggle-visibility>
</template>
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index 1cae9df713b..84b2ed010e6 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -248,5 +248,6 @@ export const INSTALL_HELP_URL = 'https://docs.gitlab.com/runner/install';
export const EXECUTORS_HELP_URL = 'https://docs.gitlab.com/runner/executors/';
export const SERVICE_COMMANDS_HELP_URL =
'https://docs.gitlab.com/runner/commands/#service-related-commands';
+export const CHANGELOG_URL = 'https://gitlab.com/gitlab-org/gitlab-runner/blob/main/CHANGELOG.md';
export const DOCKER_HELP_URL = 'https://docs.gitlab.com/runner/install/docker.html';
export const KUBERNETES_HELP_URL = 'https://docs.gitlab.com/runner/install/kubernetes.html';
diff --git a/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue b/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue
index 35c75a917c7..ea702cd770a 100644
--- a/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue
+++ b/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue
@@ -1,9 +1,9 @@
<script>
-import { GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { redirectTo, setUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
-import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
+
+import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import { DEFAULT_PLATFORM, GROUP_TYPE, PARAM_KEY_PLATFORM } from '../constants';
@@ -12,24 +12,15 @@ import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_lo
export default {
name: 'GroupNewRunnerApp',
components: {
- GlLink,
- GlSprintf,
- RunnerInstructionsModal,
+ RegistrationCompatibilityAlert,
RunnerPlatformsRadioGroup,
RunnerCreateForm,
},
- directives: {
- GlModal: GlModalDirective,
- },
props: {
groupId: {
type: String,
required: true,
},
- legacyRegistrationToken: {
- type: String,
- required: true,
- },
},
data() {
return {
@@ -51,7 +42,6 @@ export default {
createAlert({ message: error.message });
},
},
- modalId: 'runners-legacy-registration-instructions-modal',
GROUP_TYPE,
};
</script>
@@ -59,24 +49,15 @@ export default {
<template>
<div>
<h1 class="gl-font-size-h2">{{ s__('Runners|New group runner') }}</h1>
+
+ <registration-compatibility-alert />
+
<p>
- <gl-sprintf
- :message="
- s__(
- 'Runners|Create a group runner to generate a command that registers the runner with all its configurations. %{linkStart}Prefer to use a registration token to create a runner?%{linkEnd}',
- )
- "
- >
- <template #link="{ content }">
- <gl-link v-gl-modal="$options.modalId" data-testid="legacy-instructions-link">{{
- content
- }}</gl-link>
- <runner-instructions-modal
- :modal-id="$options.modalId"
- :registration-token="legacyRegistrationToken"
- />
- </template>
- </gl-sprintf>
+ {{
+ s__(
+ 'Runners|Create a group runner to generate a command that registers the runner with all its configurations.',
+ )
+ }}
</p>
<hr aria-hidden="true" />
diff --git a/app/assets/javascripts/ci/runner/group_new_runner/index.js b/app/assets/javascripts/ci/runner/group_new_runner/index.js
index b314c3aa1e7..9e056081e03 100644
--- a/app/assets/javascripts/ci/runner/group_new_runner/index.js
+++ b/app/assets/javascripts/ci/runner/group_new_runner/index.js
@@ -12,7 +12,7 @@ export const initGroupNewRunner = (selector = '#js-group-new-runner') => {
return null;
}
- const { legacyRegistrationToken, groupId } = el.dataset;
+ const { groupId } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@@ -25,7 +25,6 @@ export const initGroupNewRunner = (selector = '#js-group-new-runner') => {
return h(GroupNewRunnerApp, {
props: {
groupId,
- legacyRegistrationToken,
},
});
},
diff --git a/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue
index f8386214698..9f3e6f247d7 100644
--- a/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue
@@ -217,7 +217,9 @@ export default {
<template>
<div>
- <div class="gl-display-flex gl-align-items-center">
+ <div
+ class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
+ >
<runner-type-tabs
ref="runner-type-tabs"
v-model="search"
@@ -229,20 +231,23 @@ export default {
nav-class="gl-border-none!"
/>
- <template v-if="shouldShowCreateRunnerWorkflow">
- <gl-button v-if="newRunnerPath" :href="newRunnerPath" variant="confirm">
+ <div class="gl-w-full gl-md-w-auto gl-display-flex">
+ <gl-button
+ v-if="shouldShowCreateRunnerWorkflow && newRunnerPath"
+ :href="newRunnerPath"
+ variant="confirm"
+ >
{{ s__('Runners|New group runner') }}
</gl-button>
- </template>
- <registration-dropdown
- v-else-if="registrationToken"
- class="gl-ml-auto"
- :registration-token="registrationToken"
- :type="$options.GROUP_TYPE"
- right
- />
+ <registration-dropdown
+ v-if="registrationToken"
+ class="gl-ml-3"
+ :registration-token="registrationToken"
+ :type="$options.GROUP_TYPE"
+ right
+ />
+ </div>
</div>
-
<div
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-gap-3"
:class="$options.FILTER_CSS_CLASSES"
diff --git a/app/assets/javascripts/ci/runner/project_new_runner/index.js b/app/assets/javascripts/ci/runner/project_new_runner/index.js
index 5236f28752a..44f1a0ffdab 100644
--- a/app/assets/javascripts/ci/runner/project_new_runner/index.js
+++ b/app/assets/javascripts/ci/runner/project_new_runner/index.js
@@ -12,7 +12,7 @@ export const initProjectNewRunner = (selector = '#js-project-new-runner') => {
return null;
}
- const { legacyRegistrationToken, projectId } = el.dataset;
+ const { projectId } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@@ -25,7 +25,6 @@ export const initProjectNewRunner = (selector = '#js-project-new-runner') => {
return h(ProjectNewRunnerApp, {
props: {
projectId,
- legacyRegistrationToken,
},
});
},
diff --git a/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
index 94d3e949e86..abc2eca38f4 100644
--- a/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
+++ b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
@@ -1,8 +1,6 @@
<script>
-import { GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { s__ } from '~/locale';
-import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import { DEFAULT_PLATFORM, PROJECT_TYPE } from '../constants';
@@ -10,24 +8,14 @@ import { DEFAULT_PLATFORM, PROJECT_TYPE } from '../constants';
export default {
name: 'ProjectNewRunnerApp',
components: {
- GlLink,
- GlSprintf,
- RunnerInstructionsModal,
RunnerPlatformsRadioGroup,
RunnerCreateForm,
},
- directives: {
- GlModal: GlModalDirective,
- },
props: {
projectId: {
type: String,
required: true,
},
- legacyRegistrationToken: {
- type: String,
- required: true,
- },
},
data() {
return {
@@ -45,7 +33,6 @@ export default {
createAlert({ message: error.message });
},
},
- modalId: 'runners-legacy-registration-instructions-modal',
PROJECT_TYPE,
};
</script>
@@ -54,23 +41,11 @@ export default {
<div>
<h1 class="gl-font-size-h2">{{ s__('Runners|New project runner') }}</h1>
<p>
- <gl-sprintf
- :message="
- s__(
- 'Runners|Create a project runner to generate a command that registers the runner with all its configurations. %{linkStart}Prefer to use a registration token to create a runner?%{linkEnd}',
- )
- "
- >
- <template #link="{ content }">
- <gl-link v-gl-modal="$options.modalId" data-testid="legacy-instructions-link">{{
- content
- }}</gl-link>
- <runner-instructions-modal
- :modal-id="$options.modalId"
- :registration-token="legacyRegistrationToken"
- />
- </template>
- </gl-sprintf>
+ {{
+ s__(
+ 'Runners|Create a project runner to generate a command that registers the runner with all its configurations.',
+ )
+ }}
</p>
<hr aria-hidden="true" />
diff --git a/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
index 0bbbc795fff..e08eb853ad7 100644
--- a/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
+++ b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
@@ -1,12 +1,11 @@
<script>
-/* global Mousetrap */
-import 'mousetrap';
import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import {
keysFor,
ISSUE_PREVIOUS_DESIGN,
ISSUE_NEXT_DESIGN,
} from '~/behaviors/shortcuts/keybindings';
+import { Mousetrap } from '~/lib/mousetrap';
import { s__, sprintf } from '~/locale';
import allDesignsMixin from '../../mixins/all_designs';
import { DESIGN_ROUTE_NAME } from '../../router/constants';
diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue
index 2f2b2ed1a90..eeb36e59b89 100644
--- a/app/assets/javascripts/design_management/pages/design/index.vue
+++ b/app/assets/javascripts/design_management/pages/design/index.vue
@@ -1,7 +1,7 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { isNull } from 'lodash';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import { keysFor, ISSUE_CLOSE_DESIGN } from '~/behaviors/shortcuts/keybindings';
import { createAlert } from '~/alert';
import { fetchPolicies } from '~/lib/graphql';
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 9b3db78724d..dff95bde269 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -1,7 +1,6 @@
<script>
import { GlLoadingIcon, GlPagination, GlSprintf, GlAlert } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import Mousetrap from 'mousetrap';
import { mapState, mapGetters, mapActions } from 'vuex';
import api from '~/api';
import {
@@ -15,6 +14,7 @@ import { createAlert } from '~/alert';
import { isSingleViewStyle } from '~/helpers/diffs_helper';
import { helpPagePath } from '~/helpers/help_page_helper';
import { parseBoolean } from '~/lib/utils/common_utils';
+import { Mousetrap } from '~/lib/mousetrap';
import { updateHistory } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
diff --git a/app/assets/javascripts/docs/docs_bundle.js b/app/assets/javascripts/docs/docs_bundle.js
index 897439f56b0..32aa4a22cba 100644
--- a/app/assets/javascripts/docs/docs_bundle.js
+++ b/app/assets/javascripts/docs/docs_bundle.js
@@ -1,4 +1,4 @@
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
function addMousetrapClick(el, key) {
el.addEventListener('click', () => Mousetrap.trigger(key));
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index 44944a4a205..70f1dddcac9 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -360,6 +360,9 @@
},
"rules": {
"$ref": "#/definitions/rules"
+ },
+ "inputs": {
+ "$ref": "#/definitions/inputs"
}
},
"required": [
@@ -395,6 +398,9 @@
}
}
]
+ },
+ "inputs": {
+ "$ref": "#/definitions/inputs"
}
},
"required": [
@@ -411,6 +417,9 @@
"type": "string",
"format": "uri-reference",
"pattern": "\\.ya?ml$"
+ },
+ "inputs": {
+ "$ref": "#/definitions/inputs"
}
},
"required": [
@@ -425,6 +434,9 @@
"description": "Local path to component directory or full path to external component directory.",
"type": "string",
"format": "uri-reference"
+ },
+ "inputs": {
+ "$ref": "#/definitions/inputs"
}
},
"required": [
@@ -440,6 +452,9 @@
"type": "string",
"format": "uri-reference",
"pattern": "^https?://.+\\.ya?ml$"
+ },
+ "inputs": {
+ "$ref": "#/definitions/inputs"
}
},
"required": [
@@ -1252,6 +1267,10 @@
"markdownDescription": "Interruptible is used to indicate that a job should be canceled if made redundant by a newer pipeline run. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#interruptible).",
"default": false
},
+ "inputs": {
+ "markdownDescription": "Used to pass input values to included templates or components. [Learn More](https://docs.gitlab.com/ee/ci/yaml/includes.html#set-input-parameter-values-with-includeinputs).",
+ "type": "object"
+ },
"job": {
"allOf": [
{
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/stages_dropdown.vue b/app/assets/javascripts/jobs/components/job/sidebar/stages_dropdown.vue
index e3afe9b7c67..28a17abb20b 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/stages_dropdown.vue
@@ -1,7 +1,7 @@
<script>
import { GlLink, GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
import { isEmpty } from 'lodash';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import { s__ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
diff --git a/app/assets/javascripts/lib/mousetrap.js b/app/assets/javascripts/lib/mousetrap.js
new file mode 100644
index 00000000000..ef3f54ec314
--- /dev/null
+++ b/app/assets/javascripts/lib/mousetrap.js
@@ -0,0 +1,59 @@
+// This is the only file allowed to import directly from the package.
+// eslint-disable-next-line no-restricted-imports
+import Mousetrap from 'mousetrap';
+
+const additionalStopCallbacks = [];
+const originalStopCallback = Mousetrap.prototype.stopCallback;
+
+Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) {
+ for (const callback of additionalStopCallbacks) {
+ const returnValue = callback.call(this, e, element, combo);
+ if (returnValue !== undefined) return returnValue;
+ }
+
+ return originalStopCallback.call(this, e, element, combo);
+};
+
+/**
+ * Add a stop callback to Mousetrap.
+ *
+ * This allows overriding the default behaviour of Mousetrap#stopCallback,
+ * which is to stop the bound key handler/callback from being called if the key
+ * combo is pressed inside form fields (input, select, textareas, etc). See
+ * https://craig.is/killing/mice#api.stopCallback.
+ *
+ * The stopCallback registered here has the same signature as
+ * Mousetrap#stopCallback, with the one difference being that the callback
+ * should return `undefined` if it has no opinion on whether the current key
+ * combo should be stopped or not, and the next stop callback should be
+ * consulted instead. If a boolean is returned, no other stop callbacks are
+ * called.
+ *
+ * Note: This approach does not always work as expected when coupled with
+ * Mousetrap's pause plugin, which is used for enabling/disabling all keyboard
+ * shortcuts. That plugin assumes it's the first to execute and overwrite
+ * Mousetrap's `stopCallback` method, whereas to work correctly with this, it
+ * must execute last. This is not guaranteed or even attempted.
+ *
+ * To work correctly, we may need to reimplement the pause plugin here.
+ *
+ * @param {(e: Event, element: Element, combo: string) => boolean|undefined}
+ * stopCallback The additional stop callback function to add to the chain
+ * of stop callbacks.
+ * @returns {void}
+ */
+export const addStopCallback = (stopCallback) => {
+ // Unshift, since we want to iterate through them in reverse order, so that
+ // the most recently added handler is called first, and the original
+ // stopCallback method is called last.
+ additionalStopCallbacks.unshift(stopCallback);
+};
+
+/**
+ * Clear additionalStopCallbacks. Used only for tests.
+ */
+export const clearStopCallbacksForTests = () => {
+ additionalStopCallbacks.length = 0;
+};
+
+export { Mousetrap };
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index ab2b713ac9f..100ef11409f 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -8,12 +8,12 @@ import {
GlSprintf,
GlLink,
} from '@gitlab/ui';
-import Mousetrap from 'mousetrap';
import VueDraggable from 'vuedraggable';
import { mapActions, mapState, mapGetters } from 'vuex';
import { createAlert } from '~/alert';
import invalidUrl from '~/lib/utils/invalid_url';
import { ESC_KEY } from '~/lib/utils/keys';
+import { Mousetrap } from '~/lib/mousetrap';
import { mergeUrlParams, updateHistory } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { defaultTimeRange } from '~/vue_shared/constants';
diff --git a/app/assets/javascripts/notes/components/discussion_navigator.vue b/app/assets/javascripts/notes/components/discussion_navigator.vue
index 03bdc7a2cc6..faef8595998 100644
--- a/app/assets/javascripts/notes/components/discussion_navigator.vue
+++ b/app/assets/javascripts/notes/components/discussion_navigator.vue
@@ -1,12 +1,11 @@
<script>
-/* global Mousetrap */
-import 'mousetrap';
import { throttle } from 'lodash';
import {
keysFor,
MR_NEXT_UNRESOLVED_DISCUSSION,
MR_PREVIOUS_UNRESOLVED_DISCUSSION,
} from '~/behaviors/shortcuts/keybindings';
+import { Mousetrap } from '~/lib/mousetrap';
import eventHub from '~/notes/event_hub';
import discussionNavigation from '~/notes/mixins/discussion_navigation';
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index 097b2f33aa9..244d1d5590e 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -22,7 +22,6 @@ export default () => {
// eslint-disable-next-line no-new
new ShortcutsBlob({
- skipResetBindings: true,
fileBlobPermalinkUrl,
fileBlobPermalinkUrlElement,
});
diff --git a/app/assets/javascripts/projects/commits/index.js b/app/assets/javascripts/projects/commits/index.js
index 3179fcb14fd..1a5681adb04 100644
--- a/app/assets/javascripts/projects/commits/index.js
+++ b/app/assets/javascripts/projects/commits/index.js
@@ -48,7 +48,6 @@ export const initCommitsRefSwitcher = () => {
projectId,
value: useSymbolicRefNames ? `refs/${refType}/${ref}` : ref,
useSymbolicRefNames,
- refType,
},
on: {
input(selected) {
diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue
index 9a84726d42f..7f58b394547 100644
--- a/app/assets/javascripts/ref/components/ref_selector.vue
+++ b/app/assets/javascripts/ref/components/ref_selector.vue
@@ -47,11 +47,6 @@ export default {
required: false,
default: () => {},
},
- refType: {
- type: String,
- required: false,
- default: null,
- },
projectId: {
type: String,
required: true,
diff --git a/app/assets/javascripts/repository/components/blob_controls.vue b/app/assets/javascripts/repository/components/blob_controls.vue
index d3e306619bf..460db0fe2ae 100644
--- a/app/assets/javascripts/repository/components/blob_controls.vue
+++ b/app/assets/javascripts/repository/components/blob_controls.vue
@@ -101,7 +101,6 @@ export default {
fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
// eslint-disable-next-line no-new
new ShortcutsBlob({
- skipResetBindings: true,
fileBlobPermalinkUrl,
fileBlobPermalinkUrlElement,
});
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index 4f312c72de5..050b05a6324 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import { keysFor, TOGGLE_SUPER_SIDEBAR } from '~/behaviors/shortcuts/keybindings';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
diff --git a/app/assets/javascripts/vue_shared/components/file_finder/index.vue b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
index 5c892377f40..18f9d26a13d 100644
--- a/app/assets/javascripts/vue_shared/components/file_finder/index.vue
+++ b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
@@ -1,8 +1,8 @@
<script>
import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
-import Mousetrap from 'mousetrap';
import VirtualList from 'vue-virtual-scroll-list';
+import { Mousetrap, addStopCallback } from '~/lib/mousetrap';
import { keysFor, MR_GO_TO_FILE } from '~/behaviors/shortcuts/keybindings';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import Item from './item.vue';
@@ -10,8 +10,6 @@ import Item from './item.vue';
export const MAX_FILE_FINDER_RESULTS = 40;
export const FILE_FINDER_ROW_HEIGHT = 55;
-const originalStopCallback = Mousetrap.prototype.stopCallback;
-
export default {
components: {
GlIcon,
@@ -140,7 +138,7 @@ export default {
this.toggle(!this.visible);
});
- Mousetrap.prototype.stopCallback = function customStopCallback(e, el, combo) {
+ addStopCallback(function fileFinderStopCallback(e, el, combo) {
if (
(combo === 't' && el.classList.contains('dropdown-input-field')) ||
el.classList.contains('inputarea')
@@ -150,8 +148,8 @@ export default {
return false;
}
- return originalStopCallback.call(this, e, el, combo);
- };
+ return undefined;
+ });
},
methods: {
toggle(visible) {
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 155e983cbf6..c942278ba00 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -13,7 +13,6 @@ import {
} from '@gitlab/ui';
import noAccessSvg from '@gitlab/svgs/dist/illustrations/analytics/no-access.svg';
import * as Sentry from '@sentry/browser';
-import { fetchPolicies } from '~/lib/graphql';
import { s__ } from '~/locale';
import { getParameterByName, updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { isPositiveInteger } from '~/lib/utils/number_utils';
@@ -149,8 +148,6 @@ export default {
error() {
this.setEmptyState();
},
- fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
- notifyOnNetworkStatusChange: true,
result(res) {
// need to handle this when the res is loading: true, netWorkStatus: 1, partial: true
if (!res.data) {
@@ -331,7 +328,7 @@ export default {
const widgetHierarchy = this.workItem.widgets.find(
(widget) => widget.type === WIDGET_TYPE_HIERARCHY,
);
- return widgetHierarchy.children.nodes;
+ return widgetHierarchy.children?.nodes;
},
workItemBodyClass() {
return {
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
index 72efe3990d1..dd0d50861e4 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
@@ -1,9 +1,5 @@
<script>
-import { isEmpty } from 'lodash';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { TYPENAME_WORK_ITEM } from '~/graphql_shared/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import { getParameterByName } from '~/lib/utils/url_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
@@ -89,26 +85,6 @@ export default {
)
.some((hierarchy) => hierarchy.hasChildren);
},
- childUrlParams() {
- const params = {};
- if (this.fetchByIid) {
- const iid = getParameterByName('work_item_iid');
- if (iid) {
- params.iid = iid;
- }
- } else {
- const workItemId = getParameterByName('work_item_id');
- if (workItemId) {
- params.id = convertToGraphQLId(TYPENAME_WORK_ITEM, workItemId);
- }
- }
- return params;
- },
- },
- mounted() {
- if (!isEmpty(this.childUrlParams)) {
- this.addWorkItemQuery(this.childUrlParams);
- }
},
methods: {
showAddForm(formType, childType) {
@@ -123,6 +99,19 @@ export default {
hideAddForm() {
this.isShownAddForm = false;
},
+ prefetchWorkItem({ id, iid }) {
+ if (this.workItemType !== WORK_ITEM_TYPE_VALUE_OBJECTIVE) {
+ this.prefetch = setTimeout(
+ () => this.addWorkItemQuery({ id, iid }),
+ DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
+ );
+ }
+ },
+ clearPrefetching() {
+ if (this.prefetch) {
+ clearTimeout(this.prefetch);
+ }
+ },
addWorkItemQuery({ id, iid }) {
const variables = this.fetchByIid
? {
@@ -145,19 +134,6 @@ export default {
},
});
},
- prefetchWorkItem({ id, iid }) {
- if (this.workItemType !== WORK_ITEM_TYPE_VALUE_OBJECTIVE) {
- this.prefetch = setTimeout(
- () => this.addWorkItemQuery({ id, iid }),
- DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
- );
- }
- },
- clearPrefetching() {
- if (this.prefetch) {
- clearTimeout(this.prefetch);
- }
- },
},
};
</script>
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index ccb9d05bc90..6634d0cc617 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -6,7 +6,7 @@
import autosize from 'autosize';
import Dropzone from 'dropzone';
import $ from 'jquery';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause';
import { scrollToElement } from '~/lib/utils/common_utils';
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index ef75c650853..bb838437939 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -336,6 +336,7 @@ kbd kbd {
border: 0;
}
.gl-avatar {
+ display: inline-flex;
border-width: 1px;
border-style: solid;
border-color: rgba(251, 250, 253, 0.08);
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 0dfc6be356f..079e59f5521 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -336,6 +336,7 @@ kbd kbd {
border: 0;
}
.gl-avatar {
+ display: inline-flex;
border-width: 1px;
border-style: solid;
border-color: rgba(31, 30, 36, 0.08);
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index 654b8309937..821c3cc1635 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -6,7 +6,6 @@ module Admin
before_action :find_broadcast_message, only: [:edit, :update, :destroy]
before_action :find_broadcast_messages, only: [:index, :create]
- before_action :push_features, only: [:index, :edit]
feature_category :onboarding
urgency :low
@@ -15,8 +14,7 @@ module Admin
@broadcast_message = BroadcastMessage.new
end
- def edit
- end
+ def edit; end
def create
@broadcast_message = BroadcastMessage.new(broadcast_message_params)
@@ -88,18 +86,14 @@ module Admin
def broadcast_message_params
params.require(:broadcast_message)
.permit(%i[
- theme
- ends_at
- message
- starts_at
- target_path
- broadcast_type
- dismissable
- ], target_access_levels: []).reverse_merge!(target_access_levels: [])
- end
-
- def push_features
- push_frontend_feature_flag(:role_targeted_broadcast_messages, current_user)
+ theme
+ ends_at
+ message
+ starts_at
+ target_path
+ broadcast_type
+ dismissable
+ ], target_access_levels: []).reverse_merge!(target_access_levels: [])
end
end
end
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index f267c1cb857..4b52617d287 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -36,8 +36,6 @@ class Groups::RunnersController < Groups::ApplicationController
def new
render_404 unless create_runner_workflow_for_namespace_enabled?
-
- @group_runner_registration_token = @group.runners_token
end
def register
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index bc3527565a6..d3a196cc0de 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -99,7 +99,6 @@ module BroadcastMessagesHelper
private
def current_user_access_level_for_project_or_group
- return if Feature.disabled?(:role_targeted_broadcast_messages)
return unless current_user.present?
strong_memoize(:current_user_access_level_for_project_or_group) do
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 196000ad441..02b8cf9ee28 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -864,33 +864,6 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
false
end
- # Overriding the enum check for `email_confirmation_setting` as the feature flag is being removed and is taking a
- # release M, M.N+1 strategy as noted in:
- # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107302#note_1286005956
- def email_confirmation_setting_off?
- if Feature.enabled?(:soft_email_confirmation)
- false
- else
- super
- end
- end
-
- def email_confirmation_setting_soft?
- if Feature.enabled?(:soft_email_confirmation)
- true
- else
- super
- end
- end
-
- def email_confirmation_setting_hard?
- if Feature.enabled?(:soft_email_confirmation)
- false
- else
- super
- end
- end
-
private
def self.human_attribute_name(attribute, *options)
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 14aecbc9420..733018160cd 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -88,10 +88,8 @@ class BroadcastMessage < MainClusterwide::ApplicationRecord
private
- def fetch_messages(cache_key, current_path, user_access_level)
- messages = cache.fetch(cache_key, as: BroadcastMessage, expires_in: cache_expires_in) do
- yield
- end
+ def fetch_messages(cache_key, current_path, user_access_level, &block)
+ messages = cache.fetch(cache_key, as: BroadcastMessage, expires_in: cache_expires_in, &block)
now_or_future = messages.select(&:now_or_future?)
@@ -134,7 +132,6 @@ class BroadcastMessage < MainClusterwide::ApplicationRecord
end
def matches_current_user_access_level?(user_access_level)
- return false if target_access_levels.present? && Feature.disabled?(:role_targeted_broadcast_messages)
return true unless target_access_levels.present?
target_access_levels.include? user_access_level
@@ -148,9 +145,7 @@ class BroadcastMessage < MainClusterwide::ApplicationRecord
# This fixes a mismatch between requests in the GUI and CLI
#
# This has to be reassigned due to frozen strings being provided.
- unless current_path.start_with?("/")
- current_path = "/#{current_path}"
- end
+ current_path = "/#{current_path}" unless current_path.start_with?("/")
escaped = Regexp.escape(target_path).gsub('\\*', '.*')
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
diff --git a/app/services/packages/generic/create_package_file_service.rb b/app/services/packages/generic/create_package_file_service.rb
index 78c97000654..09e3fb4a825 100644
--- a/app/services/packages/generic/create_package_file_service.rb
+++ b/app/services/packages/generic/create_package_file_service.rb
@@ -47,7 +47,11 @@ module Packages
end
def target_file_is_duplicate?(package)
- package.package_files.with_file_name(params[:file_name]).exists?
+ package
+ .package_files
+ .with_file_name(params[:file_name])
+ .not_pending_destruction
+ .exists?
end
end
end
diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb
index 4ec07bb4c5f..387d36cc7d1 100644
--- a/app/services/spam/spam_verdict_service.rb
+++ b/app/services/spam/spam_verdict_service.rb
@@ -14,57 +14,47 @@ module Spam
end
def execute
- spamcheck_result = nil
- spamcheck_attribs = {}
- spamcheck_error = false
+ spamcheck_verdict = nil
external_spam_check_round_trip_time = Benchmark.realtime do
- spamcheck_result, spamcheck_attribs, spamcheck_error = spamcheck_verdict
+ spamcheck_verdict = get_spamcheck_verdict
end
- label = spamcheck_error ? 'ERROR' : spamcheck_result.to_s.upcase
+ histogram.observe({ result: spamcheck_verdict.upcase }, external_spam_check_round_trip_time) if spamcheck_verdict
- histogram.observe({ result: label }, external_spam_check_round_trip_time)
-
- # assign result to a var for logging it before reassigning to nil when monitorMode is true
- original_spamcheck_result = spamcheck_result
-
- spamcheck_result = nil if spamcheck_attribs&.fetch("monitorMode", "false") == "true"
-
- akismet_result = akismet_verdict
+ akismet_verdict = get_akismet_verdict
# filter out anything we don't recognise, including nils.
- valid_results = [spamcheck_result, akismet_result].compact.select { |r| SUPPORTED_VERDICTS.key?(r) }
+ valid_verdicts = [spamcheck_verdict, akismet_verdict].compact.select { |r| SUPPORTED_VERDICTS.key?(r) }
# Treat nils - such as service unavailable - as ALLOW
- return ALLOW unless valid_results.any?
+ return ALLOW unless valid_verdicts.any?
- # Favour the most restrictive result.
- verdict = valid_results.min_by { |v| SUPPORTED_VERDICTS[v][:priority] }
+ # Favour the most restrictive verdict
+ final_verdict = valid_verdicts.min_by { |v| SUPPORTED_VERDICTS[v][:priority] }
# The target can override the verdict via the `allow_possible_spam` application setting
- verdict = OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM if override_via_allow_possible_spam?(verdict: verdict)
+ final_verdict = OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM if override_via_allow_possible_spam?(verdict: final_verdict)
logger.info(class: self.class.name,
akismet_verdict: akismet_verdict,
- spam_check_verdict: original_spamcheck_result,
- extra_attributes: spamcheck_attribs,
+ spam_check_verdict: spamcheck_verdict,
spam_check_rtt: external_spam_check_round_trip_time.real,
- final_verdict: verdict,
+ final_verdict: final_verdict,
username: user.username,
user_id: user.id,
target_type: target.class.to_s,
project_id: target.project_id
)
- verdict
+ final_verdict
end
private
attr_reader :user, :target, :options, :context, :extra_features
- def akismet_verdict
+ def get_akismet_verdict
if akismet.spam?
Gitlab::Recaptcha.enabled? ? CONDITIONAL_ALLOW : DISALLOW
else
@@ -72,23 +62,14 @@ module Spam
end
end
- def spamcheck_verdict
+ def get_spamcheck_verdict
return unless Gitlab::CurrentSettings.spam_check_endpoint_enabled
begin
- result, attribs, _error = spamcheck_client.spam?(spammable: target, user: user, context: context,
- extra_features: extra_features)
- # @TODO log if error is not nil https://gitlab.com/gitlab-org/gitlab/-/issues/329545
-
- return [nil, attribs] unless result
-
- [result, attribs]
-
+ spamcheck_client.spam?(spammable: target, user: user, context: context, extra_features: extra_features).verdict
rescue StandardError => e
Gitlab::ErrorTracking.log_exception(e, error: ERROR_TYPE)
-
- # Default to ALLOW if any errors occur
- [ALLOW, attribs, true]
+ nil
end
end
diff --git a/app/views/admin/runners/new.html.haml b/app/views/admin/runners/new.html.haml
index dd93ecfcf8c..c4e87761fee 100644
--- a/app/views/admin/runners/new.html.haml
+++ b/app/views/admin/runners/new.html.haml
@@ -2,4 +2,4 @@
- breadcrumb_title s_('Runners|New')
- page_title s_('Runners|Create an instance runner')
-#js-admin-new-runner{ data: { legacy_registration_token: Gitlab::CurrentSettings.runners_registration_token } }
+#js-admin-new-runner
diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml
index a24ef5d8ea4..04d99657cfd 100644
--- a/app/views/admin/sessions/new.html.haml
+++ b/app/views/admin/sessions/new.html.haml
@@ -4,7 +4,7 @@
.row.justify-content-center
.col-md-5.new-session-forms-container
.login-page
- #signin-container
+ #signin-container{ class: "#{'borderless' if Feature.enabled?(:restyle_login_page, @project)}" }
- if any_form_based_providers_enabled?
= render 'devise/shared/tabs_ldap', show_password_form: allow_admin_mode_password_authentication_for_web?, render_signup_link: false
- else
diff --git a/app/views/groups/runners/new.html.haml b/app/views/groups/runners/new.html.haml
index 12e7e458a79..db48e66185c 100644
--- a/app/views/groups/runners/new.html.haml
+++ b/app/views/groups/runners/new.html.haml
@@ -2,4 +2,4 @@
- breadcrumb_title s_('Runners|New')
- page_title s_('Runners|Create a group runner')
-#js-group-new-runner{ data: { legacy_registration_token: @group_runner_registration_token, group_id: @group.to_global_id } }
+#js-group-new-runner{ data: { group_id: @group.to_global_id } }
diff --git a/app/views/projects/runners/new.html.haml b/app/views/projects/runners/new.html.haml
index 92b79186501..4aeed910452 100644
--- a/app/views/projects/runners/new.html.haml
+++ b/app/views/projects/runners/new.html.haml
@@ -2,4 +2,4 @@
- breadcrumb_title s_('Runners|New runner')
- page_title s_('Runners|Create a project runner')
-#js-project-new-runner{ data: { legacy_registration_token: @project.runners_token, project_id: @project.to_global_id } }
+#js-project-new-runner{ data: { project_id: @project.to_global_id } }
diff --git a/app/workers/authorized_project_update/project_recalculate_worker.rb b/app/workers/authorized_project_update/project_recalculate_worker.rb
index cbf068f0b85..29ea43ad641 100644
--- a/app/workers/authorized_project_update/project_recalculate_worker.rb
+++ b/app/workers/authorized_project_update/project_recalculate_worker.rb
@@ -7,8 +7,6 @@ module AuthorizedProjectUpdate
data_consistency :always
include Gitlab::ExclusiveLeaseHelpers
- prepend WaitableWorker
-
feature_category :system_access
urgency :high
queue_namespace :authorized_project_update
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index b553a2cd14e..dde46a4e61b 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -6,7 +6,6 @@ class AuthorizedProjectsWorker
data_consistency :always
sidekiq_options retry: 3
- prepend WaitableWorker
feature_category :system_access
urgency :high
diff --git a/app/workers/concerns/waitable_worker.rb b/app/workers/concerns/waitable_worker.rb
deleted file mode 100644
index 1fe950b7570..00000000000
--- a/app/workers/concerns/waitable_worker.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module WaitableWorker
- extend ActiveSupport::Concern
-
- def perform(*args)
- notify_key = args.pop if Gitlab::JobWaiter.key?(args.last)
-
- super(*args)
- ensure
- Gitlab::JobWaiter.notify(notify_key, jid) if notify_key
- end
-end
diff --git a/config/feature_flags/development/role_targeted_broadcast_messages.yml b/config/feature_flags/development/role_targeted_broadcast_messages.yml
deleted file mode 100644
index 3de916d9e6d..00000000000
--- a/config/feature_flags/development/role_targeted_broadcast_messages.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: role_targeted_broadcast_messages
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81232
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351736
-milestone: '14.9'
-type: development
-group: group::activation
-default_enabled: false
diff --git a/config/feature_flags/development/soft_email_confirmation.yml b/config/feature_flags/development/soft_email_confirmation.yml
deleted file mode 100644
index 60ebcf1ee50..00000000000
--- a/config/feature_flags/development/soft_email_confirmation.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: soft_email_confirmation
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31245
-rollout_issue_url:
-milestone: '12.2'
-type: development
-group: group::acquisition
-default_enabled: false
diff --git a/db/post_migrate/20230420120431_create_namespaces_by_top_level_namespace_index.rb b/db/post_migrate/20230420120431_create_namespaces_by_top_level_namespace_index.rb
new file mode 100644
index 00000000000..f9fe6d4a16c
--- /dev/null
+++ b/db/post_migrate/20230420120431_create_namespaces_by_top_level_namespace_index.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class CreateNamespacesByTopLevelNamespaceIndex < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_on_namespaces_namespaces_by_top_level_namespace'
+
+ def up
+ add_concurrent_index :namespaces, '(traversal_ids[1]), type, id', name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :namespaces, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20230420120431 b/db/schema_migrations/20230420120431
new file mode 100644
index 00000000000..d3c041ba0e3
--- /dev/null
+++ b/db/schema_migrations/20230420120431
@@ -0,0 +1 @@
+33961f47238c49d09b748df67c89f9069db10e90e598ea1c0da3dd99bc6dc427 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index e84a03e9bd6..154e80dfd70 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -31504,6 +31504,8 @@ CREATE INDEX index_on_namespaces_lower_name ON namespaces USING btree (lower((na
CREATE INDEX index_on_namespaces_lower_path ON namespaces USING btree (lower((path)::text));
+CREATE INDEX index_on_namespaces_namespaces_by_top_level_namespace ON namespaces USING btree ((traversal_ids[1]), type, id);
+
CREATE INDEX index_on_oncall_schedule_escalation_rule ON incident_management_escalation_rules USING btree (oncall_schedule_id);
CREATE INDEX index_on_pages_metadata_not_migrated ON project_pages_metadata USING btree (project_id) WHERE ((deployed = true) AND (pages_deployment_id IS NULL));
diff --git a/doc/administration/geo/replication/configuration.md b/doc/administration/geo/replication/configuration.md
index 9fc2c05a492..5fa6df393b9 100644
--- a/doc/administration/geo/replication/configuration.md
+++ b/doc/administration/geo/replication/configuration.md
@@ -258,12 +258,12 @@ You can safely skip this step if:
#### Custom or self-signed certificate for inbound connections
-If your GitLab Geo **primary** site uses a custom or [self-signed certificate to secure inbound HTTPS connections](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates), this certificate can either be single-domain certificate or multi-domain.
+If your GitLab Geo **primary** site uses a custom or [self-signed certificate to secure inbound HTTPS connections](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates), this can be either a single-domain or multi-domain certificate.
Install the correct certificate based on your certificate type:
- **Multi-domain certificate** that includes both primary and secondary site domains: Install the certificate at `/etc/gitlab/ssl` on all **Rails, Sidekiq, and Gitaly** nodes in the **secondary** site.
-- **Single-domain certificate** where the certificates are specific to each Geo site domain: Generate a valid certificate for your **secondary** site's domain and install it at `/etc/gitlab/ssl` per [these instructions](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) on all **Rails, Sidekiq, and Gitaly** nodes in the **secondary** site.
+- **Single-domain certificate** where the certificates are specific to each Geo site domain: Generate a valid certificate for your **secondary** site's domain and install it at `/etc/gitlab/ssl` following [these instructions](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) on all **Rails, Sidekiq, and Gitaly** nodes in the **secondary** site.
#### Connecting to external services that use custom certificates
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 62773f66796..51201ec442f 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -730,7 +730,7 @@ for secure connections, you must:
Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
and on all Praefect clients that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) (and repeated below).
Note the following:
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index 042bca1f6c9..fcfae6cbe70 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -168,7 +168,7 @@ following:
- `http://plantuml:8080/`
- `http://localhost:8080/plantuml/`
-If you're running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl.html)
+If you're running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
you must configure this redirection, because PlantUML uses the insecure HTTP protocol.
Newer browsers such as [Google Chrome 86+](https://www.chromestatus.com/feature/4926989725073408)
don't load insecure HTTP resources on pages served over HTTPS.
diff --git a/doc/administration/load_balancer.md b/doc/administration/load_balancer.md
index a077558c7d2..e43fe851aa2 100644
--- a/doc/administration/load_balancer.md
+++ b/doc/administration/load_balancer.md
@@ -31,7 +31,7 @@ Configure your load balancers to pass connections on port 443 as 'TCP' rather
than 'HTTP(S)' protocol. This passes the connection to the application nodes
NGINX service untouched. NGINX has the SSL certificate and listen on port 443.
-See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
### Load Balancers terminate SSL without backend SSL
@@ -42,7 +42,7 @@ terminating SSL.
Because communication between the load balancers and GitLab isn't secure,
there is some additional configuration needed. See the
-[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
+[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
for details.
### Load Balancers terminate SSL with backend SSL
@@ -55,7 +55,7 @@ Traffic is secure between the load balancers and NGINX in this
scenario. There is no need to add configuration for proxied SSL because the
connection is secure all the way. However, configuration must be
added to GitLab to configure SSL certificates. See
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
## Ports
@@ -131,5 +131,5 @@ The default ciphers for a GitLab version can be
viewed in the [`files/gitlab-cookbooks/gitlab/attributes/default.rb`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb)
file and selecting the Git tag that correlates with your target GitLab version
(for example `15.0.5+ee.0`). If required by your load balancer, you can then define
-[custom SSL ciphers](https://docs.gitlab.com/omnibus/settings/ssl.html#use-custom-ssl-ciphers)
+[custom SSL ciphers](https://docs.gitlab.com/omnibus/settings/ssl/index.html#use-custom-ssl-ciphers)
for NGINX.
diff --git a/doc/administration/operations/puma.md b/doc/administration/operations/puma.md
index 5ff9208ecb9..efc55a5fbc3 100644
--- a/doc/administration/operations/puma.md
+++ b/doc/administration/operations/puma.md
@@ -182,7 +182,7 @@ steps below:
NOTE:
If using a self-signed certificate from a custom Certificate Authority (CA),
- follow [the documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
+ follow [the documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates)
to make them trusted by other GitLab components.
1. Edit `/etc/gitlab/gitlab.rb`:
diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md
index 05d2f307f02..001f4c46848 100644
--- a/doc/administration/packages/container_registry.md
+++ b/doc/administration/packages/container_registry.md
@@ -23,7 +23,7 @@ may or may not be available by default.
The Container Registry is automatically enabled and available on your GitLab domain, port 5050 if:
-- You're using the built-in [Let's Encrypt integration](https://docs.gitlab.com/omnibus/settings/ssl.html#enable-the-lets-encrypt-integration), and
+- You're using the built-in [Let's Encrypt integration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#enable-the-lets-encrypt-integration), and
- You're using GitLab 12.5 or later.
Otherwise, the Container Registry is not enabled. To enable it:
@@ -202,7 +202,7 @@ domain. For example, `*.gitlab.example.com`, is a wildcard that matches `registr
and is distinct from `*.example.com`.
As well as manually generated SSL certificates (explained here), certificates automatically
-generated by Let's Encrypt are also [supported in Omnibus installs](https://docs.gitlab.com/omnibus/settings/ssl.html).
+generated by Let's Encrypt are also [supported in Omnibus installs](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
Let's assume that you want the container Registry to be accessible at
`https://registry.gitlab.example.com`.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 415f2cc6054..9188f84d890 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -522,7 +522,7 @@ This usually results in this error:
For installation from source, this can be fixed by installing the custom Certificate
Authority (CA) in the system certificate store.
-For Omnibus, this is fixed by [installing a custom CA in Omnibus GitLab](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+For Omnibus, this is fixed by [installing a custom CA in Omnibus GitLab](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates).
### ZIP serving and cache configuration
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index 4c1161f7945..2902fa3c5da 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -311,7 +311,7 @@ Configure your load balancer to pass connections on port 443 as `TCP` rather
than `HTTP(S)` protocol. This will pass the connection to the application node's
NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
-See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
#### Load balancer terminates SSL without backend SSL
@@ -322,7 +322,7 @@ terminating SSL.
Since communication between the load balancer and GitLab will not be secure,
there is some additional configuration needed. See the
-[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
+[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
for details.
#### Load balancer terminates SSL with backend SSL
@@ -335,7 +335,7 @@ Traffic will also be secure between the load balancers and NGINX in this
scenario. There is no need to add configuration for proxied SSL since the
connection will be secure all the way. However, configuration will need to be
added to GitLab to configure SSL certificates. See
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
<div align="right">
@@ -1683,7 +1683,7 @@ for secure connections, you must:
Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
and on all Praefect clients that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) (and repeated below).
Note the following:
@@ -2088,7 +2088,7 @@ On each node perform the following:
When you specify `https` in the `external_url`, as in the previous example,
GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
certificates aren't present, NGINX will fail to start. For more information, see
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html).
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
### GitLab Rails post-configuration
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index d851963a052..86ef5149b4d 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -322,7 +322,7 @@ Configure your load balancer to pass connections on port 443 as `TCP` rather
than `HTTP(S)` protocol. This will pass the connection to the application node's
NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
-See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
#### Load balancer terminates SSL without backend SSL
@@ -333,7 +333,7 @@ terminating SSL.
Since communication between the load balancer and GitLab will not be secure,
there is some additional configuration needed. See the
-[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
+[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
for details.
#### Load balancer terminates SSL with backend SSL
@@ -346,7 +346,7 @@ Traffic will also be secure between the load balancers and NGINX in this
scenario. There is no need to add configuration for proxied SSL since the
connection will be secure all the way. However, configuration will need to be
added to GitLab to configure SSL certificates. See
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
<div align="right">
@@ -1700,7 +1700,7 @@ for secure connections, you must:
Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
and on all Praefect clients that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) (and repeated below).
Note the following:
@@ -2107,7 +2107,7 @@ On each node perform the following:
When you specify `https` in the `external_url`, as in the previous example,
GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
certificates aren't present, NGINX will fail to start. For more information, see
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html).
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
### GitLab Rails post-configuration
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index 1cd940e1af8..e94e7a162cb 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -213,7 +213,7 @@ Configure your load balancer to pass connections on port 443 as `TCP` rather
than `HTTP(S)` protocol. This will pass the connection to the application node's
NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
-See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
#### Load balancer terminates SSL without backend SSL
@@ -224,7 +224,7 @@ terminating SSL.
Since communication between the load balancer and GitLab will not be secure,
there is some additional configuration needed. See the
-[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
+[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
for details.
#### Load balancer terminates SSL with backend SSL
@@ -237,7 +237,7 @@ Traffic will also be secure between the load balancers and NGINX in this
scenario. There is no need to add configuration for proxied SSL since the
connection will be secure all the way. However, configuration will need to be
added to GitLab to configure SSL certificates. See
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
<div align="right">
@@ -556,7 +556,7 @@ You will need to bring your own certificates as this isn't provided automaticall
The certificate, or its certificate authority, must be installed on all Gitaly
nodes (including the Gitaly node using the certificate) and on all client nodes
that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates).
NOTE:
The self-signed certificate must specify the address you use to access the
@@ -762,7 +762,7 @@ On each node perform the following:
When you specify `https` in the `external_url`, as in the previous example,
GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
certificates aren't present, NGINX will fail to start. For more information, see
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html).
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
### GitLab Rails post-configuration
diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md
index f284ef1d2b6..e26d954a11b 100644
--- a/doc/administration/reference_architectures/3k_users.md
+++ b/doc/administration/reference_architectures/3k_users.md
@@ -323,7 +323,7 @@ Configure your load balancer to pass connections on port 443 as `TCP` rather
than `HTTP(S)` protocol. This will pass the connection to the application node's
NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
-See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
#### Load balancer terminates SSL without backend SSL
@@ -334,7 +334,7 @@ terminating SSL.
Since communication between the load balancer and GitLab will not be secure,
there is some additional configuration needed. See the
-[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
+[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
for details.
#### Load balancer terminates SSL with backend SSL
@@ -347,7 +347,7 @@ Traffic will also be secure between the load balancers and NGINX in this
scenario. There is no need to add configuration for proxied SSL since the
connection will be secure all the way. However, configuration will need to be
added to GitLab to configure SSL certificates. See
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
<div align="right">
@@ -1636,7 +1636,7 @@ for secure connections, you must:
Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
and on all Praefect clients that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) (and repeated below).
Note the following:
@@ -2071,7 +2071,7 @@ On each node perform the following:
When you specify `https` in the `external_url`, as in the previous example,
GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
certificates aren't present, NGINX will fail to start. For more information, see
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html).
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
### GitLab Rails post-configuration
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index a9fa085ae42..8b76d254e5c 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -320,7 +320,7 @@ Configure your load balancer to pass connections on port 443 as `TCP` rather
than `HTTP(S)` protocol. This will pass the connection to the application node's
NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
-See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
#### Load balancer terminates SSL without backend SSL
@@ -331,7 +331,7 @@ terminating SSL.
Since communication between the load balancer and GitLab will not be secure,
there is some additional configuration needed. See the
-[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
+[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
for details.
#### Load balancer terminates SSL with backend SSL
@@ -344,7 +344,7 @@ Traffic will also be secure between the load balancers and NGINX in this
scenario. There is no need to add configuration for proxied SSL since the
connection will be secure all the way. However, configuration will need to be
added to GitLab to configure SSL certificates. See
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
<div align="right">
@@ -1696,7 +1696,7 @@ for secure connections, you must:
Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
and on all Praefect clients that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) (and repeated below).
Note the following:
@@ -2106,7 +2106,7 @@ On each node perform the following:
When you specify `https` in the `external_url`, as in the previous example,
GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
certificates aren't present, NGINX will fail to start. For more information, see
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html).
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
### GitLab Rails post-configuration
diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md
index 4b6c671e856..d205993d5ab 100644
--- a/doc/administration/reference_architectures/5k_users.md
+++ b/doc/administration/reference_architectures/5k_users.md
@@ -320,7 +320,7 @@ Configure your load balancer to pass connections on port 443 as `TCP` rather
than `HTTP(S)` protocol. This will pass the connection to the application node's
NGINX service untouched. NGINX will have the SSL certificate and listen on port 443.
-See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+See the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
#### Load balancer terminates SSL without backend SSL
@@ -331,7 +331,7 @@ terminating SSL.
Since communication between the load balancer and GitLab will not be secure,
there is some additional configuration needed. See the
-[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
+[proxied SSL documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination)
for details.
#### Load balancer terminates SSL with backend SSL
@@ -344,7 +344,7 @@ Traffic will also be secure between the load balancers and NGINX in this
scenario. There is no need to add configuration for proxied SSL since the
connection will be secure all the way. However, configuration will need to be
added to GitLab to configure SSL certificates. See
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html)
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
for details on managing SSL certificates and configuring NGINX.
<div align="right">
@@ -1629,7 +1629,7 @@ for secure connections, you must:
Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
and on all Praefect clients that communicate with it following the procedure described in
-[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates) (and repeated below).
Note the following:
@@ -2066,7 +2066,7 @@ On each node perform the following:
When you specify `https` in the `external_url`, as in the previous example,
GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
certificates aren't present, NGINX fails to start. For more information, see
-the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl.html).
+the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
### GitLab Rails post-configuration
diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md
index 82ff00ff4ee..ff4604b61bf 100644
--- a/doc/architecture/blueprints/ci_pipeline_components/index.md
+++ b/doc/architecture/blueprints/ci_pipeline_components/index.md
@@ -134,7 +134,7 @@ Eventually, we want to make CI Catalog Components predictable. Including a
component by its path, using a fixed `@` version, should always return the same
configuration, regardless of a context from which it is getting included from.
The resulting configuration should be the same for a given component version
-and the set of inputs passed using `with:` keyword, hence it should be
+and the set of inputs passed using `include:inputs` keyword, therefore it should be
[deterministic](https://en.wikipedia.org/wiki/Deterministic_algorithm).
A component should not produce side effects by being included and should be
@@ -351,13 +351,13 @@ When using the component we pass the input parameters as follows:
```yaml
include:
- component: gitlab.com/org/my-component@1.0
- with:
+ inputs:
website: ${MY_WEBSITE} # variables expansion
test_run: system
environment: $[[ inputs.environment ]] # interpolation of upstream inputs
```
-Variables expansion must be supported for `with:` syntax as well as interpolation of
+Variables expansion must be supported for `include:inputs` syntax as well as interpolation of
possible [inputs provided upstream](#input-parameters-for-pipelines).
Input parameters are validated as soon as possible:
@@ -387,8 +387,8 @@ With `$[[ inputs.XXX ]]` inputs are interpolated immediately after parsing the c
### CI configuration interpolation perspectives and limitations
-With `spec:` users will be able to define input arguments for CI configuration.
-With `with:` keywords, they will pass these arguments to CI components.
+With `spec:inputs` users will be able to define input arguments for CI configuration.
+With `include:inputs`, they will pass these arguments to CI components.
`inputs` in `$[[ inputs.something ]]` is going to be an initial "object" or
"container" that we will provide, to allow users to access their arguments in
@@ -431,25 +431,25 @@ enforce contracts.
### Input parameters for existing `include:` syntax
Because we are adding input parameters to components used via `include:component` we have an opportunity to
-extend it to other `include:` types support inputs via `with:` syntax:
+extend it to other `include:` types support inputs through `inputs:` syntax:
```yaml
include:
- component: gitlab.com/org/my-component@1.0
- with:
+ inputs:
foo: bar
- local: path/to/file.yml
- with:
+ inputs:
foo: bar
- project: org/another
file: .gitlab-ci.yml
- with:
+ inputs:
foo: bar
- remote: http://example.com/ci/config
- with:
+ inputs:
foo: bar
- template: Auto-DevOps.gitlab-ci.yml
- with:
+ inputs:
foo: bar
```
@@ -463,15 +463,15 @@ spec:
# rest of the configuration
```
-If a YAML includes content using `with:` but the including YAML doesn't define `inputs:` in the specifications,
+If a YAML includes content using `include:inputs` but the including YAML doesn't define `spec:inputs` in the specifications,
an error should be raised.
-|`with:`| `inputs:` | result |
-| --- | --- | --- |
-| specified | | raise error |
-| specified | specified | validate inputs |
-| | specified | use defaults |
-| | | legacy `include:` without input passing |
+| `include:inputs` | `spec:inputs` | result |
+|------------------|---------------|-----------------------------------------|
+| specified | | raise error |
+| specified | specified | validate inputs |
+| | specified | use defaults |
+| | | legacy `include:` without input passing |
### Input parameters for pipelines
@@ -491,7 +491,7 @@ Today we have different use cases where using explicit input parameters would be
deploy-app:
trigger:
project: org/deployer
- with:
+ inputs:
provider: aws
deploy_environment: staging
```
diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md
index 94148400896..cd2cb3c96e1 100644
--- a/doc/ci/yaml/includes.md
+++ b/doc/ci/yaml/includes.md
@@ -514,7 +514,7 @@ and subject to change without notice.
### Define input parameters with `spec:inputs`
Use `spec:inputs` to define input parameters for CI/CD configuration intended to be added
-to a pipeline with `include`. Use [`include:with`](#set-input-parameter-values-with-includewith)
+to a pipeline with `include`. Use [`include:inputs`](#set-input-parameter-values-with-includeinputs)
to define the values to use when the pipeline runs.
The specs must be declared at the top of the configuration file, in a header section.
@@ -564,9 +564,11 @@ In this example:
- `user` is optional. If not defined, the value is `test-user`.
- `flags` is optional. If not defined, it has no value.
-### Set input parameter values with `include:with`
+### Set input parameter values with `include:inputs`
-Use `include:with` to set the values for the parameters when the included configuration
+> `include:with` [renamed to `include:inputs`](https://gitlab.com/gitlab-org/gitlab/-/issues/406780) in GitLab 16.0.
+
+Use `include:inputs` to set the values for the parameters when the included configuration
is added to the pipeline.
For example, to include a `custom_configuration.yml` that has the same specs
@@ -575,7 +577,7 @@ as the [example above](#define-input-parameters-with-specinputs):
```yaml
include:
- local: 'custom_configuration.yml'
- with:
+ inputs:
website: "My website"
```
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index d1e16b7f137..a8193fc819d 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -413,7 +413,7 @@ GitLab can be considered to have two layers from a process perspective:
- [Omnibus](https://github.com/certbot/certbot/blob/master/README.rst)
- [Charts](https://github.com/jetstack/cert-manager/blob/master/README.md)
- Configuration:
- - [Omnibus](https://docs.gitlab.com/omnibus/settings/ssl.html)
+ - [Omnibus](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
- [Charts](https://docs.gitlab.com/charts/installation/tls.html)
- [Source](../install/installation.md#using-https)
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/howto/https.md)
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index eb2bfd5e49a..1f9ff1dc8b1 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -68,7 +68,7 @@ To write and test your code, you will use the GitLab Development Kit.
From a project's repository, select the caret (angle-down) next to **Web IDE**,
and select **Gitpod** from the list.
1. If you want to contribute to the [website](https://about.gitlab.com/) or the [handbook](https://about.gitlab.com/handbook/),
- go to the footer of any page and select **Edit in Web IDE** to open the [Web IDE](../../user/project/web_ide/index.md).
+ go to the footer of any page and select **Edit in Web IDE** to open the [Web IDE](../../user/project/web_ide/index.md).
### Open a merge request
@@ -163,7 +163,7 @@ If you need any help while contributing to GitLab:
- Find reviewers and maintainers of GitLab projects in our
[handbook](https://about.gitlab.com/handbook/engineering/projects/) and
[mention](../../user/group/subgroups/index.md#mention-subgroups) them in a comment.
-- Join the community on the [GitLab Community Discord](https://discord.gg/gitlab) and find other
- contributors in the `#contribute` channel or [initiate a mentor session](https://about.gitlab.com//community/contribute/mentor-sessions/).
+- Join the community on the [GitLab Community Discord](https://discord.com/invite/gitlab) and find other
+ contributors in the `#contribute` channel or [initiate a mentor session](https://about.gitlab.com/community/contribute/mentor-sessions/).
- For any other questions or feedback, email `contributors@gitlab.com`.
- Did you run out of compute credits for your GitLab merge requests? Join the [GitLab community forks](https://gitlab.com/gitlab-community/meta) project.
diff --git a/doc/development/database/database_lab.md b/doc/development/database/database_lab.md
index e428b9d4b0a..b6eac0e526b 100644
--- a/doc/development/database/database_lab.md
+++ b/doc/development/database/database_lab.md
@@ -37,7 +37,7 @@ To access the DLE's services, you can:
provides `EXPLAIN` (analyze, buffers) plans for queries executed there.
- Migration testing by triggering a job as a part of a merge request.
- Direct `psql` access to DLE instead of a production replica. Available to authorized users only.
- To request `psql` access, file an [access request](https://about.gitlab.com/handbook/business-technology/team-member-enablement/onboarding-access-requests/access-requests/#individual-or-bulk-access-request).
+ To request `psql` access, file an [access request](https://about.gitlab.com/handbook/business-technology/end-user-services/onboarding-access-requests/access-requests/#individual-or-bulk-access-request).
For more assistance, use the `#database` Slack channel.
diff --git a/doc/development/fe_guide/source_editor.md b/doc/development/fe_guide/source_editor.md
index 2c115effcf9..45ec3ba1464 100644
--- a/doc/development/fe_guide/source_editor.md
+++ b/doc/development/fe_guide/source_editor.md
@@ -210,7 +210,7 @@ export default {
In the code example, `this` refers to the instance. By referring to the instance,
we can access the complete underlying
-[Monaco editor API](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneCodeEditor.html),
+[Monaco editor API](https://microsoft.github.io/monaco-editor/api/),
which includes functions like `getValue()`.
Now let's use our extension:
diff --git a/doc/index.md b/doc/index.md
index 852ecccdd37..958f7fe6111 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -45,7 +45,7 @@ Have a look at some of our most popular topics:
| [Elasticsearch integration](integration/advanced_search/elasticsearch.md) | Integrate Elasticsearch with GitLab to enable advanced search. |
| [Omnibus GitLab database settings](https://docs.gitlab.com/omnibus/settings/database.html) | Database settings for Omnibus GitLab self-managed instances. |
| [Omnibus GitLab NGINX settings](https://docs.gitlab.com/omnibus/settings/nginx.html) | NGINX settings for Omnibus GitLab self-managed instances. |
-| [Omnibus GitLab SSL configuration](https://docs.gitlab.com/omnibus/settings/ssl.html) | SSL settings for Omnibus GitLab self-managed instances. |
+| [Omnibus GitLab SSL configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html) | SSL settings for Omnibus GitLab self-managed instances. |
| [GitLab.com settings](user/gitlab_com/index.md) | Settings used for GitLab.com. |
## The entire DevOps lifecycle
diff --git a/doc/install/aws/manual_install_aws.md b/doc/install/aws/manual_install_aws.md
index a59854ba6ed..70fd000a4a6 100644
--- a/doc/install/aws/manual_install_aws.md
+++ b/doc/install/aws/manual_install_aws.md
@@ -483,7 +483,7 @@ Connect to your GitLab instance via **Bastion Host A** using [SSH Agent Forwardi
#### Disable Let's Encrypt
-Because we're adding our SSL certificate at the load balancer, we do not need the GitLab built-in support for Let's Encrypt. Let's Encrypt [is enabled by default](https://docs.gitlab.com/omnibus/settings/ssl.html#enable-the-lets-encrypt-integration) when using an `https` domain in GitLab 10.7 and later, so we must explicitly disable it:
+Because we're adding our SSL certificate at the load balancer, we do not need the GitLab built-in support for Let's Encrypt. Let's Encrypt [is enabled by default](https://docs.gitlab.com/omnibus/settings/ssl/index.html#enable-the-lets-encrypt-integration) when using an `https` domain in GitLab 10.7 and later, so we must explicitly disable it:
1. Open `/etc/gitlab/gitlab.rb` and disable it:
@@ -605,7 +605,7 @@ Now that we have our EC2 instance ready, follow the [documentation to install Gi
#### Add Support for Proxied SSL
-As we are terminating SSL at our [load balancer](#load-balancer), follow the steps at [Supporting proxied SSL](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination) to configure this in `/etc/gitlab/gitlab.rb`.
+As we are terminating SSL at our [load balancer](#load-balancer), follow the steps at [Supporting proxied SSL](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-a-reverse-proxy-or-load-balancer-ssl-termination) to configure this in `/etc/gitlab/gitlab.rb`.
Remember to run `sudo gitlab-ctl reconfigure` after saving the changes to the `gitlab.rb` file.
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index d92859d518f..088ef50c005 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -191,7 +191,7 @@ To set up the GitLab external URL:
1. Find `external_url` and replace it with your own domain name. For the sake
of this example, use the default domain name Azure sets up.
Using `https` in the URL
- [automatically enables](https://docs.gitlab.com/omnibus/settings/ssl.html#lets-encrypt-integration),
+ [automatically enables](https://docs.gitlab.com/omnibus/settings/ssl/index.html#lets-encrypt-integration),
Let's Encrypt, and sets HTTPS by default:
```ruby
diff --git a/doc/install/docker.md b/doc/install/docker.md
index 8d3c81d72c8..215ac32778e 100644
--- a/doc/install/docker.md
+++ b/doc/install/docker.md
@@ -305,7 +305,7 @@ point to a valid URL.
To receive emails from GitLab you have to configure the
[SMTP settings](https://docs.gitlab.com/omnibus/settings/smtp.html) because the GitLab Docker image doesn't
have an SMTP server installed. You may also be interested in
-[enabling HTTPS](https://docs.gitlab.com/omnibus/settings/ssl.html).
+[enabling HTTPS](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
After you make all the changes you want, you will need to restart the container to reconfigure GitLab:
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index d16ac3e2174..e492b5d75ce 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -118,7 +118,7 @@ here's how you configure GitLab to be aware of the change:
### Configuring HTTPS with the domain name
Although not needed, it's strongly recommended to secure GitLab with a
-[TLS certificate](https://docs.gitlab.com/omnibus/settings/ssl.html).
+[TLS certificate](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
### Configuring the email SMTP settings
diff --git a/doc/integration/index.md b/doc/integration/index.md
index 6be17640c6c..20036b1f5e5 100644
--- a/doc/integration/index.md
+++ b/doc/integration/index.md
@@ -87,7 +87,7 @@ As a workaround, you can do one of the following:
- [Adding trusted root certificates to the server](https://manuals.gfi.com/en/kerio/connect/content/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html)
- [How do you add a certificate authority (CA) to Ubuntu?](https://superuser.com/questions/437330/how-do-you-add-a-certificate-authority-ca-to-ubuntu)
- In Omnibus GitLab, add the certificate to the Omnibus trusted chain:
- 1. [Install the self-signed certificate](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+ 1. [Install the self-signed certificate](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates).
1. Concatenate the self-signed certificate with the GitLab trusted certificate.
The self-signed certificate might be overwritten during upgrades.
diff --git a/doc/integration/jira/dvcs/troubleshooting.md b/doc/integration/jira/dvcs/troubleshooting.md
index 04430fe5509..8eebbb84696 100644
--- a/doc/integration/jira/dvcs/troubleshooting.md
+++ b/doc/integration/jira/dvcs/troubleshooting.md
@@ -35,7 +35,7 @@ Error obtaining access token. Cannot access https://gitlab.example.com from Jira
- The [Jira integration](../index.md) requires
GitLab to connect to Jira. Any TLS issues that arise from a private certificate
authority or self-signed certificate are resolved
- [on the GitLab server](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates),
+ [on the GitLab server](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates),
as GitLab is the TLS client.
- The Jira development panel requires Jira to connect to GitLab, which
causes Jira to be the TLS client. If your GitLab server's certificate is not
diff --git a/doc/integration/security_partners/index.md b/doc/integration/security_partners/index.md
index 61519057c09..5453b3417ab 100644
--- a/doc/integration/security_partners/index.md
+++ b/doc/integration/security_partners/index.md
@@ -15,7 +15,7 @@ each security partner:
- [Anchore](https://docs.anchore.com/current/docs/configuration/integration/ci_cd/gitlab/)
- [Bridgecrew](https://docs.bridgecrew.io/docs/integrate-with-gitlab-self-managed)
- [Checkmarx](https://checkmarx.atlassian.net/wiki/spaces/SD/pages/1929937052/GitLab+Integration)
-- [Deepfactor](https://www.deepfactor.io/docs/gitlab/)
+- [Deepfactor](https://www.deepfactor.io/docs/integrate-deepfactor-scanner-in-your-ci-cd-pipelines/#gitlab)
- [GrammaTech](https://www.grammatech.com/codesonar-gitlab-integration)
- [Indeni](https://docs.cloudrail.app/#/integrations/gitlab)
- [JScrambler](https://docs.jscrambler.com/code-integrity/documentation/gitlab-ci-integration)
diff --git a/doc/topics/offline/quick_start_guide.md b/doc/topics/offline/quick_start_guide.md
index b2767f2e5eb..ce6706892bf 100644
--- a/doc/topics/offline/quick_start_guide.md
+++ b/doc/topics/offline/quick_start_guide.md
@@ -71,7 +71,7 @@ sudo EXTERNAL_URL="http://my-host.internal" dpkg -i <gitlab_package_name>.deb
## Enabling SSL
Follow these steps to enable SSL for your fresh instance. These steps reflect those for
-[manually configuring SSL in Omnibus's NGINX configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#configure-https-manually):
+[manually configuring SSL in Omnibus's NGINX configuration](https://docs.gitlab.com/omnibus/settings/ssl/index.html#configure-https-manually):
1. Make the following changes to `/etc/gitlab/gitlab.rb`:
diff --git a/doc/update/index.md b/doc/update/index.md
index c8fa091c657..657b5ecbea6 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -1631,6 +1631,7 @@ gitaly['configuration'] = {
},
# Storage could previously be configured through either gitaly['storage'] or 'git_data_dirs'. Migrate
# the relevant configuration according to the instructions below.
+ # For 'git_data_dirs', migrate only the 'path' to the gitaly['configuration'] and leave the rest of it untouched.
storage: [
{
# gitaly['storage'][<index>]['name']
diff --git a/doc/user/admin_area/settings/external_authorization.md b/doc/user/admin_area/settings/external_authorization.md
index 32a5b0a606f..139eb48ce23 100644
--- a/doc/user/admin_area/settings/external_authorization.md
+++ b/doc/user/admin_area/settings/external_authorization.md
@@ -39,7 +39,7 @@ the [Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/logs
When using TLS Authentication with a self signed certificate, the CA certificate
needs to be trusted by the OpenSSL installation. When using GitLab installed
using Omnibus, learn to install a custom CA in the
-[Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/ssl.html).
+[Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
Alternatively, learn where to install custom certificates by using
`openssl version -d`.
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index 1db62bce056..12b9a9ff173 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -27,7 +27,7 @@ There are several other benefits to enabling Service Ping:
- Analyze the users' activities over time of your GitLab installation.
- A [DevOps Score](../analytics/dev_ops_reports.md#devops-score) to give you an overview of your entire instance's adoption of concurrent DevOps from planning to monitoring.
-- More proactive support (assuming that our [Customer Success Managers (CSMs)](https://about.gitlab.com/job-families/sales/customer-success-management/) and support organization used the data to deliver more value).
+- More proactive support (assuming that our [Customer Success Managers (CSMs)](https://handbook.gitlab.com/job-families/sales/customer-success-management/) and support organization used the data to deliver more value).
- Insight and advice into how to get the most value out of your investment in GitLab.
- Reports that show how you compare against other similar organizations (anonymized), with specific advice and recommendations on how to improve your DevOps processes.
- Participation in our [Registration Features Program](#registration-features-program) to receive free paid features.
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 13f05a63181..90a7d5e72ae 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -598,7 +598,7 @@ Some analyzers can be customized with CI/CD variables.
| `SAST_GOSEC_CONFIG` | Gosec | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/328301)** in GitLab 14.0 - use custom rulesets instead. Path to configuration for Gosec (optional). |
| `PHPCS_SECURITY_AUDIT_PHP_EXTENSIONS` | phpcs-security-audit | Comma separated list of additional PHP Extensions. |
| `SAST_DISABLE_BABEL` | NodeJsScan | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64025)** in GitLab 13.5 |
-| `SAST_SEMGREP_METRICS` | Semgrep | Set to `"false"` to disable sending anonymized scan metrics to [r2c](https://r2c.dev/). Default: `true`. Introduced in GitLab 14.0 from the [confidential issue](../../project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab/-/issues/330565`. |
+| `SAST_SEMGREP_METRICS` | Semgrep | Set to `"false"` to disable sending anonymized scan metrics to [r2c](https://semgrep.dev). Default: `true`. Introduced in GitLab 14.0 from the [confidential issue](../../project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab/-/issues/330565`. |
| `SAST_SCANNER_ALLOWED_CLI_OPTS` | Semgrep | CLI options (arguments with value, or flags) that are passed to the underlying security scanner when running scan operation. Only a limited set of [options](#security-scanner-configuration) are accepted. Separate a CLI option and its value using either a blank space or equals (`=`) character. For example: `name1 value1` or `name1=value1`. Multiple options must be separated by blank spaces. For example: `name1 value1 name2 value2`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368565) in GitLab 15.3. |
#### Security scanner configuration
diff --git a/doc/user/group/saml_sso/example_saml_config.md b/doc/user/group/saml_sso/example_saml_config.md
index 0121df4fdb3..524a5d5a9bd 100644
--- a/doc/user/group/saml_sso/example_saml_config.md
+++ b/doc/user/group/saml_sso/example_saml_config.md
@@ -58,7 +58,7 @@ Attribute mapping:
NOTE:
Using the **Group ID** source attribute requires users to enter the group ID or object ID when configuring SAML group links. If available, use the **sAMAccountName** source attribute for the friendly group name instead.
-[Azure AD limits the number of groups that can be sent in a SAML response to 150](https://support.esri.com/en/technical-article/000022190). If a user is a member of more than 150 groups, Azure does not include that user's group claim in the SAML response.
+[Azure AD limits the number of groups that can be sent in a SAML response to 150](https://support.esri.com/en-us/knowledge-base/000022190'). If a user is a member of more than 150 groups, Azure does not include that user's group claim in the SAML response.
## Google Workspace
diff --git a/doc/user/project/clusters/add_eks_clusters.md b/doc/user/project/clusters/add_eks_clusters.md
index c4f4f220c9b..f4b806c3047 100644
--- a/doc/user/project/clusters/add_eks_clusters.md
+++ b/doc/user/project/clusters/add_eks_clusters.md
@@ -178,7 +178,7 @@ When you create a new cluster, you have the following settings:
| Key pair name | The [key pair](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html) that you can use to connect to your worker nodes. |
| VPC | The [VPC](https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html) to use for your EKS Cluster resources. |
| Subnets | The [subnets](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html) in your VPC where your worker nodes run. Two are required. |
-| Security group | The [security group](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html) to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets. |
+| Security group | The [security group](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-groups.html) to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets. |
| Instance type | The [instance type](https://aws.amazon.com/ec2/instance-types/) of your worker nodes. |
| Node count | The number of worker nodes. |
| GitLab-managed cluster | Check if you want GitLab to manage namespaces and service accounts for this cluster. |
diff --git a/doc/user/project/import/cvs.md b/doc/user/project/import/cvs.md
index 99221daf750..45a86da39ee 100644
--- a/doc/user/project/import/cvs.md
+++ b/doc/user/project/import/cvs.md
@@ -71,6 +71,5 @@ Here's a few links to get you started with the migration:
- [Migrate using the `cvs-fast-export` tool](https://gitlab.com/esr/cvs-fast-export)
- [Stack Overflow post on importing the CVS repository](https://stackoverflow.com/a/11490134/974710)
-- [Convert a CVS repository to Git](https://www.techrepublic.com/article/convert-cvs-repositories-to-git/)
- [Man page of the `git-cvsimport` tool](https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-cvsimport.html)
- [Migrate using `reposurgeon`](http://www.catb.org/~esr/reposurgeon/repository-editing.html#conversion)
diff --git a/doc/user/project/import/perforce.md b/doc/user/project/import/perforce.md
index a96297b1a38..3649b00b8d3 100644
--- a/doc/user/project/import/perforce.md
+++ b/doc/user/project/import/perforce.md
@@ -53,7 +53,7 @@ submit back from Git to Perforce.
Here's a few links to get you started:
- [`git-p4` manual page](https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-p4.html)
-- [`git-p4` example usage](https://git.wiki.kernel.org/index.php/Git-p4_Usage)
+- [`git-p4` example usage](https://archive.kernel.org/oldwiki/git.wiki.kernel.org/index.php/Git-p4_Usage.html)
- [Git book migration guide](https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git#_perforce_import)
`git p4` and `git filter-branch` are not very good at
diff --git a/doc/user/project/integrations/google_play.md b/doc/user/project/integrations/google_play.md
index e83602186f3..e13d6e3cad3 100644
--- a/doc/user/project/integrations/google_play.md
+++ b/doc/user/project/integrations/google_play.md
@@ -17,7 +17,7 @@ The Google Play integration works out of the box with [fastlane](https://fastlan
Prerequisites:
-- You must have a [Google Play Console](https://play.google.com/console/signup) developer account.
+- You must have a [Google Play Console](https://play.google.com/console/developers) developer account.
- You must [generate a new service account key for your project](https://developers.google.com/android-publisher/getting_started) from the Google Cloud console.
To enable the Google Play integration in GitLab:
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index 6806b724ac2..c71fe14133d 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -127,7 +127,7 @@ To view which of these problems is the cause of the issue:
```
If GitLab does not trust HTTPS connections to itself,
-[add your certificate to the GitLab trusted certificates](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+[add your certificate to the GitLab trusted certificates](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates).
If GitLab does not trust connections to Slack,
the GitLab OpenSSL trust store is incorrect. Typical causes are:
diff --git a/doc/user/project/repository/forking_workflow.md b/doc/user/project/repository/forking_workflow.md
index 423bdd178c6..a939e6104ce 100644
--- a/doc/user/project/repository/forking_workflow.md
+++ b/doc/user/project/repository/forking_workflow.md
@@ -159,7 +159,7 @@ to share objects with another repository:
## Related topics
- GitLab blog post: [Keep your fork up to date with its origin](https://about.gitlab.com/blog/2016/12/01/how-to-keep-your-fork-up-to-date-with-its-origin/)
-- GitLab community forum: [Refreshing a fork](https://forum.gitlab.com/t/refreshing-a-fork/)
+- GitLab community forum: [Refreshing a fork](https://forum.gitlab.com/t/refreshing-a-fork/32469)
## Troubleshooting
diff --git a/doc/user/project/repository/x509_signed_commits/index.md b/doc/user/project/repository/x509_signed_commits/index.md
index 4d0ffd6a70a..80538697100 100644
--- a/doc/user/project/repository/x509_signed_commits/index.md
+++ b/doc/user/project/repository/x509_signed_commits/index.md
@@ -32,7 +32,7 @@ For a commit or tag to be *verified* by GitLab:
from the certificate in the signature to a trusted certificate in the GitLab certificate store.
This chain may include intermediate certificates supplied in the signature. You may
need to add certificates, such as Certificate Authority root certificates,
- [to the GitLab certificate store](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+ [to the GitLab certificate store](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates).
- The signing time must be in the time range of the
[certificate validity](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.5),
which is usually up to three years.
@@ -342,7 +342,7 @@ step of the previous [main verification checks](#main-verification-checks).
```
1. If this fails, add the missing certificates required to establish trust
- [to the GitLab certificate store](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+ [to the GitLab certificate store](https://docs.gitlab.com/omnibus/settings/ssl/index.html#install-custom-public-certificates).
1. After adding more certificates, (if these troubleshooting steps then pass)
run the Rake task to [re-verify commits](#re-verify-commits).
diff --git a/doc/user/workspace/quick_start/index.md b/doc/user/workspace/quick_start/index.md
deleted file mode 100644
index 933dbf1766b..00000000000
--- a/doc/user/workspace/quick_start/index.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-stage: Create
-group: IDE
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
-type: reference
----
-
-> Introduced in GitLab 15.11 [with a flag](../../../administration/feature_flags.md) named `remote_development_feature_flag`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `remote_development_feature_flag`.
-On GitLab.com, this feature is not available.
-The feature is not ready for production use.
-
-# Tutorial: Create and run your first GitLab Workspace **(ULTIMATE)**
-
-This tutorial shows you how to configure and run your first Remote Development Workspace in GitLab.
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
index 553f2a2d754..6b635cdf33b 100644
--- a/lib/gitlab/ci/config/external/file/base.rb
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -116,7 +116,9 @@ module Gitlab
strong_memoize_attr :content_result
def content_inputs
- params.to_h[:with]
+ # TODO: remove support for `with` syntax in 16.1, see https://gitlab.com/gitlab-org/gitlab/-/issues/408369
+ # In the interim prefer `inputs` over `with` while allow either syntax.
+ params.to_h.slice(:inputs, :with).each_value.first
end
strong_memoize_attr :content_inputs
diff --git a/lib/gitlab/spamcheck/client.rb b/lib/gitlab/spamcheck/client.rb
index 0b9f3baa4de..e2d7b41ed3e 100644
--- a/lib/gitlab/spamcheck/client.rb
+++ b/lib/gitlab/spamcheck/client.rb
@@ -3,19 +3,13 @@ require 'spamcheck'
module Gitlab
module Spamcheck
+ Error = Class.new(StandardError)
+
class Client
include ::Spam::SpamConstants
DEFAULT_TIMEOUT_SECS = 2
- VERDICT_MAPPING = {
- ::Spamcheck::SpamVerdict::Verdict::ALLOW => ALLOW,
- ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW => CONDITIONAL_ALLOW,
- ::Spamcheck::SpamVerdict::Verdict::DISALLOW => DISALLOW,
- ::Spamcheck::SpamVerdict::Verdict::BLOCK => BLOCK_USER,
- ::Spamcheck::SpamVerdict::Verdict::NOOP => NOOP
- }.freeze
-
ACTION_MAPPING = {
create: ::Spamcheck::Action::CREATE,
update: ::Spamcheck::Action::UPDATE
@@ -40,8 +34,9 @@ module Gitlab
pb, grpc_method = build_protobuf(**protobuf_args)
response = grpc_method.call(pb, metadata: metadata)
- verdict = convert_verdict_to_gitlab_constant(response.verdict)
- [verdict, response.extra_attributes.to_h, response.error]
+ raise Error, response.error unless response.error.blank?
+
+ Result.new(response)
end
private
@@ -57,10 +52,6 @@ module Gitlab
end
end
- def convert_verdict_to_gitlab_constant(verdict)
- VERDICT_MAPPING.fetch(::Spamcheck::SpamVerdict::Verdict.resolve(verdict), verdict)
- end
-
def build_protobuf(spammable:, user:, context:, extra_features:)
protobuf_class, grpc_method = get_spammable_mappings(spammable)
pb = protobuf_class.new(**extra_features)
diff --git a/lib/gitlab/spamcheck/result.rb b/lib/gitlab/spamcheck/result.rb
new file mode 100644
index 00000000000..7cf6b020bf5
--- /dev/null
+++ b/lib/gitlab/spamcheck/result.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Spamcheck
+ class Result
+ include ::Spam::SpamConstants
+ attr_reader :response
+
+ VERDICT_MAPPING = {
+ ::Spamcheck::SpamVerdict::Verdict::ALLOW => ALLOW,
+ ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW => CONDITIONAL_ALLOW,
+ ::Spamcheck::SpamVerdict::Verdict::DISALLOW => DISALLOW,
+ ::Spamcheck::SpamVerdict::Verdict::BLOCK => BLOCK_USER,
+ ::Spamcheck::SpamVerdict::Verdict::NOOP => NOOP
+ }.freeze
+
+ def initialize(response)
+ @response = response
+ end
+
+ def score
+ response.score
+ end
+
+ def verdict
+ VERDICT_MAPPING.fetch(::Spamcheck::SpamVerdict::Verdict.resolve(response.verdict), ALLOW)
+ end
+
+ def evaluated?
+ response.evaluated
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 187df1ef8da..11b298c8264 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -38126,19 +38126,19 @@ msgstr ""
msgid "Runners|Create a group runner"
msgstr ""
-msgid "Runners|Create a group runner to generate a command that registers the runner with all its configurations. %{linkStart}Prefer to use a registration token to create a runner?%{linkEnd}"
+msgid "Runners|Create a group runner to generate a command that registers the runner with all its configurations."
msgstr ""
msgid "Runners|Create a project runner"
msgstr ""
-msgid "Runners|Create a project runner to generate a command that registers the runner with all its configurations. %{linkStart}Prefer to use a registration token to create a runner?%{linkEnd}"
+msgid "Runners|Create a project runner to generate a command that registers the runner with all its configurations."
msgstr ""
msgid "Runners|Create an instance runner"
msgstr ""
-msgid "Runners|Create an instance runner to generate a command that registers the runner with all its configurations. %{linkStart}Prefer to use a registration token to create a runner?%{linkEnd}"
+msgid "Runners|Create an instance runner to generate a command that registers the runner with all its configurations."
msgstr ""
msgid "Runners|Created %{timeAgo}"
@@ -38595,6 +38595,9 @@ msgstr ""
msgid "Runners|Stop the runner from accepting new jobs."
msgstr ""
+msgid "Runners|Support for registration tokens is deprecated"
+msgstr ""
+
msgid "Runners|Tags"
msgstr ""
@@ -38627,6 +38630,12 @@ msgstr ""
msgid "Runners|This may not be needed if you manage your runner as a %{linkStart}system or user service%{linkEnd}."
msgstr ""
+msgid "Runners|This registration process is not supported in GitLab Runner 15.9 or earlier and only available as an experimental feature in GitLab Runner 15.10 and 15.11. You should upgrade to %{linkStart}GitLab Runner 16.0%{linkEnd} or later to use a stable version of this registration process."
+msgstr ""
+
+msgid "Runners|This registration process is only supported in GitLab Runner 15.10 or later"
+msgstr ""
+
msgid "Runners|This runner has not run any jobs."
msgstr ""
diff --git a/package.json b/package.json
index 10891b7dc99..43b89812089 100644
--- a/package.json
+++ b/package.json
@@ -50,14 +50,14 @@
"@apollo/client": "^3.5.10",
"@babel/core": "^7.18.5",
"@babel/preset-env": "^7.18.2",
- "@cubejs-client/core": "^0.32.17",
- "@cubejs-client/vue": "^0.32.17",
+ "@cubejs-client/core": "^0.32.22",
+ "@cubejs-client/vue": "^0.32.22",
"@gitlab/at.js": "1.5.7",
"@gitlab/cluster-client": "^1.2.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.2.0",
"@gitlab/svgs": "3.40.0",
- "@gitlab/ui": "60.2.0",
+ "@gitlab/ui": "61.1.1",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230418150125",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
@@ -144,7 +144,7 @@
"gettext-parser": "^6.0.0",
"graphql": "^15.7.2",
"graphql-tag": "^2.11.0",
- "gridstack": "^7.1.2",
+ "gridstack": "^7.3.0",
"highlight.js": "^11.5.1",
"immer": "^9.0.15",
"ipaddr.js": "^1.9.1",
diff --git a/spec/controllers/confirmations_controller_spec.rb b/spec/controllers/confirmations_controller_spec.rb
index b1aa40d4d7b..fea43894f1c 100644
--- a/spec/controllers/confirmations_controller_spec.rb
+++ b/spec/controllers/confirmations_controller_spec.rb
@@ -205,10 +205,6 @@ RSpec.describe ConfirmationsController, feature_category: :system_access do
end
context "when `email_confirmation_setting` is not set to `soft`" do
- before do
- stub_feature_flags(soft_email_confirmation: false)
- end
-
it 'redirects to the users_almost_there path' do
perform_request
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index fee75496a1e..db0ae79c9c4 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -8,184 +8,186 @@ RSpec.describe 'Admin Appearance', feature_category: :shared do
flag_values = [true, false]
flag_values.each do |val|
- before do
- stub_feature_flags(restyle_login_page: val)
- end
-
- it 'create new appearance' do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
- visit admin_application_settings_appearances_path
-
- fill_in 'appearance_title', with: 'MyCompany'
- fill_in 'appearance_description', with: 'dev server'
- fill_in 'appearance_pwa_name', with: 'GitLab PWA'
- fill_in 'appearance_pwa_short_name', with: 'GitLab'
- fill_in 'appearance_pwa_description', with: 'GitLab as PWA'
- fill_in 'appearance_new_project_guidelines', with: 'Custom project guidelines'
- fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines'
- click_button 'Update appearance settings'
-
- expect(page).to have_current_path admin_application_settings_appearances_path, ignore_query: true
- expect(page).to have_content 'Appearance'
-
- expect(page).to have_field('appearance_title', with: 'MyCompany')
- expect(page).to have_field('appearance_description', with: 'dev server')
- expect(page).to have_field('appearance_pwa_name', with: 'GitLab PWA')
- expect(page).to have_field('appearance_pwa_short_name', with: 'GitLab')
- expect(page).to have_field('appearance_pwa_description', with: 'GitLab as PWA')
- expect(page).to have_field('appearance_new_project_guidelines', with: 'Custom project guidelines')
- expect(page).to have_field('appearance_profile_image_guidelines', with: 'Custom profile image guidelines')
- expect(page).to have_content 'Last edit'
- end
+ context "with #{val}" do
+ before do
+ stub_feature_flags(restyle_login_page: val)
+ end
- it 'preview sign-in page appearance' do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
+ it 'create new appearance' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit admin_application_settings_appearances_path
- visit admin_application_settings_appearances_path
- click_link "Sign-in page"
+ fill_in 'appearance_title', with: 'MyCompany'
+ fill_in 'appearance_description', with: 'dev server'
+ fill_in 'appearance_pwa_name', with: 'GitLab PWA'
+ fill_in 'appearance_pwa_short_name', with: 'GitLab'
+ fill_in 'appearance_pwa_description', with: 'GitLab as PWA'
+ fill_in 'appearance_new_project_guidelines', with: 'Custom project guidelines'
+ fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines'
+ click_button 'Update appearance settings'
- expect(find('#login')).to be_disabled
- expect(find('#password')).to be_disabled
- expect(find('button')).to be_disabled
+ expect(page).to have_current_path admin_application_settings_appearances_path, ignore_query: true
+ expect(page).to have_content 'Appearance'
+
+ expect(page).to have_field('appearance_title', with: 'MyCompany')
+ expect(page).to have_field('appearance_description', with: 'dev server')
+ expect(page).to have_field('appearance_pwa_name', with: 'GitLab PWA')
+ expect(page).to have_field('appearance_pwa_short_name', with: 'GitLab')
+ expect(page).to have_field('appearance_pwa_description', with: 'GitLab as PWA')
+ expect(page).to have_field('appearance_new_project_guidelines', with: 'Custom project guidelines')
+ expect(page).to have_field('appearance_profile_image_guidelines', with: 'Custom profile image guidelines')
+ expect(page).to have_content 'Last edit'
+ end
- expect_custom_sign_in_appearance(appearance)
- end
+ it 'preview sign-in page appearance' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
- it 'preview new project page appearance', :js do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
+ visit admin_application_settings_appearances_path
+ click_link "Sign-in page"
- visit admin_application_settings_appearances_path
- click_link "New project page"
+ expect(find('#login')).to be_disabled
+ expect(find('#password')).to be_disabled
+ expect(find('button')).to be_disabled
- expect_custom_new_project_appearance(appearance)
- end
+ expect_custom_sign_in_appearance(appearance)
+ end
- context 'Custom system header and footer' do
- before do
+ it 'preview new project page appearance', :js do
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
- end
- context 'when system header and footer messages are empty' do
- it 'shows custom system header and footer fields' do
- visit admin_application_settings_appearances_path
+ visit admin_application_settings_appearances_path
+ click_link "New project page"
- expect(page).to have_field('appearance_header_message', with: '')
- expect(page).to have_field('appearance_footer_message', with: '')
- expect(page).to have_field('appearance_message_background_color')
- expect(page).to have_field('appearance_message_font_color')
- end
+ expect_custom_new_project_appearance(appearance)
end
- context 'when system header and footer messages are not empty' do
+ context 'Custom system header and footer' do
before do
- appearance.update!(header_message: 'Foo', footer_message: 'Bar')
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
end
- it 'shows custom system header and footer fields' do
- visit admin_application_settings_appearances_path
+ context 'when system header and footer messages are empty' do
+ it 'shows custom system header and footer fields' do
+ visit admin_application_settings_appearances_path
- expect(page).to have_field('appearance_header_message', with: appearance.header_message)
- expect(page).to have_field('appearance_footer_message', with: appearance.footer_message)
- expect(page).to have_field('appearance_message_background_color')
- expect(page).to have_field('appearance_message_font_color')
+ expect(page).to have_field('appearance_header_message', with: '')
+ expect(page).to have_field('appearance_footer_message', with: '')
+ expect(page).to have_field('appearance_message_background_color')
+ expect(page).to have_field('appearance_message_font_color')
+ end
end
- end
- end
- it 'custom sign-in page' do
- visit new_user_session_path
+ context 'when system header and footer messages are not empty' do
+ before do
+ appearance.update!(header_message: 'Foo', footer_message: 'Bar')
+ end
- expect_custom_sign_in_appearance(appearance)
- end
+ it 'shows custom system header and footer fields' do
+ visit admin_application_settings_appearances_path
- it 'custom new project page', :js do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
- visit new_project_path
- click_link 'Create blank project'
+ expect(page).to have_field('appearance_header_message', with: appearance.header_message)
+ expect(page).to have_field('appearance_footer_message', with: appearance.footer_message)
+ expect(page).to have_field('appearance_message_background_color')
+ expect(page).to have_field('appearance_message_font_color')
+ end
+ end
+ end
- expect_custom_new_project_appearance(appearance)
- end
+ it 'custom sign-in page' do
+ visit new_user_session_path
- context 'Profile page with custom profile image guidelines' do
- before do
+ expect_custom_sign_in_appearance(appearance)
+ end
+
+ it 'custom new project page', :js do
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
- visit admin_application_settings_appearances_path
- fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines, please :smile:!'
- click_button 'Update appearance settings'
+ visit new_project_path
+ click_link 'Create blank project'
+
+ expect_custom_new_project_appearance(appearance)
end
- it 'renders guidelines when set' do
- sign_in create(:user)
- visit profile_path
+ context 'Profile page with custom profile image guidelines' do
+ before do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit admin_application_settings_appearances_path
+ fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines, please :smile:!'
+ click_button 'Update appearance settings'
+ end
+
+ it 'renders guidelines when set' do
+ sign_in create(:user)
+ visit profile_path
- expect(page).to have_content 'Custom profile image guidelines, please 😄!'
+ expect(page).to have_content 'Custom profile image guidelines, please 😄!'
+ end
end
- end
- it 'appearance logo' do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
- visit admin_application_settings_appearances_path
+ it 'appearance logo' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit admin_application_settings_appearances_path
- attach_file(:appearance_logo, logo_fixture)
- click_button 'Update appearance settings'
- expect(page).to have_css(logo_selector)
+ attach_file(:appearance_logo, logo_fixture)
+ click_button 'Update appearance settings'
+ expect(page).to have_css(logo_selector)
- click_link 'Remove logo'
- expect(page).not_to have_css(logo_selector)
- end
+ click_link 'Remove logo'
+ expect(page).not_to have_css(logo_selector)
+ end
- it 'appearance pwa icon' do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
- visit admin_application_settings_appearances_path
+ it 'appearance pwa icon' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit admin_application_settings_appearances_path
- attach_file(:appearance_pwa_icon, logo_fixture)
- click_button 'Update appearance settings'
- expect(page).to have_css(pwa_icon_selector)
+ attach_file(:appearance_pwa_icon, logo_fixture)
+ click_button 'Update appearance settings'
+ expect(page).to have_css(pwa_icon_selector)
- click_link 'Remove icon'
- expect(page).not_to have_css(pwa_icon_selector)
- end
+ click_link 'Remove icon'
+ expect(page).not_to have_css(pwa_icon_selector)
+ end
- it 'header logos' do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
- visit admin_application_settings_appearances_path
+ it 'header logos' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit admin_application_settings_appearances_path
- attach_file(:appearance_header_logo, logo_fixture)
- click_button 'Update appearance settings'
- expect(page).to have_css(header_logo_selector)
+ attach_file(:appearance_header_logo, logo_fixture)
+ click_button 'Update appearance settings'
+ expect(page).to have_css(header_logo_selector)
- click_link 'Remove header logo'
- expect(page).not_to have_css(header_logo_selector)
- end
+ click_link 'Remove header logo'
+ expect(page).not_to have_css(header_logo_selector)
+ end
- it 'Favicon' do
- sign_in(admin)
- gitlab_enable_admin_mode_sign_in(admin)
- visit admin_application_settings_appearances_path
+ it 'Favicon' do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ visit admin_application_settings_appearances_path
- attach_file(:appearance_favicon, logo_fixture)
- click_button 'Update appearance settings'
+ attach_file(:appearance_favicon, logo_fixture)
+ click_button 'Update appearance settings'
- expect(page).to have_css('.appearance-light-logo-preview')
+ expect(page).to have_css('.appearance-light-logo-preview')
- click_link 'Remove favicon'
+ click_link 'Remove favicon'
- expect(page).not_to have_css('.appearance-light-logo-preview')
+ expect(page).not_to have_css('.appearance-light-logo-preview')
- # allowed file types
- attach_file(:appearance_favicon, Rails.root.join('spec', 'fixtures', 'sanitized.svg'))
- click_button 'Update appearance settings'
+ # allowed file types
+ attach_file(:appearance_favicon, Rails.root.join('spec', 'fixtures', 'sanitized.svg'))
+ click_button 'Update appearance settings'
- expect(page).to have_content 'Favicon You are not allowed to upload "svg" files, allowed types: png, ico'
+ expect(page).to have_content 'Favicon You are not allowed to upload "svg" files, allowed types: png, ico'
+ end
end
end
diff --git a/spec/features/admin/admin_mode/login_spec.rb b/spec/features/admin/admin_mode/login_spec.rb
index 853e4763872..247584701d3 100644
--- a/spec/features/admin/admin_mode/login_spec.rb
+++ b/spec/features/admin/admin_mode/login_spec.rb
@@ -15,249 +15,251 @@ RSpec.describe 'Admin Mode Login', feature_category: :system_access do
flag_values = [true, false]
flag_values.each do |val|
- before do
- stub_feature_flags(restyle_login_page: val)
- end
- context 'with valid username/password' do
- let(:user) { create(:admin, :two_factor) }
+ context "with #{val}" do
+ before do
+ stub_feature_flags(restyle_login_page: val)
+ end
+ context 'with valid username/password' do
+ let(:user) { create(:admin, :two_factor) }
- context 'using one-time code' do
- it 'blocks login if we reuse the same code immediately' do
- gitlab_sign_in(user, remember: true)
+ context 'using one-time code' do
+ it 'blocks login if we reuse the same code immediately' do
+ gitlab_sign_in(user, remember: true)
- expect(page).to have_content(_('Enter verification code'))
+ expect(page).to have_content(_('Enter verification code'))
- repeated_otp = user.current_otp
- enter_code(repeated_otp)
- gitlab_enable_admin_mode_sign_in(user)
+ repeated_otp = user.current_otp
+ enter_code(repeated_otp)
+ gitlab_enable_admin_mode_sign_in(user)
- expect(page).to have_content(_('Enter verification code'))
+ expect(page).to have_content(_('Enter verification code'))
- enter_code(repeated_otp)
+ enter_code(repeated_otp)
- expect(page).to have_current_path admin_session_path, ignore_query: true
- expect(page).to have_content('Invalid two-factor code')
- end
+ expect(page).to have_current_path admin_session_path, ignore_query: true
+ expect(page).to have_content('Invalid two-factor code')
+ end
- context 'not re-using codes' do
- before do
- gitlab_sign_in(user, remember: true)
+ context 'not re-using codes' do
+ before do
+ gitlab_sign_in(user, remember: true)
- expect(page).to have_content('Enter verification code')
+ expect(page).to have_content('Enter verification code')
- enter_code(user.current_otp)
- gitlab_enable_admin_mode_sign_in(user)
+ enter_code(user.current_otp)
+ gitlab_enable_admin_mode_sign_in(user)
- expect(page).to have_content(_('Enter verification code'))
- end
+ expect(page).to have_content(_('Enter verification code'))
+ end
- it 'allows login with valid code' do
- # Cannot reuse the TOTP
- travel_to(30.seconds.from_now) do
- enter_code(user.current_otp)
+ it 'allows login with valid code' do
+ # Cannot reuse the TOTP
+ travel_to(30.seconds.from_now) do
+ enter_code(user.current_otp)
- expect(page).to have_current_path admin_root_path, ignore_query: true
- expect(page).to have_content('Admin mode enabled')
+ expect(page).to have_current_path admin_root_path, ignore_query: true
+ expect(page).to have_content('Admin mode enabled')
+ end
end
- end
- it 'blocks login with invalid code' do
- # Cannot reuse the TOTP
- travel_to(30.seconds.from_now) do
- enter_code('foo')
+ it 'blocks login with invalid code' do
+ # Cannot reuse the TOTP
+ travel_to(30.seconds.from_now) do
+ enter_code('foo')
- expect(page).to have_content('Invalid two-factor code')
+ expect(page).to have_content('Invalid two-factor code')
+ end
end
- end
- it 'allows login with invalid code, then valid code' do
- # Cannot reuse the TOTP
- travel_to(30.seconds.from_now) do
- enter_code('foo')
+ it 'allows login with invalid code, then valid code' do
+ # Cannot reuse the TOTP
+ travel_to(30.seconds.from_now) do
+ enter_code('foo')
- expect(page).to have_content('Invalid two-factor code')
+ expect(page).to have_content('Invalid two-factor code')
- enter_code(user.current_otp)
+ enter_code(user.current_otp)
- expect(page).to have_current_path admin_root_path, ignore_query: true
- expect(page).to have_content('Admin mode enabled')
+ expect(page).to have_current_path admin_root_path, ignore_query: true
+ expect(page).to have_content('Admin mode enabled')
+ end
end
- end
- context 'using backup code' do
- let(:codes) { user.generate_otp_backup_codes! }
+ context 'using backup code' do
+ let(:codes) { user.generate_otp_backup_codes! }
- before do
- expect(codes.size).to eq 10
+ before do
+ expect(codes.size).to eq 10
- # Ensure the generated codes get saved
- user.save!
- end
+ # Ensure the generated codes get saved
+ user.save!
+ end
- context 'with valid code' do
- it 'allows login' do
- enter_code(codes.sample)
+ context 'with valid code' do
+ it 'allows login' do
+ enter_code(codes.sample)
- expect(page).to have_current_path admin_root_path, ignore_query: true
- expect(page).to have_content('Admin mode enabled')
- end
+ expect(page).to have_current_path admin_root_path, ignore_query: true
+ expect(page).to have_content('Admin mode enabled')
+ end
- it 'invalidates the used code' do
- expect { enter_code(codes.sample) }
- .to change { user.reload.otp_backup_codes.size }.by(-1)
+ it 'invalidates the used code' do
+ expect { enter_code(codes.sample) }
+ .to change { user.reload.otp_backup_codes.size }.by(-1)
+ end
end
- end
- context 'with invalid code' do
- it 'blocks login' do
- code = codes.sample
- expect(user.invalidate_otp_backup_code!(code)).to eq true
+ context 'with invalid code' do
+ it 'blocks login' do
+ code = codes.sample
+ expect(user.invalidate_otp_backup_code!(code)).to eq true
- user.save!
- expect(user.reload.otp_backup_codes.size).to eq 9
+ user.save!
+ expect(user.reload.otp_backup_codes.size).to eq 9
- enter_code(code)
+ enter_code(code)
- expect(page).to have_content('Invalid two-factor code.')
+ expect(page).to have_content('Invalid two-factor code.')
+ end
end
end
end
end
- end
- context 'when logging in via omniauth' do
- let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: 'my-uid', provider: 'saml', password_automatically_set: false) }
- let(:mock_saml_response) do
- File.read('spec/fixtures/authentication/saml_response.xml')
- end
-
- before do
- stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config_with_upstream_two_factor_authn_contexts])
- end
-
- context 'when authn_context is worth two factors' do
+ context 'when logging in via omniauth' do
+ let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: 'my-uid', provider: 'saml', password_automatically_set: false) }
let(:mock_saml_response) do
File.read('spec/fixtures/authentication/saml_response.xml')
- .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
- 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS')
end
- it 'signs user in without prompting for second factor' do
- sign_in_using_saml!
+ before do
+ stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config_with_upstream_two_factor_authn_contexts])
+ end
+
+ context 'when authn_context is worth two factors' do
+ let(:mock_saml_response) do
+ File.read('spec/fixtures/authentication/saml_response.xml')
+ .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS')
+ end
+
+ it 'signs user in without prompting for second factor' do
+ sign_in_using_saml!
- expect(page).not_to have_content(_('Enter verification code'))
+ expect(page).not_to have_content(_('Enter verification code'))
- enable_admin_mode_using_saml!
+ enable_admin_mode_using_saml!
- expect(page).not_to have_content(_('Enter verification code'))
- expect(page).to have_current_path admin_root_path, ignore_query: true
- expect(page).to have_content('Admin mode enabled')
+ expect(page).not_to have_content(_('Enter verification code'))
+ expect(page).to have_current_path admin_root_path, ignore_query: true
+ expect(page).to have_content('Admin mode enabled')
+ end
end
- end
- context 'when two factor authentication is required' do
- it 'shows 2FA prompt after omniauth login' do
- sign_in_using_saml!
+ context 'when two factor authentication is required' do
+ it 'shows 2FA prompt after omniauth login' do
+ sign_in_using_saml!
- expect(page).to have_content(_('Enter verification code'))
- enter_code(user.current_otp)
+ expect(page).to have_content(_('Enter verification code'))
+ enter_code(user.current_otp)
- enable_admin_mode_using_saml!
+ enable_admin_mode_using_saml!
- expect(page).to have_content(_('Enter verification code'))
+ expect(page).to have_content(_('Enter verification code'))
- # Cannot reuse the TOTP
- travel_to(30.seconds.from_now) do
- enter_code(user.current_otp)
+ # Cannot reuse the TOTP
+ travel_to(30.seconds.from_now) do
+ enter_code(user.current_otp)
- expect(page).to have_current_path admin_root_path, ignore_query: true
- expect(page).to have_content('Admin mode enabled')
+ expect(page).to have_current_path admin_root_path, ignore_query: true
+ expect(page).to have_content('Admin mode enabled')
+ end
end
end
- end
- def sign_in_using_saml!
- gitlab_sign_in_via('saml', user, 'my-uid', mock_saml_response)
- end
+ def sign_in_using_saml!
+ gitlab_sign_in_via('saml', user, 'my-uid', mock_saml_response)
+ end
- def enable_admin_mode_using_saml!
- gitlab_enable_admin_mode_sign_in_via('saml', user, 'my-uid', mock_saml_response)
+ def enable_admin_mode_using_saml!
+ gitlab_enable_admin_mode_sign_in_via('saml', user, 'my-uid', mock_saml_response)
+ end
end
- end
- context 'when logging in via ldap' do
- let(:uid) { 'my-uid' }
- let(:provider_label) { 'Main LDAP' }
- let(:provider_name) { 'main' }
- let(:provider) { "ldap#{provider_name}" }
- let(:ldap_server_config) do
- {
- 'label' => provider_label,
- 'provider_name' => provider,
- 'attributes' => {},
- 'encryption' => 'plain',
- 'uid' => 'uid',
- 'base' => 'dc=example,dc=com'
- }
- end
+ context 'when logging in via ldap' do
+ let(:uid) { 'my-uid' }
+ let(:provider_label) { 'Main LDAP' }
+ let(:provider_name) { 'main' }
+ let(:provider) { "ldap#{provider_name}" }
+ let(:ldap_server_config) do
+ {
+ 'label' => provider_label,
+ 'provider_name' => provider,
+ 'attributes' => {},
+ 'encryption' => 'plain',
+ 'uid' => 'uid',
+ 'base' => 'dc=example,dc=com'
+ }
+ end
- let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: uid, provider: provider) }
+ let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: uid, provider: provider) }
- before do
- setup_ldap(provider, user, uid, ldap_server_config)
- end
+ before do
+ setup_ldap(provider, user, uid, ldap_server_config)
+ end
- context 'when two factor authentication is required' do
- it 'shows 2FA prompt after ldap login' do
- sign_in_using_ldap!(user, provider_label)
- expect(page).to have_content(_('Enter verification code'))
+ context 'when two factor authentication is required' do
+ it 'shows 2FA prompt after ldap login' do
+ sign_in_using_ldap!(user, provider_label)
+ expect(page).to have_content(_('Enter verification code'))
- enter_code(user.current_otp)
- enable_admin_mode_using_ldap!(user)
+ enter_code(user.current_otp)
+ enable_admin_mode_using_ldap!(user)
- expect(page).to have_content(_('Enter verification code'))
+ expect(page).to have_content(_('Enter verification code'))
- # Cannot reuse the TOTP
- travel_to(30.seconds.from_now) do
- enter_code(user.current_otp)
+ # Cannot reuse the TOTP
+ travel_to(30.seconds.from_now) do
+ enter_code(user.current_otp)
- expect(page).to have_current_path admin_root_path, ignore_query: true
- expect(page).to have_content('Admin mode enabled')
+ expect(page).to have_current_path admin_root_path, ignore_query: true
+ expect(page).to have_content('Admin mode enabled')
+ end
end
end
- end
- def setup_ldap(provider, user, uid, ldap_server_config)
- stub_ldap_setting(enabled: true)
+ def setup_ldap(provider, user, uid, ldap_server_config)
+ stub_ldap_setting(enabled: true)
- allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config])
- allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym])
+ allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config])
+ allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym])
- Ldap::OmniauthCallbacksController.define_providers!
- Rails.application.reload_routes!
+ Ldap::OmniauthCallbacksController.define_providers!
+ Rails.application.reload_routes!
- mock_auth_hash(provider, uid, user.email)
- allow(Gitlab::Auth::Ldap::Access).to receive(:allowed?).with(user).and_return(true)
+ mock_auth_hash(provider, uid, user.email)
+ allow(Gitlab::Auth::Ldap::Access).to receive(:allowed?).with(user).and_return(true)
- allow_any_instance_of(ActionDispatch::Routing::RoutesProxy)
- .to receive(:"user_#{provider}_omniauth_callback_path")
- .and_return("/users/auth/#{provider}/callback")
- end
+ allow_any_instance_of(ActionDispatch::Routing::RoutesProxy)
+ .to receive(:"user_#{provider}_omniauth_callback_path")
+ .and_return("/users/auth/#{provider}/callback")
+ end
- def sign_in_using_ldap!(user, provider_label)
- visit new_user_session_path
- click_link provider_label
- fill_in 'username', with: user.username
- fill_in 'password', with: user.password
- click_button 'Sign in'
- end
+ def sign_in_using_ldap!(user, provider_label)
+ visit new_user_session_path
+ click_link provider_label
+ fill_in 'username', with: user.username
+ fill_in 'password', with: user.password
+ click_button 'Sign in'
+ end
- def enable_admin_mode_using_ldap!(user)
- visit new_admin_session_path
- click_link provider_label
- fill_in 'username', with: user.username
- fill_in 'password', with: user.password
- click_button 'Enter Admin Mode'
+ def enable_admin_mode_using_ldap!(user)
+ visit new_admin_session_path
+ click_link provider_label
+ fill_in 'username', with: user.username
+ fill_in 'password', with: user.password
+ click_button 'Enter Admin Mode'
+ end
end
end
end
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
index 403fd49fc65..18d62cb585f 100644
--- a/spec/features/admin/users/user_spec.rb
+++ b/spec/features/admin/users/user_spec.rb
@@ -273,8 +273,11 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do
end
context 'when viewing the confirm email warning', :js do
- let_it_be(:another_user) { create(:user, :unconfirmed) }
+ before do
+ stub_application_setting_enum('email_confirmation_setting', 'soft')
+ end
+ let_it_be(:another_user) { create(:user, :unconfirmed) }
let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') }
let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } }
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 615b26169b9..3b1716230cd 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do
sign_out(:user)
end
- it "redirects to signin then back to new issue after signin", :js do
+ it "redirects to signin then back to new issue after signin", :js, quarantine: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/1486' do
create(:issue, project: project)
visit project_issues_path(project)
diff --git a/spec/features/nav/pinned_nav_items_spec.rb b/spec/features/nav/pinned_nav_items_spec.rb
index fa8224848f9..b17af444cb8 100644
--- a/spec/features/nav/pinned_nav_items_spec.rb
+++ b/spec/features/nav/pinned_nav_items_spec.rb
@@ -173,7 +173,11 @@ RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigatio
def drag_item(item, to:)
item.hover
drag_handle = item.find('[data-testid="grip-icon"]')
- drag_handle.drag_to(to)
+
+ # Reduce delay to make it less likely for draggables to
+ # change position during drag operation, which reduces
+ # flakiness.
+ drag_handle.drag_to(to, delay: 0.01)
wait_for_requests
end
end
diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb
index f78a356b13d..e4a944eb837 100644
--- a/spec/finders/packages/group_packages_finder_spec.rb
+++ b/spec/finders/packages/group_packages_finder_spec.rb
@@ -203,7 +203,9 @@ RSpec.describe Packages::GroupPackagesFinder do
end
context 'group has package of all types' do
- package_types.each { |pt| let_it_be("package_#{pt}") { create("#{pt}_package", project: project) } }
+ package_types.each do |pt| # rubocop:disable RSpec/UselessDynamicDefinition
+ let_it_be("package_#{pt}") { create("#{pt}_package", project: project) }
+ end
package_types.each do |package_type|
it_behaves_like 'with package type', package_type
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 13263698cfe..c68ae443231 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -161,7 +161,7 @@ RSpec.describe ProjectsFinder do
end
end
- context 'when updated_after equals updated_before' do
+ context 'when updated_after equals updated_before', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408387' do
let(:params) { { updated_after: internal_project.updated_at, updated_before: internal_project.updated_at } }
it 'allows an exact match' do
diff --git a/spec/frontend/__mocks__/mousetrap/index.js b/spec/frontend/__mocks__/mousetrap/index.js
deleted file mode 100644
index 63c92fa9a09..00000000000
--- a/spec/frontend/__mocks__/mousetrap/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/* global Mousetrap */
-// `mousetrap` uses amd which webpack understands but Jest does not
-// Thankfully it also writes to a global export so we can es6-ify it
-import 'mousetrap';
-
-export default Mousetrap;
diff --git a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
index ba8b9dd1345..db87e8f09aa 100644
--- a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
+++ b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
@@ -36,10 +36,9 @@ describe('MessageForm', () => {
const findSubmitButton = () => wrapper.findComponent('[data-testid=submit-button]');
const findForm = () => wrapper.findComponent(GlForm);
- function createComponent({ broadcastMessage = {}, glFeatures = {} }) {
+ function createComponent({ broadcastMessage = {} } = {}) {
wrapper = mount(MessageForm, {
provide: {
- glFeatures,
targetAccessLevelOptions: MOCK_TARGET_ACCESS_LEVELS,
messagesPath,
previewPath: '_preview_path_',
@@ -100,15 +99,10 @@ describe('MessageForm', () => {
});
describe('target roles checkboxes', () => {
- it('renders when roleTargetedBroadcastMessages feature is enabled', () => {
- createComponent({ glFeatures: { roleTargetedBroadcastMessages: true } });
+ it('renders target roles', () => {
+ createComponent();
expect(findTargetRoles().exists()).toBe(true);
});
-
- it('does not render when roleTargetedBroadcastMessages feature is disabled', () => {
- createComponent({ glFeatures: { roleTargetedBroadcastMessages: false } });
- expect(findTargetRoles().exists()).toBe(false);
- });
});
describe('form submit button', () => {
diff --git a/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js b/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js
index 432bfefeb18..6d536b2d0e4 100644
--- a/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js
+++ b/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js
@@ -9,11 +9,8 @@ describe('MessagesTable', () => {
const findTargetRoles = () => wrapper.find('[data-testid="target-roles-th"]');
const findDeleteButton = (id) => wrapper.find(`[data-testid="delete-message-${id}"]`);
- function createComponent(props = {}, glFeatures = {}) {
+ function createComponent(props = {}) {
wrapper = mount(MessagesTable, {
- provide: {
- glFeatures,
- },
propsData: {
messages: MOCK_MESSAGES,
...props,
@@ -27,14 +24,10 @@ describe('MessagesTable', () => {
expect(findRows()).toHaveLength(MOCK_MESSAGES.length);
});
- it('renders the "Target Roles" column when roleTargetedBroadcastMessages is enabled', () => {
- createComponent({}, { roleTargetedBroadcastMessages: true });
- expect(findTargetRoles().exists()).toBe(true);
- });
-
- it('does not render the "Target Roles" column when roleTargetedBroadcastMessages is disabled', () => {
+ it('renders the "Target Roles" column', () => {
createComponent();
- expect(findTargetRoles().exists()).toBe(false);
+
+ expect(findTargetRoles().exists()).toBe(true);
});
it('emits a delete-message event when a delete button is clicked', () => {
diff --git a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
index 65336edd0d8..a454066b457 100644
--- a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
+++ b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
@@ -2,12 +2,10 @@ import { GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import AdminNewRunnerApp from '~/ci/runner/admin_new_runner/admin_new_runner_app.vue';
import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage';
-import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
import {
PARAM_KEY_PLATFORM,
@@ -17,7 +15,7 @@ import {
} from '~/ci/runner/constants';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import { redirectTo } from '~/lib/utils/url_utility';
-import { runnerCreateResult, mockRegistrationToken } from '../mock_data';
+import { runnerCreateResult } from '../mock_data';
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
jest.mock('~/alert');
@@ -31,19 +29,11 @@ const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner;
describe('AdminNewRunnerApp', () => {
let wrapper;
- const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link');
- const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal);
const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup);
const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm);
const createComponent = () => {
wrapper = shallowMountExtended(AdminNewRunnerApp, {
- propsData: {
- legacyRegistrationToken: mockRegistrationToken,
- },
- directives: {
- GlModal: createMockDirective('gl-modal'),
- },
stubs: {
GlSprintf,
},
@@ -54,20 +44,6 @@ describe('AdminNewRunnerApp', () => {
createComponent();
});
- describe('Shows legacy modal', () => {
- it('passes legacy registration to modal', () => {
- expect(findRunnerInstructionsModal().props('registrationToken')).toEqual(
- mockRegistrationToken,
- );
- });
-
- it('opens a modal with the legacy instructions', () => {
- const modalId = getBinding(findLegacyInstructionsLink().element, 'gl-modal').value;
-
- expect(findRunnerInstructionsModal().props('modalId')).toBe(modalId);
- });
- });
-
describe('Platform', () => {
it('shows the platforms radio group', () => {
expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM);
diff --git a/spec/frontend/ci/runner/components/registration/registration_compatibility_alert_spec.js b/spec/frontend/ci/runner/components/registration/registration_compatibility_alert_spec.js
new file mode 100644
index 00000000000..ba8a1ac6744
--- /dev/null
+++ b/spec/frontend/ci/runner/components/registration/registration_compatibility_alert_spec.js
@@ -0,0 +1,32 @@
+import { GlAlert, GlLink } from '@gitlab/ui';
+import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue';
+import { CHANGELOG_URL } from '~/ci/runner/constants';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+
+describe('RegistrationCompatibilityAlert', () => {
+ let wrapper;
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findLink = () => wrapper.findComponent(GlLink);
+
+ const createComponent = (mountFn = shallowMountExtended) => {
+ wrapper = mountFn(RegistrationCompatibilityAlert);
+ };
+
+ it('alert has warning appearance', () => {
+ createComponent();
+
+ expect(findAlert().props()).toMatchObject({
+ dismissible: false,
+ variant: 'warning',
+ title: expect.any(String),
+ });
+ });
+
+ it('shows alert content and link', () => {
+ createComponent(mountExtended);
+
+ expect(findAlert().text()).not.toBe('');
+ expect(findLink().attributes('href')).toBe(CHANGELOG_URL);
+ });
+});
diff --git a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js
index d23723807b1..9df7a974af3 100644
--- a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js
+++ b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js
@@ -1,4 +1,4 @@
-import { GlModal, GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui';
+import { GlModal, GlDropdown, GlDropdownItem, GlDropdownForm, GlIcon } from '@gitlab/ui';
import { createWrapper } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
@@ -29,7 +29,7 @@ describe('RegistrationDropdown', () => {
let wrapper;
const findDropdown = () => wrapper.findComponent(GlDropdown);
-
+ const findDropdownBtn = () => findDropdown().find('button');
const findRegistrationInstructionsDropdownItem = () => wrapper.findComponent(GlDropdownItem);
const findTokenDropdownItem = () => wrapper.findComponent(GlDropdownForm);
const findRegistrationToken = () => wrapper.findComponent(RegistrationToken);
@@ -90,12 +90,25 @@ describe('RegistrationDropdown', () => {
expect(wrapper.text()).toContain('Register an instance runner');
});
- it('Passes attributes to the dropdown component', () => {
+ it('Passes attributes to dropdown', () => {
createComponent({ attrs: { right: true } });
expect(findDropdown().attributes()).toMatchObject({ right: 'true' });
});
+ it('Passes default props and attributes to dropdown', () => {
+ createComponent();
+
+ expect(findDropdown().props()).toMatchObject({
+ category: 'primary',
+ variant: 'confirm',
+ });
+
+ expect(findDropdown().attributes()).toMatchObject({
+ toggleclass: '',
+ });
+ });
+
describe('Instructions dropdown item', () => {
it('Displays "Show runner" dropdown item', () => {
createComponent();
@@ -196,4 +209,51 @@ describe('RegistrationDropdown', () => {
expect(findModalContent()).toContain(newToken);
});
});
+
+ describe.each([
+ { createRunnerWorkflowForAdmin: true },
+ { createRunnerWorkflowForNamespace: true },
+ ])('When showing a "deprecated" warning', (glFeatures) => {
+ it('Passes deprecated variant props and attributes to dropdown', () => {
+ createComponent({
+ provide: { glFeatures },
+ });
+
+ expect(findDropdown().props()).toMatchObject({
+ category: 'tertiary',
+ variant: 'default',
+ text: '',
+ });
+
+ expect(findDropdown().attributes()).toMatchObject({
+ toggleclass: 'gl-px-3!',
+ });
+ });
+
+ it('shows warning text', () => {
+ createComponent(
+ {
+ provide: { glFeatures },
+ },
+ mountExtended,
+ );
+
+ const text = wrapper.findByText(s__('Runners|Support for registration tokens is deprecated'));
+
+ expect(text.exists()).toBe(true);
+ });
+
+ it('button shows only ellipsis icon', () => {
+ createComponent(
+ {
+ provide: { glFeatures },
+ },
+ mountExtended,
+ );
+
+ expect(findDropdownBtn().text()).toBe('');
+ expect(findDropdownBtn().findComponent(GlIcon).props('name')).toBe('ellipsis_v');
+ expect(findDropdownBtn().findAllComponents(GlIcon)).toHaveLength(1);
+ });
+ });
});
diff --git a/spec/frontend/ci/runner/components/registration/registration_token_spec.js b/spec/frontend/ci/runner/components/registration/registration_token_spec.js
index fc659f7974f..869c032c0b5 100644
--- a/spec/frontend/ci/runner/components/registration/registration_token_spec.js
+++ b/spec/frontend/ci/runner/components/registration/registration_token_spec.js
@@ -13,13 +13,14 @@ describe('RegistrationToken', () => {
const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility);
- const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
wrapper = mountFn(RegistrationToken, {
propsData: {
value: mockRegistrationToken,
inputId: 'token-value',
...props,
},
+ ...options,
});
showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null;
@@ -61,4 +62,23 @@ describe('RegistrationToken', () => {
expect(showToast).toHaveBeenCalledWith('Registration token copied!');
});
});
+
+ describe('When slots are used', () => {
+ const slotName = 'label-description';
+ const slotContent = 'Label Description';
+
+ beforeEach(() => {
+ createComponent({
+ slots: {
+ [slotName]: slotContent,
+ },
+ });
+ });
+
+ it('passes slots to the input component', () => {
+ const slot = findInputCopyToggleVisibility().vm.$scopedSlots[slotName];
+
+ expect(slot()[0].text).toBe(slotContent);
+ });
+ });
});
diff --git a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js
index 520c9eed003..add2e58045c 100644
--- a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js
+++ b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js
@@ -2,12 +2,10 @@ import { GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import GroupRunnerRunnerApp from '~/ci/runner/group_new_runner/group_new_runner_app.vue';
import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage';
-import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
import {
PARAM_KEY_PLATFORM,
@@ -17,7 +15,7 @@ import {
} from '~/ci/runner/constants';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import { redirectTo } from '~/lib/utils/url_utility';
-import { runnerCreateResult, mockRegistrationToken } from '../mock_data';
+import { runnerCreateResult } from '../mock_data';
const mockGroupId = 'gid://gitlab/Group/72';
@@ -33,8 +31,6 @@ const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner;
describe('GroupRunnerRunnerApp', () => {
let wrapper;
- const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link');
- const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal);
const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup);
const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm);
@@ -42,10 +38,6 @@ describe('GroupRunnerRunnerApp', () => {
wrapper = shallowMountExtended(GroupRunnerRunnerApp, {
propsData: {
groupId: mockGroupId,
- legacyRegistrationToken: mockRegistrationToken,
- },
- directives: {
- GlModal: createMockDirective('gl-modal'),
},
stubs: {
GlSprintf,
@@ -57,20 +49,6 @@ describe('GroupRunnerRunnerApp', () => {
createComponent();
});
- describe('Shows legacy modal', () => {
- it('passes legacy registration to modal', () => {
- expect(findRunnerInstructionsModal().props('registrationToken')).toEqual(
- mockRegistrationToken,
- );
- });
-
- it('opens a modal with the legacy instructions', () => {
- const modalId = getBinding(findLegacyInstructionsLink().element, 'gl-modal').value;
-
- expect(findRunnerInstructionsModal().props('modalId')).toBe(modalId);
- });
- });
-
describe('Platform', () => {
it('shows the platforms radio group', () => {
expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM);
diff --git a/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js
index 701a93352ef..6949108fb1f 100644
--- a/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js
+++ b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js
@@ -2,11 +2,9 @@ import { GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import ProjectRunnerRunnerApp from '~/ci/runner/project_new_runner/project_new_runner_app.vue';
-import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
import { PROJECT_TYPE, DEFAULT_PLATFORM } from '~/ci/runner/constants';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
@@ -26,8 +24,6 @@ const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner;
describe('ProjectRunnerRunnerApp', () => {
let wrapper;
- const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link');
- const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal);
const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup);
const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm);
@@ -37,9 +33,6 @@ describe('ProjectRunnerRunnerApp', () => {
projectId: mockProjectId,
legacyRegistrationToken: mockRegistrationToken,
},
- directives: {
- GlModal: createMockDirective('gl-modal'),
- },
stubs: {
GlSprintf,
},
@@ -50,20 +43,6 @@ describe('ProjectRunnerRunnerApp', () => {
createComponent();
});
- describe('Shows legacy modal', () => {
- it('passes legacy registration to modal', () => {
- expect(findRunnerInstructionsModal().props('registrationToken')).toEqual(
- mockRegistrationToken,
- );
- });
-
- it('opens a modal with the legacy instructions', () => {
- const modalId = getBinding(findLegacyInstructionsLink().element, 'gl-modal').value;
-
- expect(findRunnerInstructionsModal().props('modalId')).toBe(modalId);
- });
- });
-
describe('Platform', () => {
it('shows the platforms radio group', () => {
expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM);
diff --git a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
index 28d6b7118be..a557a1a98f3 100644
--- a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
+++ b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
@@ -1,11 +1,10 @@
-/* global Mousetrap */
-import 'mousetrap';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlButtonGroup } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DesignNavigation from '~/design_management/components/toolbar/design_navigation.vue';
import { DESIGN_ROUTE_NAME } from '~/design_management/router/constants';
+import { Mousetrap } from '~/lib/mousetrap';
import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index f24ce8ba4ce..e58082a798f 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -1,7 +1,6 @@
import { GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import Mousetrap from 'mousetrap';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
@@ -19,6 +18,7 @@ import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import { Mousetrap } from '~/lib/mousetrap';
import * as urlUtils from '~/lib/utils/url_utility';
import { stubPerformanceWebAPI } from 'helpers/performance';
import createDiffsStore from '../create_diffs_store';
diff --git a/spec/frontend/jobs/components/job/stages_dropdown_spec.js b/spec/frontend/jobs/components/job/stages_dropdown_spec.js
index f782d5600e6..9d01dc50e96 100644
--- a/spec/frontend/jobs/components/job/stages_dropdown_spec.js
+++ b/spec/frontend/jobs/components/job/stages_dropdown_spec.js
@@ -1,6 +1,6 @@
import { GlDropdown, GlDropdownItem, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import StagesDropdown from '~/jobs/components/job/sidebar/stages_dropdown.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
diff --git a/spec/frontend/lib/mousetrap_spec.js b/spec/frontend/lib/mousetrap_spec.js
new file mode 100644
index 00000000000..0ea221300a9
--- /dev/null
+++ b/spec/frontend/lib/mousetrap_spec.js
@@ -0,0 +1,113 @@
+// eslint-disable-next-line no-restricted-imports
+import Mousetrap from 'mousetrap';
+
+const originalMethodReturnValue = {};
+// Create a mock stopCallback method before ~/lib/utils/mousetrap overwrites
+// it. This allows us to spy on calls to it.
+const mockOriginalStopCallbackMethod = jest.fn().mockReturnValue(originalMethodReturnValue);
+Mousetrap.prototype.stopCallback = mockOriginalStopCallbackMethod;
+
+describe('mousetrap utils', () => {
+ describe('addStopCallback', () => {
+ let addStopCallback;
+ let clearStopCallbacksForTests;
+ const mockMousetrapInstance = { isMockMousetrap: true };
+ const mockKeyboardEvent = { type: 'keydown', key: 'Enter' };
+ const mockCombo = 'enter';
+
+ const mockKeydown = ({
+ instance = mockMousetrapInstance,
+ event = mockKeyboardEvent,
+ element = document,
+ combo = mockCombo,
+ } = {}) => Mousetrap.prototype.stopCallback.call(instance, event, element, combo);
+
+ beforeEach(async () => {
+ // Import async since it mutates the Mousetrap instance, by design.
+ ({ addStopCallback, clearStopCallbacksForTests } = await import('~/lib/mousetrap'));
+ clearStopCallbacksForTests();
+ });
+
+ it('delegates to the original stopCallback method when no additional callbacks added', () => {
+ const returnValue = mockKeydown();
+
+ expect(mockOriginalStopCallbackMethod).toHaveBeenCalledTimes(1);
+
+ const [thisArg] = mockOriginalStopCallbackMethod.mock.contexts;
+ const [eventArg, element, combo] = mockOriginalStopCallbackMethod.mock.calls[0];
+
+ expect(thisArg).toBe(mockMousetrapInstance);
+ expect(eventArg).toBe(mockKeyboardEvent);
+ expect(element).toBe(document);
+ expect(combo).toBe(mockCombo);
+
+ expect(returnValue).toBe(originalMethodReturnValue);
+ });
+
+ it('passes the expected arguments to the given stop callback', () => {
+ const callback = jest.fn();
+
+ addStopCallback(callback);
+
+ mockKeydown();
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ const [thisArg] = callback.mock.contexts;
+ const [eventArg, element, combo] = callback.mock.calls[0];
+
+ expect(thisArg).toBe(mockMousetrapInstance);
+ expect(eventArg).toBe(mockKeyboardEvent);
+ expect(element).toBe(document);
+ expect(combo).toBe(mockCombo);
+ });
+
+ describe.each([true, false])('when a stop handler returns %p', (stopCallbackReturnValue) => {
+ let methodReturnValue;
+ const stopCallback = jest.fn().mockReturnValue(stopCallbackReturnValue);
+
+ beforeEach(() => {
+ addStopCallback(stopCallback);
+
+ methodReturnValue = mockKeydown();
+ });
+
+ it(`returns ${stopCallbackReturnValue}`, () => {
+ expect(methodReturnValue).toBe(stopCallbackReturnValue);
+ });
+
+ it('calls stop callback', () => {
+ expect(stopCallback).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not call mockOriginalStopCallbackMethod', () => {
+ expect(mockOriginalStopCallbackMethod).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when a stop handler returns undefined', () => {
+ let methodReturnValue;
+ const stopCallback = jest.fn().mockReturnValue(undefined);
+
+ beforeEach(() => {
+ addStopCallback(stopCallback);
+
+ methodReturnValue = mockKeydown();
+ });
+
+ it('returns originalMethodReturnValue', () => {
+ expect(methodReturnValue).toBe(originalMethodReturnValue);
+ });
+
+ it('calls stop callback', () => {
+ expect(stopCallback).toHaveBeenCalledTimes(1);
+ });
+
+ // Because this is the only registered stop callback, the next callback
+ // is the original method.
+ it('does call original stopCallback method', () => {
+ expect(mockOriginalStopCallbackMethod).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/notes/components/discussion_navigator_spec.js b/spec/frontend/notes/components/discussion_navigator_spec.js
index 6e095f63003..14181287381 100644
--- a/spec/frontend/notes/components/discussion_navigator_spec.js
+++ b/spec/frontend/notes/components/discussion_navigator_spec.js
@@ -1,5 +1,3 @@
-/* global Mousetrap */
-import 'mousetrap';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import {
@@ -7,6 +5,7 @@ import {
MR_NEXT_UNRESOLVED_DISCUSSION,
MR_PREVIOUS_UNRESOLVED_DISCUSSION,
} from '~/behaviors/shortcuts/keybindings';
+import { Mousetrap } from '~/lib/mousetrap';
import DiscussionNavigator from '~/notes/components/discussion_navigator.vue';
import eventHub from '~/notes/event_hub';
diff --git a/spec/frontend/projects/settings/components/default_branch_selector_spec.js b/spec/frontend/projects/settings/components/default_branch_selector_spec.js
index c1412d01b53..9baea5c5517 100644
--- a/spec/frontend/projects/settings/components/default_branch_selector_spec.js
+++ b/spec/frontend/projects/settings/components/default_branch_selector_spec.js
@@ -28,7 +28,6 @@ describe('projects/settings/components/default_branch_selector', () => {
value: persistedDefaultBranch,
enabledRefTypes: [REF_TYPE_BRANCHES],
projectId,
- refType: null,
state: true,
toggleButtonClass: null,
translations: {
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index 5e15ba26ece..290cde29866 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -22,8 +22,6 @@ import {
REF_TYPE_BRANCHES,
REF_TYPE_TAGS,
REF_TYPE_COMMITS,
- BRANCH_REF_TYPE,
- TAG_REF_TYPE,
} from '~/ref/constants';
import createStore from '~/ref/stores/';
@@ -323,7 +321,7 @@ describe('Ref selector component', () => {
describe('branches', () => {
describe('when the branches search returns results', () => {
beforeEach(() => {
- createComponent({}, { refType: BRANCH_REF_TYPE, useSymbolicRefNames: true });
+ createComponent({}, { useSymbolicRefNames: true });
return waitForRequests();
});
@@ -386,7 +384,7 @@ describe('Ref selector component', () => {
describe('tags', () => {
describe('when the tags search returns results', () => {
beforeEach(() => {
- createComponent({}, { refType: TAG_REF_TYPE, useSymbolicRefNames: true });
+ createComponent({}, { useSymbolicRefNames: true });
return waitForRequests();
});
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js
index d1371ca0ef9..4e74bdc5895 100644
--- a/spec/frontend/shortcuts_spec.js
+++ b/spec/frontend/shortcuts_spec.js
@@ -1,16 +1,8 @@
import $ from 'jquery';
import { flatten } from 'lodash';
+import { Mousetrap } from '~/lib/mousetrap';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import Shortcuts from '~/behaviors/shortcuts/shortcuts';
-
-const mockMousetrap = {
- bind: jest.fn(),
- unbind: jest.fn(),
-};
-
-jest.mock('mousetrap', () => {
- return jest.fn().mockImplementation(() => mockMousetrap);
-});
+import Shortcuts, { LOCAL_MOUSETRAP_DATA_KEY } from '~/behaviors/shortcuts/shortcuts';
jest.mock('mousetrap/plugins/pause/mousetrap-pause', () => {});
@@ -20,6 +12,11 @@ describe('Shortcuts', () => {
$.Event(type, {
target,
});
+ let shortcuts;
+
+ beforeAll(() => {
+ shortcuts = new Shortcuts();
+ });
beforeEach(() => {
loadHTMLFixture(fixtureName);
@@ -28,7 +25,9 @@ describe('Shortcuts', () => {
jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus');
jest.spyOn(document.querySelector('#search'), 'focus');
- new Shortcuts(); // eslint-disable-line no-new
+ jest.spyOn(Mousetrap.prototype, 'stopCallback');
+ jest.spyOn(Mousetrap.prototype, 'bind').mockImplementation();
+ jest.spyOn(Mousetrap.prototype, 'unbind').mockImplementation();
});
afterEach(() => {
@@ -61,7 +60,7 @@ describe('Shortcuts', () => {
});
describe('markdown shortcuts', () => {
- let shortcuts;
+ let shortcutElements;
beforeEach(() => {
// Get all shortcuts specified with md-shortcuts attributes in the fixture.
@@ -71,7 +70,7 @@ describe('Shortcuts', () => {
// [ 'mod+i' ],
// [ 'mod+k' ]
// ]
- shortcuts = $('.edit-note .js-md')
+ shortcutElements = $('.edit-note .js-md')
.map(function getShortcutsFromToolbarBtn() {
const mdShortcuts = $(this).data('md-shortcuts');
@@ -83,19 +82,26 @@ describe('Shortcuts', () => {
});
describe('initMarkdownEditorShortcuts', () => {
+ let $textarea;
+ let localMousetrapInstance;
+
beforeEach(() => {
- Shortcuts.initMarkdownEditorShortcuts($('.edit-note textarea'));
+ $textarea = $('.edit-note textarea');
+ Shortcuts.initMarkdownEditorShortcuts($textarea);
+ localMousetrapInstance = $textarea.data(LOCAL_MOUSETRAP_DATA_KEY);
});
it('attaches a Mousetrap handler for every markdown shortcut specified with md-shortcuts', () => {
- const expectedCalls = shortcuts.map((s) => [s, expect.any(Function)]);
+ const expectedCalls = shortcutElements.map((s) => [s, expect.any(Function)]);
- expect(mockMousetrap.bind.mock.calls).toEqual(expectedCalls);
+ expect(Mousetrap.prototype.bind.mock.calls).toEqual(expectedCalls);
});
it('attaches a stopCallback that allows each markdown shortcut specified with md-shortcuts', () => {
- flatten(shortcuts).forEach((s) => {
- expect(mockMousetrap.stopCallback(null, null, s)).toBe(false);
+ flatten(shortcutElements).forEach((s) => {
+ expect(
+ localMousetrapInstance.stopCallback.call(localMousetrapInstance, null, null, s),
+ ).toBe(false);
});
});
});
@@ -104,16 +110,16 @@ describe('Shortcuts', () => {
it('does nothing if initMarkdownEditorShortcuts was not previous called', () => {
Shortcuts.removeMarkdownEditorShortcuts($('.edit-note textarea'));
- expect(mockMousetrap.unbind.mock.calls).toEqual([]);
+ expect(Mousetrap.prototype.unbind.mock.calls).toEqual([]);
});
it('removes Mousetrap handlers for every markdown shortcut specified with md-shortcuts', () => {
Shortcuts.initMarkdownEditorShortcuts($('.edit-note textarea'));
Shortcuts.removeMarkdownEditorShortcuts($('.edit-note textarea'));
- const expectedCalls = shortcuts.map((s) => [s]);
+ const expectedCalls = shortcutElements.map((s) => [s]);
- expect(mockMousetrap.unbind.mock.calls).toEqual(expectedCalls);
+ expect(Mousetrap.prototype.unbind.mock.calls).toEqual(expectedCalls);
});
});
});
@@ -136,4 +142,35 @@ describe('Shortcuts', () => {
});
});
});
+
+ describe('bindCommand(s)', () => {
+ it('bindCommand calls Mousetrap.bind correctly', () => {
+ const mockCommand = { defaultKeys: ['m'] };
+ const mockCallback = () => {};
+
+ shortcuts.bindCommand(mockCommand, mockCallback);
+
+ expect(Mousetrap.prototype.bind).toHaveBeenCalledTimes(1);
+ const [callArguments] = Mousetrap.prototype.bind.mock.calls;
+ expect(callArguments[0]).toEqual(mockCommand.defaultKeys);
+ expect(callArguments[1]).toBe(mockCallback);
+ });
+
+ it('bindCommands calls Mousetrap.bind correctly', () => {
+ const mockCommandsAndCallbacks = [
+ [{ defaultKeys: ['1'] }, () => {}],
+ [{ defaultKeys: ['2'] }, () => {}],
+ ];
+
+ shortcuts.bindCommands(mockCommandsAndCallbacks);
+
+ expect(Mousetrap.prototype.bind).toHaveBeenCalledTimes(mockCommandsAndCallbacks.length);
+ const { calls } = Mousetrap.prototype.bind.mock;
+
+ mockCommandsAndCallbacks.forEach(([mockCommand, mockCallback], i) => {
+ expect(calls[i][0]).toEqual(mockCommand.defaultKeys);
+ expect(calls[i][1]).toBe(mockCallback);
+ });
+ });
+ });
});
diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
index 2d535b0c727..16b8dfd211f 100644
--- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js
+++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
@@ -1,5 +1,5 @@
import { nextTick } from 'vue';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue';
import HelpCenter from '~/super_sidebar/components/help_center.vue';
diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js
index 9708d689245..bb0b12d205a 100644
--- a/spec/frontend/vue_shared/components/file_finder/index_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js
@@ -1,7 +1,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import Mousetrap from 'mousetrap';
import { nextTick } from 'vue';
import VirtualList from 'vue-virtual-scroll-list';
+import { Mousetrap } from '~/lib/mousetrap';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { file } from 'jest/ide/helpers';
import FindFileComponent from '~/vue_shared/components/file_finder/index.vue';
diff --git a/spec/frontend/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js
index 025a92464f1..3e66380ac46 100644
--- a/spec/frontend/zen_mode_spec.js
+++ b/spec/frontend/zen_mode_spec.js
@@ -2,7 +2,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Dropzone from 'dropzone';
import $ from 'jquery';
-import Mousetrap from 'mousetrap';
+import { Mousetrap } from '~/lib/mousetrap';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import GLForm from '~/gl_form';
import * as utils from '~/lib/utils/common_utils';
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index 8d2245c820f..480aeb94876 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -12,11 +12,8 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do
end
shared_examples 'returns role-targeted broadcast message when in project, group, or sub-group URL' do
- let(:feature_flag_state) { true }
-
before do
- stub_feature_flags(role_targeted_broadcast_messages: feature_flag_state)
- allow(helper).to receive(:cookies) { {} }
+ allow(helper).to receive(:cookies).and_return({})
end
context 'when in a project page' do
@@ -30,12 +27,6 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do
end
it { is_expected.to eq message }
-
- context 'when feature flag is disabled' do
- let(:feature_flag_state) { false }
-
- it { is_expected.to be_nil }
- end
end
context 'when in a group page' do
@@ -49,22 +40,10 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do
end
it { is_expected.to eq message }
-
- context 'when feature flag is disabled' do
- let(:feature_flag_state) { false }
-
- it { is_expected.to be_nil }
- end
end
context 'when not in a project, group, or sub-group page' do
it { is_expected.to be_nil }
-
- context 'when feature flag is disabled' do
- let(:feature_flag_state) { false }
-
- it { is_expected.to be_nil }
- end
end
end
@@ -72,7 +51,10 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do
subject { helper.current_broadcast_notification_message }
context 'with available broadcast notification messages' do
- let!(:broadcast_message_1) { create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now - 1.day) }
+ let!(:broadcast_message_1) do
+ create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now - 1.day)
+ end
+
let!(:broadcast_message_2) { create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now) }
it { is_expected.to eq broadcast_message_2 }
@@ -91,7 +73,13 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do
end
describe 'user access level targeted messages' do
- let_it_be(:message) { create(:broadcast_message, broadcast_type: 'notification', starts_at: Time.now, target_access_levels: [Gitlab::Access::DEVELOPER]) }
+ let_it_be(:message) do
+ create(:broadcast_message,
+ broadcast_type: 'notification',
+ starts_at: Time.now,
+ target_access_levels: [Gitlab::Access::DEVELOPER]
+ )
+ end
include_examples 'returns role-targeted broadcast message when in project, group, or sub-group URL'
end
@@ -99,7 +87,13 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do
describe '#current_broadcast_banner_messages' do
describe 'user access level targeted messages' do
- let_it_be(:message) { create(:broadcast_message, broadcast_type: 'banner', starts_at: Time.now, target_access_levels: [Gitlab::Access::DEVELOPER]) }
+ let_it_be(:message) do
+ create(:broadcast_message,
+ broadcast_type: 'banner',
+ starts_at: Time.now,
+ target_access_levels: [Gitlab::Access::DEVELOPER]
+ )
+ end
subject { helper.current_broadcast_banner_messages.first }
diff --git a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
index ea1e42de901..087dacd5ef0 100644
--- a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
@@ -253,7 +253,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact, feature_category: :
YAML
end
- let(:params) { { artifact: 'generated.yml', job: 'generator', with: { env: 'production' } } }
+ let(:params) { { artifact: 'generated.yml', job: 'generator', inputs: { env: 'production' } } }
it 'correctly interpolates content' do
expect(external_file.to_hash).to eq({ deploy: { script: 'deploy production' } })
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 6c0242050a6..0643bf0c046 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -242,7 +242,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
end
let(:location) { '/lib/gitlab/ci/templates/existent-file.yml' }
- let(:params) { { local: location, with: { website: 'gitlab.com' } } }
+ let(:params) { { local: location, inputs: { website: 'gitlab.com' } } }
before do
allow_any_instance_of(described_class)
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 947d8ebc698..636241ed763 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -314,7 +314,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project, feature_category: :p
end
let(:params) do
- { file: 'template-file.yml', ref: 'master', project: project.full_path, with: { name: 'abc' } }
+ { file: 'template-file.yml', ref: 'master', project: project.full_path, inputs: { name: 'abc' } }
end
it 'correctly interpolates the content' do
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index 30a407d3a8f..8e2339f6701 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -285,7 +285,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
YAML
end
- let(:params) { { remote: location, with: { include: 'some-file.yml' } } }
+ let(:params) { { remote: location, inputs: { include: 'some-file.yml' } } }
let(:context_params) do
{ sha: '12345', variables: variables, project: project }
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
index 89b8240ce9b..078b8831dc3 100644
--- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -155,7 +155,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Template, feature_category: :
end
let(:params) do
- { template: template, with: { env: 'production' } }
+ { template: template, inputs: { env: 'production' } }
end
it 'correctly interpolates the content' do
diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
index 73c0d0dba88..1069666ac50 100644
--- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
@@ -26,11 +26,13 @@ RSpec.describe Gitlab::Diff::Formatters::ImageFormatter do
it { is_expected.to eq(subject) }
[:width, :height, :x, :y].each do |attr|
- let(:other_formatter) do
- described_class.new(attrs.merge(attr => 9))
- end
+ context "with attribute:#{attr}" do
+ let(:other_formatter) do
+ described_class.new(attrs.merge(attr => 9))
+ end
- it { is_expected.not_to eq(other_formatter) }
+ it { is_expected.not_to eq(other_formatter) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/spamcheck/client_spec.rb b/spec/lib/gitlab/spamcheck/client_spec.rb
index 2fe978125c4..28b624da61b 100644
--- a/spec/lib/gitlab/spamcheck/client_spec.rb
+++ b/spec/lib/gitlab/spamcheck/client_spec.rb
@@ -2,19 +2,14 @@
require 'spec_helper'
-RSpec.describe Gitlab::Spamcheck::Client do
+RSpec.describe Gitlab::Spamcheck::Client, feature_category: :instance_resiliency do
include_context 'includes Spam constants'
let(:endpoint) { 'grpc://grpc.test.url' }
let_it_be(:user) { create(:user, organization: 'GitLab') }
let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW }
- let(:error_value) { "" }
-
- let(:attribs_value) do
- extra_attributes = Google::Protobuf::Map.new(:string, :string)
- extra_attributes["monitorMode"] = "false"
- extra_attributes
- end
+ let(:verdict_score) { 0.01 }
+ let(:verdict_evaluated) { true }
let_it_be(:issue) { create(:issue, description: 'Test issue description') }
let_it_be(:snippet) { create(:personal_snippet, :public, description: 'Test issue description') }
@@ -22,8 +17,8 @@ RSpec.describe Gitlab::Spamcheck::Client do
let(:response) do
verdict = ::Spamcheck::SpamVerdict.new
verdict.verdict = verdict_value
- verdict.error = error_value
- verdict.extra_attributes = attribs_value
+ verdict.evaluated = verdict_evaluated
+ verdict.score = verdict_score
verdict
end
@@ -67,19 +62,19 @@ RSpec.describe Gitlab::Spamcheck::Client do
using RSpec::Parameterized::TableSyntax
- where(:verdict, :expected) do
- ::Spamcheck::SpamVerdict::Verdict::ALLOW | Spam::SpamConstants::ALLOW
- ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW | Spam::SpamConstants::CONDITIONAL_ALLOW
- ::Spamcheck::SpamVerdict::Verdict::DISALLOW | Spam::SpamConstants::DISALLOW
- ::Spamcheck::SpamVerdict::Verdict::BLOCK | Spam::SpamConstants::BLOCK_USER
- ::Spamcheck::SpamVerdict::Verdict::NOOP | Spam::SpamConstants::NOOP
+ where(:verdict_value, :expected, :verdict_evaluated, :verdict_score) do
+ ::Spamcheck::SpamVerdict::Verdict::ALLOW | Spam::SpamConstants::ALLOW | true | 0.01
+ ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW | Spam::SpamConstants::CONDITIONAL_ALLOW | true | 0.50
+ ::Spamcheck::SpamVerdict::Verdict::DISALLOW | Spam::SpamConstants::DISALLOW | true | 0.75
+ ::Spamcheck::SpamVerdict::Verdict::BLOCK | Spam::SpamConstants::BLOCK_USER | true | 0.99
+ ::Spamcheck::SpamVerdict::Verdict::NOOP | Spam::SpamConstants::NOOP | false | 0.0
end
with_them do
- let(:verdict_value) { verdict }
-
- it "returns expected spam constant" do
- expect(subject).to eq([expected, { "monitorMode" => "false" }, ""])
+ it "returns expected spam result", :aggregate_failures do
+ expect(subject.verdict).to eq(expected)
+ expect(subject.evaluated?).to eq(verdict_evaluated)
+ expect(subject.score).to be_within(0.000001).of(verdict_score)
end
end
diff --git a/spec/lib/gitlab/spamcheck/result_spec.rb b/spec/lib/gitlab/spamcheck/result_spec.rb
new file mode 100644
index 00000000000..69bd61da8bf
--- /dev/null
+++ b/spec/lib/gitlab/spamcheck/result_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Spamcheck::Result, feature_category: :instance_resiliency do
+ include_context 'includes Spam constants'
+
+ describe "#initialize", :aggregate_failures do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(response) }
+
+ where(:verdict_value, :expected, :verdict_evaluated, :verdict_score) do
+ ::Spamcheck::SpamVerdict::Verdict::ALLOW | Spam::SpamConstants::ALLOW | true | 0.01
+ ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW | Spam::SpamConstants::CONDITIONAL_ALLOW | true | 0.50
+ ::Spamcheck::SpamVerdict::Verdict::DISALLOW | Spam::SpamConstants::DISALLOW | true | 0.75
+ ::Spamcheck::SpamVerdict::Verdict::BLOCK | Spam::SpamConstants::BLOCK_USER | true | 0.99
+ ::Spamcheck::SpamVerdict::Verdict::NOOP | Spam::SpamConstants::NOOP | false | 0.0
+ end
+
+ with_them do
+ let(:response) do
+ verdict = ::Spamcheck::SpamVerdict.new
+ verdict.verdict = verdict_value
+ verdict.evaluated = verdict_evaluated
+ verdict.score = verdict_score
+ verdict
+ end
+
+ it "returns expected verdict" do
+ expect(subject.verdict).to eq(expected)
+ end
+
+ it "returns expected evaluated?" do
+ expect(subject.evaluated?).to eq(verdict_evaluated)
+ end
+
+ it "returns expected score" do
+ expect(subject.score).to be_within(0.000001).of(verdict_score)
+ end
+ end
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index d364e46d833..784554d6e73 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -1574,50 +1574,4 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
expect(setting.personal_access_tokens_disabled?).to eq(false)
end
end
-
- describe 'email_confirmation_setting prefixes' do
- before do
- described_class.create_from_defaults
- end
-
- context 'when feature flag `soft_email_confirmation` is not enabled' do
- before do
- stub_feature_flags(soft_email_confirmation: false)
- end
-
- where(:email_confirmation_setting, :off, :soft, :hard) do
- 'off' | true | false | false
- 'soft' | false | true | false
- 'hard' | false | false | true
- end
-
- with_them do
- it 'returns the correct value when prefixed' do
- stub_application_setting_enum('email_confirmation_setting', email_confirmation_setting)
-
- expect(described_class.last.email_confirmation_setting_off?).to be off
- expect(described_class.last.email_confirmation_setting_soft?).to be soft
- expect(described_class.last.email_confirmation_setting_hard?).to be hard
- end
- end
-
- it 'calls super' do
- expect(described_class.last.email_confirmation_setting_off?).to be true
- expect(described_class.last.email_confirmation_setting_soft?).to be false
- expect(described_class.last.email_confirmation_setting_hard?).to be false
- end
- end
-
- context 'when feature flag `soft_email_confirmation` is enabled' do
- before do
- stub_feature_flags(soft_email_confirmation: true)
- end
-
- it 'returns correct value when enum is prefixed' do
- expect(described_class.last.email_confirmation_setting_off?).to be false
- expect(described_class.last.email_confirmation_setting_soft?).to be true
- expect(described_class.last.email_confirmation_setting_hard?).to be false
- end
- end
- end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 8fdc9852f6e..5fcf6813b0a 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -24,7 +24,11 @@ RSpec.describe BroadcastMessage do
it { is_expected.to allow_value(1).for(:broadcast_type) }
it { is_expected.not_to allow_value(nil).for(:broadcast_type) }
it { is_expected.not_to allow_value(nil).for(:target_access_levels) }
- it { is_expected.to validate_inclusion_of(:target_access_levels).in_array(described_class::ALLOWED_TARGET_ACCESS_LEVELS) }
+
+ it do
+ is_expected.to validate_inclusion_of(:target_access_levels)
+ .in_array(described_class::ALLOWED_TARGET_ACCESS_LEVELS)
+ end
end
describe 'default values' do
@@ -187,32 +191,6 @@ RSpec.describe BroadcastMessage do
shared_examples "matches with user access level" do |broadcast_type|
let_it_be(:target_access_levels) { [Gitlab::Access::GUEST] }
- let(:feature_flag_state) { true }
-
- before do
- stub_feature_flags(role_targeted_broadcast_messages: feature_flag_state)
- end
-
- context 'when feature flag is disabled' do
- let(:feature_flag_state) { false }
-
- context 'when message is role-targeted' do
- let_it_be(:message) { create(:broadcast_message, target_access_levels: target_access_levels, broadcast_type: broadcast_type) }
-
- it 'does not return the message' do
- expect(subject.call(nil, Gitlab::Access::GUEST)).to be_empty
- end
- end
-
- context 'when message is not role-targeted' do
- let_it_be(:message) { create(:broadcast_message, target_access_levels: [], broadcast_type: broadcast_type) }
-
- it 'returns the message' do
- expect(subject.call(nil, Gitlab::Access::GUEST)).to include(message)
- end
- end
- end
-
context 'when target_access_levels is empty' do
let_it_be(:message) { create(:broadcast_message, target_access_levels: [], broadcast_type: broadcast_type) }
@@ -226,7 +204,9 @@ RSpec.describe BroadcastMessage do
end
context 'when target_access_levels is not empty' do
- let_it_be(:message) { create(:broadcast_message, target_access_levels: target_access_levels, broadcast_type: broadcast_type) }
+ let_it_be(:message) do
+ create(:broadcast_message, target_access_levels: target_access_levels, broadcast_type: broadcast_type)
+ end
it "does not return the message if user access level is nil" do
expect(subject.call).to be_empty
@@ -250,26 +230,18 @@ RSpec.describe BroadcastMessage do
before do
cache.write(described_class::BANNER_CACHE_KEY, [message])
- allow(BroadcastMessage).to receive(:cache) { cache }
+ allow(described_class).to receive(:cache) { cache }
end
it 'does not raise error (e.g. NoMethodError from nil.empty?)' do
expect { subject.call }.not_to raise_error
end
-
- context 'when feature flag is disabled' do
- it 'does not raise error (e.g. NoMethodError from nil.empty?)' do
- stub_feature_flags(role_targeted_broadcast_messages: false)
-
- expect { subject.call }.not_to raise_error
- end
- end
end
end
describe '.current', :use_clean_rails_memory_store_caching do
subject do
- -> (path = nil, user_access_level = nil) do
+ ->(path = nil, user_access_level = nil) do
described_class.current(current_path: path, user_access_level: user_access_level)
end
end
@@ -301,7 +273,7 @@ RSpec.describe BroadcastMessage do
describe '.current_banner_messages', :use_clean_rails_memory_store_caching do
subject do
- -> (path = nil, user_access_level = nil) do
+ ->(path = nil, user_access_level = nil) do
described_class.current_banner_messages(current_path: path, user_access_level: user_access_level)
end
end
@@ -331,7 +303,7 @@ RSpec.describe BroadcastMessage do
describe '.current_notification_messages', :use_clean_rails_memory_store_caching do
subject do
- -> (path = nil, user_access_level = nil) do
+ ->(path = nil, user_access_level = nil) do
described_class.current_notification_messages(current_path: path, user_access_level: user_access_level)
end
end
diff --git a/spec/models/concerns/has_user_type_spec.rb b/spec/models/concerns/has_user_type_spec.rb
index e7f041296b7..b5abd114f9a 100644
--- a/spec/models/concerns/has_user_type_spec.rb
+++ b/spec/models/concerns/has_user_type_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe User, feature_category: :system_access do
end
describe 'scopes & predicates' do
- User::USER_TYPES.keys.each do |type|
+ User::USER_TYPES.keys.each do |type| # rubocop:disable RSpec/UselessDynamicDefinition
let_it_be(type) { create(:user, username: type, user_type: type) }
end
let(:bots) { User::BOT_USER_TYPES.map { |type| public_send(type) } }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b36599b1273..c05b7274317 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -7156,7 +7156,7 @@ RSpec.describe User, feature_category: :user_profile do
context 'when email confirmation setting is set to `off`' do
before do
- stub_feature_flags(soft_email_confirmation: false)
+ stub_application_setting_enum('email_confirmation_setting', 'off')
end
it { is_expected.to be(true) }
@@ -7186,7 +7186,6 @@ RSpec.describe User, feature_category: :user_profile do
context 'when email confirmation setting is set to `hard`' do
before do
- stub_feature_flags(soft_email_confirmation: false)
stub_application_setting_enum('email_confirmation_setting', 'hard')
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 6d1c25360e8..79e2755f34c 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -27,9 +27,15 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let_it_be(:user, reload: true) { create(:user, note: '2018-11-05 | 2FA removed | user requested | www.gitlab.com') }
describe 'POST /users' do
+ let(:path) { '/users' }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { attributes_for(:user).merge({ note: 'Awesome Note' }) }
+ end
+
context 'when unauthenticated' do
it 'return authentication error' do
- post api('/users')
+ post api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -41,7 +47,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
optional_attributes = { note: 'Awesome Note' }
attributes = attributes_for(:user).merge(optional_attributes)
- post api('/users', admin, admin_mode: true), params: attributes
+ post api(path, admin, admin_mode: true), params: attributes
expect(response).to have_gitlab_http_status(:created)
expect(json_response['note']).to eq(optional_attributes[:note])
@@ -50,7 +56,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'as a regular user' do
it 'does not allow creating new user' do
- post api('/users', user), params: attributes_for(:user)
+ post api(path, user), params: attributes_for(:user)
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -59,12 +65,18 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "PUT /users/:id" do
+ let(:path) { "/users/#{user.id}" }
+
+ it_behaves_like 'PUT request permissions for admin mode' do
+ let(:params) { { note: 'new note' } }
+ end
+
context 'when user is an admin' do
it "updates note of the user" do
new_note = '2019-07-07 | Email changed | user requested | www.gitlab.com'
expect do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { note: new_note }
+ put api(path, admin, admin_mode: true), params: { note: new_note }
end.to change { user.reload.note }
.from('2018-11-05 | 2FA removed | user requested | www.gitlab.com')
.to(new_note)
@@ -77,7 +89,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when user is not an admin' do
it "cannot update their own note" do
expect do
- put api("/users/#{user.id}", user), params: { note: 'new note' }
+ put api(path, user), params: { note: 'new note' }
end.not_to change { user.reload.note }
expect(response).to have_gitlab_http_status(:forbidden)
@@ -159,9 +171,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /users/' do
+ let(:path) { '/users' }
+
context 'when unauthenticated' do
it "does not contain certain fields" do
- get api("/users"), params: { username: user.username }
+ get api(path), params: { username: user.username }
expect(json_response.first).not_to have_key('note')
expect(json_response.first).not_to have_key('namespace_id')
@@ -172,8 +186,9 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when authenticated' do
context 'as a regular user' do
it 'does not contain certain fields' do
- get api("/users", user), params: { username: user.username }
+ get api(path, user), params: { username: user.username }
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first).not_to have_key('note')
expect(json_response.first).not_to have_key('namespace_id')
expect(json_response.first).not_to have_key('created_by')
@@ -182,7 +197,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'as an admin' do
it 'contains the note of users' do
- get api("/users", admin, admin_mode: true), params: { username: user.username }
+ get api(path, admin, admin_mode: true), params: { username: user.username }
expect(response).to have_gitlab_http_status(:success)
expect(json_response.first).to have_key('note')
@@ -191,7 +206,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'with `created_by` details' do
it 'has created_by as nil with a self-registered account' do
- get api("/users", admin, admin_mode: true), params: { username: user.username }
+ get api(path, admin, admin_mode: true), params: { username: user.username }
expect(response).to have_gitlab_http_status(:success)
expect(json_response.first).to have_key('created_by')
@@ -201,7 +216,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'is created_by a user and has those details' do
created = create(:user, created_by_id: user.id)
- get api("/users", admin, admin_mode: true), params: { username: created.username }
+ get api(path, admin, admin_mode: true), params: { username: created.username }
expect(response).to have_gitlab_http_status(:success)
expect(json_response.first['created_by'].symbolize_keys)
@@ -217,7 +232,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'avoids N+1 queries when requested by admin' do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/users", admin)
+ get api(path, admin)
end.count
create_list(:user, 3)
@@ -227,19 +242,19 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
# Refer issue https://gitlab.com/gitlab-org/gitlab/-/issues/367080
expect do
- get api("/users", admin)
+ get api(path, admin)
end.not_to exceed_all_query_limit(control_count + 3)
end
it 'avoids N+1 queries when requested by a regular user' do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/users", user)
+ get api(path, user)
end.count
create_list(:user, 3)
expect do
- get api("/users", user)
+ get api(path, user)
end.not_to exceed_all_query_limit(control_count)
end
end
@@ -247,11 +262,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /user' do
+ let(:path) { '/user' }
+
context 'when authenticated' do
context 'as an admin' do
context 'accesses their own profile' do
it 'contains the note of the user' do
- get api("/user", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(json_response).to have_key('note')
expect(json_response['note']).to eq(admin.note)
@@ -286,7 +303,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'as a regular user' do
it 'does not contain the note of the user' do
- get api("/user", user)
+ get api(path, user)
expect(json_response).not_to have_key('note')
expect(json_response).not_to have_key('namespace_id')
@@ -318,15 +335,17 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /users' do
+ let(:path) { '/users' }
+
context "when unauthenticated" do
it "returns authorization error when the `username` parameter is not passed" do
- get api("/users")
+ get api(path)
expect(response).to have_gitlab_http_status(:forbidden)
end
it "returns the user when a valid `username` parameter is passed" do
- get api("/users"), params: { username: user.username }
+ get api(path), params: { username: user.username }
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(json_response.size).to eq(1)
@@ -335,7 +354,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns the user when a valid `username` parameter is passed (case insensitive)" do
- get api("/users"), params: { username: user.username.upcase }
+ get api(path), params: { username: user.username.upcase }
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(json_response.size).to eq(1)
@@ -344,7 +363,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns an empty response when an invalid `username` parameter is passed" do
- get api("/users"), params: { username: 'invalid' }
+ get api(path), params: { username: 'invalid' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
@@ -352,14 +371,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "does not return the highest role" do
- get api("/users"), params: { username: user.username }
+ get api(path), params: { username: user.username }
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(json_response.first.keys).not_to include 'highest_role'
end
it "does not return the current or last sign-in ip addresses" do
- get api("/users"), params: { username: user.username }
+ get api(path), params: { username: user.username }
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(json_response.first.keys).not_to include 'current_sign_in_ip'
@@ -372,13 +391,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
- get api("/users"), params: { username: user.username }
+ get api(path), params: { username: user.username }
expect(response).to have_gitlab_http_status(:forbidden)
end
it "returns authorization error when the `username` parameter is not passed" do
- get api("/users")
+ get api(path)
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -394,7 +413,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when authenticate as a regular user' do
it "renders 200" do
- get api("/users", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basics')
end
@@ -402,7 +421,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when authenticate as an admin' do
it "renders 200" do
- get api("/users", admin)
+ get api(path, admin)
expect(response).to match_response_schema('public_api/v4/user/basics')
end
@@ -410,7 +429,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns an array of users" do
- get api("/users", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(response).to include_pagination_headers
@@ -466,7 +485,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'does not reveal the `is_admin` flag of the user' do
- get api('/users', user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(json_response.first.keys).not_to include 'is_admin'
@@ -536,14 +555,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns an array of users" do
- get api("/users", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(response).to include_pagination_headers
end
it "users contain the `namespace_id` field" do
- get api("/users", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:success)
expect(response).to match_response_schema('public_api/v4/user/admins')
@@ -615,7 +634,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
# - admin
# - user
- get api('/users', admin, admin_mode: true), params: { order_by: 'id', sort: 'asc' }
+ get api(path, admin, admin_mode: true), params: { order_by: 'id', sort: 'asc' }
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(json_response.size).to eq(2)
@@ -626,7 +645,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns users with 2fa enabled' do
user_with_2fa = create(:user, :two_factor_via_otp)
- get api('/users', admin, admin_mode: true), params: { two_factor: 'enabled' }
+ get api(path, admin, admin_mode: true), params: { two_factor: 'enabled' }
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(json_response.size).to eq(1)
@@ -638,7 +657,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
create(:project, namespace: user.namespace)
create(:project, namespace: admin.namespace)
- get api('/users', admin, admin_mode: true), params: { without_projects: true }
+ get api(path, admin, admin_mode: true), params: { without_projects: true }
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(json_response.size).to eq(1)
@@ -646,7 +665,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns 400 when provided incorrect sort params' do
- get api('/users', admin, admin_mode: true), params: { order_by: 'magic', sort: 'asc' }
+ get api(path, admin, admin_mode: true), params: { order_by: 'magic', sort: 'asc' }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -666,34 +685,36 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe "GET /users/:id" do
let_it_be(:user2, reload: true) { create(:user, username: 'another_user') }
+ let(:path) { "/users/#{user.id}" }
+
before do
allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?)
.with(:users_get_by_id, scope: user, users_allowlist: []).and_return(false)
end
it "returns a user by id" do
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response['username']).to eq(user.username)
end
it "does not return the user's `is_admin` flag" do
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response.keys).not_to include 'is_admin'
end
it "does not return the user's `highest_role`" do
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response.keys).not_to include 'highest_role'
end
it "does not return the user's sign in IPs" do
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response.keys).not_to include 'current_sign_in_ip'
@@ -701,7 +722,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "does not contain plan or trial data" do
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response.keys).not_to include 'plan'
@@ -760,7 +781,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'does not contain the note of the user' do
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(json_response).not_to have_key('note')
expect(json_response).not_to have_key('sign_in_count')
@@ -772,7 +793,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
.to receive(:throttled?).with(:users_get_by_id, scope: user, users_allowlist: [])
.and_return(false)
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
end
@@ -785,7 +806,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
.to receive(:throttled?).with(:users_get_by_id, scope: user, users_allowlist: [])
.and_return(true)
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:too_many_requests)
end
@@ -794,7 +815,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
expect(Gitlab::ApplicationRateLimiter)
.not_to receive(:throttled?)
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok)
end
@@ -812,7 +833,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
.to receive(:throttled?).with(:users_get_by_id, scope: user, users_allowlist: allowlist)
.and_call_original
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
end
@@ -827,7 +848,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns job title of a user' do
- get api("/users/#{user.id}", user)
+ get api(path, user)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response['job_title']).to eq(job_title)
@@ -836,7 +857,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when authenticated as admin' do
it 'contains the note of the user' do
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(json_response).to have_key('note')
expect(json_response['note']).to eq(user.note)
@@ -844,7 +865,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'includes the `is_admin` field' do
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response['is_admin']).to be(false)
@@ -858,14 +879,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'includes the `highest_role` field' do
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response['highest_role']).to be(0)
end
it 'includes the `namespace_id` field' do
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:success)
expect(response).to match_response_schema('public_api/v4/user/admin')
@@ -874,13 +895,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
if Gitlab.ee?
it 'does not include values for plan or trial' do
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to match_response_schema('public_api/v4/user/basic')
end
else
it 'does not include plan or trial data' do
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response.keys).not_to include 'plan'
@@ -890,7 +911,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when user has not logged in' do
it 'does not include the sign in IPs' do
- get api("/users/#{user.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response).to include('current_sign_in_ip' => nil, 'last_sign_in_ip' => nil)
@@ -912,14 +933,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'for an anonymous user' do
it 'returns 403' do
- get api("/users/#{user.id}")
+ get api(path)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
it "returns a 404 error if user id not found" do
- get api("/users/0", user)
+ get api("/users/#{non_existing_record_id}", user)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
@@ -954,10 +975,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'POST /users/:id/follow' do
let(:followee) { create(:user) }
+ let(:path) { "/users/#{followee.id}/follow" }
context 'on an unfollowed user' do
it 'follows the user' do
- post api("/users/#{followee.id}/follow", user)
+ post api(path, user)
expect(user.followees).to contain_exactly(followee)
expect(response).to have_gitlab_http_status(:created)
@@ -967,7 +989,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
stub_const('Users::UserFollowUser::MAX_FOLLOWEE_LIMIT', 2)
Users::UserFollowUser::MAX_FOLLOWEE_LIMIT.times { user.follow(create(:user)) }
- post api("/users/#{followee.id}/follow", user)
+ post api(path, user)
expect(response).to have_gitlab_http_status(:bad_request)
expected_message = format(_("You can't follow more than %{limit} users. To follow more users, unfollow some others."), limit: Users::UserFollowUser::MAX_FOLLOWEE_LIMIT)
expect(json_response['message']).to eq(expected_message)
@@ -981,7 +1003,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'does not change following' do
- post api("/users/#{followee.id}/follow", user)
+ post api(path, user)
expect(user.followees).to contain_exactly(followee)
expect(response).to have_gitlab_http_status(:not_modified)
@@ -991,6 +1013,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'POST /users/:id/unfollow' do
let(:followee) { create(:user) }
+ let(:path) { "/users/#{followee.id}/unfollow" }
context 'on a followed user' do
before do
@@ -998,7 +1021,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'unfollow the user' do
- post api("/users/#{followee.id}/unfollow", user)
+ post api(path, user)
expect(user.followees).to be_empty
expect(response).to have_gitlab_http_status(:created)
@@ -1007,7 +1030,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'on an unfollowed user' do
it 'does not change following' do
- post api("/users/#{followee.id}/unfollow", user)
+ post api(path, user)
expect(user.followees).to be_empty
expect(response).to have_gitlab_http_status(:not_modified)
@@ -1017,6 +1040,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'GET /users/:id/followers' do
let(:follower) { create(:user) }
+ let(:path) { "/users/#{user.id}/followers" }
context 'for an anonymous user' do
it 'returns 403' do
@@ -1030,7 +1054,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'lists followers' do
follower.follow(user)
- get api("/users/#{user.id}/followers", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -1049,7 +1073,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'user does not have any follower' do
it 'does list nothing' do
- get api("/users/#{user.id}/followers", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -1060,6 +1084,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'GET /users/:id/following' do
let(:followee) { create(:user) }
+ let(:path) { "/users/#{user.id}/followers" }
context 'for an anonymous user' do
it 'returns 403' do
@@ -1073,7 +1098,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'lists following user' do
user.follow(followee)
- get api("/users/#{user.id}/following", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -1092,7 +1117,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'user does not have any follower' do
it 'does list nothing' do
- get api("/users/#{user.id}/following", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -1102,14 +1127,20 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "POST /users" do
+ let(:path) { '/users' }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { attributes_for(:user, projects_limit: 3) }
+ end
+
it "creates user" do
expect do
- post api("/users", admin, admin_mode: true), params: attributes_for(:user, projects_limit: 3)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, projects_limit: 3)
end.to change { User.count }.by(1)
end
it "creates user with correct attributes" do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user, admin: true, can_create_group: true)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, admin: true, can_create_group: true)
expect(response).to have_gitlab_http_status(:created)
user_id = json_response['id']
new_user = User.find(user_id)
@@ -1121,13 +1152,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
optional_attributes = { confirm: true, theme_id: 2, color_scheme_id: 4 }
attributes = attributes_for(:user).merge(optional_attributes)
- post api('/users', admin, admin_mode: true), params: attributes
+ post api(path, admin, admin_mode: true), params: attributes
expect(response).to have_gitlab_http_status(:created)
end
it "creates non-admin user" do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user, admin: false, can_create_group: false)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, admin: false, can_create_group: false)
expect(response).to have_gitlab_http_status(:created)
user_id = json_response['id']
new_user = User.find(user_id)
@@ -1136,7 +1167,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "creates non-admin users by default" do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user)
expect(response).to have_gitlab_http_status(:created)
user_id = json_response['id']
new_user = User.find(user_id)
@@ -1144,13 +1175,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns 201 Created on success" do
- post api("/users", admin, admin_mode: true), params: attributes_for(:user, projects_limit: 3)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, projects_limit: 3)
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(response).to have_gitlab_http_status(:created)
end
it 'creates non-external users by default' do
- post api("/users", admin, admin_mode: true), params: attributes_for(:user)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user)
expect(response).to have_gitlab_http_status(:created)
user_id = json_response['id']
@@ -1159,7 +1190,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'allows an external user to be created' do
- post api("/users", admin, admin_mode: true), params: attributes_for(:user, external: true)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, external: true)
expect(response).to have_gitlab_http_status(:created)
user_id = json_response['id']
@@ -1168,7 +1199,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "creates user with reset password" do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user, reset_password: true).except(:password)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, reset_password: true).except(:password)
expect(response).to have_gitlab_http_status(:created)
@@ -1181,7 +1212,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "creates user with random password" do
params = attributes_for(:user, force_random_password: true)
params.delete(:password)
- post api('/users', admin, admin_mode: true), params: params
+ post api(path, admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:created)
@@ -1192,7 +1223,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "creates user with private profile" do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user, private_profile: true)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, private_profile: true)
expect(response).to have_gitlab_http_status(:created)
@@ -1204,7 +1235,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "creates user with view_diffs_file_by_file" do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user, view_diffs_file_by_file: true)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, view_diffs_file_by_file: true)
expect(response).to have_gitlab_http_status(:created)
@@ -1217,7 +1248,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "creates user with avatar" do
workhorse_form_with_file(
- api('/users', admin, admin_mode: true),
+ api(path, admin, admin_mode: true),
method: :post,
file_key: :avatar,
params: attributes_for(:user, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
@@ -1232,7 +1263,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "does not create user with invalid email" do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
email: 'invalid email',
password: User.random_password,
@@ -1242,22 +1273,22 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns 400 error if name not given' do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:name)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user).except(:name)
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 error if password not given' do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:password)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user).except(:password)
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 error if email not given' do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:email)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user).except(:email)
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 error if username not given' do
- post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:username)
+ post api(path, admin, admin_mode: true), params: attributes_for(:user).except(:username)
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -1265,13 +1296,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
optional_attributes = { theme_id: 50, color_scheme_id: 50 }
attributes = attributes_for(:user).merge(optional_attributes)
- post api('/users', admin, admin_mode: true), params: attributes
+ post api(path, admin, admin_mode: true), params: attributes
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 error if user does not validate' do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
password: 'pass',
email: 'test@example.com',
@@ -1293,7 +1324,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'tracks weak password errors' do
attributes = attributes_for(:user).merge({ password: "password" })
- post api('/users', admin, admin_mode: true), params: attributes
+ post api(path, admin, admin_mode: true), params: attributes
expect(json_response['message']['password'])
.to eq(['must not contain commonly used combinations of words and letters'])
@@ -1306,13 +1337,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "is not available for non admin users" do
- post api("/users", user), params: attributes_for(:user)
+ post api(path, user), params: attributes_for(:user)
expect(response).to have_gitlab_http_status(:forbidden)
end
context 'with existing user' do
before do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
email: 'test@example.com',
password: User.random_password,
@@ -1323,7 +1354,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 409 conflict error if user with same email exists' do
expect do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
name: 'foo',
email: 'test@example.com',
@@ -1337,7 +1368,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 409 conflict error if same username exists' do
expect do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
name: 'foo',
email: 'foo@example.com',
@@ -1351,7 +1382,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 409 conflict error if same username exists (case insensitive)' do
expect do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
name: 'foo',
email: 'foo@example.com',
@@ -1364,7 +1395,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'creates user with new identity' do
- post api("/users", admin, admin_mode: true), params: attributes_for(:user, provider: 'github', extern_uid: '67890')
+ post api(path, admin, admin_mode: true), params: attributes_for(:user, provider: 'github', extern_uid: '67890')
expect(response).to have_gitlab_http_status(:created)
expect(json_response['identities'].first['extern_uid']).to eq('67890')
@@ -1378,7 +1409,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 409 conflict error' do
expect do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
name: 'foo',
email: confirmed_user.email,
@@ -1396,7 +1427,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 409 conflict error' do
expect do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
name: 'foo',
email: unconfirmed_user.email,
@@ -1416,7 +1447,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 409 conflict error' do
expect do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
name: 'foo',
email: email.email,
@@ -1434,7 +1465,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'does not create user' do
expect do
- post api('/users', admin, admin_mode: true),
+ post api(path, admin, admin_mode: true),
params: {
name: 'foo',
email: email.email,
@@ -1465,7 +1496,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
shared_examples_for 'creates the user with the value of `private_profile` based on the application setting' do
specify do
- post api("/users", admin, admin_mode: true), params: params
+ post api(path, admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:created)
user = User.find_by(id: json_response['id'], private_profile: true)
@@ -1479,7 +1510,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when the attribute is overridden in params' do
it 'creates the user with the value of `private_profile` same as the value of the overridden param' do
- post api("/users", admin, admin_mode: true), params: params.merge(private_profile: false)
+ post api(path, admin, admin_mode: true), params: params.merge(private_profile: false)
expect(response).to have_gitlab_http_status(:created)
user = User.find_by(id: json_response['id'], private_profile: false)
@@ -1497,8 +1528,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "PUT /users/:id" do
+ let(:path) { "/users/#{user.id}" }
+
+ it_behaves_like 'PUT request permissions for admin mode' do
+ let(:params) { { bio: 'new test bio' } }
+ end
+
it "returns 200 OK on success" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: 'new test bio' }
+ put api(path, admin, admin_mode: true), params: { bio: 'new test bio' }
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(response).to have_gitlab_http_status(:ok)
@@ -1564,7 +1601,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "updates user with new bio" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: 'new test bio' }
+ put api(path, admin, admin_mode: true), params: { bio: 'new test bio' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['bio']).to eq('new test bio')
@@ -1574,7 +1611,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "updates user with empty bio" do
user.update!(bio: 'previous bio')
- put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: '' }
+ put api(path, admin, admin_mode: true), params: { bio: '' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['bio']).to eq('')
@@ -1582,7 +1619,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'updates user with nil bio' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: nil }
+ put api(path, admin, admin_mode: true), params: { bio: nil }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['bio']).to eq('')
@@ -1590,7 +1627,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "updates user with organization" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { organization: 'GitLab' }
+ put api(path, admin, admin_mode: true), params: { organization: 'GitLab' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['organization']).to eq('GitLab')
@@ -1599,7 +1636,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'updates user with avatar' do
workhorse_form_with_file(
- api("/users/#{user.id}", admin, admin_mode: true),
+ api(path, admin, admin_mode: true),
method: :put,
file_key: :avatar,
params: { avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
@@ -1615,7 +1652,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'updates user with a new email' do
old_email = user.email
old_notification_email = user.notification_email_or_default
- put api("/users/#{user.id}", admin, admin_mode: true), params: { email: 'new@email.com' }
+ put api(path, admin, admin_mode: true), params: { email: 'new@email.com' }
user.reload
@@ -1627,7 +1664,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'skips reconfirmation when requested' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { email: 'new@email.com', skip_reconfirmation: true }
+ put api(path, admin, admin_mode: true), params: { email: 'new@email.com', skip_reconfirmation: true }
user.reload
@@ -1637,7 +1674,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'updates user with their own username' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { username: user.username }
+ put api(path, admin, admin_mode: true), params: { username: user.username }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['username']).to eq(user.username)
@@ -1652,7 +1689,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'updates user with new identity' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { provider: 'github', extern_uid: 'john' }
+ put api(path, admin, admin_mode: true), params: { provider: 'github', extern_uid: 'john' }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.identities.first.extern_uid).to eq('john')
@@ -1660,14 +1697,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "updates admin status" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { admin: true }
+ put api(path, admin, admin_mode: true), params: { admin: true }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.admin).to eq(true)
end
it "updates external status" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { external: true }
+ put api(path, admin, admin_mode: true), params: { external: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['external']).to eq(true)
@@ -1675,14 +1712,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "does have default values for theme and color-scheme ID" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: {}
+ put api(path, admin, admin_mode: true), params: {}
expect(user.reload.theme_id).to eq(Gitlab::Themes.default.id)
expect(user.reload.color_scheme_id).to eq(Gitlab::ColorSchemes.default.id)
end
it "updates viewing diffs file by file" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { view_diffs_file_by_file: true }
+ put api(path, admin, admin_mode: true), params: { view_diffs_file_by_file: true }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.user_preference.view_diffs_file_by_file?).to eq(true)
@@ -1693,7 +1730,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
current_value = user.private_profile
new_value = !current_value
- put api("/users/#{user.id}", admin, admin_mode: true), params: { private_profile: new_value }
+ put api(path, admin, admin_mode: true), params: { private_profile: new_value }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.private_profile).to eq(new_value)
@@ -1707,7 +1744,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "updates private_profile to value of the application setting" do
user.update!(private_profile: false)
- put api("/users/#{user.id}", admin, admin_mode: true), params: { private_profile: nil }
+ put api(path, admin, admin_mode: true), params: { private_profile: nil }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.private_profile).to eq(true)
@@ -1717,7 +1754,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "does not modify private profile when field is not provided" do
user.update!(private_profile: true)
- put api("/users/#{user.id}", admin, admin_mode: true), params: {}
+ put api(path, admin, admin_mode: true), params: {}
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.private_profile).to eq(true)
@@ -1730,7 +1767,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.update!(theme_id: theme.id, color_scheme_id: scheme.id)
- put api("/users/#{user.id}", admin, admin_mode: true), params: {}
+ put api(path, admin, admin_mode: true), params: {}
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.theme_id).to eq(theme.id)
@@ -1748,35 +1785,35 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "does not allow invalid update" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { email: 'invalid email' }
+ put api(path, admin, admin_mode: true), params: { email: 'invalid email' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(user.reload.email).not_to eq('invalid email')
end
it "updates theme id" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { theme_id: 5 }
+ put api(path, admin, admin_mode: true), params: { theme_id: 5 }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.theme_id).to eq(5)
end
it "does not update invalid theme id" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { theme_id: 50 }
+ put api(path, admin, admin_mode: true), params: { theme_id: 50 }
expect(response).to have_gitlab_http_status(:bad_request)
expect(user.reload.theme_id).not_to eq(50)
end
it "updates color scheme id" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { color_scheme_id: 5 }
+ put api(path, admin, admin_mode: true), params: { color_scheme_id: 5 }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.color_scheme_id).to eq(5)
end
it "does not update invalid color scheme id" do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { color_scheme_id: 50 }
+ put api(path, admin, admin_mode: true), params: { color_scheme_id: 50 }
expect(response).to have_gitlab_http_status(:bad_request)
expect(user.reload.color_scheme_id).not_to eq(50)
@@ -1785,7 +1822,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when the current user is not an admin' do
it "is not available" do
expect do
- put api("/users/#{user.id}", user), params: attributes_for(:user)
+ put api(path, user), params: attributes_for(:user)
end.not_to change { user.reload.attributes }
expect(response).to have_gitlab_http_status(:forbidden)
@@ -1793,7 +1830,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns 404 for non-existing user" do
- put api("/users/0", admin, admin_mode: true), params: { bio: 'update should fail' }
+ put api("/users/#{non_existing_record_id}", admin, admin_mode: true), params: { bio: 'update should fail' }
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1806,7 +1843,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns 400 error if user does not validate' do
- put api("/users/#{user.id}", admin, admin_mode: true),
+ put api(path, admin, admin_mode: true),
params: {
password: 'pass',
email: 'test@example.com',
@@ -1874,7 +1911,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:confirmed_user) { create(:user, email: 'foo@example.com') }
it 'returns 409 conflict error' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { email: confirmed_user.email }
+ put api(path, admin, admin_mode: true), params: { email: confirmed_user.email }
expect(response).to have_gitlab_http_status(:conflict)
expect(user.reload.email).not_to eq(confirmed_user.email)
@@ -1885,7 +1922,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:unconfirmed_user) { create(:user, :unconfirmed, email: 'foo@example.com') }
it 'returns 409 conflict error' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { email: unconfirmed_user.email }
+ put api(path, admin, admin_mode: true), params: { email: unconfirmed_user.email }
expect(response).to have_gitlab_http_status(:conflict)
expect(user.reload.email).not_to eq(unconfirmed_user.email)
@@ -1898,7 +1935,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
it 'returns 409 conflict error' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { email: email.email }
+ put api(path, admin, admin_mode: true), params: { email: email.email }
expect(response).to have_gitlab_http_status(:conflict)
expect(user.reload.email).not_to eq(email.email)
@@ -1909,7 +1946,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:email) { create(:email, email: 'foo@example.com') }
it 'does not update email' do
- put api("/users/#{user.id}", admin, admin_mode: true), params: { email: email.email }
+ put api(path, admin, admin_mode: true), params: { email: email.email }
expect(response).to have_gitlab_http_status(:bad_request)
expect(user.reload.email).not_to eq(email.email)
@@ -1921,6 +1958,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe "PUT /user/:id/credit_card_validation" do
let(:credit_card_validated_time) { Time.utc(2020, 1, 1) }
let(:expiration_year) { Date.today.year + 10 }
+ let(:path) { "/user/#{user.id}/credit_card_validation" }
let(:params) do
{
credit_card_validated_at: credit_card_validated_time,
@@ -1932,9 +1970,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
}
end
+ it_behaves_like 'PUT request permissions for admin mode'
+
context 'when unauthenticated' do
it 'returns authentication error' do
- put api("/user/#{user.id}/credit_card_validation"), params: {}
+ put api(path), params: {}
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -1942,7 +1982,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when authenticated as non-admin' do
it "does not allow updating user's credit card validation" do
- put api("/user/#{user.id}/credit_card_validation", user), params: params
+ put api(path, user), params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -1950,7 +1990,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when authenticated as admin' do
it "updates user's credit card validation" do
- put api("/user/#{user.id}/credit_card_validation", admin, admin_mode: true), params: params
+ put api(path, admin, admin_mode: true), params: params
user.reload
@@ -1965,7 +2005,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns 400 error if credit_card_validated_at is missing" do
- put api("/user/#{user.id}/credit_card_validation", admin, admin_mode: true), params: {}
+ put api(path, admin, admin_mode: true), params: {}
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -1981,10 +2021,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe "DELETE /users/:id/identities/:provider" do
let(:test_user) { create(:omniauth_user, provider: 'ldapmain') }
+ let(:path) { "/users/#{test_user.id}/identities/ldapmain" }
+
+ it_behaves_like 'DELETE request permissions for admin mode'
context 'when unauthenticated' do
it 'returns authentication error' do
- delete api("/users/#{test_user.id}/identities/ldapmain")
+ delete api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -1993,17 +2036,17 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when authenticated' do
it 'deletes identity of given provider' do
expect do
- delete api("/users/#{test_user.id}/identities/ldapmain", admin, admin_mode: true)
+ delete api(path, admin, admin_mode: true)
end.to change { test_user.identities.count }.by(-1)
expect(response).to have_gitlab_http_status(:no_content)
end
it_behaves_like '412 response' do
- let(:request) { api("/users/#{test_user.id}/identities/ldapmain", admin, admin_mode: true) }
+ let(:request) { api(path, admin, admin_mode: true) }
end
it 'returns 404 error if user not found' do
- delete api("/users/0/identities/ldapmain", admin, admin_mode: true)
+ delete api("/users/#{non_existing_record_id}/identities/ldapmain", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
@@ -2019,15 +2062,21 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "POST /users/:id/keys" do
+ let(:path) { "/users/#{user.id}/keys" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { attributes_for(:key, usage_type: :signing) }
+ end
+
it "does not create invalid ssh key" do
- post api("/users/#{user.id}/keys", admin, admin_mode: true), params: { title: "invalid key" }
+ post api(path, admin, admin_mode: true), params: { title: "invalid key" }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('key is missing')
end
it 'does not create key without title' do
- post api("/users/#{user.id}/keys", admin, admin_mode: true), params: { key: 'some key' }
+ post api(path, admin, admin_mode: true), params: { key: 'some key' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('title is missing')
@@ -2037,7 +2086,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
key_attrs = attributes_for(:key, usage_type: :signing)
expect do
- post api("/users/#{user.id}/keys", admin, admin_mode: true), params: key_attrs
+ post api(path, admin, admin_mode: true), params: key_attrs
end.to change { user.keys.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
@@ -2052,20 +2101,21 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
optional_attributes = { expires_at: 3.weeks.from_now }
attributes = attributes_for(:key).merge(optional_attributes)
- post api("/users/#{user.id}/keys", admin, admin_mode: true), params: attributes
+ post api(path, admin, admin_mode: true), params: attributes
expect(response).to have_gitlab_http_status(:created)
expect(json_response['expires_at'].to_date).to eq(optional_attributes[:expires_at].to_date)
end
it "returns 400 for invalid ID" do
- post api("/users/0/keys", admin, admin_mode: true)
+ post api("/users/#{non_existing_record_id}/keys", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
end
end
describe 'GET /users/:id/project_deploy_keys' do
let(:project) { create(:project) }
+ let(:path) { "/users/#{user.id}/project_deploy_keys" }
before do
project.add_maintainer(user)
@@ -2082,7 +2132,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns array of project deploy keys with pagination' do
- get api("/users/#{user.id}/project_deploy_keys", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2094,7 +2144,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
dev_user = create(:user)
project.add_developer(dev_user)
- get api("/users/#{user.id}/project_deploy_keys", dev_user)
+ get api(path, dev_user)
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden - No common authorized project found')
@@ -2113,7 +2163,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'when no common projects for user and current_user' do
it 'forbids' do
- get api("/users/#{user.id}/project_deploy_keys", second_user)
+ get api(path, second_user)
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden - No common authorized project found')
@@ -2125,11 +2175,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
project.add_maintainer(second_user)
end
+ let(:path) { "/users/#{second_user.id}/project_deploy_keys" }
+
it 'lists only common project keys' do
expect(second_user.project_deploy_keys).to contain_exactly(
project.deploy_keys.first, second_project.deploy_keys.first)
- get api("/users/#{second_user.id}/project_deploy_keys", user)
+ get api(path, user)
expect(json_response.count).to eq(1)
expect(json_response.first['key']).to eq(project.deploy_keys.first.key)
@@ -2144,7 +2196,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
create(:deploy_key, user: second_user)
create(:deploy_key, user: third_user)
- get api("/users/#{second_user.id}/project_deploy_keys", third_user)
+ get api(path, third_user)
expect(json_response.count).to eq(2)
expect([json_response.first['key'], json_response.second['key']]).to contain_exactly(
@@ -2155,14 +2207,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
second_project.add_maintainer(user)
control_count = ActiveRecord::QueryRecorder.new do
- get api("/users/#{second_user.id}/project_deploy_keys", user)
+ get api(path, user)
end.count
deploy_key = create(:deploy_key, user: second_user)
create(:deploy_keys_project, project: second_project, deploy_key_id: deploy_key.id)
expect do
- get api("/users/#{second_user.id}/project_deploy_keys", user)
+ get api(path, user)
end.not_to exceed_query_limit(control_count)
end
end
@@ -2170,6 +2222,10 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /user/:id/keys' do
+ subject(:request) { get api(path) }
+
+ let(:path) { "/users/#{user.id}/keys" }
+
it 'returns 404 for non-existing user' do
get api("/users/#{non_existing_record_id}/keys")
@@ -2180,7 +2236,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns array of ssh keys' do
user.keys << key
- get api("/users/#{user.id}/keys")
+ request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2190,7 +2246,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns array of ssh keys with comments replaced with'\
'a simple identifier of username + hostname' do
- get api("/users/#{user.id}/keys")
+ request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2202,24 +2258,26 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'N+1 queries' do
before do
- get api("/users/#{user.id}/keys")
+ request
end
it 'avoids N+1 queries', :request_store do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/users/#{user.id}/keys")
+ request
end.count
create_list(:key, 2, user: user)
expect do
- get api("/users/#{user.id}/keys")
+ request
end.not_to exceed_all_query_limit(control_count)
end
end
end
describe 'GET /user/:user_id/keys' do
+ let(:path) { "/users/#{user.username}/keys" }
+
it 'returns 404 for non-existing user' do
get api("/users/#{non_existing_record_id}/keys")
@@ -2230,7 +2288,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns array of ssh keys' do
user.keys << key
- get api("/users/#{user.username}/keys")
+ get api(path)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2240,10 +2298,12 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /user/:id/keys/:key_id' do
+ let(:path) { "/users/#{user.id}/keys/#{key.id}" }
+
it 'gets existing key' do
user.keys << key
- get api("/users/#{user.id}/keys/#{key.id}")
+ get api(path)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['title']).to eq(key.title)
@@ -2252,7 +2312,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 404 error if user not found' do
user.keys << key
- get api("/users/0/keys/#{key.id}")
+ get api("/users/#{non_existing_record_id}/keys/#{key.id}")
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
@@ -2267,6 +2327,10 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'DELETE /user/:id/keys/:key_id' do
+ let(:path) { "/users/#{user.id}/keys/#{key.id}" }
+
+ it_behaves_like 'DELETE request permissions for admin mode'
+
context 'when unauthenticated' do
it 'returns authentication error' do
delete api("/users/#{user.id}/keys/#{non_existing_record_id}")
@@ -2279,20 +2343,20 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.keys << key
expect do
- delete api("/users/#{user.id}/keys/#{key.id}", admin, admin_mode: true)
+ delete api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { user.keys.count }.by(-1)
end
it_behaves_like '412 response' do
- let(:request) { api("/users/#{user.id}/keys/#{key.id}", admin, admin_mode: true) }
+ let(:request) { api(path, admin, admin_mode: true) }
end
it 'returns 404 error if user not found' do
user.keys << key
- delete api("/users/0/keys/#{key.id}", admin, admin_mode: true)
+ delete api("/users/#{non_existing_record_id}/keys/#{key.id}", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -2306,8 +2370,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /users/:id/gpg_keys' do
+ let(:path) { "/users/#{user.id}/gpg_keys" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { attributes_for :gpg_key, key: GpgHelpers::User2.public_key }
+ end
+
it 'does not create invalid GPG key' do
- post api("/users/#{user.id}/gpg_keys", admin, admin_mode: true)
+ post api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('key is missing')
@@ -2317,22 +2387,24 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
key_attrs = attributes_for :gpg_key, key: GpgHelpers::User2.public_key
expect do
- post api("/users/#{user.id}/gpg_keys", admin, admin_mode: true), params: key_attrs
+ post api(path, admin, admin_mode: true), params: key_attrs
expect(response).to have_gitlab_http_status(:created)
end.to change { user.gpg_keys.count }.by(1)
end
it 'returns 400 for invalid ID' do
- post api('/users/0/gpg_keys', admin, admin_mode: true)
+ post api("/users/#{non_existing_record_id}/gpg_keys", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
end
end
describe 'GET /user/:id/gpg_keys' do
+ let(:path) { "/users/#{user.id}/gpg_keys" }
+
it 'returns 404 for non-existing user' do
- get api('/users/0/gpg_keys')
+ get api("/users/#{non_existing_record_id}/gpg_keys")
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
@@ -2341,7 +2413,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns array of GPG keys' do
user.gpg_keys << gpg_key
- get api("/users/#{user.id}/gpg_keys")
+ get api(path)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2351,15 +2423,17 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /user/:id/gpg_keys/:key_id' do
+ let(:path) { "/users/#{user.id}/gpg_keys/#{gpg_key.id}" }
+
it 'returns 404 for non-existing user' do
- get api('/users/0/gpg_keys/1')
+ get api("/users/#{non_existing_record_id}/gpg_keys/1")
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'returns 404 for non-existing key' do
- get api("/users/#{user.id}/gpg_keys/0")
+ get api("/users/#{user.id}/gpg_keys/#{non_existing_record_id}")
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 GPG Key Not Found')
@@ -2368,7 +2442,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns a single GPG key' do
user.gpg_keys << gpg_key
- get api("/users/#{user.id}/gpg_keys/#{gpg_key.id}")
+ get api(path)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['key']).to eq(gpg_key.key)
@@ -2376,6 +2450,10 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'DELETE /user/:id/gpg_keys/:key_id' do
+ let(:path) { "/users/#{user.id}/gpg_keys/#{gpg_key.id}" }
+
+ it_behaves_like 'DELETE request permissions for admin mode'
+
context 'when unauthenticated' do
it 'returns authentication error' do
delete api("/users/#{user.id}/keys/#{non_existing_record_id}")
@@ -2389,7 +2467,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.gpg_keys << gpg_key
expect do
- delete api("/users/#{user.id}/gpg_keys/#{gpg_key.id}", admin, admin_mode: true)
+ delete api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { user.gpg_keys.count }.by(-1)
@@ -2398,7 +2476,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 404 error if user not found' do
user.keys << key
- delete api("/users/0/gpg_keys/#{gpg_key.id}", admin, admin_mode: true)
+ delete api("/users/#{non_existing_record_id}/gpg_keys/#{gpg_key.id}", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
@@ -2414,6 +2492,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /user/:id/gpg_keys/:key_id/revoke' do
+ let(:path) { "/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { {} }
+ let(:success_status_code) { :accepted }
+ end
+
context 'when unauthenticated' do
it 'returns authentication error' do
post api("/users/#{user.id}/gpg_keys/#{non_existing_record_id}/revoke")
@@ -2427,7 +2512,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.gpg_keys << gpg_key
expect do
- post api("/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke", admin, admin_mode: true)
+ post api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:accepted)
end.to change { user.gpg_keys.count }.by(-1)
@@ -2436,7 +2521,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 404 error if user not found' do
user.gpg_keys << gpg_key
- post api("/users/0/gpg_keys/#{gpg_key.id}/revoke", admin, admin_mode: true)
+ post api("/users/#{non_existing_record_id}/gpg_keys/#{gpg_key.id}/revoke", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
@@ -2452,8 +2537,19 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "POST /users/:id/emails", :mailer do
+ let(:path) { "/users/#{user.id}/emails" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ before do
+ email_attrs[:skip_confirmation] = true
+ end
+
+ let(:email_attrs) { attributes_for :email }
+ let(:params) { email_attrs }
+ end
+
it "does not create invalid email" do
- post api("/users/#{user.id}/emails", admin, admin_mode: true), params: {}
+ post api(path, admin, admin_mode: true), params: {}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('email is missing')
@@ -2464,7 +2560,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
perform_enqueued_jobs do
expect do
- post api("/users/#{user.id}/emails", admin, admin_mode: true), params: email_attrs
+ post api(path, admin, admin_mode: true), params: email_attrs
end.to change { user.emails.count }.by(1)
end
@@ -2473,7 +2569,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "returns a 400 for invalid ID" do
- post api("/users/0/emails", admin, admin_mode: true)
+ post api("/users/#{non_existing_record_id}/emails", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -2482,7 +2578,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
email_attrs = attributes_for :email
email_attrs[:skip_confirmation] = true
- post api("/users/#{user.id}/emails", admin, admin_mode: true), params: email_attrs
+ post api(path, admin, admin_mode: true), params: email_attrs
expect(response).to have_gitlab_http_status(:created)
@@ -2494,7 +2590,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:confirmed_user) { create(:user, email: 'foo@example.com') }
it 'returns 400 error' do
- post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: confirmed_user.email }
+ post api(path, admin, admin_mode: true), params: { email: confirmed_user.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -2504,7 +2600,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:unconfirmed_user) { create(:user, :unconfirmed, email: 'foo@example.com') }
it 'returns 400 error' do
- post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: unconfirmed_user.email }
+ post api(path, admin, admin_mode: true), params: { email: unconfirmed_user.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -2516,7 +2612,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
it 'returns 400 error' do
- post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: email.email }
+ post api(path, admin, admin_mode: true), params: { email: email.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -2526,7 +2622,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let!(:email) { create(:email, email: 'foo@example.com') }
it 'returns 400 error' do
- post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: email.email }
+ post api(path, admin, admin_mode: true), params: { email: email.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -2535,16 +2631,18 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /user/:id/emails' do
+ let(:path) { "/users/#{user.id}/emails" }
+
context 'when unauthenticated' do
it 'returns authentication error' do
- get api("/users/#{user.id}/emails")
+ get api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when authenticated' do
it 'returns 404 for non-existing user' do
- get api('/users/0/emails', admin, admin_mode: true)
+ get api("/users/#{non_existing_record_id}/emails", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -2552,7 +2650,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns array of emails' do
user.emails << email
- get api("/users/#{user.id}/emails", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2570,6 +2668,10 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'DELETE /user/:id/emails/:email_id' do
+ let(:path) { "/users/#{user.id}/emails/#{email.id}" }
+
+ it_behaves_like 'DELETE request permissions for admin mode'
+
context 'when unauthenticated' do
it 'returns authentication error' do
delete api("/users/#{user.id}/emails/#{non_existing_record_id}")
@@ -2582,20 +2684,20 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.emails << email
expect do
- delete api("/users/#{user.id}/emails/#{email.id}", admin, admin_mode: true)
+ delete api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { user.emails.count }.by(-1)
end
it_behaves_like '412 response' do
- let(:request) { api("/users/#{user.id}/emails/#{email.id}", admin, admin_mode: true) }
+ subject(:request) { api(path, admin, admin_mode: true) }
end
it 'returns 404 error if user not found' do
user.emails << email
- delete api("/users/0/emails/#{email.id}", admin, admin_mode: true)
+ delete api("/users/#{non_existing_record_id}/emails/#{email.id}", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -2616,9 +2718,12 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe "DELETE /users/:id" do
let_it_be(:issue) { create(:issue, author: user) }
+ let(:path) { "/users/#{user.id}" }
+
+ it_behaves_like 'DELETE request permissions for admin mode'
it "deletes user", :sidekiq_inline do
- perform_enqueued_jobs { delete api("/users/#{user.id}", admin, admin_mode: true) }
+ perform_enqueued_jobs { delete api(path, admin, admin_mode: true) }
expect(response).to have_gitlab_http_status(:no_content)
expect(Users::GhostUserMigration.where(user: user,
@@ -2630,7 +2735,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context "hard delete disabled" do
it "does not delete user" do
- perform_enqueued_jobs { delete api("/users/#{user.id}", admin, admin_mode: true) }
+ perform_enqueued_jobs { delete api(path, admin, admin_mode: true) }
expect(response).to have_gitlab_http_status(:conflict)
end
end
@@ -2661,21 +2766,21 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it_behaves_like '412 response' do
- let(:request) { api("/users/#{user.id}", admin, admin_mode: true) }
+ let(:request) { api(path, admin, admin_mode: true) }
end
it "does not delete for unauthenticated user" do
- perform_enqueued_jobs { delete api("/users/#{user.id}") }
+ perform_enqueued_jobs { delete api(path) }
expect(response).to have_gitlab_http_status(:unauthorized)
end
it "is not available for non admin users" do
- perform_enqueued_jobs { delete api("/users/#{user.id}", user) }
+ perform_enqueued_jobs { delete api(path, user) }
expect(response).to have_gitlab_http_status(:forbidden)
end
it "returns 404 for non-existing user" do
- perform_enqueued_jobs { delete api("/users/0", admin, admin_mode: true) }
+ perform_enqueued_jobs { delete api("/users/#{non_existing_record_id}", admin, admin_mode: true) }
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -2688,7 +2793,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context "hard delete disabled" do
it "moves contributions to the ghost user", :sidekiq_might_not_need_inline do
- perform_enqueued_jobs { delete api("/users/#{user.id}", admin, admin_mode: true) }
+ perform_enqueued_jobs { delete api(path, admin, admin_mode: true) }
expect(response).to have_gitlab_http_status(:no_content)
expect(issue.reload).to be_persisted
@@ -2711,6 +2816,8 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "GET /user" do
+ let(:path) { '/user' }
+
shared_examples 'get user info' do |version|
context 'with regular user' do
context 'with personal access token' do
@@ -2724,7 +2831,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns current user without private token when sudo not defined' do
- get api("/user", user, version: version)
+ get api(path, user, version: version)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/user/public')
@@ -2732,7 +2839,6 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context "scopes" do
- let(:path) { "/user" }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope', version
@@ -2761,7 +2867,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'with unauthenticated user' do
it "returns 401 error if user is unauthenticated" do
- get api("/user", version: version)
+ get api(path, version: version)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -2773,9 +2879,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "GET /user/preferences" do
+ let(:path) { '/user/preferences' }
+
context "when unauthenticated" do
it "returns authentication error" do
- get api("/user/preferences")
+ get api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
@@ -2786,7 +2894,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.user_preference.show_whitespace_in_diffs = true
user.save!
- get api("/user/preferences", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["view_diffs_file_by_file"]).to eq(user.user_preference.view_diffs_file_by_file)
@@ -2796,6 +2904,10 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "GET /user/keys" do
+ subject(:request) { get api(path, user) }
+
+ let(:path) { "/user/keys" }
+
context "when unauthenticated" do
it "returns authentication error" do
get api("/user/keys")
@@ -2807,7 +2919,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "returns array of ssh keys" do
user.keys << key
- get api("/user/keys", user)
+ request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2817,7 +2929,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns array of ssh keys with comments replaced with'\
'a simple identifier of username + hostname' do
- get api("/user/keys", user)
+ request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -2829,24 +2941,23 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'N+1 queries' do
before do
- get api("/user/keys", user)
+ request
end
it 'avoids N+1 queries', :request_store do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/user/keys", user)
+ request
end.count
create_list(:key, 2, user: user)
expect do
- get api("/user/keys", user)
+ request
end.not_to exceed_all_query_limit(control_count)
end
end
context "scopes" do
- let(:path) { "/user/keys" }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
@@ -2855,16 +2966,18 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "GET /user/keys/:key_id" do
+ let(:path) { "/user/keys/#{key.id}" }
+
it "returns single key" do
user.keys << key
- get api("/user/keys/#{key.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["title"]).to eq(key.title)
end
it 'exposes SSH key comment as a simple identifier of username + hostname' do
- get api("/user/keys/#{key.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['key']).to include("#{key.user_name} (#{Gitlab.config.gitlab.host})")
@@ -2881,7 +2994,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.keys << key
admin
- get api("/user/keys/#{key.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Key Not Found')
end
@@ -2893,7 +3006,6 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context "scopes" do
- let(:path) { "/user/keys/#{key.id}" }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
@@ -2901,11 +3013,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "POST /user/keys" do
+ let(:path) { "/user/keys" }
+
it "creates ssh key" do
key_attrs = attributes_for(:key, usage_type: :signing)
expect do
- post api("/user/keys", user), params: key_attrs
+ post api(path, user), params: key_attrs
end.to change { user.keys.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
@@ -2920,19 +3034,19 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
optional_attributes = { expires_at: 3.weeks.from_now }
attributes = attributes_for(:key).merge(optional_attributes)
- post api("/user/keys", user), params: attributes
+ post api(path, user), params: attributes
expect(response).to have_gitlab_http_status(:created)
expect(json_response['expires_at'].to_date).to eq(optional_attributes[:expires_at].to_date)
end
it "returns a 401 error if unauthorized" do
- post api("/user/keys"), params: { title: 'some title', key: 'some key' }
+ post api(path), params: { title: 'some title', key: 'some key' }
expect(response).to have_gitlab_http_status(:unauthorized)
end
it "does not create ssh key without key" do
- post api("/user/keys", user), params: { title: 'title' }
+ post api(path, user), params: { title: 'title' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('key is missing')
@@ -2946,24 +3060,26 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it "does not create ssh key without title" do
- post api("/user/keys", user), params: { key: "somekey" }
+ post api(path, user), params: { key: "somekey" }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
describe "DELETE /user/keys/:key_id" do
+ let(:path) { "/user/keys/#{key.id}" }
+
it "deletes existed key" do
user.keys << key
expect do
- delete api("/user/keys/#{key.id}", user)
+ delete api(path, user)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { user.keys.count }.by(-1)
end
it_behaves_like '412 response' do
- let(:request) { api("/user/keys/#{key.id}", user) }
+ let(:request) { api(path, user) }
end
it "returns 404 if key ID not found" do
@@ -2976,7 +3092,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "returns 401 error if unauthorized" do
user.keys << key
- delete api("/user/keys/#{key.id}")
+ delete api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -2988,9 +3104,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /user/gpg_keys' do
+ let(:path) { '/user/gpg_keys' }
+
context 'when unauthenticated' do
it 'returns authentication error' do
- get api('/user/gpg_keys')
+ get api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -3000,7 +3118,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns array of GPG keys' do
user.gpg_keys << gpg_key
- get api('/user/gpg_keys', user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -3009,7 +3127,6 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context 'scopes' do
- let(:path) { '/user/gpg_keys' }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
@@ -3018,10 +3135,12 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET /user/gpg_keys/:key_id' do
+ let(:path) { "/user/gpg_keys/#{gpg_key.id}" }
+
it 'returns a single key' do
user.gpg_keys << gpg_key
- get api("/user/gpg_keys/#{gpg_key.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['key']).to eq(gpg_key.key)
@@ -3037,7 +3156,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "returns 404 error if admin accesses user's GPG key" do
user.gpg_keys << gpg_key
- get api("/user/gpg_keys/#{gpg_key.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 GPG Key Not Found')
@@ -3050,7 +3169,6 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context 'scopes' do
- let(:path) { "/user/gpg_keys/#{gpg_key.id}" }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
@@ -3058,24 +3176,26 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /user/gpg_keys' do
+ let(:path) { '/user/gpg_keys' }
+
it 'creates a GPG key' do
key_attrs = attributes_for :gpg_key, key: GpgHelpers::User2.public_key
expect do
- post api('/user/gpg_keys', user), params: key_attrs
+ post api(path, user), params: key_attrs
expect(response).to have_gitlab_http_status(:created)
end.to change { user.gpg_keys.count }.by(1)
end
it 'returns a 401 error if unauthorized' do
- post api('/user/gpg_keys'), params: { key: 'some key' }
+ post api(path), params: { key: 'some key' }
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'does not create GPG key without key' do
- post api('/user/gpg_keys', user)
+ post api(path, user)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('key is missing')
@@ -3116,11 +3236,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'DELETE /user/gpg_keys/:key_id' do
+ let(:path) { "/user/gpg_keys/#{gpg_key.id}" }
+
it 'deletes existing GPG key' do
user.gpg_keys << gpg_key
expect do
- delete api("/user/gpg_keys/#{gpg_key.id}", user)
+ delete api(path, user)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { user.gpg_keys.count }.by(-1)
@@ -3136,7 +3258,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'returns 401 error if unauthorized' do
user.gpg_keys << gpg_key
- delete api("/user/gpg_keys/#{gpg_key.id}")
+ delete api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -3149,9 +3271,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "GET /user/emails" do
+ let(:path) { '/user/emails' }
+
context "when unauthenticated" do
it "returns authentication error" do
- get api("/user/emails")
+ get api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
@@ -3160,7 +3284,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "returns array of emails" do
user.emails << email
- get api("/user/emails", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -3170,7 +3294,6 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context "scopes" do
- let(:path) { "/user/emails" }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
@@ -3179,10 +3302,12 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "GET /user/emails/:email_id" do
+ let(:path) { "/user/emails/#{email.id}" }
+
it "returns single email" do
user.emails << email
- get api("/user/emails/#{email.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["email"]).to eq(email.email)
end
@@ -3197,7 +3322,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
user.emails << email
admin
- get api("/user/emails/#{email.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Email Not Found')
end
@@ -3209,7 +3334,6 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context "scopes" do
- let(:path) { "/user/emails/#{email.id}" }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
@@ -3217,21 +3341,23 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "POST /user/emails" do
+ let(:path) { '/user/emails' }
+
it "creates email" do
email_attrs = attributes_for :email
expect do
- post api("/user/emails", user), params: email_attrs
+ post api(path, user), params: email_attrs
end.to change { user.emails.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
end
it "returns a 401 error if unauthorized" do
- post api("/user/emails"), params: { email: 'some email' }
+ post api(path), params: { email: 'some email' }
expect(response).to have_gitlab_http_status(:unauthorized)
end
it "does not create email with invalid email" do
- post api("/user/emails", user), params: {}
+ post api(path, user), params: {}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('email is missing')
@@ -3239,18 +3365,20 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "DELETE /user/emails/:email_id" do
+ let(:path) { "/user/emails/#{email.id}" }
+
it "deletes existed email" do
user.emails << email
expect do
- delete api("/user/emails/#{email.id}", user)
+ delete api(path, user)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { user.emails.count }.by(-1)
end
it_behaves_like '412 response' do
- let(:request) { api("/user/emails/#{email.id}", user) }
+ let(:request) { api(path, user) }
end
it "returns 404 if email ID not found" do
@@ -3263,7 +3391,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it "returns 401 error if unauthorized" do
user.emails << email
- delete api("/user/emails/#{email.id}")
+ delete api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -3283,12 +3411,18 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /users/:id/activate' do
- subject(:activate) { post api("/users/#{user_id}/activate", api_user, admin_mode: true) }
+ subject(:activate) { post api(path, api_user, **params) }
let(:user_id) { user.id }
+ let(:path) { "/users/#{user_id}/activate" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { {} }
+ end
context 'performed by a non-admin user' do
let(:api_user) { user }
+ let(:params) { { admin_mode: false } }
it 'is not authorized to perform the action' do
activate
@@ -3299,6 +3433,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'performed by an admin user' do
let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'for a deactivated user' do
let(:user_id) { deactivated_user.id }
@@ -3351,7 +3486,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context 'for a user that does not exist' do
- let(:user_id) { 0 }
+ let(:user_id) { non_existing_record_id }
before do
activate
@@ -3363,12 +3498,18 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /users/:id/deactivate' do
- subject(:deactivate) { post api("/users/#{user_id}/deactivate", api_user, admin_mode: true) }
+ subject(:deactivate) { post api(path, api_user, **params) }
let(:user_id) { user.id }
+ let(:path) { "/users/#{user_id}/deactivate" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { {} }
+ end
context 'performed by a non-admin user' do
let(:api_user) { user }
+ let(:params) { { admin_mode: false } }
it 'is not authorized to perform the action' do
deactivate
@@ -3379,6 +3520,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'performed by an admin user' do
let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'for an active user' do
let(:activity) { {} }
@@ -3457,7 +3599,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
context 'for a user that does not exist' do
- let(:user_id) { 0 }
+ let(:user_id) { non_existing_record_id }
before do
deactivate
@@ -3480,11 +3622,19 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /users/:id/approve' do
- subject(:approve) { post api("/users/#{user_id}/approve", api_user, admin_mode: true) }
+ subject(:approve) { post api(path, api_user, **params) }
+
+ let(:path) { "/users/#{user_id}/approve" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:user_id) { pending_user.id }
+ let(:params) { {} }
+ end
context 'performed by a non-admin user' do
let(:api_user) { user }
let(:user_id) { pending_user.id }
+ let(:params) { { admin_mode: false } }
it 'is not authorized to perform the action' do
expect { approve }.not_to change { pending_user.reload.state }
@@ -3495,6 +3645,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'performed by an admin user' do
let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'for a deactivated user' do
let(:user_id) { deactivated_user.id }
@@ -3559,7 +3710,15 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /users/:id/reject' do
- subject(:reject) { post api("/users/#{user_id}/reject", api_user, admin_mode: true) }
+ subject(:reject) { post api(path, api_user, **params) }
+
+ let(:path) { "/users/#{user_id}/reject" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:user_id) { pending_user.id }
+ let(:params) { {} }
+ let(:success_status_code) { :success }
+ end
shared_examples 'returns 409' do
it 'returns 409' do
@@ -3573,6 +3732,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'performed by a non-admin user' do
let(:api_user) { user }
let(:user_id) { pending_user.id }
+ let(:params) { { admin_mode: false } }
it 'returns 403' do
expect { reject }.not_to change { pending_user.reload.state }
@@ -3583,6 +3743,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'performed by an admin user' do
let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'for an pending approval user' do
let(:user_id) { pending_user.id }
@@ -3649,12 +3810,20 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /users/:id/block' do
+ subject(:block_user) { post api(path, api_user, **params) }
+
+ let(:user_id) { user.id }
+ let(:path) { "/users/#{user_id}/block" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { {} }
+ end
+
context 'when admin' do
- subject(:block_user) { post api("/users/#{user_id}/block", admin, admin_mode: true) }
+ let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'with an existing user' do
- let(:user_id) { user.id }
-
it 'blocks existing user' do
block_user
@@ -3730,21 +3899,34 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
end
- it 'is not available for non admin users' do
- post api("/users/#{user.id}/block", user)
+ context 'performed by a non-admin user' do
+ let(:api_user) { user }
+ let(:params) { { admin_mode: false } }
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(user.reload.state).to eq('active')
+ it 'returns 403' do
+ block_user
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(user.reload.state).to eq('active')
+ end
end
end
describe 'POST /users/:id/unblock' do
+ subject(:unblock_user) { post api(path, api_user, **params) }
+
+ let(:path) { "/users/#{user_id}/unblock" }
+ let(:user_id) { user.id }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { {} }
+ end
+
context 'when admin' do
- subject(:unblock_user) { post api("/users/#{user_id}/unblock", admin, admin_mode: true) }
+ let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'with an existing user' do
- let(:user_id) { user.id }
-
it 'unblocks existing user' do
unblock_user
@@ -3817,20 +3999,34 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
end
- it 'is not available for non admin users' do
- post api("/users/#{user.id}/unblock", user)
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(user.reload.state).to eq('active')
+ context 'performed by a non-admin user' do
+ let(:api_user) { user }
+ let(:params) { { admin_mode: false } }
+
+ it 'returns 403' do
+ unblock_user
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(user.reload.state).to eq('active')
+ end
end
end
describe 'POST /users/:id/ban' do
+ subject(:ban_user) { post api(path, api_user, **params) }
+
+ let(:path) { "/users/#{user_id}/ban" }
+ let(:user_id) { user.id }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { {} }
+ end
+
context 'when admin' do
- subject(:ban_user) { post api("/users/#{user_id}/ban", admin, admin_mode: true) }
+ let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'with an active user' do
- let(:user_id) { user.id }
-
it 'bans an active user' do
ban_user
@@ -3898,17 +4094,32 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
end
- it 'is not available for non-admin users' do
- post api("/users/#{user.id}/ban", user)
+ context 'performed by a non-admin user' do
+ let(:api_user) { user }
+ let(:params) { { admin_mode: false } }
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(user.reload.state).to eq('active')
+ it 'returns 403' do
+ ban_user
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(user.reload.state).to eq('active')
+ end
end
end
describe 'POST /users/:id/unban' do
+ subject(:unban_user) { post api(path, api_user, **params) }
+
+ let(:path) { "/users/#{user_id}/unban" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:user_id) { banned_user.id }
+ let(:params) { {} }
+ end
+
context 'when admin' do
- subject(:unban_user) { post api("/users/#{user_id}/unban", admin, admin_mode: true) }
+ let(:api_user) { admin }
+ let(:params) { { admin_mode: true } }
context 'with a banned user' do
let(:user_id) { banned_user.id }
@@ -3979,39 +4190,42 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
end
- it 'is not available for non admin users' do
- post api("/users/#{banned_user.id}/unban", user)
+ context 'performed by a non-admin user' do
+ let(:api_user) { user }
+ let(:params) { { admin_mode: false } }
+ let(:user_id) { banned_user.id }
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(user.reload.state).to eq('active')
+ it 'returns 403' do
+ unban_user
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(user.reload.state).to eq('active')
+ end
end
end
describe "GET /users/:id/memberships" do
+ subject(:request) { get api(path, requesting_user, admin_mode: true) }
+
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
let(:requesting_user) { create(:user) }
+ let(:path) { "/users/#{user.id}/memberships" }
before_all do
project.add_guest(user)
group.add_guest(user)
end
- it "responses with 403" do
- get api("/users/#{user.id}/memberships", requesting_user)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
+ it_behaves_like 'GET request permissions for admin mode'
context 'requested by admin user' do
let(:requesting_user) { create(:user, :admin) }
- subject { get api("/users/#{user.id}/memberships", requesting_user, admin_mode: true) }
-
it "responses successfully" do
- subject
+ request
aggregate_failures 'expect successful response including groups and projects' do
expect(response).to have_gitlab_http_status(:ok)
@@ -4026,17 +4240,17 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
it 'does not submit N+1 DB queries' do
# Avoid setup queries
- subject
+ request
expect(response).to have_gitlab_http_status(:ok)
control = ActiveRecord::QueryRecorder.new do
- subject
+ request
end
create_list(:project, 5).map { |project| project.add_guest(user) }
expect do
- subject
+ request
end.not_to exceed_query_limit(control)
end
@@ -4071,10 +4285,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context "user activities", :clean_gitlab_redis_shared_state do
let_it_be(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) }
let_it_be(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) }
+ let(:path) { '/user/activities' }
+
+ it_behaves_like 'GET request permissions for admin mode'
context 'last activity as normal user' do
it 'has no permission' do
- get api("/user/activities", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -4082,7 +4299,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'as admin' do
it 'returns the activities from the last 6 months' do
- get api("/user/activities", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to include_pagination_headers
expect(json_response.size).to eq(1)
@@ -4096,7 +4313,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'passing a :from parameter' do
it 'returns the activities from the given date' do
- get api("/user/activities?from=2000-1-1", admin, admin_mode: true)
+ get api("#{path}?from=2000-1-1", admin, admin_mode: true)
expect(response).to include_pagination_headers
expect(json_response.size).to eq(2)
@@ -4116,6 +4333,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let(:user_with_status) { user_status.user }
let(:params) { {} }
let(:request_user) { user }
+ let(:path) { '/user/status' }
shared_examples '/user/status successful response' do
context 'when request is successful' do
@@ -4197,13 +4415,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'GET' do
- let(:path) { '/user/status' }
-
it_behaves_like 'rendering user status'
end
describe 'PUT' do
- subject(:set_user_status) { put api('/user/status', request_user), params: params }
+ subject(:set_user_status) { put api(path, request_user), params: params }
include_examples '/user/status successful response'
@@ -4238,7 +4454,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'PATCH' do
- subject(:set_user_status) { patch api('/user/status', request_user), params: params }
+ subject(:set_user_status) { patch api(path, request_user), params: params }
include_examples '/user/status successful response'
@@ -4277,57 +4493,41 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let(:name) { 'new pat' }
let(:expires_at) { 3.days.from_now.to_date.to_s }
let(:scopes) { %w(api read_user) }
+ let(:path) { "/users/#{user.id}/personal_access_tokens" }
+ let(:params) { { name: name, scopes: scopes, expires_at: expires_at } }
+
+ it_behaves_like 'POST request permissions for admin mode'
it 'returns error if required attributes are missing' do
- post api("/users/#{user.id}/personal_access_tokens", admin, admin_mode: true)
+ post api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('name is missing, scopes is missing, scopes does not have a valid value')
end
it 'returns a 404 error if user not found' do
- post api("/users/#{non_existing_record_id}/personal_access_tokens", admin, admin_mode: true),
- params: {
- name: name,
- scopes: scopes,
- expires_at: expires_at
- }
+ post api("/users/#{non_existing_record_id}/personal_access_tokens", admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'returns a 401 error when not authenticated' do
- post api("/users/#{user.id}/personal_access_tokens"),
- params: {
- name: name,
- scopes: scopes,
- expires_at: expires_at
- }
+ post api(path), params: params
expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response['message']).to eq('401 Unauthorized')
end
it 'returns a 403 error when authenticated as normal user' do
- post api("/users/#{user.id}/personal_access_tokens", user),
- params: {
- name: name,
- scopes: scopes,
- expires_at: expires_at
- }
+ post api(path, user), params: params
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden')
end
it 'creates a personal access token when authenticated as admin' do
- post api("/users/#{user.id}/personal_access_tokens", admin, admin_mode: true),
- params: {
- name: name,
- expires_at: expires_at,
- scopes: scopes
- }
+ post api(path, admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(name)
@@ -4354,12 +4554,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns the error' do
- post api("/users/#{user.id}/personal_access_tokens", personal_access_token: admin_personal_access_token),
- params: {
- name: name,
- expires_at: expires_at,
- scopes: scopes
- }
+ post api(path, personal_access_token: admin_personal_access_token), params: params
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq(error_message)
@@ -4373,6 +4568,9 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let_it_be(:expired_personal_access_token) { create(:personal_access_token, :expired, user: user) }
let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
let_it_be(:revoked_impersonation_token) { create(:personal_access_token, :impersonation, :revoked, user: user) }
+ let(:path) { "/users/#{user.id}/impersonation_tokens" }
+
+ it_behaves_like 'GET request permissions for admin mode'
it 'returns a 404 error if user not found' do
get api("/users/#{non_existing_record_id}/impersonation_tokens", admin, admin_mode: true)
@@ -4389,7 +4587,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns an array of all impersonated tokens' do
- get api("/users/#{user.id}/impersonation_tokens", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -4398,7 +4596,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns an array of active impersonation tokens if state active' do
- get api("/users/#{user.id}/impersonation_tokens?state=active", admin, admin_mode: true)
+ get api("#{path}?state=active", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -4408,7 +4606,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns an array of inactive personal access tokens if active is set to false' do
- get api("/users/#{user.id}/impersonation_tokens?state=inactive", admin, admin_mode: true)
+ get api("#{path}?state=inactive", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
@@ -4422,9 +4620,13 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let(:expires_at) { '2016-12-28' }
let(:scopes) { %w(api read_user) }
let(:impersonation) { true }
+ let(:path) { "/users/#{user.id}/impersonation_tokens" }
+ let(:params) { { name: name, expires_at: expires_at, scopes: scopes, impersonation: impersonation } }
+
+ it_behaves_like 'POST request permissions for admin mode'
it 'returns validation error if impersonation token misses some attributes' do
- post api("/users/#{user.id}/impersonation_tokens", admin, admin_mode: true)
+ post api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('name is missing')
@@ -4442,7 +4644,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns a 403 error when authenticated as normal user' do
- post api("/users/#{user.id}/impersonation_tokens", user),
+ post api(path, user),
params: {
name: name,
expires_at: expires_at
@@ -4453,13 +4655,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'creates a impersonation token' do
- post api("/users/#{user.id}/impersonation_tokens", admin, admin_mode: true),
- params: {
- name: name,
- expires_at: expires_at,
- scopes: scopes,
- impersonation: impersonation
- }
+ post api(path, admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(name)
@@ -4477,6 +4673,9 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'GET /users/:user_id/impersonation_tokens/:impersonation_token_id' do
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+ let(:path) { "/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}" }
+
+ it_behaves_like 'GET request permissions for admin mode'
it 'returns 404 error if user not found' do
get api("/users/#{non_existing_record_id}/impersonation_tokens/1", admin, admin_mode: true)
@@ -4500,14 +4699,14 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns a 403 error when authenticated as normal user' do
- get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden')
end
it 'returns an impersonation token' do
- get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['token']).not_to be_present
@@ -4518,6 +4717,9 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'DELETE /users/:user_id/impersonation_tokens/:impersonation_token_id' do
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
+ let(:path) { "/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}" }
+
+ it_behaves_like 'DELETE request permissions for admin mode'
it 'returns a 404 error if user not found' do
delete api("/users/#{non_existing_record_id}/impersonation_tokens/1", admin, admin_mode: true)
@@ -4541,18 +4743,18 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns a 403 error when authenticated as normal user' do
- delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", user)
+ delete api(path, user)
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden')
end
it_behaves_like '412 response' do
- let(:request) { api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin, admin_mode: true) }
+ let(:request) { api(path, admin, admin_mode: true) }
end
it 'revokes a impersonation token' do
- delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin, admin_mode: true)
+ delete api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:no_content)
expect(impersonation_token.revoked).to be_falsey
@@ -4563,6 +4765,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'GET /users/:id/associations_count' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :public, group: group) }
+ let(:path) { "/users/#{user.id}/associations_count" }
let(:associations) do
{
groups_count: 1,
@@ -4579,9 +4782,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
create_list(:issue, 2, project: project, author: user)
end
+ it_behaves_like 'GET request permissions for admin mode'
+
context 'as an unauthorized user' do
it 'returns 401 unauthorized' do
- get api("/users/#{user.id}/associations_count", nil)
+ get api(path, nil)
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -4598,7 +4803,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'with the current user id' do
it 'returns valid JSON response' do
- get api("/users/#{user.id}/associations_count", user)
+ get api(path, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_a Hash
@@ -4618,7 +4823,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
context 'with valid user id' do
it 'returns valid JSON response' do
- get api("/users/#{user.id}/associations_count", admin, admin_mode: true)
+ get api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_a Hash
@@ -4634,7 +4839,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe 'POST /user/runners', feature_category: :runner_fleet do
- subject(:request) { post api('/user/runners', current_user, **post_args), params: runner_attrs }
+ subject(:request) { post api(path, current_user, **post_args), params: runner_attrs }
let_it_be(:group_owner) { create(:user) }
let_it_be(:group) { create(:group) }
@@ -4642,6 +4847,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
let(:post_args) { { admin_mode: true } }
let(:runner_attrs) { { runner_type: 'instance_type' } }
+ let(:path) { '/user/runners' }
before do
group.add_owner(group_owner)
@@ -4791,7 +4997,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
it 'returns a 401 error if unauthorized' do
- post api('/user/runners'), params: runner_attrs
+ post api(path), params: runner_attrs
expect(response).to have_gitlab_http_status(:unauthorized)
end
diff --git a/spec/requests/registrations_controller_spec.rb b/spec/requests/registrations_controller_spec.rb
index 89681485de3..8b857046a4d 100644
--- a/spec/requests/registrations_controller_spec.rb
+++ b/spec/requests/registrations_controller_spec.rb
@@ -12,7 +12,6 @@ RSpec.describe RegistrationsController, type: :request, feature_category: :syste
before do
stub_application_setting_enum('email_confirmation_setting', 'hard')
stub_application_setting(require_admin_approval_after_user_signup: false)
- stub_feature_flags(soft_email_confirmation: false)
end
it 'redirects to the `users_almost_there_path`', unless: Gitlab.ee? do
diff --git a/spec/rubocop/cop/background_migration/feature_category_spec.rb b/spec/rubocop/cop/background_migration/feature_category_spec.rb
index 359520b1d9f..1d1b6cfad5a 100644
--- a/spec/rubocop/cop/background_migration/feature_category_spec.rb
+++ b/spec/rubocop/cop/background_migration/feature_category_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/background_migration/feature_category'
RSpec.describe RuboCop::Cop::BackgroundMigration::FeatureCategory, feature_category: :database do
- let(:cop) { described_class.new }
-
context 'for non background migrations' do
before do
allow(cop).to receive(:in_background_migration?).and_return(false)
diff --git a/spec/rubocop/cop/gitlab/avoid_feature_get_spec.rb b/spec/rubocop/cop/gitlab/avoid_feature_get_spec.rb
index b5017bebd28..1531042c23a 100644
--- a/spec/rubocop/cop/gitlab/avoid_feature_get_spec.rb
+++ b/spec/rubocop/cop/gitlab/avoid_feature_get_spec.rb
@@ -7,8 +7,6 @@ require_relative '../../../../rubocop/cop/gitlab/avoid_feature_get'
RSpec.describe RuboCop::Cop::Gitlab::AvoidFeatureGet do
let(:msg) { described_class::MSG }
- subject(:cop) { described_class.new }
-
it 'bans use of Feature.ban' do
expect_offense(<<~RUBY)
Feature.get
diff --git a/spec/rubocop/cop/gitlab/keys_first_and_values_first_spec.rb b/spec/rubocop/cop/gitlab/keys_first_and_values_first_spec.rb
index 073c78e78c0..b62742d52e2 100644
--- a/spec/rubocop/cop/gitlab/keys_first_and_values_first_spec.rb
+++ b/spec/rubocop/cop/gitlab/keys_first_and_values_first_spec.rb
@@ -7,8 +7,6 @@ require_relative '../../../../rubocop/cop/gitlab/keys_first_and_values_first'
RSpec.describe RuboCop::Cop::Gitlab::KeysFirstAndValuesFirst do
let(:msg) { described_class::MSG }
- subject(:cop) { described_class.new }
-
shared_examples 'inspect use of keys or values first' do |method, autocorrect|
describe ".#{method}.first" do
it 'flags and autocorrects' do
diff --git a/spec/rubocop/cop/gitlab/service_response_spec.rb b/spec/rubocop/cop/gitlab/service_response_spec.rb
index 84cf0dbff52..f90c84701c6 100644
--- a/spec/rubocop/cop/gitlab/service_response_spec.rb
+++ b/spec/rubocop/cop/gitlab/service_response_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/gitlab/service_response'
RSpec.describe RuboCop::Cop::Gitlab::ServiceResponse do
- subject(:cop) { described_class.new }
-
it 'does not flag the `http_status:` param on a homonym method' do
expect_no_offenses("MyClass.error(http_status: :ok)")
end
diff --git a/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb b/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
index 7cc88946cf1..c2f0053718a 100644
--- a/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
+++ b/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/add_columns_to_wide_tables'
RSpec.describe RuboCop::Cop::Migration::AddColumnsToWideTables do
- let(:cop) { described_class.new }
-
context 'when outside of a migration' do
it 'does not register any offenses' do
expect_no_offenses(<<~RUBY)
diff --git a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
index aa39f5f1603..98cfcb5c2e2 100644
--- a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
+++ b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/add_concurrent_foreign_key'
RSpec.describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
- let(:cop) { described_class.new }
-
context 'when outside of a migration' do
it 'does not register any offenses' do
expect_no_offenses('def up; add_foreign_key(:projects, :users, column: :user_id); end')
diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb
index bb3fe7068b4..7e6d14261c8 100644
--- a/spec/rubocop/cop/migration/add_reference_spec.rb
+++ b/spec/rubocop/cop/migration/add_reference_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/add_reference'
RSpec.describe RuboCop::Cop::Migration::AddReference do
- let(:cop) { described_class.new }
-
context 'when outside of a migration' do
it 'does not register any offenses' do
expect_no_offenses(<<~RUBY)
diff --git a/spec/rubocop/cop/migration/background_migrations_spec.rb b/spec/rubocop/cop/migration/background_migrations_spec.rb
index 681bbd84562..78c38f669ad 100644
--- a/spec/rubocop/cop/migration/background_migrations_spec.rb
+++ b/spec/rubocop/cop/migration/background_migrations_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/background_migrations'
RSpec.describe RuboCop::Cop::Migration::BackgroundMigrations do
- let(:cop) { described_class.new }
-
context 'when queue_background_migration_jobs_by_range_at_intervals is used' do
it 'registers an offense' do
expect_offense(<<~RUBY)
diff --git a/spec/rubocop/cop/migration/batch_migrations_post_only_spec.rb b/spec/rubocop/cop/migration/batch_migrations_post_only_spec.rb
index b5e2e83e788..a33557dc1ce 100644
--- a/spec/rubocop/cop/migration/batch_migrations_post_only_spec.rb
+++ b/spec/rubocop/cop/migration/batch_migrations_post_only_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/batch_migrations_post_only'
RSpec.describe RuboCop::Cop::Migration::BatchMigrationsPostOnly do
- let(:cop) { described_class.new }
-
before do
allow(cop).to receive(:in_post_deployment_migration?).and_return post_migration?
end
diff --git a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
index 072edb5827b..3337c3ae85c 100644
--- a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
+++ b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
@@ -4,8 +4,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/create_table_with_foreign_keys'
RSpec.describe RuboCop::Cop::Migration::CreateTableWithForeignKeys do
- let(:cop) { described_class.new }
-
context 'outside of a migration' do
it 'does not register any offenses' do
expect_no_offenses(<<~RUBY)
diff --git a/spec/rubocop/cop/migration/schedule_async_spec.rb b/spec/rubocop/cop/migration/schedule_async_spec.rb
index 59e03db07c0..30f774c48b0 100644
--- a/spec/rubocop/cop/migration/schedule_async_spec.rb
+++ b/spec/rubocop/cop/migration/schedule_async_spec.rb
@@ -5,7 +5,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/schedule_async'
RSpec.describe RuboCop::Cop::Migration::ScheduleAsync do
- let(:cop) { described_class.new }
let(:source) do
<<~SOURCE
def up
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index 005d3fb6b2a..25381fc0281 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -5,7 +5,6 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/update_column_in_batches'
RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
- let(:cop) { described_class.new }
let(:tmp_rails_root) { rails_root_join('tmp', 'rails_root') }
let(:migration_code) do
<<-END
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 9e1a1a9e445..acdbeee40a9 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1962,6 +1962,143 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
<<~YAML
include:
- component: #{component_path}
+ inputs:
+ stage: my-stage
+
+ stages:
+ - my-stage
+
+ test-1:
+ stage: my-stage
+ script: run test-1
+ YAML
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ context 'when there is no version with specified tag' do
+ before do
+ components_project.repository.add_tag(user, 'v0.01', sha)
+ end
+
+ it 'does not create a pipeline' do
+ response = execute_service(save_on_errors: true)
+
+ pipeline = response.payload
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.yaml_errors)
+ .to include "my-component@v0.1' - content not found"
+ end
+ end
+
+ context 'when there is a proper revision available' do
+ before do
+ components_project.repository.add_tag(user, 'v0.1', sha)
+ end
+
+ context 'when component is valid' do
+ it 'creates a pipeline using a pipeline component' do
+ response = execute_service(save_on_errors: true)
+
+ pipeline = response.payload
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.yaml_errors).to be_blank
+ expect(pipeline.statuses.count).to eq 2
+ expect(pipeline.statuses.map(&:name)).to match_array %w[test-1 test-my-job]
+ end
+ end
+
+ context 'when interpolation is invalid' do
+ let(:template) do
+ <<~YAML
+ spec:
+ inputs:
+ stage:
+ ---
+ test:
+ stage: $[[ inputs.stage ]]
+ script: rspec --suite $[[ inputs.suite ]]
+ YAML
+ end
+
+ it 'does not create a pipeline' do
+ response = execute_service(save_on_errors: true)
+
+ pipeline = response.payload
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.yaml_errors)
+ .to include 'interpolation interrupted by errors, unknown interpolation key: `suite`'
+ end
+ end
+
+ context 'when there is a syntax error in the template' do
+ let(:template) do
+ <<~YAML
+ spec:
+ inputs:
+ stage:
+ ---
+ :test
+ stage: $[[ inputs.stage ]]
+ YAML
+ end
+
+ it 'does not create a pipeline' do
+ response = execute_service(save_on_errors: true)
+
+ pipeline = response.payload
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.yaml_errors)
+ .to include 'content does not have a valid YAML syntax'
+ end
+ end
+ end
+ end
+
+ # TODO: Remove this test section when include:with is removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/408369
+ describe 'pipeline components using include:with instead of include:inputs' do
+ let(:components_project) do
+ create(:project, :repository, creator: user, namespace: user.namespace)
+ end
+
+ let(:component_path) do
+ "#{Gitlab.config.gitlab.host}/#{components_project.full_path}/my-component@v0.1"
+ end
+
+ let(:template) do
+ <<~YAML
+ spec:
+ inputs:
+ stage:
+ suffix:
+ default: my-job
+ ---
+ test-$[[ inputs.suffix ]]:
+ stage: $[[ inputs.stage ]]
+ script: run tests
+ YAML
+ end
+
+ let(:sha) do
+ components_project.repository.create_file(
+ user,
+ 'my-component/template.yml',
+ template,
+ message: 'Add my first CI component',
+ branch_name: 'master'
+ )
+ end
+
+ let(:config) do
+ <<~YAML
+ include:
+ - component: #{component_path}
with:
stage: my-stage
diff --git a/spec/services/metrics/dashboard/panel_preview_service_spec.rb b/spec/services/metrics/dashboard/panel_preview_service_spec.rb
index 2a70c667ee5..584be717d7c 100644
--- a/spec/services/metrics/dashboard/panel_preview_service_spec.rb
+++ b/spec/services/metrics/dashboard/panel_preview_service_spec.rb
@@ -64,18 +64,20 @@ RSpec.describe Metrics::Dashboard::PanelPreviewService, feature_category: :metri
Gitlab::Config::Loader::Yaml::DataTooLargeError,
Gitlab::Config::Loader::FormatError
].each do |error_class|
- before do
- allow_next_instance_of(::Gitlab::Metrics::Dashboard::Processor) do |processor|
- allow(processor).to receive(:process).and_raise(error_class.new('error'))
+ context "with #{error_class}" do
+ before do
+ allow_next_instance_of(::Gitlab::Metrics::Dashboard::Processor) do |processor|
+ allow(processor).to receive(:process).and_raise(error_class.new('error'))
+ end
end
- end
- it 'returns error service response' do
- expect(service_response.error?).to be_truthy
- end
+ it 'returns error service response' do
+ expect(service_response.error?).to be_truthy
+ end
- it 'returns error message' do
- expect(service_response.message).to eq('error')
+ it 'returns error message' do
+ expect(service_response.message).to eq('error')
+ end
end
end
end
diff --git a/spec/services/packages/generic/create_package_file_service_spec.rb b/spec/services/packages/generic/create_package_file_service_spec.rb
index 08c4cbdbe11..06a78c7820f 100644
--- a/spec/services/packages/generic/create_package_file_service_spec.rb
+++ b/spec/services/packages/generic/create_package_file_service_spec.rb
@@ -81,9 +81,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService, feature_category: :p
it_behaves_like 'assigns build to package file'
context 'with existing package' do
- before do
- create(:package_file, package: package, file_name: file_name)
- end
+ let_it_be(:duplicate_file) { create(:package_file, package: package, file_name: file_name) }
it { expect { execute_service }.to change { project.package_files.count }.by(1) }
@@ -97,6 +95,16 @@ RSpec.describe Packages::Generic::CreatePackageFileService, feature_category: :p
.and change { project.package_files.count }.by(0)
end
+ context 'when the file is pending destruction' do
+ before do
+ duplicate_file.update_column(:status, :pending_destruction)
+ end
+
+ it 'allows creating the file' do
+ expect { execute_service }.to change { project.package_files.count }.by(1)
+ end
+ end
+
context 'when the package name matches the exception regex' do
before do
package.project.namespace.package_settings.update!(generic_duplicate_exception_regex: '.*')
diff --git a/spec/services/packages/npm/generate_metadata_service_spec.rb b/spec/services/packages/npm/generate_metadata_service_spec.rb
index c22a9ef1428..1e3b0f71972 100644
--- a/spec/services/packages/npm/generate_metadata_service_spec.rb
+++ b/spec/services/packages/npm/generate_metadata_service_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe ::Packages::Npm::GenerateMetadataService, feature_category: :pack
with_them do
if params[:has_dependencies]
- ::Packages::DependencyLink.dependency_types.each_key do |dependency_type|
+ ::Packages::DependencyLink.dependency_types.each_key do |dependency_type| # rubocop:disable RSpec/UselessDynamicDefinition
let_it_be("package_dependency_link_for_#{dependency_type}") do
create(:packages_dependency_link, package: package1, dependency_type: dependency_type)
end
diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb
index e6845517aa7..8cdb4f543e1 100644
--- a/spec/services/spam/spam_verdict_service_spec.rb
+++ b/spec/services/spam/spam_verdict_service_spec.rb
@@ -14,6 +14,16 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
'HTTP_REFERER' => fake_referer }
end
+ let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW }
+
+ let(:response) do
+ ::Spamcheck::SpamVerdict.new(verdict: verdict_value)
+ end
+
+ let(:spam_client_result) do
+ Gitlab::Spamcheck::Result.new(response)
+ end
+
let(:check_for_spam) { true }
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue, author: user) }
@@ -23,43 +33,38 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
described_class.new(user: user, target: target, options: {})
end
- let(:attribs) do
- extra_attributes = { "monitorMode" => "false" }
- extra_attributes
- end
-
shared_examples 'execute spam verdict service' do
- subject { service.execute }
+ subject(:execute) { service.execute }
before do
- allow(service).to receive(:akismet_verdict).and_return(nil)
- allow(service).to receive(:spamcheck_verdict).and_return([nil, attribs])
+ allow(service).to receive(:get_akismet_verdict).and_return(nil)
+ allow(service).to receive(:get_spamcheck_verdict).and_return(nil)
end
context 'if all services return nil' do
it 'renders ALLOW verdict' do
- expect(subject).to eq ALLOW
+ is_expected.to eq ALLOW
end
end
context 'if only one service returns a verdict' do
context 'and it is supported' do
before do
- allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
+ allow(service).to receive(:get_akismet_verdict).and_return(DISALLOW)
end
it 'renders that verdict' do
- expect(subject).to eq DISALLOW
+ is_expected.to eq DISALLOW
end
end
context 'and it is unexpected' do
before do
- allow(service).to receive(:akismet_verdict).and_return("unexpected")
+ allow(service).to receive(:get_akismet_verdict).and_return("unexpected")
end
it 'allows' do
- expect(subject).to eq ALLOW
+ is_expected.to eq ALLOW
end
end
end
@@ -67,50 +72,34 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
context 'if more than one service returns a verdict' do
context 'and they are supported' do
before do
- allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
- allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs])
+ allow(service).to receive(:get_akismet_verdict).and_return(DISALLOW)
+ allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
- expect(subject).to eq BLOCK_USER
+ is_expected.to eq BLOCK_USER
end
end
context 'and one is supported' do
before do
- allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs])
+ allow(service).to receive(:get_akismet_verdict).and_return('nonsense')
+ allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
- expect(subject).to eq BLOCK_USER
+ is_expected.to eq BLOCK_USER
end
end
context 'and none are supported' do
before do
- allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spamcheck_verdict).and_return(['rubbish', attribs])
- end
-
- it 'renders the more restrictive verdict' do
- expect(subject).to eq ALLOW
- end
- end
-
- context 'and attribs - monitorMode is true' do
- let(:attribs) do
- extra_attributes = { "monitorMode" => "true" }
- extra_attributes
- end
-
- before do
- allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
- allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs])
+ allow(service).to receive(:get_akismet_verdict).and_return('nonsense')
+ allow(service).to receive(:get_spamcheck_verdict).and_return('rubbish')
end
it 'renders the more restrictive verdict' do
- expect(subject).to eq(DISALLOW)
+ is_expected.to eq ALLOW
end
end
end
@@ -122,21 +111,21 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
context 'and a service returns a verdict that should be overridden' do
before do
- allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs])
+ allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
end
it 'overrides and renders the override verdict' do
- expect(subject).to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
+ is_expected.to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
end
end
context 'and a service returns a verdict that does not need to be overridden' do
before do
- allow(service).to receive(:spamcheck_verdict).and_return([ALLOW, attribs])
+ allow(service).to receive(:get_spamcheck_verdict).and_return(ALLOW)
end
it 'does not override and renders the original verdict' do
- expect(subject).to eq ALLOW
+ is_expected.to eq ALLOW
end
end
end
@@ -146,24 +135,23 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
using RSpec::Parameterized::TableSyntax
- where(:verdict, :error, :label) do
- Spam::SpamConstants::ALLOW | false | 'ALLOW'
- Spam::SpamConstants::ALLOW | true | 'ERROR'
- Spam::SpamConstants::CONDITIONAL_ALLOW | false | 'CONDITIONAL_ALLOW'
- Spam::SpamConstants::BLOCK_USER | false | 'BLOCK'
- Spam::SpamConstants::DISALLOW | false | 'DISALLOW'
- Spam::SpamConstants::NOOP | false | 'NOOP'
+ where(:verdict, :label) do
+ Spam::SpamConstants::ALLOW | 'ALLOW'
+ Spam::SpamConstants::CONDITIONAL_ALLOW | 'CONDITIONAL_ALLOW'
+ Spam::SpamConstants::BLOCK_USER | 'BLOCK'
+ Spam::SpamConstants::DISALLOW | 'DISALLOW'
+ Spam::SpamConstants::NOOP | 'NOOP'
end
with_them do
before do
allow(Gitlab::Metrics).to receive(:histogram).with(:gitlab_spamcheck_request_duration_seconds, anything).and_return(histogram)
- allow(service).to receive(:spamcheck_verdict).and_return([verdict, attribs, error])
+ allow(service).to receive(:get_spamcheck_verdict).and_return(verdict)
end
it 'records duration with labels' do
expect(histogram).to receive(:observe).with(a_hash_including(result: label), anything)
- subject
+ execute
end
end
end
@@ -171,7 +159,8 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
shared_examples 'akismet verdict' do
let(:target) { issue }
- subject { service.send(:akismet_verdict) }
+
+ subject(:get_akismet_verdict) { service.send(:get_akismet_verdict) }
context 'if Akismet is enabled' do
before do
@@ -190,7 +179,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
it 'returns conditionally allow verdict' do
- expect(subject).to eq CONDITIONAL_ALLOW
+ is_expected.to eq CONDITIONAL_ALLOW
end
end
@@ -200,7 +189,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
it 'renders disallow verdict' do
- expect(subject).to eq DISALLOW
+ is_expected.to eq DISALLOW
end
end
end
@@ -209,7 +198,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
let(:akismet_result) { false }
it 'renders allow verdict' do
- expect(subject).to eq ALLOW
+ is_expected.to eq ALLOW
end
end
end
@@ -220,13 +209,13 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
it 'renders allow verdict' do
- expect(subject).to eq ALLOW
+ is_expected.to eq ALLOW
end
end
end
shared_examples 'spamcheck verdict' do
- subject { service.send(:spamcheck_verdict) }
+ subject(:get_spamcheck_verdict) { service.send(:get_spamcheck_verdict) }
context 'if a Spam Check endpoint enabled and set to a URL' do
let(:spam_check_body) { {} }
@@ -242,45 +231,24 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
context 'if the endpoint is accessible' do
- let(:error) { '' }
- let(:verdict) { nil }
-
- let(:attribs) do
- extra_attributes = { "monitorMode" => "false" }
- extra_attributes
- end
-
before do
allow(service).to receive(:spamcheck_client).and_return(spam_client)
- allow(spam_client).to receive(:spam?).and_return([verdict, attribs, error])
+ allow(spam_client).to receive(:spam?).and_return(spam_client_result)
end
context 'if the result is a NOOP verdict' do
- let(:verdict) { NOOP }
-
- it 'returns the verdict' do
- expect(subject).to eq([NOOP, attribs])
- end
- end
-
- context 'if attribs - monitorMode is true' do
- let(:attribs) do
- extra_attributes = { "monitorMode" => "true" }
- extra_attributes
- end
-
- let(:verdict) { ALLOW }
+ let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::NOOP }
it 'returns the verdict' do
- expect(subject).to eq([ALLOW, attribs])
+ is_expected.to eq(NOOP)
end
end
context 'the result is a valid verdict' do
- let(:verdict) { ALLOW }
+ let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW }
it 'returns the verdict' do
- expect(subject).to eq([ALLOW, attribs])
+ is_expected.to eq(ALLOW)
end
end
@@ -291,20 +259,16 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
using RSpec::Parameterized::TableSyntax
- # rubocop: disable Lint/BinaryOperatorWithIdenticalOperands
where(:verdict_value, :expected) do
- ::Spam::SpamConstants::ALLOW | ::Spam::SpamConstants::ALLOW
- ::Spam::SpamConstants::CONDITIONAL_ALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW
- ::Spam::SpamConstants::DISALLOW | ::Spam::SpamConstants::DISALLOW
- ::Spam::SpamConstants::BLOCK_USER | ::Spam::SpamConstants::BLOCK_USER
+ ::Spamcheck::SpamVerdict::Verdict::ALLOW | ::Spam::SpamConstants::ALLOW
+ ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW
+ ::Spamcheck::SpamVerdict::Verdict::DISALLOW | ::Spam::SpamConstants::DISALLOW
+ ::Spamcheck::SpamVerdict::Verdict::BLOCK | ::Spam::SpamConstants::BLOCK_USER
end
- # rubocop: enable Lint/BinaryOperatorWithIdenticalOperands
with_them do
- let(:verdict) { verdict_value }
-
it "returns expected spam constant" do
- expect(subject).to eq([expected, attribs])
+ is_expected.to eq(expected)
end
end
end
@@ -314,56 +278,23 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
allow(Gitlab::Recaptcha).to receive(:enabled?).and_return(false)
end
- [::Spam::SpamConstants::ALLOW,
- ::Spam::SpamConstants::CONDITIONAL_ALLOW,
- ::Spam::SpamConstants::DISALLOW,
- ::Spam::SpamConstants::BLOCK_USER].each do |verdict_value|
- context "with verdict_value:#{verdict_value}" do
- let(:verdict) { verdict_value }
- let(:expected) { [verdict_value, attribs] }
-
- it "returns expected spam constant" do
- expect(subject).to eq(expected)
- end
- end
- end
- end
-
- context 'the verdict is an unexpected value' do
- let(:verdict) { :this_is_fine }
-
- it 'returns the string' do
- expect(subject).to eq([verdict, attribs])
- end
- end
-
- context 'the verdict is an empty string' do
- let(:verdict) { '' }
-
- it 'returns nil' do
- expect(subject).to eq([verdict, attribs])
- end
- end
-
- context 'the verdict is nil' do
- let(:verdict) { nil }
+ using RSpec::Parameterized::TableSyntax
- it 'returns nil' do
- expect(subject).to eq([nil, attribs])
+ where(:verdict_value, :expected) do
+ ::Spamcheck::SpamVerdict::Verdict::ALLOW | ::Spam::SpamConstants::ALLOW
+ ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW
+ ::Spamcheck::SpamVerdict::Verdict::DISALLOW | ::Spam::SpamConstants::DISALLOW
+ ::Spamcheck::SpamVerdict::Verdict::BLOCK | ::Spam::SpamConstants::BLOCK_USER
end
- end
-
- context 'there is an error' do
- let(:error) { "Sorry Dave, I can't do that" }
- it 'returns nil' do
- expect(subject).to eq([nil, attribs])
+ with_them do
+ it "returns expected spam constant" do
+ is_expected.to eq(expected)
+ end
end
end
context 'the requested is aborted' do
- let(:attribs) { nil }
-
before do
allow(spam_client).to receive(:spam?).and_raise(GRPC::Aborted)
end
@@ -372,22 +303,11 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
an_instance_of(GRPC::Aborted), error: ::Spam::SpamConstants::ERROR_TYPE
)
- expect(subject).to eq([ALLOW, attribs, true])
- end
- end
-
- context 'the confused API endpoint returns both an error and a verdict' do
- let(:verdict) { 'disallow' }
- let(:error) { 'oh noes!' }
-
- it 'renders the verdict' do
- expect(subject).to eq [DISALLOW, attribs]
+ is_expected.to be_nil
end
end
context 'if the endpoint times out' do
- let(:attribs) { nil }
-
before do
allow(spam_client).to receive(:spam?).and_raise(GRPC::DeadlineExceeded)
end
@@ -396,7 +316,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
an_instance_of(GRPC::DeadlineExceeded), error: ::Spam::SpamConstants::ERROR_TYPE
)
- expect(subject).to eq([ALLOW, attribs, true])
+ is_expected.to be_nil
end
end
end
@@ -408,7 +328,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
it 'returns nil' do
- expect(subject).to be_nil
+ is_expected.to be_nil
end
end
@@ -418,7 +338,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
it 'returns nil' do
- expect(subject).to be_nil
+ is_expected.to be_nil
end
end
end
@@ -437,7 +357,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
end
- describe '#akismet_verdict' do
+ describe '#get_akismet_verdict' do
describe 'issue' do
let(:target) { issue }
@@ -451,7 +371,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
end
- describe '#spamcheck_verdict' do
+ describe '#get_spamcheck_verdict' do
describe 'issue' do
let(:target) { issue }
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 970a2588d0f..c81c5c41844 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -1628,7 +1628,6 @@
- './ee/spec/models/boards/epic_user_preference_spec.rb'
- './ee/spec/models/board_spec.rb'
- './ee/spec/models/board_user_preference_spec.rb'
-- './ee/spec/models/broadcast_message_spec.rb'
- './ee/spec/models/burndown_spec.rb'
- './ee/spec/models/ci/bridge_spec.rb'
- './ee/spec/models/ci/daily_build_group_report_result_spec.rb'
@@ -5025,7 +5024,6 @@
- './spec/helpers/boards_helper_spec.rb'
- './spec/helpers/branches_helper_spec.rb'
- './spec/helpers/breadcrumbs_helper_spec.rb'
-- './spec/helpers/broadcast_messages_helper_spec.rb'
- './spec/helpers/button_helper_spec.rb'
- './spec/helpers/calendar_helper_spec.rb'
- './spec/helpers/ci/builds_helper_spec.rb'
@@ -7468,7 +7466,6 @@
- './spec/models/board_group_recent_visit_spec.rb'
- './spec/models/board_project_recent_visit_spec.rb'
- './spec/models/board_spec.rb'
-- './spec/models/broadcast_message_spec.rb'
- './spec/models/bulk_imports/configuration_spec.rb'
- './spec/models/bulk_imports/entity_spec.rb'
- './spec/models/bulk_imports/export_spec.rb'
diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb
deleted file mode 100644
index 9cb34bbbab9..00000000000
--- a/spec/workers/concerns/waitable_worker_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe WaitableWorker, feature_category: :shared do
- let(:worker) do
- Class.new do
- def self.name
- 'Gitlab::Foo::Bar::DummyWorker'
- end
-
- cattr_accessor(:counter) { 0 }
-
- include ApplicationWorker
- prepend WaitableWorker
-
- def perform(count = 0)
- self.class.counter += count
- end
- end
- end
-
- subject(:job) { worker.new }
-
- describe '#perform' do
- shared_examples 'perform' do
- it 'notifies the JobWaiter when done if the key is provided' do
- key = Gitlab::JobWaiter.new.key
- expect(Gitlab::JobWaiter).to receive(:notify).with(key, job.jid)
-
- job.perform(*args, key)
- end
-
- it 'does not notify the JobWaiter when done if no key is provided' do
- expect(Gitlab::JobWaiter).not_to receive(:notify)
-
- job.perform(*args)
- end
- end
-
- context 'when the worker takes arguments' do
- let(:args) { [1] }
-
- it_behaves_like 'perform'
- end
-
- context 'when the worker takes no arguments' do
- let(:args) { [] }
-
- it_behaves_like 'perform'
- end
- end
-end
diff --git a/yarn.lock b/yarn.lock
index 7ea077bed7b..374d635bc81 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1005,10 +1005,10 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.1.tgz#b6b8d81780b9a9f6459f4bfe9226ac6aefaefe87"
integrity sha512-aG20vknL4/YjQF9BSV7ts4EWm/yrjagAN7OWBNmlbEOUiu0llj4OGrFoOKK3g2vey4/p2omKCoHrWtPxSwV3HA==
-"@cubejs-client/core@^0.32.17":
- version "0.32.17"
- resolved "https://registry.yarnpkg.com/@cubejs-client/core/-/core-0.32.17.tgz#08189daca4976f87177f72607ed3db595446b268"
- integrity sha512-uK2QM01nvJJVKQzHFfIjImuiveFa2S4Dvt5/hm6DerE6mDbkGasOQWBv5c+WZ3JVCZ1xxThSrDV2NY4EUcwk9w==
+"@cubejs-client/core@^0.32.22":
+ version "0.32.22"
+ resolved "https://registry.yarnpkg.com/@cubejs-client/core/-/core-0.32.22.tgz#fe3bc9382e7858cb1b9c7dd726e3327acc3e9508"
+ integrity sha512-JYA8lgr0Pjus/Y+GA5gbP6iY/X2+7wO+DXg4fjAWJjn2MEU8X478OU+uLENzgIqLyZRSjvEZoI6Vt8BicJboEQ==
dependencies:
"@babel/runtime" "^7.1.2"
core-js "^3.6.5"
@@ -1018,12 +1018,12 @@
url-search-params-polyfill "^7.0.0"
uuid "^8.3.2"
-"@cubejs-client/vue@^0.32.17":
- version "0.32.17"
- resolved "https://registry.yarnpkg.com/@cubejs-client/vue/-/vue-0.32.17.tgz#f33a853afead1b5aa3c82d0e92a8f168f4078d79"
- integrity sha512-hfuEWkYQvSJzCZb4YyT3MyRPVOpV2rLLORwXN2Mn7gPmJuWE/JazrxU8cegxArNAN60BvGM3z2SsODQdwCxP4g==
+"@cubejs-client/vue@^0.32.22":
+ version "0.32.22"
+ resolved "https://registry.yarnpkg.com/@cubejs-client/vue/-/vue-0.32.22.tgz#2a16a0e2b9f1c084fc11d1416f2cd52ed30fdbbb"
+ integrity sha512-FuApIKcLmM1JaR4wOjLDPONsJE8PeRw6QAo+nBOZJNFBjXPmDhd7EH73xvlT3PY0hhBwD1ts6sUWuvjV2uPQGA==
dependencies:
- "@cubejs-client/core" "^0.32.17"
+ "@cubejs-client/core" "^0.32.22"
core-js "^3.6.5"
ramda "^0.27.2"
@@ -1115,10 +1115,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.40.0.tgz#f1ebb2fcdbb1181550d53f0db827eca1f5060af0"
integrity sha512-9CVkIbV0VnIFfVBjWcW8+nHzpMhHhC73C9mGPEktEPfpEbaaRws2UywgDEH+C2B8Ba1QdBo/aFr68RDu2VwvfA==
-"@gitlab/ui@60.2.0":
- version "60.2.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-60.2.0.tgz#d3c8c6f6cb9f8209ad4ca1f4e530810e04440285"
- integrity sha512-AvVYA49B1InCtq+on3Zgu7+I2GX5iA320qjrrK5h7bUcq1Gywxrq28+GZrkIV7eicC05slReWRGqebwpsqrrOg==
+"@gitlab/ui@61.1.1":
+ version "61.1.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-61.1.1.tgz#fcd0f66e307456aacfb072d026bf9096bef43658"
+ integrity sha512-dd9LqCV/6Ju7pMId0g8vbTyHYa1MavVbvB2WsJpnr+VsCPCLsl8hp9XB238kijhvfQqcExUsqwm0Plsu/nE1xQ==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.23.1"
@@ -6626,10 +6626,10 @@ graphql@^15.7.2:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.7.2.tgz#85ab0eeb83722977151b3feb4d631b5f2ab287ef"
integrity sha512-AnnKk7hFQFmU/2I9YSQf3xw44ctnSFCfp3zE0N6W174gqe9fWG/2rKaKxROK7CcI3XtERpjEKFqts8o319Kf7A==
-gridstack@^7.1.2:
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-7.1.2.tgz#288ccccf786e0440094a48d5f8d654064fb18566"
- integrity sha512-vc0sHheyOCKe2VE9JgNlNjHroaYJAbOsl/R4sF6TY5WqKTa6owJz4pj3D+W3QQZ43zS18yttLw6m+R9UNuoC3A==
+gridstack@^7.3.0:
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-7.3.0.tgz#7b32395edcd885bc39b84068ac86f2831f7a2451"
+ integrity sha512-JKZgsHzm1ljkn1NnBZpf8j4NDOBCXTuw0m1ZC0sr6NKUh0BFWzXAONIxtX1hWGUVeKLj5l1VcmnTwCXw5ypDNw==
gzip-size@^6.0.0:
version "6.0.0"