summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo.yml8
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue27
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/gl_form.js87
-rw-r--r--app/assets/javascripts/helpers/indent_helper.js182
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/success_message.vue4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js65
-rw-r--r--app/assets/javascripts/lib/utils/keycodes.js10
-rw-r--r--app/assets/javascripts/lib/utils/undo_stack.js105
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/index.js2
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue4
-rw-r--r--app/assets/javascripts/projects/gke_cluster_namespace/index.js35
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue41
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/lists.scss16
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb4
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/projects/repositories_controller.rb60
-rw-r--r--app/controllers/sessions_controller.rb11
-rw-r--r--app/helpers/submodule_helper.rb2
-rw-r--r--app/mailers/emails/members.rb17
-rw-r--r--app/mailers/notify.rb12
-rw-r--r--app/mailers/previews/notify_preview.rb8
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/concerns/relative_positioning.rb52
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/issue.rb6
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/repository.rb3
-rw-r--r--app/serializers/stage_entity.rb8
-rw-r--r--app/services/ci/process_pipeline_service.rb4
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb17
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/views/clusters/clusters/_namespace.html.haml14
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml9
-rw-r--r--app/views/clusters/platforms/kubernetes/_form.html.haml8
-rw-r--r--app/views/shared/notes/_hints.html.haml11
-rw-r--r--app/views/shared/projects/_project.html.haml4
-rw-r--r--changelogs/unreleased/63568-access-email-notifications-custom-email.yml5
-rw-r--r--changelogs/unreleased/64257-warden_set_user_fix.yml5
-rw-r--r--changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml5
-rw-r--r--changelogs/unreleased/add-caching-to-archive-endpoint.yml5
-rw-r--r--changelogs/unreleased/delete-designs-v2.yml4
-rw-r--r--changelogs/unreleased/dm-submodule-links-nil.yml5
-rw-r--r--changelogs/unreleased/extract_auto_deploy_into_base_image.yml5
-rw-r--r--changelogs/unreleased/je-separate-namespace-fe.yml5
-rw-r--r--changelogs/unreleased/label-descr-push-opts.yml5
-rw-r--r--changelogs/unreleased/mh-editor-indents.yml5
-rw-r--r--changelogs/unreleased/sh-support-docker-oci-images.yml5
-rw-r--r--config/initializers/0_license.rb2
-rw-r--r--config/initializers/active_record_migration.rb10
-rw-r--r--config/initializers/load_balancing.rb2
-rw-r--r--db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb2
-rw-r--r--db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb27
-rw-r--r--db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb30
-rw-r--r--db/schema.rb6
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/logs.md2
-rw-r--r--doc/api/README.md7
-rw-r--r--doc/api/dependencies.md4
-rw-r--r--doc/api/group_clusters.md2
-rw-r--r--doc/api/project_clusters.md2
-rw-r--r--doc/ci/README.md6
-rw-r--r--doc/ci/introduction/index.md5
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md36
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.pngbin0 -> 63661 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.pngbin0 -> 30760 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md69
-rw-r--r--doc/ci/pipelines.md5
-rw-r--r--doc/development/README.md4
-rw-r--r--doc/development/code_review.md25
-rw-r--r--doc/development/database_review.md11
-rw-r--r--doc/development/documentation/index.md43
-rw-r--r--doc/development/documentation/styleguide.md2
-rw-r--r--doc/development/filtering_by_label.md166
-rw-r--r--doc/development/new_fe_guide/dependencies.md12
-rw-r--r--doc/development/testing_guide/frontend_testing.md2
-rw-r--r--doc/install/azure/index.md7
-rw-r--r--doc/integration/README.md4
-rw-r--r--doc/integration/slack.md4
-rw-r--r--doc/topics/autodevops/index.md92
-rw-r--r--doc/university/README.md4
-rw-r--r--doc/user/admin_area/abuse_reports.md16
-rw-r--r--doc/user/admin_area/broadcast_messages.md16
-rw-r--r--doc/user/admin_area/custom_project_templates.md45
-rw-r--r--doc/user/application_security/container_scanning/index.md8
-rw-r--r--doc/user/application_security/dast/index.md16
-rw-r--r--doc/user/application_security/dependency_scanning/index.md20
-rw-r--r--doc/user/application_security/index.md38
-rw-r--r--doc/user/application_security/license_management/index.md16
-rw-r--r--doc/user/application_security/sast/index.md16
-rw-r--r--doc/user/application_security/security_dashboard/img/dashboard.pngbin58585 -> 0 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard.pngbin0 -> 226261 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/project_security_dashboard.pngbin126356 -> 166559 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md26
-rw-r--r--doc/user/clusters/applications.md3
-rw-r--r--doc/user/group/saml_sso/img/scim_attribute_mapping.pngbin95420 -> 113191 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_name_identifier_mapping.pngbin0 -> 175281 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_provisioning_status.pngbin0 -> 23006 bytes
-rw-r--r--doc/user/group/saml_sso/scim_setup.md51
-rw-r--r--doc/user/markdown.md66
-rw-r--r--doc/user/profile/index.md31
-rw-r--r--doc/user/project/clusters/index.md414
-rw-r--r--doc/user/project/clusters/runbooks/index.md4
-rw-r--r--doc/user/project/clusters/serverless/index.md5
-rw-r--r--doc/user/project/container_registry.md71
-rw-r--r--doc/user/project/img/container_registry.pngbin35202 -> 0 bytes
-rw-r--r--doc/user/project/integrations/prometheus.md5
-rw-r--r--doc/user/project/merge_requests/index.md24
-rw-r--r--doc/user/project/protected_branches.md1
-rw-r--r--lib/api/validations/types/labels_list.rb2
-rw-r--r--lib/banzai/reference_redactor.rb2
-rw-r--r--lib/container_registry/client.rb9
-rw-r--r--lib/gitlab/auth/activity.rb7
-rw-r--r--lib/gitlab/auth/o_auth/auth_hash.rb3
-rw-r--r--lib/gitlab/background_migration/backfill_project_repositories.rb2
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb2
-rw-r--r--lib/gitlab/ci/config/normalizer.rb2
-rw-r--r--lib/gitlab/ci/status/factory.rb6
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml421
-rw-r--r--lib/gitlab/database/subquery.rb6
-rw-r--r--lib/gitlab/profiler.rb2
-rw-r--r--lib/gitlab/push_options.rb4
-rw-r--r--lib/gitlab/regex.rb6
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb23
-rw-r--r--lib/gitlab/submodule_links.rb11
-rw-r--r--lib/gitlab/utils.rb2
-rw-r--r--lib/tasks/gitlab/db.rake2
-rw-r--r--locale/gitlab.pot63
-rw-r--r--package.json4
-rw-r--r--qa/qa/page/project/issue/show.rb5
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/resource/project.rb6
-rwxr-xr-xscripts/insert-rspec-profiling-data4
-rwxr-xr-xscripts/review_apps/review-apps.sh2
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb47
-rw-r--r--spec/features/markdown/metrics_spec.rb60
-rw-r--r--spec/features/oauth_login_spec.rb1
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb12
-rw-r--r--spec/features/security/project/private_access_spec.rb2
-rw-r--r--spec/features/users/login_spec.rb38
-rw-r--r--spec/features/users/user_browses_projects_on_user_page_spec.rb13
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json6
-rw-r--r--spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json4
-rw-r--r--spec/frontend/helpers/indent_helper_spec.js371
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js180
-rw-r--r--spec/frontend/lib/utils/undo_stack_spec.js237
-rw-r--r--spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js61
-rw-r--r--spec/helpers/submodule_helper_spec.rb13
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js2
-rw-r--r--spec/javascripts/badges/components/badge_spec.js2
-rw-r--r--spec/javascripts/boards/board_list_spec.js2
-rw-r--r--spec/javascripts/registry/components/app_spec.js2
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js12
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js2
-rw-r--r--spec/lib/container_registry/client_spec.rb36
-rw-r--r--spec/lib/container_registry/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb7
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb3
-rw-r--r--spec/lib/gitlab/submodule_links_spec.rb47
-rw-r--r--spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb73
-rw-r--r--spec/mailers/notify_spec.rb21
-rw-r--r--spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb21
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb242
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/issue_spec.rb8
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb154
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb4
-rw-r--r--spec/support/api/boards_shared_examples.rb2
-rw-r--r--spec/support/api/issues_resolving_discussions_shared_examples.rb2
-rw-r--r--spec/support/api/members_shared_examples.rb2
-rw-r--r--spec/support/api/milestones_shared_examples.rb2
-rw-r--r--spec/support/api/repositories_shared_context.rb2
-rw-r--r--spec/support/api/schema_matcher.rb2
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb2
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb2
-rw-r--r--spec/support/banzai/reference_filter_shared_examples.rb2
-rw-r--r--spec/support/batch_loader.rb2
-rw-r--r--spec/support/capybara.rb2
-rw-r--r--spec/support/carrierwave.rb2
-rw-r--r--spec/support/chunked_io/chunked_io_helpers.rb2
-rw-r--r--spec/support/commit_trailers_spec_helper.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_context.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb2
-rw-r--r--spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb2
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb2
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb2
-rw-r--r--spec/support/db_cleaner.rb2
-rw-r--r--spec/support/external_authorization_service_helpers.rb2
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb2
-rw-r--r--spec/support/features/resolving_discussions_in_issues_shared_examples.rb2
-rw-r--r--spec/support/features/rss_shared_examples.rb2
-rw-r--r--spec/support/features/variable_list_shared_examples.rb2
-rw-r--r--spec/support/forgery_protection.rb2
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb2
-rw-r--r--spec/support/helpers/api_helpers.rb11
-rw-r--r--spec/support/helpers/assets_helpers.rb2
-rw-r--r--spec/support/helpers/bare_repo_operations.rb2
-rw-r--r--spec/support/helpers/board_helpers.rb2
-rw-r--r--spec/support/helpers/capybara_helpers.rb2
-rw-r--r--spec/support/helpers/ci_artifact_metadata_generator.rb2
-rw-r--r--spec/support/helpers/cookie_helper.rb2
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb2
-rw-r--r--spec/support/helpers/database_connection_helpers.rb2
-rw-r--r--spec/support/helpers/devise_helpers.rb2
-rw-r--r--spec/support/helpers/drag_to_helper.rb2
-rw-r--r--spec/support/helpers/dropzone_helper.rb2
-rw-r--r--spec/support/helpers/email_helpers.rb2
-rw-r--r--spec/support/helpers/exclusive_lease_helpers.rb2
-rw-r--r--spec/support/helpers/expect_next_instance_of.rb2
-rw-r--r--spec/support/helpers/expect_offense.rb2
-rw-r--r--spec/support/helpers/fake_blob_helpers.rb2
-rw-r--r--spec/support/helpers/fake_migration_classes.rb2
-rw-r--r--spec/support/helpers/fake_u2f_device.rb2
-rw-r--r--spec/support/helpers/features/branches_helpers.rb2
-rw-r--r--spec/support/helpers/features/notes_helpers.rb2
-rw-r--r--spec/support/helpers/features/sorting_helpers.rb2
-rw-r--r--spec/support/helpers/filter_spec_helper.rb2
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb2
-rw-r--r--spec/support/helpers/fixture_helpers.rb2
-rw-r--r--spec/support/helpers/git_http_helpers.rb2
-rw-r--r--spec/support/helpers/gitlab_verify_helpers.rb2
-rw-r--r--spec/support/helpers/graphql_helpers.rb2
-rw-r--r--spec/support/helpers/import_spec_helper.rb2
-rw-r--r--spec/support/helpers/input_helper.rb2
-rw-r--r--spec/support/helpers/inspect_requests.rb2
-rw-r--r--spec/support/helpers/issue_helpers.rb2
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb2
-rw-r--r--spec/support/helpers/jira_service_helper.rb2
-rw-r--r--spec/support/helpers/key_generator_helper.rb2
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb2
-rw-r--r--spec/support/helpers/ldap_helpers.rb2
-rw-r--r--spec/support/helpers/live_debugger.rb2
-rw-r--r--spec/support/helpers/login_helpers.rb2
-rw-r--r--spec/support/helpers/markdown_feature.rb2
-rw-r--r--spec/support/helpers/merge_request_diff_helpers.rb2
-rw-r--r--spec/support/helpers/merge_request_helpers.rb2
-rw-r--r--spec/support/helpers/migrations_helpers.rb2
-rw-r--r--spec/support/helpers/mobile_helpers.rb2
-rw-r--r--spec/support/helpers/note_interaction_helpers.rb2
-rw-r--r--spec/support/helpers/notification_helpers.rb2
-rw-r--r--spec/support/helpers/project_forks_helper.rb2
-rw-r--r--spec/support/helpers/prometheus_helpers.rb10
-rw-r--r--spec/support/helpers/query_recorder.rb2
-rw-r--r--spec/support/helpers/quick_actions_helpers.rb2
-rw-r--r--spec/support/helpers/rake_helpers.rb2
-rw-r--r--spec/support/helpers/reactive_caching_helpers.rb2
-rw-r--r--spec/support/helpers/redis_without_keys.rb2
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb2
-rw-r--r--spec/support/helpers/repo_helpers.rb2
-rw-r--r--spec/support/helpers/routes_helpers.rb2
-rw-r--r--spec/support/helpers/search_helpers.rb2
-rw-r--r--spec/support/helpers/seed_repo.rb2
-rw-r--r--spec/support/helpers/select2_helper.rb2
-rw-r--r--spec/support/helpers/selection_helper.rb2
-rw-r--r--spec/support/helpers/sorting_helper.rb2
-rw-r--r--spec/support/helpers/stub_configuration.rb2
-rw-r--r--spec/support/helpers/stub_env.rb2
-rw-r--r--spec/support/helpers/stub_feature_flags.rb2
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb2
-rw-r--r--spec/support/helpers/stub_gitlab_data.rb2
-rw-r--r--spec/support/helpers/stub_metrics.rb2
-rw-r--r--spec/support/helpers/stub_object_storage.rb2
-rw-r--r--spec/support/helpers/stub_requests.rb2
-rw-r--r--spec/support/helpers/stub_worker.rb2
-rw-r--r--spec/support/helpers/terms_helper.rb2
-rw-r--r--spec/support/helpers/test_env.rb2
-rw-r--r--spec/support/helpers/upload_helpers.rb2
-rw-r--r--spec/support/helpers/wait_for_requests.rb2
-rw-r--r--spec/support/helpers/wait_helpers.rb2
-rw-r--r--spec/support/helpers/wiki_helpers.rb2
-rw-r--r--spec/support/helpers/workhorse_helpers.rb2
-rw-r--r--spec/support/http_io/http_io_helpers.rb2
-rw-r--r--spec/support/import_export/common_util.rb2
-rw-r--r--spec/support/import_export/configuration_helper.rb2
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/inspect_squelch.rb2
-rw-r--r--spec/support/issuables_requiring_filter_shared_examples.rb2
-rw-r--r--spec/support/json_response.rb2
-rw-r--r--spec/support/matchers/abort_matcher.rb2
-rw-r--r--spec/support/matchers/access_matchers.rb2
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb2
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb2
-rw-r--r--spec/support/matchers/be_a_binary_string.rb2
-rw-r--r--spec/support/matchers/be_like_time.rb2
-rw-r--r--spec/support/matchers/be_url.rb2
-rw-r--r--spec/support/matchers/be_utf8.rb2
-rw-r--r--spec/support/matchers/be_valid_commit.rb2
-rw-r--r--spec/support/matchers/disallow_request_matchers.rb2
-rw-r--r--spec/support/matchers/exceed_query_limit.rb2
-rw-r--r--spec/support/matchers/execute_check.rb2
-rw-r--r--spec/support/matchers/gitaly_matchers.rb2
-rw-r--r--spec/support/matchers/gitlab_git_matchers.rb2
-rw-r--r--spec/support/matchers/graphql_matchers.rb2
-rw-r--r--spec/support/matchers/have_emoji.rb2
-rw-r--r--spec/support/matchers/have_gitlab_http_status.rb2
-rw-r--r--spec/support/matchers/have_issuable_counts.rb2
-rw-r--r--spec/support/matchers/include_module.rb2
-rw-r--r--spec/support/matchers/issuable_matchers.rb2
-rw-r--r--spec/support/matchers/markdown_matchers.rb2
-rw-r--r--spec/support/matchers/match_file.rb2
-rw-r--r--spec/support/matchers/match_ids.rb2
-rw-r--r--spec/support/matchers/metric_counter_matcher.rb2
-rw-r--r--spec/support/matchers/navigation_matcher.rb2
-rw-r--r--spec/support/matchers/pagination_matcher.rb2
-rw-r--r--spec/support/matchers/query_matcher.rb2
-rw-r--r--spec/support/matchers/satisfy_matchers.rb2
-rw-r--r--spec/support/matchers/security_header_matcher.rb2
-rw-r--r--spec/support/migrations_helpers/track_untracked_uploads_helpers.rb2
-rw-r--r--spec/support/omni_auth.rb2
-rw-r--r--spec/support/prometheus/additional_metrics_shared_examples.rb2
-rw-r--r--spec/support/prometheus/metric_builders.rb2
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb2
-rw-r--r--spec/support/redis/redis_helpers.rb2
-rw-r--r--spec/support/redis/redis_shared_examples.rb2
-rw-r--r--spec/support/rspec.rb2
-rw-r--r--spec/support/seed.rb2
-rw-r--r--spec/support/services/clusters/create_service_shared.rb2
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb2
-rw-r--r--spec/support/services/issuable_update_service_shared_examples.rb2
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb2
-rw-r--r--spec/support/setup_builds_storage.rb2
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/users_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/json_response_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/merge_requests_allowing_collaboration.rb2
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/url_shared_context.rb2
-rw-r--r--spec/support/shared_examples/chat_slash_commands_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/common_system_notes_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/todos_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/variables_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/email_format_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/fast_destroy_all.rb2
-rw-r--r--spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb2
-rw-r--r--spec/support/shared_examples/features/search_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/file_finder.rb2
-rw-r--r--spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb2
-rw-r--r--spec/support/shared_examples/gitlab_verify.rb2
-rw-r--r--spec/support/shared_examples/graphql/issuable_state_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/group_members_shared_example.rb2
-rw-r--r--spec/support/shared_examples/helm_generated_script.rb2
-rw-r--r--spec/support/shared_examples/issuable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/issuables_list_metadata_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/issue_tracker_service_shared_example.rb2
-rw-r--r--spec/support/shared_examples/ldap_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/legacy_path_redirect_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb57
-rw-r--r--spec/support/shared_examples/malicious_regexp_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/mentionable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/milestone_tabs_examples.rb2
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/chat_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_core_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/members_notifications_shared_example.rb2
-rw-r--r--spec/support/shared_examples/models/project_hook_data_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/with_uploads_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/position_formatters.rb2
-rw-r--r--spec/support/shared_examples/reference_parser_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/relative_positioning_shared_examples.rb253
-rw-r--r--spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/diff_discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issuable_participants_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/notes.rb2
-rw-r--r--spec/support/shared_examples/requests/api/resolvable_discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/status_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/graphql_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/serializers/note_entity_examples.rb2
-rw-r--r--spec/support/shared_examples/services/boards/boards_create_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/boards_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/issues_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_destroy_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_move_service.rb2
-rw-r--r--spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/snippet_visibility_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/taskable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/throttled_touch.rb2
-rw-r--r--spec/support/shared_examples/unique_ip_check_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/update_invalid_issuable.rb2
-rw-r--r--spec/support/shared_examples/updating_mentions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uploaders/object_storage_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/url_validator_examples.rb2
-rw-r--r--spec/support/sidekiq.rb2
-rw-r--r--spec/support/stored_repositories.rb2
-rw-r--r--spec/support/test_reports/test_reports_helper.rb2
-rw-r--r--spec/support/trace/trace_helpers.rb2
-rw-r--r--spec/support/webmock.rb2
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb1
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb1
-rw-r--r--yarn.lock10
429 files changed, 4147 insertions, 1383 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 72de4f0b2aa..3898206e3b5 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -262,14 +262,6 @@ Naming/HeredocDelimiterNaming:
Naming/RescuedExceptionsVariableName:
Enabled: false
-# Offense count: 3
-# Cop supports --auto-correct.
-Performance/ReverseEach:
- Exclude:
- - 'app/models/commit.rb'
- - 'db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb'
- - 'lib/gitlab/profiler.rb'
-
# Offense count: 7081
# Configuration parameters: Prefixes.
# Prefixes: when, with, without
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 6754abf4019..ebf48cee2ae 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
import Flash from '~/flash';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import { visitUrl } from '~/lib/utils/url_utility';
@@ -86,12 +87,12 @@ export default {
},
buttonText() {
if (this.isNewForm) {
- return 'Create board';
+ return __('Create board');
}
if (this.isDeleteForm) {
- return 'Delete';
+ return __('Delete');
}
- return 'Save changes';
+ return __('Save changes');
},
buttonKind() {
if (this.isNewForm) {
@@ -104,15 +105,15 @@ export default {
},
title() {
if (this.isNewForm) {
- return 'Create new board';
+ return __('Create new board');
}
if (this.isDeleteForm) {
- return 'Delete board';
+ return __('Delete board');
}
if (this.readonly) {
- return 'Board scope';
+ return __('Board scope');
}
- return 'Edit board';
+ return __('Edit board');
},
readonly() {
return !this.canAdminBoard;
@@ -138,7 +139,7 @@ export default {
visitUrl(boardsStore.rootPath);
})
.catch(() => {
- Flash('Failed to delete board. Please try again.');
+ Flash(__('Failed to delete board. Please try again.'));
this.isLoading = false;
});
} else {
@@ -149,7 +150,7 @@ export default {
visitUrl(data.board_path);
})
.catch(() => {
- Flash('Unable to save your changes. Please try again.');
+ Flash(__('Unable to save your changes. Please try again.'));
this.isLoading = false;
});
}
@@ -182,17 +183,19 @@ export default {
@submit="submit"
>
<template slot="body">
- <p v-if="isDeleteForm">Are you sure you want to delete this board?</p>
+ <p v-if="isDeleteForm">{{ __('Are you sure you want to delete this board?') }}</p>
<form v-else class="js-board-config-modal" @submit.prevent>
<div v-if="!readonly" class="append-bottom-20">
- <label class="form-section-title label-bold" for="board-new-name"> Board name </label>
+ <label class="form-section-title label-bold" for="board-new-name">{{
+ __('Board name')
+ }}</label>
<input
id="board-new-name"
ref="name"
v-model="board.name"
class="form-control"
type="text"
- placeholder="Enter board name"
+ :placeholder="__('Enter board name')"
@keyup.enter="submit"
/>
</div>
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 7a6ad3dc771..daa941a63cd 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -12,6 +12,7 @@ import 'core-js/es/promise/finally';
import 'core-js/es/string/code-point-at';
import 'core-js/es/string/from-code-point';
import 'core-js/es/string/includes';
+import 'core-js/es/string/repeat';
import 'core-js/es/string/starts-with';
import 'core-js/es/string/ends-with';
import 'core-js/es/symbol';
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index a66555838ba..b98fe9f6ce2 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -3,9 +3,16 @@ import autosize from 'autosize';
import GfmAutoComplete, { defaultAutocompleteConfig } from 'ee_else_ce/gfm_auto_complete';
import dropzoneInput from './dropzone_input';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
+import IndentHelper from './helpers/indent_helper';
+import { keystroke } from './lib/utils/common_utils';
+import * as keys from './lib/utils/keycodes';
+import UndoStack from './lib/utils/undo_stack';
export default class GLForm {
constructor(form, enableGFM = {}) {
+ this.handleKeyShortcuts = this.handleKeyShortcuts.bind(this);
+ this.setState = this.setState.bind(this);
+
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = Object.assign({}, defaultAutocompleteConfig, enableGFM);
@@ -16,6 +23,10 @@ export default class GLForm {
this.enableGFM[item] = Boolean(dataSources[item]);
}
});
+
+ this.undoStack = new UndoStack();
+ this.indentHelper = new IndentHelper(this.textarea[0]);
+
// Before we start, we should clean up any previous data for this form
this.destroy();
// Set up the form
@@ -85,9 +96,84 @@ export default class GLForm {
clearEventListeners() {
this.textarea.off('focus');
this.textarea.off('blur');
+ this.textarea.off('keydown');
removeMarkdownListeners(this.form);
}
+ setState(state) {
+ const selection = [this.textarea[0].selectionStart, this.textarea[0].selectionEnd];
+ this.textarea.val(state);
+ this.textarea[0].setSelectionRange(selection[0], selection[1]);
+ }
+
+ /*
+ Handle keypresses for a custom undo/redo stack.
+ We need this because the toolbar buttons and indentation helpers mess with the browser's
+ native undo/redo capability.
+ */
+ handleUndo(event) {
+ const content = this.textarea.val();
+ const { selectionStart, selectionEnd } = this.textarea[0];
+ const stack = this.undoStack;
+
+ if (stack.isEmpty()) {
+ // ==== Save initial state in undo history ====
+ stack.save(content);
+ }
+
+ if (keystroke(event, keys.Z_KEY_CODE, 'l')) {
+ // ==== Undo ====
+ event.preventDefault();
+ stack.save(content);
+ if (stack.canUndo()) {
+ this.setState(stack.undo());
+ }
+ } else if (keystroke(event, keys.Z_KEY_CODE, 'ls') || keystroke(event, keys.Y_KEY_CODE, 'l')) {
+ // ==== Redo ====
+ event.preventDefault();
+ if (stack.canRedo()) {
+ this.setState(stack.redo());
+ }
+ } else if (
+ keystroke(event, keys.SPACE_KEY_CODE) ||
+ keystroke(event, keys.ENTER_KEY_CODE) ||
+ selectionStart !== selectionEnd
+ ) {
+ // ==== Save after finishing a word or before deleting a large selection ====
+ stack.save(content);
+ } else if (content === '') {
+ // ==== Save after deleting everything ====
+ stack.save('');
+ } else {
+ // ==== Save after 1 second of inactivity ====
+ stack.scheduleSave(content);
+ }
+ }
+
+ handleIndent(event) {
+ if (keystroke(event, keys.LEFT_BRACKET_KEY_CODE, 'l')) {
+ // ==== Unindent selected lines ====
+ event.preventDefault();
+ this.indentHelper.unindent();
+ } else if (keystroke(event, keys.RIGHT_BRACKET_KEY_CODE, 'l')) {
+ // ==== Indent selected lines ====
+ event.preventDefault();
+ this.indentHelper.indent();
+ } else if (keystroke(event, keys.ENTER_KEY_CODE)) {
+ // ==== Auto-indent new lines ====
+ event.preventDefault();
+ this.indentHelper.newline();
+ } else if (keystroke(event, keys.BACKSPACE_KEY_CODE)) {
+ // ==== Auto-delete indents at the beginning of the line ====
+ this.indentHelper.backspace(event);
+ }
+ }
+
+ handleKeyShortcuts(event) {
+ this.handleIndent(event);
+ this.handleUndo(event);
+ }
+
addEventListeners() {
this.textarea.on('focus', function focusTextArea() {
$(this)
@@ -99,5 +185,6 @@ export default class GLForm {
.closest('.md-area')
.removeClass('is-focused');
});
+ this.textarea.on('keydown', e => this.handleKeyShortcuts(e.originalEvent));
}
}
diff --git a/app/assets/javascripts/helpers/indent_helper.js b/app/assets/javascripts/helpers/indent_helper.js
new file mode 100644
index 00000000000..a8815fac04e
--- /dev/null
+++ b/app/assets/javascripts/helpers/indent_helper.js
@@ -0,0 +1,182 @@
+const INDENT_SEQUENCE = ' ';
+
+function countLeftSpaces(text) {
+ const i = text.split('').findIndex(c => c !== ' ');
+ return i === -1 ? text.length : i;
+}
+
+/**
+ * IndentHelper provides methods that allow manual and smart indentation in
+ * textareas. It supports line indent/unindent, selection indent/unindent,
+ * auto indentation on newlines, and smart deletion of indents with backspace.
+ */
+export default class IndentHelper {
+ /**
+ * Creates a new IndentHelper and binds it to the given `textarea`. You can provide a custom indent sequence in the second parameter, but the `newline` and `backspace` operations may work funny if the indent sequence isn't spaces only.
+ */
+ constructor(textarea, indentSequence = INDENT_SEQUENCE) {
+ this.element = textarea;
+ this.seq = indentSequence;
+ }
+
+ getSelection() {
+ return { start: this.element.selectionStart, end: this.element.selectionEnd };
+ }
+
+ isRangeSelection() {
+ return this.element.selectionStart !== this.element.selectionEnd;
+ }
+
+ /**
+ * Re-implementation of textarea's setRangeText method, because IE/Edge don't support it.
+ *
+ * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea%2Finput-setrangetext
+ */
+ setRangeText(replacement, start, end, selectMode) {
+ // Disable eslint to remain as faithful as possible to the above linked spec
+ /* eslint-disable no-param-reassign, no-case-declarations */
+ const text = this.element.value;
+
+ if (start > end) {
+ throw new RangeError('setRangeText: start index must be less than or equal to end index');
+ }
+
+ // Clamp to [0, len]
+ start = Math.max(0, Math.min(start, text.length));
+ end = Math.max(0, Math.min(end, text.length));
+
+ let selection = { start: this.element.selectionStart, end: this.element.selectionEnd };
+
+ this.element.value = text.slice(0, start) + replacement + text.slice(end);
+
+ const newLength = replacement.length;
+ const newEnd = start + newLength;
+
+ switch (selectMode) {
+ case 'select':
+ selection = { start, newEnd };
+ break;
+ case 'start':
+ selection = { start, end: start };
+ break;
+ case 'end':
+ selection = { start: newEnd, end: newEnd };
+ break;
+ case 'preserve':
+ default:
+ const oldLength = end - start;
+ const delta = newLength - oldLength;
+ if (selection.start > end) {
+ selection.start += delta;
+ } else if (selection.start > start) {
+ selection.start = start;
+ }
+ if (selection.end > end) {
+ selection.end += delta;
+ } else if (selection.end > start) {
+ selection.end = newEnd;
+ }
+ }
+
+ this.element.setSelectionRange(selection.start, selection.end);
+
+ /* eslint-enable no-param-reassign, no-case-declarations */
+ }
+
+ /**
+ * Returns an array of lines in the textarea, with information about their
+ * start/end offsets and whether they are included in the current selection.
+ */
+ splitLines() {
+ const { start, end } = this.getSelection();
+
+ const lines = this.element.value.split('\n');
+ let textStart = 0;
+ const lineObjects = [];
+ lines.forEach(line => {
+ const lineObj = {
+ text: line,
+ start: textStart,
+ end: textStart + line.length,
+ };
+ lineObj.inSelection = lineObj.start <= end && lineObj.end >= start;
+ lineObjects.push(lineObj);
+ textStart += line.length + 1;
+ });
+ return lineObjects;
+ }
+
+ /**
+ * Indents selected lines by one level.
+ */
+ indent() {
+ const { start } = this.getSelection();
+
+ const selectedLines = this.splitLines().filter(line => line.inSelection);
+ if (!this.isRangeSelection() && start === selectedLines[0].start) {
+ // Special case: if cursor is at the beginning of the line, move it one
+ // indent right.
+ const line = selectedLines[0];
+ this.setRangeText(this.seq, line.start, line.start, 'end');
+ } else {
+ selectedLines.reverse();
+ selectedLines.forEach(line => {
+ this.setRangeText(INDENT_SEQUENCE, line.start, line.start, 'preserve');
+ });
+ }
+ }
+
+ /**
+ * Unindents selected lines by one level.
+ */
+ unindent() {
+ const lines = this.splitLines().filter(line => line.inSelection);
+ lines.reverse();
+ lines
+ .filter(line => line.text.startsWith(this.seq))
+ .forEach(line => {
+ this.setRangeText('', line.start, line.start + this.seq.length, 'preserve');
+ });
+ }
+
+ /**
+ * Emulates a newline keypress, automatically indenting the new line.
+ */
+ newline() {
+ const { start, end } = this.getSelection();
+
+ if (this.isRangeSelection()) {
+ // Manually kill the selection before calculating the indent
+ this.setRangeText('', start, end, 'start');
+ }
+
+ // Auto-indent the next line
+ const currentLine = this.splitLines().find(line => line.end >= start);
+ const spaces = countLeftSpaces(currentLine.text);
+ this.setRangeText(`\n${' '.repeat(spaces)}`, start, start, 'end');
+ }
+
+ /**
+ * If the cursor is positioned at the end of a line's leading indents,
+ * emulates a backspace keypress by deleting a single level of indents.
+ * @param event The DOM KeyboardEvent that triggers this action, or null.
+ */
+ backspace(event) {
+ const { start } = this.getSelection();
+
+ // If the cursor is at the end of leading indents, delete an indent.
+ if (!this.isRangeSelection()) {
+ const currentLine = this.splitLines().find(line => line.end >= start);
+ const cursorPosition = start - currentLine.start;
+ if (countLeftSpaces(currentLine.text) === cursorPosition && cursorPosition > 0) {
+ if (event) event.preventDefault();
+
+ let spacesToDelete = cursorPosition % this.seq.length;
+ if (spacesToDelete === 0) {
+ spacesToDelete = this.seq.length;
+ }
+ this.setRangeText('', start - spacesToDelete, start, 'start');
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
index b1d5de8682d..137f8bb18c7 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
@@ -10,7 +10,9 @@ export default {
<template>
<div class="multi-file-commit-panel-success-message" aria-live="assertive">
- <div class="svg-content svg-80"><img :src="committedStateSvgPath" alt="" /></div>
+ <div class="svg-content svg-80">
+ <img :src="committedStateSvgPath" :alt="s__('IDE|Successful commit')" />
+ </div>
<div class="append-right-default prepend-left-default">
<div class="text-content text-center">
<h4>{{ __('All changes are committed') }}</h4>
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 5e90893b684..1a94aee2398 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -203,6 +203,71 @@ export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
// 3) Middle-click or Mouse Wheel Click (e.which is 2)
export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2;
+export const getPlatformLeaderKey = () => {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ if (navigator && navigator.platform && navigator.platform.startsWith('Mac')) {
+ return 'meta';
+ }
+ return 'ctrl';
+};
+
+export const getPlatformLeaderKeyHTML = () => {
+ if (getPlatformLeaderKey() === 'meta') {
+ return '&#8984;';
+ }
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return 'Ctrl';
+};
+
+export const isPlatformLeaderKey = e => {
+ if (getPlatformLeaderKey() === 'meta') {
+ return Boolean(e.metaKey);
+ }
+ return Boolean(e.ctrlKey);
+};
+
+/**
+ * Tests if a KeyboardEvent corresponds exactly to a keystroke.
+ *
+ * This function avoids hacking around an old version of Mousetrap, which we ship at the moment. It should be removed after we upgrade to the newest Mousetrap. See:
+ * - https://gitlab.com/gitlab-org/gitlab-ce/issues/63182
+ * - https://gitlab.com/gitlab-org/gitlab-ce/issues/64246
+ *
+ * @example
+ * // Matches the enter key with exactly zero modifiers
+ * keystroke(event, 13)
+ *
+ * @example
+ * // Matches Control-Shift-Z
+ * keystroke(event, 90, 'cs')
+ *
+ * @param e The KeyboardEvent to test.
+ * @param keyCode The key code of the key to test. Why keycodes? IE/Edge don't support the more convenient `key` and `code` properties.
+ * @param modifiers A string of modifiers keys. Each modifier key is represented by one character. The set of pressed modifier keys must match the given string exactly. Available options are 'a' for Alt/Option, 'c' for Control, 'm' for Meta/Command, 's' for Shift, and 'l' for the leader key (Meta on MacOS and Control otherwise).
+ * @returns {boolean} True if the KeyboardEvent corresponds to the given keystroke.
+ */
+export const keystroke = (e, keyCode, modifiers = '') => {
+ if (!e || !keyCode) {
+ return false;
+ }
+
+ const leader = getPlatformLeaderKey();
+ const mods = modifiers.toLowerCase().replace('l', leader.charAt(0));
+
+ // Match depressed modifier keys
+ if (
+ e.altKey !== mods.includes('a') ||
+ e.ctrlKey !== mods.includes('c') ||
+ e.metaKey !== mods.includes('m') ||
+ e.shiftKey !== mods.includes('s')
+ ) {
+ return false;
+ }
+
+ // Match the depressed key
+ return keyCode === (e.keyCode || e.which);
+};
+
export const contentTop = () => {
const perfBar = $('#js-peek').outerHeight() || 0;
const mrTabsHeight = $('.merge-request-tabs').outerHeight() || 0;
diff --git a/app/assets/javascripts/lib/utils/keycodes.js b/app/assets/javascripts/lib/utils/keycodes.js
index 5e0f9b612a2..e24fcf47d71 100644
--- a/app/assets/javascripts/lib/utils/keycodes.js
+++ b/app/assets/javascripts/lib/utils/keycodes.js
@@ -1,4 +1,10 @@
-export const UP_KEY_CODE = 38;
-export const DOWN_KEY_CODE = 40;
+export const BACKSPACE_KEY_CODE = 8;
export const ENTER_KEY_CODE = 13;
export const ESC_KEY_CODE = 27;
+export const SPACE_KEY_CODE = 32;
+export const UP_KEY_CODE = 38;
+export const DOWN_KEY_CODE = 40;
+export const Y_KEY_CODE = 89;
+export const Z_KEY_CODE = 90;
+export const LEFT_BRACKET_KEY_CODE = 219;
+export const RIGHT_BRACKET_KEY_CODE = 221;
diff --git a/app/assets/javascripts/lib/utils/undo_stack.js b/app/assets/javascripts/lib/utils/undo_stack.js
new file mode 100644
index 00000000000..6cfdc2a0a0f
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/undo_stack.js
@@ -0,0 +1,105 @@
+/**
+ * UndoStack provides a custom implementation of an undo/redo engine. It was originally written for GitLab's Markdown editor (`gl_form.js`), whose rich text editing capabilities broke native browser undo/redo behaviour.
+ *
+ * UndoStack supports predictable undos/redos, debounced saves, maximum history length, and duplicate detection.
+ *
+ * Usage:
+ * - `stack = new UndoStack();`
+ * - Saves a state to the stack with `stack.save(state)`.
+ * - Get the current state with `stack.current()`.
+ * - Revert to the previous state with `stack.undo()`.
+ * - Redo a previous undo with `stack.redo()`;
+ * - Queue a future save with `stack.scheduleSave(state, delay)`. Useful for text editors.
+ * - See the full undo history in `stack.history`.
+ */
+export default class UndoStack {
+ constructor(maxLength = 1000) {
+ this.clear();
+ this.maxLength = maxLength;
+
+ // If you're storing reference-types in the undo stack, you might want to
+ // reassign this property to some deep-equals function.
+ this.comparator = (a, b) => a === b;
+ }
+
+ current() {
+ if (this.cursor === -1) {
+ return undefined;
+ }
+ return this.history[this.cursor];
+ }
+
+ isEmpty() {
+ return this.history.length === 0;
+ }
+
+ clear() {
+ this.clearPending();
+ this.history = [];
+ this.cursor = -1;
+ }
+
+ save(state) {
+ this.clearPending();
+ if (this.comparator(state, this.current())) {
+ // Don't save state if it's the same as the current state
+ return;
+ }
+
+ this.history.length = this.cursor + 1;
+ this.history.push(state);
+ this.cursor += 1;
+
+ if (this.history.length > this.maxLength) {
+ this.history.shift();
+ this.cursor -= 1;
+ }
+ }
+
+ scheduleSave(state, delay = 1000) {
+ this.clearPending();
+ this.pendingState = state;
+ this.timeout = setTimeout(this.saveNow.bind(this), delay);
+ }
+
+ saveNow() {
+ // Persists scheduled saves immediately
+ this.save(this.pendingState);
+ this.clearPending();
+ }
+
+ clearPending() {
+ // Cancels any scheduled saves
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ delete this.timeout;
+ delete this.pendingState;
+ }
+ }
+
+ canUndo() {
+ return this.cursor > 0;
+ }
+
+ undo() {
+ this.clearPending();
+ if (!this.canUndo()) {
+ return undefined;
+ }
+ this.cursor -= 1;
+ return this.history[this.cursor];
+ }
+
+ canRedo() {
+ return this.cursor >= 0 && this.cursor < this.history.length - 1;
+ }
+
+ redo() {
+ this.clearPending();
+ if (!this.canRedo()) {
+ return undefined;
+ }
+ this.cursor += 1;
+ return this.history[this.cursor];
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
index 8001d2dd1da..f091c01fc98 100644
--- a/app/assets/javascripts/pages/projects/clusters/show/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -1,5 +1,7 @@
import ClustersBundle from '~/clusters/clusters_bundle';
+import initGkeNamespace from '~/projects/gke_cluster_namespace';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
+ initGkeNamespace();
});
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index d4bd02c14e9..55c377ebec0 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,4 +1,5 @@
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+import initGkeNamespace from '~/projects/gke_cluster_namespace';
import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
@@ -16,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => {
PersistentUserCallout.factory(callout);
initGkeDropdowns();
+ initGkeNamespace();
}
new Project(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index f05db8376a4..73c9c60765a 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -43,13 +43,13 @@ export default {
},
{
metric: 'rugged',
- header: 'Rugged calls',
+ header: s__('PerformanceBar|Rugged calls'),
details: 'details',
keys: ['feature', 'args'],
},
{
metric: 'redis',
- header: 'Redis calls',
+ header: s__('PerformanceBar|Redis calls'),
details: 'details',
keys: ['cmd'],
},
diff --git a/app/assets/javascripts/projects/gke_cluster_namespace/index.js b/app/assets/javascripts/projects/gke_cluster_namespace/index.js
new file mode 100644
index 00000000000..288740203ad
--- /dev/null
+++ b/app/assets/javascripts/projects/gke_cluster_namespace/index.js
@@ -0,0 +1,35 @@
+/**
+ * Disables & hides the namespace inputs when the gitlab-managed checkbox is checked/unchecked.
+ */
+
+const setDisabled = (el, isDisabled) => {
+ if (isDisabled) {
+ el.classList.add('hidden');
+ el.querySelector('input').setAttribute('disabled', true);
+ } else {
+ el.classList.remove('hidden');
+ el.querySelector('input').removeAttribute('disabled');
+ }
+};
+
+const setState = glManagedCheckbox => {
+ const glManaged = document.querySelector('.js-namespace-prefixed');
+ const selfManaged = document.querySelector('.js-namespace');
+
+ if (glManagedCheckbox.checked) {
+ setDisabled(glManaged, false);
+ setDisabled(selfManaged, true);
+ } else {
+ setDisabled(glManaged, true);
+ setDisabled(selfManaged, false);
+ }
+};
+
+const initGkeNamespace = () => {
+ const glManagedCheckbox = document.querySelector('.js-gl-managed');
+
+ setState(glManagedCheckbox); // this is needed in order to set the initial state
+ glManagedCheckbox.addEventListener('change', () => setState(glManagedCheckbox));
+};
+
+export default initGkeNamespace;
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index 8ce5b615795..21c44b59520 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -1,5 +1,6 @@
<script>
import { GlLink } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
export default {
components: {
@@ -22,8 +23,28 @@ export default {
},
},
computed: {
- hasQuickActionsDocsPath() {
- return this.quickActionsDocsPath !== '';
+ toolbarHelpHtml() {
+ const mdLinkStart = `<a href="${this.markdownDocsPath}" target="_blank" rel="noopener noreferrer" tabindex="-1">`;
+ const actionsLinkStart = `<a href="${this.quickActionsDocsPath}" target="_blank" rel="noopener noreferrer" tabindex="-1">`;
+ const linkEnd = '</a>';
+
+ if (this.markdownDocsPath && !this.quickActionsDocsPath) {
+ return sprintf(
+ s__('Editor|%{mdLinkStart}Markdown is supported%{mdLinkEnd}'),
+ { mdLinkStart, mdLinkEnd: linkEnd },
+ false,
+ );
+ } else if (this.markdownDocsPath && this.quickActionsDocsPath) {
+ return sprintf(
+ s__(
+ 'Editor|%{mdLinkStart}Markdown%{mdLinkEnd} and %{actionsLinkStart}quick actions%{actionsLinkEnd} are supported',
+ ),
+ { mdLinkStart, mdLinkEnd: linkEnd, actionsLinkStart, actionsLinkEnd: linkEnd },
+ false,
+ );
+ }
+
+ return null;
},
},
};
@@ -32,21 +53,7 @@ export default {
<template>
<div class="comment-toolbar clearfix">
<div class="toolbar-text">
- <template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">{{
- __('Markdown is supported')
- }}</gl-link>
- </template>
- <template v-if="hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">{{
- __('Markdown')
- }}</gl-link>
- and
- <gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">{{
- __('quick actions')
- }}</gl-link>
- are supported
- </template>
+ <span v-html="toolbarHelpHtml"></span>
</div>
<span v-if="canAttachFile" class="uploading-container">
<span class="uploading-progress-container hide">
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 61ab0476c42..6b44834cc52 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -441,6 +441,7 @@ img.emoji {
.mw-460 { max-width: 460px; }
.mw-6em { max-width: 6em; }
.mw-70p { max-width: 70%; }
+.mw-90p { max-width: 90%; }
.min-height-0 { min-height: 0; }
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 460d9ea9526..ecd32dcd0ce 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -285,3 +285,19 @@ ul.indent-list {
max-width: 350px;
}
}
+
+.horizontal-list {
+ padding-left: 0;
+ list-style: none;
+
+ > li {
+ float: left;
+ }
+
+ &.list-items-separated {
+ > li:not(:last-child)::after {
+ content: '\00b7';
+ margin: 0 $gl-padding-4;
+ }
+ }
+}
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 4926062f9ca..8c8f0b3a22e 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -55,7 +55,7 @@ module AuthenticatesWithTwoFactor
remember_me(user) if user_params[:remember_me] == '1'
user.save!
- sign_in(user, message: :two_factor_authenticated)
+ sign_in(user, message: :two_factor_authenticated, event: :authentication)
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
@@ -72,7 +72,7 @@ module AuthenticatesWithTwoFactor
session.delete(:challenge)
remember_me(user) if user_params[:remember_me] == '1'
- sign_in(user, message: :two_factor_authenticated)
+ sign_in(user, message: :two_factor_authenticated, event: :authentication)
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 2a8dd997d04..b1efa767154 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -139,7 +139,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if user.two_factor_enabled? && !auth_user.bypass_two_factor?
prompt_for_two_factor(user)
else
- sign_in_and_redirect(user)
+ sign_in_and_redirect(user, event: :authentication)
end
else
fail_login(user)
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 3b4215b766e..a51759641e4 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -6,6 +6,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project, except: :create
before_action :assign_archive_vars, only: :archive
+ before_action :assign_append_sha, only: :archive
before_action :authorize_download_code!
before_action :authorize_admin_project!, only: :create
@@ -16,19 +17,64 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- append_sha = params[:append_sha]
+ set_cache_headers
+ return if archive_not_modified?
- if @ref
- shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
- append_sha = false if @filename == shortname
- end
-
- send_git_archive @repository, ref: @ref, path: params[:path], format: params[:format], append_sha: append_sha
+ send_git_archive @repository, **repo_params
rescue => ex
logger.error("#{self.class.name}: #{ex}")
git_not_found!
end
+ private
+
+ def repo_params
+ @repo_params ||= { ref: @ref, path: params[:path], format: params[:format], append_sha: @append_sha }
+ end
+
+ def set_cache_headers
+ expires_in cache_max_age(archive_metadata['CommitId']), public: project.public?
+ fresh_when(etag: archive_metadata['ArchivePath'])
+ end
+
+ def archive_not_modified?
+ # Check response freshness (Last-Modified and ETag)
+ # against request If-Modified-Since and If-None-Match conditions.
+ request.fresh?(response)
+ end
+
+ def archive_metadata
+ @archive_metadata ||= @repository.archive_metadata(
+ @ref,
+ '', # Where archives are stored isn't really important for ETag purposes
+ repo_params[:format],
+ path: repo_params[:path],
+ append_sha: @append_sha
+ )
+ end
+
+ def cache_max_age(commit_id)
+ if @ref == commit_id
+ # This is a link to an archive by a commit SHA. That means that the archive
+ # is immutable. The only reason to invalidate the cache is if the commit
+ # was deleted or if the user lost access to the repository.
+ Repository::ARCHIVE_CACHE_TIME_IMMUTABLE
+ else
+ # A branch or tag points at this archive. That means that the expected archive
+ # content may change over time.
+ Repository::ARCHIVE_CACHE_TIME
+ end
+ end
+
+ def assign_append_sha
+ @append_sha = params[:append_sha]
+
+ if @ref
+ shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
+ @append_sha = false if @filename == shortname
+ end
+ end
+
def assign_archive_vars
if params[:id]
@ref, @filename = extract_ref(params[:id])
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 7604b31467a..1880bead3ee 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -26,6 +26,17 @@ class SessionsController < Devise::SessionsController
after_action :log_failed_login, if: -> { action_name == 'new' && failed_login? }
helper_method :captcha_enabled?
+ # protect_from_forgery is already prepended in ApplicationController but
+ # authenticate_with_two_factor which signs in the user is prepended before
+ # that here.
+ # We need to make sure CSRF token is verified before authenticating the user
+ # because Devise.clean_up_csrf_token_on_authentication is set to true by
+ # default to avoid CSRF token fixation attacks. Authenticating the user first
+ # would cause the CSRF token to be cleared and then
+ # RequestForgeryProtection#verify_authenticity_token would fail because of
+ # token mismatch.
+ protect_from_forgery with: :exception, prepend: true
+
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
def new
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 9a281065b90..e683e2959d1 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -13,6 +13,8 @@ module SubmoduleHelper
end
def submodule_links_for_url(submodule_item_id, url, repository)
+ return [nil, nil] unless url
+
if url == '.' || url == './'
url = File.join(Gitlab.config.gitlab.url, repository.project.full_path)
end
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 2bfa59774d7..76fa7236ab1 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -9,11 +9,11 @@ module Emails
helper_method :member_source, :member
end
- def member_access_requested_email(member_source_type, member_id, recipient_notification_email)
+ def member_access_requested_email(member_source_type, member_id, recipient_id)
@member_source_type = member_source_type
@member_id = member_id
- mail(to: recipient_notification_email,
+ mail(to: recipient(recipient_id, notification_group),
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end
@@ -21,16 +21,15 @@ module Emails
@member_source_type = member_source_type
@member_id = member_id
- mail(to: member.user.notification_email,
+ mail(to: recipient(member.user, notification_group),
subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted"))
end
def member_access_denied_email(member_source_type, source_id, user_id)
@member_source_type = member_source_type
@member_source = member_source_class.find(source_id)
- requester = User.find(user_id)
- mail(to: requester.notification_email,
+ mail(to: recipient(user_id, notification_group),
subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was denied"))
end
@@ -48,7 +47,7 @@ module Emails
@member_id = member_id
return unless member.created_by
- mail(to: member.created_by.notification_email,
+ mail(to: recipient(member.created_by, notification_group),
subject: subject('Invitation accepted'))
end
@@ -59,7 +58,7 @@ module Emails
@member_source = member_source_class.find(source_id)
@invite_email = invite_email
- mail(to: recipient(created_by_id, member_source_type == 'Project' ? @member_source.group : @member_source),
+ mail(to: recipient(created_by_id, notification_group),
subject: subject('Invitation declined'))
end
@@ -71,6 +70,10 @@ module Emails
@member_source ||= member.source
end
+ def notification_group
+ @member_source_type.casecmp?('project') ? member_source.group : member_source
+ end
+
private
def member_source_class
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 8ef20a03541..5d292094a05 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -71,14 +71,18 @@ class Notify < BaseMailer
address.format
end
- # Look up a User by their ID and return their email address
+ # Look up a User's notification email for a particular context.
+ # Can look up by their ID or can accept a User object.
#
- # recipient_id - User ID
+ # recipient - User object OR a User ID
# notification_group - The parent group of the notification
#
# Returns a String containing the User's email address.
- def recipient(recipient_id, notification_group = nil)
- User.find(recipient_id).notification_email_for(notification_group)
+ def recipient(recipient, notification_group = nil)
+ user = recipient if recipient.is_a?(User)
+ user ||= User.find(recipient)
+
+ user.notification_email_for(notification_group)
end
# Formats arguments into a String suitable for use as an email subject
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 80e0a17c312..b3fab930922 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -105,11 +105,11 @@ class NotifyPreview < ActionMailer::Preview
end
def member_access_granted_email
- Notify.member_access_granted_email('project', user.id).message
+ Notify.member_access_granted_email(member.source_type, member.id).message
end
def member_access_requested_email
- Notify.member_access_requested_email('group', user.id, 'some@example.com').message
+ Notify.member_access_requested_email('group', user.id, user.id).message
end
def member_invite_accepted_email
@@ -183,6 +183,10 @@ class NotifyPreview < ActionMailer::Preview
@user ||= User.last
end
+ def member
+ @member ||= Member.last
+ end
+
def create_note(params)
Notes::CreateService.new(project, user, params).execute
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index be37fa2e76f..0889ce7e287 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -346,7 +346,7 @@ class Commit
if commits_in_merge_request.present?
message_body << ""
- commits_in_merge_request.reverse.each do |commit_in_merge|
+ commits_in_merge_request.reverse_each do |commit_in_merge|
message_body << "#{commit_in_merge.short_id} #{commit_in_merge.title}"
end
end
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index af387c99f3d..0648b4a78e1 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -47,7 +47,7 @@ module ProtectedRef
def access_levels_for_ref(ref, action:, protected_refs: nil)
self.matching(ref, protected_refs: protected_refs)
- .map(&:"#{action}_access_levels").flatten
+ .flat_map(&:"#{action}_access_levels")
end
# Returns all protected refs that match the given ref name.
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index e4fe46d722a..9cd7b8d6258 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -1,5 +1,26 @@
# frozen_string_literal: true
+# This module makes it possible to handle items as a list, where the order of items can be easily altered
+# Requirements:
+#
+# - Only works for ActiveRecord models
+# - relative_position integer field must present on the model
+# - This module uses GROUP BY: the model should have a parent relation, example: project -> issues, project is the parent relation (issues table has a parent_id column)
+#
+# Setup like this in the body of your class:
+#
+# include RelativePositioning
+#
+# # base query used for the position calculation
+# def self.relative_positioning_query_base(issue)
+# where(deleted: false)
+# end
+#
+# # column that should be used in GROUP BY
+# def self.relative_positioning_parent_column
+# :project_id
+# end
+#
module RelativePositioning
extend ActiveSupport::Concern
@@ -93,7 +114,7 @@ module RelativePositioning
return move_after(before) unless after
return move_before(after) unless before
- # If there is no place to insert an issue we need to create one by moving the before issue closer
+ # If there is no place to insert an item we need to create one by moving the before item closer
# to its predecessor. This process will recursively move all the predecessors until we have a place
if (after.relative_position - before.relative_position) < 2
before.move_before
@@ -108,11 +129,11 @@ module RelativePositioning
pos_after = before.next_relative_position
if before.shift_after?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_after)
- issue_to_move.move_after
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ item_to_move = self.class.relative_positioning_query_base(self).find_by!(relative_position: pos_after)
+ item_to_move.move_after
+ @positionable_neighbours = [item_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
- pos_after = issue_to_move.relative_position
+ pos_after = item_to_move.relative_position
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -123,11 +144,11 @@ module RelativePositioning
pos_before = after.prev_relative_position
if after.shift_before?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_before)
- issue_to_move.move_before
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ item_to_move = self.class.relative_positioning_query_base(self).find_by!(relative_position: pos_before)
+ item_to_move.move_before
+ @positionable_neighbours = [item_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
- pos_before = issue_to_move.relative_position
+ pos_before = item_to_move.relative_position
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -141,13 +162,13 @@ module RelativePositioning
self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end
- # Indicates if there is an issue that should be shifted to free the place
+ # Indicates if there is an item that should be shifted to free the place
def shift_after?
next_pos = next_relative_position
next_pos && (next_pos - relative_position) == 1
end
- # Indicates if there is an issue that should be shifted to free the place
+ # Indicates if there is an item that should be shifted to free the place
def shift_before?
prev_pos = prev_relative_position
prev_pos && (relative_position - prev_pos) == 1
@@ -159,7 +180,7 @@ module RelativePositioning
def save_positionable_neighbours
return unless @positionable_neighbours
- status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
+ status = @positionable_neighbours.all? { |item| item.save(touch: false) }
@positionable_neighbours = nil
status
@@ -170,16 +191,15 @@ module RelativePositioning
# When calculating across projects, this is much more efficient than
# MAX(relative_position) without the GROUP BY, due to index usage:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54276#note_119340977
- relation = self.class
- .in_parents(parent_ids)
+ relation = self.class.relative_positioning_query_base(self)
.order(Gitlab::Database.nulls_last_order('position', 'DESC'))
+ .group(self.class.relative_positioning_parent_column)
.limit(1)
- .group(self.class.parent_column)
relation = yield relation if block_given?
relation
- .pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
+ .pluck(self.class.relative_positioning_parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
.first&.
last
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 26ce2957e9b..65a6705b6c0 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -388,7 +388,7 @@ class Group < Namespace
variables = Ci::GroupVariable.where(group: list_of_ids)
variables = variables.unprotected unless project.protected_for?(ref)
variables = variables.group_by(&:group_id)
- list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
+ list_of_ids.reverse.flat_map { |group| variables[group.id] }.compact
end
def group_member(user)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 8c5dd5e382e..164858dc432 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -91,11 +91,11 @@ class Issue < ApplicationRecord
end
end
- class << self
- alias_method :in_parents, :in_projects
+ def self.relative_positioning_query_base(issue)
+ in_projects(issue.parent_ids)
end
- def self.parent_column
+ def self.relative_positioning_parent_column
:project_id
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 7ab79242cc3..d08fcd8954d 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -31,7 +31,7 @@ class JiraService < IssueTrackerService
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def self.reference_pattern(only_long: true)
- @reference_pattern ||= /(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)/
+ @reference_pattern ||= /(?<issue>\b#{Gitlab::Regex.jira_issue_key_regex})/
end
def initialize_properties
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 187382ad182..a89f573e3d6 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -7,6 +7,9 @@ class Repository
REF_KEEP_AROUND = 'keep-around'.freeze
REF_ENVIRONMENTS = 'environments'.freeze
+ ARCHIVE_CACHE_TIME = 60 # Cache archives referred to by a (mutable) ref for 1 minute
+ ARCHIVE_CACHE_TIME_IMMUTABLE = 3600 # Cache archives referred to by an immutable reference for 1 hour
+
RESERVED_REFS_NAMES = %W[
heads
tags
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
index 029dd3d0684..0b0454c5282 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -59,14 +59,14 @@ class StageEntity < Grape::Entity
end
def latest_statuses
- HasStatus::ORDERED_STATUSES.map do |ordered_status|
+ HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
grouped_statuses.fetch(ordered_status, [])
- end.flatten
+ end
end
def retried_statuses
- HasStatus::ORDERED_STATUSES.map do |ordered_status|
+ HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
grouped_retried_statuses.fetch(ordered_status, [])
- end.flatten
+ end
end
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index aaf56048b5c..207cc5017d0 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -10,13 +10,13 @@ module Ci
update_retried
new_builds =
- stage_indexes_of_created_processables.map do |index|
+ stage_indexes_of_created_processables.flat_map do |index|
process_stage(index)
end
@pipeline.update_status
- new_builds.flatten.any?
+ new_builds.any?
end
private
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index 6d70b5106c7..b210004e6e1 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -118,7 +118,14 @@ module MergeRequests
end
def base_params
- params = {}
+ params = {
+ title: push_options[:title],
+ description: push_options[:description],
+ target_branch: push_options[:target],
+ force_remove_source_branch: push_options[:remove_source_branch]
+ }
+
+ params.compact!
if push_options.key?(:merge_when_pipeline_succeeds)
params.merge!(
@@ -127,14 +134,6 @@ module MergeRequests
)
end
- if push_options.key?(:remove_source_branch)
- params[:force_remove_source_branch] = push_options[:remove_source_branch]
- end
-
- if push_options.key?(:target)
- params[:target_branch] = push_options[:target]
- end
-
params
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index a55771ed538..21fab22e0d4 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -595,7 +595,7 @@ class NotificationService
end
def deliver_access_request_email(recipient, member)
- mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.notification_email).deliver_later
+ mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.id).deliver_later
end
def fallback_to_group_owners_maintainers?(recipients, member)
diff --git a/app/views/clusters/clusters/_namespace.html.haml b/app/views/clusters/clusters/_namespace.html.haml
new file mode 100644
index 00000000000..0c64819ad62
--- /dev/null
+++ b/app/views/clusters/clusters/_namespace.html.haml
@@ -0,0 +1,14 @@
+- managed_namespace_help_text = s_('ClusterIntegration|Choose a prefix to be used for your namespaces. Defaults to your project path.')
+- non_managed_namespace_help_text = s_('ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, pod logs, and Web terminals.')
+- managed_namespace_help_link = link_to _('More information'), help_page_path('user/project/clusters/index.md',
+ anchor: 'gitlab-managed-clusters'), target: '_blank'
+
+.js-namespace-prefixed
+ = platform_field.text_field :namespace,
+ label: s_('ClusterIntegration|Project namespace prefix (optional, unique)'), label_class: 'label-bold',
+ help: '%{help_text} %{help_link}'.html_safe % { help_text: managed_namespace_help_text, help_link: managed_namespace_help_link }
+.js-namespace.hidden
+ = platform_field.text_field :namespace,
+ label: s_('ClusterIntegration|Project namespace (optional, unique)'), label_class: 'label-bold',
+ help: '%{help_text}'.html_safe % { help_text: non_managed_namespace_help_text },
+ disabled: true
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index f2fc5ac93fb..1d212553c3b 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -34,10 +34,6 @@
autocomplete: 'off', label_class: 'label-bold',
help: '%{help_text} %{help_link}'.html_safe % { help_text: token_help_text, help_link: more_info_link }
- - if @user_cluster.allow_user_defined_namespace?
- = platform_kubernetes_field.text_field :namespace,
- label: s_('ClusterIntegration|Project namespace (optional, unique)'), label_class: 'label-bold'
-
= platform_kubernetes_field.form_group :authorization_type,
{ help: '%{help_text} %{help_link}'.html_safe % { help_text: rbac_help_text, help_link: rbac_help_link } } do
= platform_kubernetes_field.check_box :authorization_type,
@@ -46,10 +42,15 @@
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
+ class: 'js-gl-managed',
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
= link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank'
+ = field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field|
+ - if @user_cluster.allow_user_defined_namespace?
+ = render('clusters/clusters/namespace', platform_field: platform_kubernetes_field)
+
.form-group
= field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml
index f2e44462226..e50c573bd90 100644
--- a/app/views/clusters/platforms/kubernetes/_form.html.haml
+++ b/app/views/clusters/platforms/kubernetes/_form.html.haml
@@ -36,10 +36,6 @@
label: s_('ClusterIntegration|Service Token'), label_class: 'label-bold',
input_group_class: 'gl-field-error-anchor', append: show_token_btn + copy_token_btn
- - if cluster.allow_user_defined_namespace?
- = platform_field.text_field :namespace, label: s_('ClusterIntegration|Project namespace (optional, unique)'),
- label_class: 'label-bold'
-
= platform_field.form_group :authorization_type do
= platform_field.check_box :authorization_type, { disabled: true, label: s_('ClusterIntegration|RBAC-enabled cluster'),
label_class: 'label-bold', inline: true }, 'rbac', 'abac'
@@ -49,10 +45,14 @@
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
+ class: 'js-gl-managed',
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
= link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank'
+ - if cluster.allow_user_defined_namespace?
+ = render('clusters/clusters/namespace', platform_field: platform_field)
+
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index fae7d6526e8..72ede50dd8c 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -1,14 +1,13 @@
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
.comment-toolbar.clearfix
.toolbar-text
- = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank', tabindex: -1
+ - md_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" tabindex="-1">'.html_safe % { url: help_page_path('user/markdown') }
+ - actions_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" tabindex="-1">'.html_safe % { url: help_page_path('user/project/quick_actions') }
+ - link_end = '</a>'.html_safe
- if supports_quick_actions
- and
- = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
- are
+ = s_('Editor|%{mdLinkStart}Markdown%{mdLinkEnd} and %{actionsLinkStart}quick actions%{actionsLinkEnd} are supported').html_safe % { mdLinkStart: md_link_start, mdLinkEnd: link_end, actionsLinkStart: actions_link_start, actionsLinkEnd: link_end }
- else
- is
- supported
+ = s_('Editor|%{mdLinkStart}Markdown is supported%{mdLinkEnd}').html_safe % { mdLinkStart: md_link_start, mdLinkEnd: link_end }
%span.uploading-container
%span.uploading-progress-container.hide
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index c4d1bdad2c4..f40a9cffb29 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -89,4 +89,6 @@
%span.icon-wrapper.pipeline-status
= render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), type: 'commit', tooltip_placement: 'top', path: pipeline_path
.updated-note
- %span #{_('Updated')} #{updated_tooltip}
+ %span
+ = _('Updated')
+ = updated_tooltip
diff --git a/changelogs/unreleased/63568-access-email-notifications-custom-email.yml b/changelogs/unreleased/63568-access-email-notifications-custom-email.yml
new file mode 100644
index 00000000000..ece6442d7cf
--- /dev/null
+++ b/changelogs/unreleased/63568-access-email-notifications-custom-email.yml
@@ -0,0 +1,5 @@
+---
+title: Respect group notification email when sending group access notifications
+merge_request: 31089
+author:
+type: fixed
diff --git a/changelogs/unreleased/64257-warden_set_user_fix.yml b/changelogs/unreleased/64257-warden_set_user_fix.yml
new file mode 100644
index 00000000000..7b6818876fb
--- /dev/null
+++ b/changelogs/unreleased/64257-warden_set_user_fix.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure Warden triggers after_authentication callback
+merge_request: 31138
+author:
+type: fixed
diff --git a/changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml b/changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml
new file mode 100644
index 00000000000..dd74b8443bc
--- /dev/null
+++ b/changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect use of message interpolation
+merge_request: 31121
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-caching-to-archive-endpoint.yml b/changelogs/unreleased/add-caching-to-archive-endpoint.yml
new file mode 100644
index 00000000000..770ec16e804
--- /dev/null
+++ b/changelogs/unreleased/add-caching-to-archive-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Return an ETag header for the archive endpoint
+merge_request: 30581
+author:
+type: added
diff --git a/changelogs/unreleased/delete-designs-v2.yml b/changelogs/unreleased/delete-designs-v2.yml
new file mode 100644
index 00000000000..a678e4f93b9
--- /dev/null
+++ b/changelogs/unreleased/delete-designs-v2.yml
@@ -0,0 +1,4 @@
+---
+title: Adds event enum column to DesignsVersions join table
+merge_request: 30745
+type: added
diff --git a/changelogs/unreleased/dm-submodule-links-nil.yml b/changelogs/unreleased/dm-submodule-links-nil.yml
new file mode 100644
index 00000000000..c09ca41d01d
--- /dev/null
+++ b/changelogs/unreleased/dm-submodule-links-nil.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error rendering submodules in MR diffs when there is no .gitmodules
+merge_request: 31162
+author:
+type: fixed
diff --git a/changelogs/unreleased/extract_auto_deploy_into_base_image.yml b/changelogs/unreleased/extract_auto_deploy_into_base_image.yml
new file mode 100644
index 00000000000..ff0d1f3bd71
--- /dev/null
+++ b/changelogs/unreleased/extract_auto_deploy_into_base_image.yml
@@ -0,0 +1,5 @@
+---
+title: Extract Auto DevOps deploy functions into a base image
+merge_request: 30404
+author:
+type: changed
diff --git a/changelogs/unreleased/je-separate-namespace-fe.yml b/changelogs/unreleased/je-separate-namespace-fe.yml
new file mode 100644
index 00000000000..fb6be071eb6
--- /dev/null
+++ b/changelogs/unreleased/je-separate-namespace-fe.yml
@@ -0,0 +1,5 @@
+---
+title: Update namespace label for GitLab-managed clusters
+merge_request: 30935
+author:
+type: added
diff --git a/changelogs/unreleased/label-descr-push-opts.yml b/changelogs/unreleased/label-descr-push-opts.yml
new file mode 100644
index 00000000000..9b01cfdaed2
--- /dev/null
+++ b/changelogs/unreleased/label-descr-push-opts.yml
@@ -0,0 +1,5 @@
+---
+title: Support setting of merge request title and description using git push options
+merge_request: 31068
+author:
+type: added
diff --git a/changelogs/unreleased/mh-editor-indents.yml b/changelogs/unreleased/mh-editor-indents.yml
new file mode 100644
index 00000000000..a282c0f505d
--- /dev/null
+++ b/changelogs/unreleased/mh-editor-indents.yml
@@ -0,0 +1,5 @@
+---
+title: Markdown editors now have indentation shortcuts and auto-indentation
+merge_request: 28914
+author:
+type: added
diff --git a/changelogs/unreleased/sh-support-docker-oci-images.yml b/changelogs/unreleased/sh-support-docker-oci-images.yml
new file mode 100644
index 00000000000..2dcf980fa50
--- /dev/null
+++ b/changelogs/unreleased/sh-support-docker-oci-images.yml
@@ -0,0 +1,5 @@
+---
+title: Support Docker OCI images
+merge_request: 31127
+author:
+type: fixed
diff --git a/config/initializers/0_license.rb b/config/initializers/0_license.rb
index f750022dfdf..19c71c34904 100644
--- a/config/initializers/0_license.rb
+++ b/config/initializers/0_license.rb
@@ -10,7 +10,7 @@ Gitlab.ee do
end
# Needed to run migration
- if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
if ::License.block_changes? && message.present?
warn "WARNING: #{message}"
diff --git a/config/initializers/active_record_migration.rb b/config/initializers/active_record_migration.rb
deleted file mode 100644
index 04c06be7834..00000000000
--- a/config/initializers/active_record_migration.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'active_record/migration'
-
-module ActiveRecord
- class Migration
- # data_source_exists? is not available in 4.2.10, table_exists deprecated in 5.0
- def table_exists?(table_name)
- ActiveRecord::Base.connection.data_source_exists?(table_name)
- end
- end
-end
diff --git a/config/initializers/load_balancing.rb b/config/initializers/load_balancing.rb
index 029c0ff4277..a49bcbe1f96 100644
--- a/config/initializers/load_balancing.rb
+++ b/config/initializers/load_balancing.rb
@@ -3,7 +3,7 @@
# We need to run this initializer after migrations are done so it doesn't fail on CI
Gitlab.ee do
- if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
if Gitlab::Database::LoadBalancing.enable?
Gitlab::Database.disable_prepared_statements
diff --git a/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
index 0048268ca6f..bf7f7b44dec 100644
--- a/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
+++ b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
@@ -35,7 +35,7 @@ class AddIndexesForMergeRequestDiffsQuery < ActiveRecord::Migration[5.0]
end
def down
- INDEX_SPECS.reverse.each do |spec|
+ INDEX_SPECS.reverse_each do |spec|
remove_concurrent_index(*spec)
end
end
diff --git a/db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb b/db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb
new file mode 100644
index 00000000000..81a8b0a3271
--- /dev/null
+++ b/db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# This migration sets up a event enum on the DesignsVersions join table
+class AddEventTypeToDesignManagementDesignsVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # We disable these cops here because adding this column is safe. The table does not
+ # have any data in it.
+ # rubocop: disable Migration/AddIndex
+ # rubocop: disable Migration/AddColumn
+ def up
+ add_column(:design_management_designs_versions, :event, :integer,
+ limit: 2,
+ null: false,
+ default: 0)
+ add_index(:design_management_designs_versions, :event)
+ end
+
+ # rubocop: disable Migration/RemoveIndex
+ def down
+ remove_index(:design_management_designs_versions, :event)
+ remove_column(:design_management_designs_versions, :event)
+ end
+end
diff --git a/db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb b/db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb
new file mode 100644
index 00000000000..21b00e0b7d9
--- /dev/null
+++ b/db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ChangeOutboundLocalRequestsWhitelistDefault < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ class ApplicationSetting < ActiveRecord::Base
+ self.table_name = 'application_settings'
+ end
+
+ def up
+ default_value = []
+
+ change_column_default(:application_settings, :outbound_local_requests_whitelist, default_value)
+
+ ApplicationSetting
+ .where(outbound_local_requests_whitelist: nil)
+ .update(outbound_local_requests_whitelist: default_value)
+
+ change_column_null(:application_settings, :outbound_local_requests_whitelist, false)
+ end
+
+ def down
+ change_column_null(:application_settings, :outbound_local_requests_whitelist, true)
+
+ change_column_default(:application_settings, :outbound_local_requests_whitelist, nil)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2ec8fcce8f8..67479937b47 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_07_15_142138) do
+ActiveRecord::Schema.define(version: 2019_07_25_012225) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -228,7 +228,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_142138) do
t.boolean "lock_memberships_to_ldap", default: false, null: false
t.boolean "time_tracking_limit_to_hours", default: false, null: false
t.string "grafana_url", default: "/-/grafana", null: false
- t.string "outbound_local_requests_whitelist", limit: 255, array: true
+ t.string "outbound_local_requests_whitelist", limit: 255, default: [], null: false, array: true
t.integer "raw_blob_request_limit", default: 300, null: false
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
@@ -1097,8 +1097,10 @@ ActiveRecord::Schema.define(version: 2019_07_15_142138) do
create_table "design_management_designs_versions", id: false, force: :cascade do |t|
t.bigint "design_id", null: false
t.bigint "version_id", null: false
+ t.integer "event", limit: 2, default: 0, null: false
t.index ["design_id", "version_id"], name: "design_management_designs_versions_uniqueness", unique: true
t.index ["design_id"], name: "index_design_management_designs_versions_on_design_id"
+ t.index ["event"], name: "index_design_management_designs_versions_on_event"
t.index ["version_id"], name: "index_design_management_designs_versions_on_version_id"
end
diff --git a/doc/README.md b/doc/README.md
index bf7017d8fb4..af675582a99 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -302,7 +302,7 @@ The following documentation relates to the DevOps **Configure** stage:
| Configure Topics | Description |
|:-----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
| [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. |
-| [Easy creation of Kubernetes<br/>clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. |
+| [Create Kubernetes clusters on GKE](user/project/clusters/index.md#add-new-gke-cluster) | Use Google Kubernetes Engine and GitLab. |
| [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. |
| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. |
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 44a33eb7bcf..31876dd178a 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -242,7 +242,7 @@ I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory
User clone/fetch activity using ssh transport appears in this log as `executing git command <gitaly-upload-pack...`.
-## `unicorn\_stderr.log`
+## `unicorn_stderr.log`
This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/unicorn_stderr.log` for
diff --git a/doc/api/README.md b/doc/api/README.md
index 91bc8ca8aab..70540420544 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -515,7 +515,7 @@ more than 10,000, the `X-Total` and `X-Total-Pages` headers as well as the
## Namespaced path encoding
-If using namespaced API calls, make sure that the `NAMESPACE/PROJECT_NAME` is
+If using namespaced API calls, make sure that the `NAMESPACE/PROJECT_PATH` is
URL-encoded.
For example, `/` is represented by `%2F`:
@@ -524,6 +524,11 @@ For example, `/` is represented by `%2F`:
GET /api/v4/projects/diaspora%2Fdiaspora
```
+NOTE: **Note:**
+A project's **path** is not necessarily the same as its **name**. A
+project's path can found in the project's URL or in the project's settings
+under **General > Advanced > Change path**.
+
## Branches and tags name encoding
If your branch or tag contains a `/`, make sure the branch/tag name is
diff --git a/doc/api/dependencies.md b/doc/api/dependencies.md
index ed5ebdade19..2496b038c7f 100644
--- a/doc/api/dependencies.md
+++ b/doc/api/dependencies.md
@@ -17,8 +17,8 @@ supported by Gemnasium.
```
GET /projects/:id/dependencies
-GET /projects/:id/vulnerabilities?package_manger=maven
-GET /projects/:id/vulnerabilities?package_manger=yarn,bundler
+GET /projects/:id/vulnerabilities?package_manager=maven
+GET /projects/:id/vulnerabilities?package_manager=yarn,bundler
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/group_clusters.md b/doc/api/group_clusters.md
index 31c0e6abead..29e58d9279a 100644
--- a/doc/api/group_clusters.md
+++ b/doc/api/group_clusters.md
@@ -210,7 +210,7 @@ Parameters:
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
-through the ["Add an existing Kubernetes Cluster"](../user/project/clusters/index.md#adding-an-existing-kubernetes-cluster) option or
+through the ["Add existing Kubernetes cluster"](../user/project/clusters/index.md#add-existing-kubernetes-cluster) option or
through the ["Add existing cluster to group"](#add-existing-cluster-to-group) endpoint.
Example request:
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
index 614ea41d572..762a4ad95ab 100644
--- a/doc/api/project_clusters.md
+++ b/doc/api/project_clusters.md
@@ -261,7 +261,7 @@ Parameters:
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
-through the ["Add an existing Kubernetes Cluster"](../user/project/clusters/index.md#adding-an-existing-kubernetes-cluster) option or
+through the ["Add existing Kubernetes cluster"](../user/project/clusters/index.md#add-existing-kubernetes-cluster) option or
through the ["Add existing cluster to project"](#add-existing-cluster-to-project) endpoint.
Example request:
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 7048ceaac41..6923d07bb1d 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -13,6 +13,11 @@ through the [continuous methodologies](introduction/index.md#introduction-to-cic
- Continuous Delivery (CD)
- Continuous Deployment (CD)
+NOTE: **Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.**
+Watch our
+["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
+
## Overview
Continuous Integration works by pushing small code chunks to your
@@ -155,6 +160,7 @@ for your CI/CD infrastructure:
- [Why we chose GitLab CI for our CI/CD solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/)
- [Building our web-app on GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/)
+- [5 Teams that made the switch to GitLab CI/CD](https://about.gitlab.com/2019/04/25/5-teams-that-made-the-switch-to-gitlab-ci-cd/)
See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJaIOzCX4Vqg3dlwfELC3u2jEeCBbDk) presentation.
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index 6401ff90a0a..366aca3442e 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -9,6 +9,11 @@ In this document we'll present an overview of the concepts of Continuous Integra
Continuous Delivery, and Continuous Deployment, as well as an introduction to
GitLab CI/CD.
+NOTE: **Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.**
+Watch our
+["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
+
## Introduction to CI/CD methodologies
The continuous methodologies of software development are based on
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
index f63b17a9e5a..ad07c662965 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
@@ -62,18 +62,32 @@ CAUTION: **Warning:**
Make sure your `gitlab-ci.yml` file is [configured properly for pipelines for merge requests](../index.md#configuring-pipelines-for-merge-requests),
otherwise pipelines for merged results won't run and your merge requests will be stuck in an unresolved state.
-## Merge Trains **(PREMIUM)**
+## Troubleshooting
-Read the [documentation on Merge Trains](merge_trains/index.md).
+### Pipelines for merged results not created even with new change pushed to merge request
-<!-- ## Troubleshooting
+Can be caused by some disabled feature flags. Please make sure that
+the following feature flags are enabled on your GitLab instance:
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+- `:ci_use_merge_request_ref`
+- `:merge_ref_auto_sync`
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+To check these feature flag values, please ask administrator to execute the following commands:
+
+```shell
+> sudo gitlab-rails console # Login to Rails console of GitLab instance.
+> Feature.enabled?(:ci_use_merge_request_ref) # Check if it's enabled or not.
+> Feature.enable(:ci_use_merge_request_ref) # Enable the feature flag.
+```
+
+## Using Merge Trains **(PREMIUM)**
+
+By enabling [Pipelines for merged results](#pipelines-for-merged-results-premium),
+GitLab will [automatically display](merge_trains/index.md#how-to-add-a-merge-request-to-a-merge-train)
+a **Start/Add Merge Train button** as the most recommended merge strategy.
+
+Generally, this is a safer option than merging merge requests immediately as your
+merge request will be evaluated with an expected post-merge result before the actual
+merge happens.
+
+For more information, read the [documentation on Merge Trains](merge_trains/index.md).
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png
new file mode 100644
index 00000000000..a8916e5721c
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png
new file mode 100644
index 00000000000..65ff7e3d674
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
index 44cbcde264c..80a1c264bc4 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
@@ -80,14 +80,65 @@ button while the latest pipeline is running.
![Add to merge train when pipeline succeeds](img/merge_train_start_when_pipeline_succeeds_v12_0.png)
-<!-- ## Troubleshooting
+## Immediately merge a merge request with a merge train
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+In case, you have a high-priority merge request (e.g. critical patch) to be merged urgently,
+you can use **Merge Immediately** option for bypassing the merge train.
+This is the fastest option to get the change merged into the target branch.
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+![Merge Immediately](img/merge_train_immediate_merge.png)
+
+However, every time you merge a merge request immediately, it could affect the
+existing merge train to be reconstructed, specifically, it regenerates expected
+merge commits and pipelines. This means, merging immediately essentially wastes
+CI resources.
+
+## Troubleshooting
+
+### Merge request dropped from the merge train immediately
+
+If a merge request is not mergeable (for example, it's WIP, there is a merge
+conflict, etc), your merge request will be dropped from the merge train automatically.
+
+In these cases, the reason for dropping the merge request is in the **system notes**.
+
+To check the reason:
+
+1. Open the merge request that was dropped from the merge train.
+1. Open the **Discussion** tab.
+1. Find a system note that includes either:
+ - The text **... removed this merge request from the merge train because ...**
+ - **... aborted this merge request from the merge train because ...**
+ The reason is given in the text after the **because ...** phrase.
+
+![Merge Train Failure](img/merge_train_failure.png)
+
+### Merge When Pipeline Succeeds cannot be chosen
+
+[Merge When Pipeline Succeeds](../../../../user/project/merge_requests/merge_when_pipeline_succeeds.md)
+is unavailable when
+[Pipelines for Merged Results is enabled](../index.md#enabling-pipelines-for-merged-results).
+
+Follow [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/12267) to
+track progress on this issue.
+
+### Merge Train disturbs your workflow
+
+First of all, please check if [merge immediately](#immediately-merge-a-merge-request-with-a-merge-train)
+is available as a workaround in your workflow. This is the most recommended
+workaround you'd be able to take immediately. If it's not available or acceptable,
+please read through this section.
+
+Merge train is enabled by default when you enable [Pipelines for merged results](../index.md),
+however, you can forcibly disable this feature by disabling the feature flag `:merge_trains_enabled`.
+After you disabled this feature, all the existing merge trains will be aborted and
+you will no longer see the **Start/Add Merge Train** button in merge requests.
+
+To check if the feature flag is enabled on your GitLab instance,
+please ask administrator to execute the following commands:
+
+```shell
+> sudo gitlab-rails console # Login to Rails console of GitLab instance.
+> Feature.enabled?(:merge_trains_enabled) # Check if it's enabled or not.
+> Feature.disable(:merge_trains_enabled) # Disable the feature flag.
+``` \ No newline at end of file
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 98f30350968..be8f66c741f 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -6,6 +6,11 @@ type: reference
> Introduced in GitLab 8.8.
+NOTE: **Tip:**
+Watch our
+["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+webcast to see a comprehensive demo of GitLab CI/CD pipeline.
+
## Introduction
Pipelines are the top-level component of continuous integration, delivery, and deployment.
diff --git a/doc/development/README.md b/doc/development/README.md
index ea5d9e10e2c..99c88146be5 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -113,6 +113,10 @@ description: 'Learn how to contribute to GitLab.'
- [Database helper modules](database_helpers.md)
- [Code comments](code_comments.md)
+## Case studies
+
+- [Database case study: Filtering by label](filtering_by_label.md)
+
## Integration guides
- [Jira Connect app](integrations/jira_connect.md)
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 709cd0c806b..b7d74b17eb3 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -365,6 +365,31 @@ Enterprise Edition instance. This has some implications:
1. **Filesystem access** can be slow, so try to avoid
[shared files](shared_files.md) when an alternative solution is available.
+## Examples
+
+How code reviews are conducted can surprise new contributors. Here are some examples of code reviews that should help to orient you as to what to expect.
+
+**["Modify `DiffNote` to reuse it for Designs"](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/13703):**
+It contained everything from nitpicks around newlines to reasoning
+about what versions for designs are, how we should compare them
+if there was no previous version of a certain file (parent vs.
+blank `sha` vs empty tree).
+
+**["Support multi-line suggestions"](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25211)**:
+The MR itself consists of a collaboration between FE and BE,
+and documenting comments from the author for the reviewer.
+There's some nitpicks, some questions for information, and
+towards the end, a security vulnerability.
+
+**["Allow multiple repositories per project"](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10251)**:
+ZJ referred to the other projects (workhorse) this might impact,
+suggested some improvements for consistency. And James' comments
+helped us with overall code quality (using delegation, `&.` those
+types of things), and making the code more robust.
+
+**["Support multiple assignees for merge requests"](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10161)**:
+A good example of collaboration on an MR touching multiple parts of the codebase. Nick pointed out interesting edge cases, James Lopes also joined in raising concerns on import/export feature.
+
### Credits
Largely based on the [thoughtbot code review guide].
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index 1413c2f69fb..3d10a0c84e5 100644
--- a/doc/development/database_review.md
+++ b/doc/development/database_review.md
@@ -68,6 +68,17 @@ make sure you have applied the ~database label and rerun the
`danger-review` CI job, or pick someone from the
[`@gl-database` team](https://gitlab.com/groups/gl-database/-/group_members).
+### How to prepare for speedy database reviews
+
+In order to make reviewing easier and therefore faster, please consider preparing a comment
+and details for a database reviewer:
+
+- Provide queries in SQL form rather than ActiveRecord.
+- Format any queries with a SQL query formatter, for example with [sqlformat.darold.net](http://sqlformat.darold.net).
+- Consider providing query plans via a link to [explain.depesz.com](https://explain.depesz.com) or another tool instead of textual form.
+- For query changes, it is best to provide the SQL query along with a plan *before* and *after* the change. This helps to spot differences quickly.
+- When providing query plans, make sure to use good parameter values, so that the query executed is a good example and also hits enough data. Usually, the `gitlab-org` namespace (`namespace_id = 9970`) and the `gitlab-org/gitlab-ce` project (`project_id = 13083`) provides enough data to serve as a good example.
+
### How to review for database
- Check migrations
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 8ce855594df..d717b17136e 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -57,35 +57,22 @@ See the [Structure](styleguide.md#structure) section of the [Documentation Style
We currently maintain two sets of docs: one in the
[gitlab-ce](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc) repo and
one in [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc).
-They are similar, and most pages are identical, but they are different repositories.
-With the single codebase effort, we want to make those two sets identical, so when the
-time comes to have only one codebase, we'll be ready.
-
-Here are some links to get you up to speed with the current effort:
-
-- [CE/EE codebases blueprint](https://about.gitlab.com/handbook/engineering/infrastructure/blueprint/ce-ee-codebases/)
-- [CE/EE codebases merge design](https://about.gitlab.com/handbook/engineering/infrastructure/design/merge-ce-ee-codebases/)
-- [Single docs codebase epic](https://gitlab.com/groups/gitlab-org/-/epics/199)
-- [Issue board of related issues](https://gitlab.com/groups/gitlab-org/-/boards/981090?&label_name[]=Documentation&label_name[]=single%20codebase)
-- [Related merge requests](https://gitlab.com/groups/gitlab-org/-/merge_requests?scope=all&utf8=%E2%9C%93&state=all&label_name[]=Documentation&label_name[]=single%20codebase)
-- [Visualize the existing diffs](https://leipert-projects.gitlab.io/is-gitlab-pretty-yet/diff/?search=%5Edoc)
+They are identical, but they are different repositories. When the
+time comes to have only one codebase for the GitLab project, we'll be ready.
### CE first
-After a given documentation path is aligned across CE and EE, all merge requests
-affecting that path must be submitted to CE, regardless of the content it has.
-This means that:
+All merge requests for documentation must be submitted to CE, regardless of the content
+it has. This means that:
-- For **EE-only docs changes**, you only have to submit a CE MR.
+- For **EE-only docs changes**, you only have to submit an MR in the CE project.
- For **EE-only features** that touch both the code and the docs, you have to submit
- an EE MR containing all changes, and a CE MR containing only the docs changes
+ an EE MR containing all code changes, and a CE MR containing only the docs changes
and without a changelog entry.
This might seem like a duplicate effort, but it's only for the short term.
-A list of the already aligned docs can be found in
-[the epic description](https://gitlab.com/groups/gitlab-org/-/epics/199#ee-specific-lines-check).
-Since the docs will be combined, it's crucial to add the relevant
+Since the CE and EE docs are combined, it's crucial to add the relevant
[product badges](styleguide.md#product-badges) for all EE documentation, so that
we can discern which features belong to which tier.
@@ -94,19 +81,9 @@ we can discern which features belong to which tier.
There's a special test in place
([`ee_specific_check.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/ee_specific_check/ee_specific_check.rb)),
which, among others, checks and prevents creating/editing new files and directories
-in EE under `doc/`.
-
-We have a long list of documentation paths that are either whitelisted or not.
-Paths in the whitelist (not commented out) will not be subject to the test,
-which means you are allowed to create/change docs content in EE for the time
-being. The goal is to not have any doc whitelisted.
-
-At the time of this writing, the only items left to be aligned are the API docs:
-
-- `doc/api/*` ([issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60045) / [merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27491))
-
-Eventually, once all docs are aligned, we'll remove any doc reference from that
-script, so it catches everything.
+in EE under `doc/`. This should fail when changes to anything in `/doc` are submitted
+in an EE MR. To pass the test, simply remove the docs changes from the EE MR, and
+[submit them in CE](#ce-first).
## Changing document location
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 36ffc02644e..dd798777c12 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -1092,6 +1092,6 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "domain_
[cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
-[gfm]: https://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation"
+[gfm]: ../../user/markdown.md#newlines "GitLab flavored markdown documentation"
[ce-1242]: https://gitlab.com/gitlab-org/gitlab-ce/issues/1242
[doc-restart]: ../../administration/restart_gitlab.md "GitLab restart documentation"
diff --git a/doc/development/filtering_by_label.md b/doc/development/filtering_by_label.md
new file mode 100644
index 00000000000..6e6b71b1787
--- /dev/null
+++ b/doc/development/filtering_by_label.md
@@ -0,0 +1,166 @@
+# Filtering by label
+
+## Introduction
+
+GitLab has [labels](../user/project/labels.md) that can be assigned to issues,
+merge requests, and epics. Labels on those objects are a many-to-many relation
+through the polymorphic `label_links` table.
+
+To filter these objects by multiple labels - for instance, 'all open
+issues with the label ~Plan and the label ~backend' - we generate a
+query containing a `GROUP BY` clause. In a simple form, this looks like:
+
+```sql
+SELECT
+ issues.*
+FROM
+ issues
+ INNER JOIN label_links ON label_links.target_id = issues.id
+ AND label_links.target_type = 'Issue'
+ INNER JOIN labels ON labels.id = label_links.label_id
+WHERE
+ issues.project_id = 13083
+ AND (issues.state IN ('opened'))
+ AND labels.title IN ('Plan',
+ 'backend')
+GROUP BY
+ issues.id
+HAVING (COUNT(DISTINCT labels.title) = 2)
+ORDER BY
+ issues.updated_at DESC,
+ issues.id DESC
+LIMIT 20 OFFSET 0
+```
+
+In particular, note that:
+
+1. We `GROUP BY issues.id` so that we can ...
+2. Use the `HAVING (COUNT(DISTINCT labels.title) = 2)` condition to ensure that
+ all matched issues have both labels.
+
+This is more complicated than is ideal. It makes the query construction more
+prone to errors (such as
+[gitlab-org/gitlab-ce#15557](https://gitlab.com/gitlab-org/gitlab-ce/issues/15557)).
+
+## Attempt A: WHERE EXISTS
+
+### Attempt A1: use multiple subqueries with WHERE EXISTS
+
+In
+[gitlab-org/gitlab-ce#37137](https://gitlab.com/gitlab-org/gitlab-ce/issues/37137)
+and its associated merge request
+[gitlab-org/gitlab-ce!14022](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14022),
+we tried to replace the `GROUP BY` with multiple uses of `WHERE EXISTS`. For the
+example above, this would give:
+
+```sql
+WHERE (EXISTS (
+ SELECT
+ TRUE
+ FROM
+ label_links
+ INNER JOIN labels ON labels.id = label_links.label_id
+ WHERE
+ labels.title = 'Plan'
+ AND target_type = 'Issue'
+ AND target_id = issues.id))
+AND (EXISTS (
+ SELECT
+ TRUE
+ FROM
+ label_links
+ INNER JOIN labels ON labels.id = label_links.label_id
+ WHERE
+ labels.title = 'backend'
+ AND target_type = 'Issue'
+ AND target_id = issues.id))
+```
+
+While this worked without schema changes, and did improve readability somewhat,
+it did not improve query performance.
+
+## Attempt B: Denormalize using an array column
+
+Having [removed MySQL support in GitLab
+12.1](https://about.gitlab.com/2019/06/27/removing-mysql-support/), using
+[Postgres's arrays](https://www.postgresql.org/docs/9.6/arrays.html) became more
+tractable as we didn't have to support two databases. We discussed denormalizing
+the `label_links` table for querying in
+[gitlab-org/gitlab-ce#49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651),
+with two options: label IDs and titles.
+
+We can think of both of those as array columns on `issues`, `merge_requests`,
+and `epics`: `issues.label_ids` would be an array column of label IDs, and
+`issues.label_titles` would be an array of label titles.
+
+These array columns can be complemented with [GIN
+indexes](https://www.postgresql.org/docs/9.6/gin-intro.html) to improve
+matching.
+
+### Attempt B1: store label IDs for each object
+
+This has some strong advantages over titles:
+
+1. Unless a label is deleted, or a project is moved, we never need to
+ bulk-update the denormalized column.
+2. It uses less storage than the titles.
+
+Unfortunately, our application design makes this hard. If we were able to query
+just by label ID easily, we wouldn't need the `INNER JOIN labels` in the initial
+query at the start of this document. GitLab allows users to filter by label
+title across projects and even across groups, so a filter by the label ~Plan may
+include labels with multiple distinct IDs.
+
+We do not want users to have to know about the different IDs, which means that
+given this data set:
+
+| Project | ~Plan label ID | ~backend label ID |
+| --- | --- | --- |
+| A | 11 | 12 |
+| B | 21 | 22 |
+| C | 31 | 32 |
+
+We would need something like:
+
+```sql
+WHERE
+ label_ids @> ARRAY[11, 12]
+ OR label_ids @> ARRAY[21, 22]
+ OR label_ids @> ARRAY[31, 32]
+```
+
+This can get even more complicated when we consider that in some cases, there
+might be two ~backend labels - with different IDs - that could apply to the same
+object, so the number of combinations would balloon further.
+
+### Attempt B2: store label titles for each object
+
+From the perspective of updating the labelable object, this is the worst
+option. We have to bulk update the objects when:
+
+1. The objects are moved from one project to another.
+1. The project is moved from one group to another.
+1. The label is renamed.
+1. The label is deleted.
+
+It also uses much more storage. Querying is simple, though:
+
+```sql
+WHERE
+ label_titles @> ARRAY['Plan', 'backend']
+```
+
+And our [tests in
+gitlab-org/gitlab-ce#49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651#note_188777346)
+showed that this could be fast.
+
+However, at present, the disadvantages outweigh the advantages.
+
+## Conclusion
+
+We have yet to find a method that is demonstratably better than the current
+method, when considering:
+
+1. Query performance.
+1. Readability.
+1. Ease of maintaining schema consistency.
diff --git a/doc/development/new_fe_guide/dependencies.md b/doc/development/new_fe_guide/dependencies.md
index 12a4f089d41..8a6930acd37 100644
--- a/doc/development/new_fe_guide/dependencies.md
+++ b/doc/development/new_fe_guide/dependencies.md
@@ -15,6 +15,18 @@ Exceptions are made for some tools that we require in the
`gitlab:assets:compile` CI job such as `webpack-bundle-analyzer` to analyze our
production assets post-compile.
+To add or upgrade a dependency, run:
+
+```sh
+yarn add <your dependency here>
+```
+
+This may introduce duplicate dependencies. To de-duplicate `yarn.lock`, run:
+
+```sh
+node_modules/.bin/yarn-deduplicate --list --strategy fewer yarn.lock && yarn install
+```
+
---
> TODO: Add Dependencies
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index ff28c2ea5e2..2985278cc92 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -588,7 +588,7 @@ end
[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html
[karma]: http://karma-runner.github.io/
-[vue-test]: https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components
+[vue-test]: ../fe_guide/vue.md#testing-vue-components
[rspec]: https://github.com/rspec/rspec-rails#feature-specs
[capybara]: https://github.com/teamcapybara/capybara
[jasmine]: https://jasmine.github.io/
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index c0e1b0ebbc8..543a222bd25 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -70,7 +70,7 @@ The first items we need to configure are the basic settings of the underlying vi
> **Note:** if you're unsure which authentication type to use, select **Password**
1. If you chose **SSH public key** - enter your `SSH public key` into the field provided
- _(read the [SSH documentation][GitLab-Docs-SSH] to learn more about how to set up SSH
+ _(read the [SSH documentation](../../ssh/README.md) to learn more about how to set up SSH
public keys)_
1. If you chose **Password** - enter the password you wish to use _(this is the password that you
will use later in this tutorial to [SSH] into the VM, so make sure it's a strong password/passphrase)_
@@ -407,7 +407,7 @@ on any cloud service you choose.
## Where to next?
-Check out our other [Technical Articles][GitLab-Technical-Articles] or browse the [GitLab Documentation][GitLab-Docs] to learn more about GitLab.
+Check out our other [Technical Articles](../../articles/index.md) or browse the [GitLab Documentation][GitLab-Docs](../../README.md) to learn more about GitLab.
### Useful links
@@ -423,9 +423,6 @@ Check out our other [Technical Articles][GitLab-Technical-Articles] or browse th
- [SSH], [PuTTY] and [Using SSH in PuTTY][Using-SSH-In-Putty]
[Original-Blog-Post]: https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/ "How to Set up a GitLab Instance on Microsoft Azure"
-[GitLab-Docs]: https://docs.gitlab.com/ce/README.html "GitLab Documentation"
-[GitLab-Technical-Articles]: https://docs.gitlab.com/ce/articles/index.html "GitLab Technical Articles"
-[GitLab-Docs-SSH]: https://docs.gitlab.com/ce/ssh/README.html "GitLab Documentation: SSH"
[CE]: https://about.gitlab.com/features/
[EE]: https://about.gitlab.com/features/#ee-starter
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 135952a1b08..55f9666e3a3 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -27,7 +27,7 @@ See the documentation below for details on how to configure these services.
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [Trello](trello_power_up.md) Integrate Trello with GitLab
-> GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
+> GitLab Enterprise Edition contains [advanced Jenkins support](jenkins.md).
## Project services
@@ -70,5 +70,3 @@ After that restart GitLab with:
```bash
sudo gitlab-ctl restart
```
-
-[jenkins]: https://docs.gitlab.com/ee/integration/jenkins.html
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index f84ab769218..9fcf2c2d99a 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../project_services/slack.md'
+redirect_to: '../user/project/integrations/slack.md'
---
-This document was moved to [project_services/slack.md](../project_services/slack.md).
+This document was moved to [project_services/slack.md](../user/project/integrations/slack.md).
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index c416dbac492..c6219126b30 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -192,10 +192,10 @@ The following table is an example of how the three different clusters would
be configured.
| Cluster name | Cluster environment scope | `KUBE_INGRESS_BASE_DOMAIN` variable value | Variable environment scope | Notes |
-| ------------ | -------------- | ----------------------------- | ------------- | ------ |
-| review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. |
-| staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). |
-| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production-premium). |
+|--------------|---------------------------|-------------------------------------------|----------------------------|---|
+| review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. |
+| staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). |
+| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production-premium). |
To add a different cluster for each environment:
@@ -665,10 +665,6 @@ to the desired environment. See [Limiting environment scopes of variables](../..
### Customizing `.gitlab-ci.yml`
-Everything about Auto DevOps is customizable since the [Auto DevOps template]
-is just an example of a [`.gitlab-ci.yml`](../../ci/yaml/README.md) and uses
-only features that are available to any `.gitlab-ci.yml`.
-
Auto DevOps is completely customizable because the [Auto DevOps template]:
- Is just an implementation of a [`.gitlab-ci.yml`](../../ci/yaml/README.md) file.
@@ -725,47 +721,47 @@ The following variables can be used for setting up the Auto DevOps domain,
providing a custom Helm chart, or scaling your application. PostgreSQL can
also be customized, and you can easily use a [custom buildpack](#custom-buildpacks).
-| **Variable** | **Description** |
-| ------------ | --------------- |
-| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
-| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
-| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, this variable can be used to set the name of the helm repository; defaults to "gitlab" |
+| **Variable** | **Description** |
+|-----------------------------------------|------------------------------------|
+| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
+| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
+| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, this variable can be used to set the name of the helm repository; defaults to "gitlab" |
| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From Gitlab 11.11, this variable can be used to set a username to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD) |
| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From Gitlab 11.11, this variable can be used to set a password to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_USERNAME) |
-| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
-| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. Takes precedence over `REPLICAS` and defaults to 1. For zero downtime upgrades, set to 2 or greater. |
-| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md); defaults to 1 |
-| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
-| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
-| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
-| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
-| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
-| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
-| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-environment-variables). Set it to use a custom database name. |
-| `POSTGRES_VERSION` | Tag for the [`postgres` Docker image](https://hub.docker.com/_/postgres) to use. Defaults to `9.6.2`. |
-| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
-| `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.|
-| `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).|
-| `DB_INITIALIZE` | From GitLab 11.4, this variable can be used to specify the command to run to initialize the application's PostgreSQL database. It runs inside the application pod. |
-| `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. |
-| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
-| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). |
-| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment. Set to `manual` for manual deployment jobs or `timed` for automatic rollout deployments with a 5 minute delay each one. |
-| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
-| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
-| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, this variable can be used to disable the `license_management` job. If the variable is present, the job will not be created. |
-| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. |
-| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. |
-| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast:container` job. If the variable is present, the job will not be created. |
-| `REVIEW_DISABLED` | From GitLab 11.0, this variable can be used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. |
-| `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. |
-| `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. |
-| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
-| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
-| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, this variable allows specification of the resource type being deployed when using a custom helm chart. Default value is `deployment`. |
-| `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, this variable allows to disable rollout status check because it doesn't support all resource types, for example, `cronjob`. |
-| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. **Tip:** you can use this variable to [customize the Auto Deploy helm chart](https://docs.gitlab.com/ee/topics/autodevops/index.html#custom-helm-chart) by applying custom override values with `--values my-values.yaml`. |
-| `HELM_RELEASE_NAME` | From GitLab 12.1, this variable allows the `helm` release name to be overridden, this can be used to assign unique release names when deploying multiple projects to a single namespace |
+| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
+| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. Takes precedence over `REPLICAS` and defaults to 1. For zero downtime upgrades, set to 2 or greater. |
+| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md); defaults to 1. |
+| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1. |
+| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
+| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
+| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
+| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
+| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
+| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-environment-variables). Set it to use a custom database name. |
+| `POSTGRES_VERSION` | Tag for the [`postgres` Docker image](https://hub.docker.com/_/postgres) to use. Defaults to `9.6.2`. |
+| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142`. |
+| `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`. |
+| `DS_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](../../user/application_security/dependency_scanning/index.md#remote-checks). |
+| `DB_INITIALIZE` | From GitLab 11.4, this variable can be used to specify the command to run to initialize the application's PostgreSQL database. It runs inside the application pod. |
+| `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. |
+| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
+| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). |
+| `INCREMENTAL_ROLLOUT_MODE` | From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment. Set to `manual` for manual deployment jobs or `timed` for automatic rollout deployments with a 5 minute delay each one. |
+| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
+| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
+| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, this variable can be used to disable the `license_management` job. If the variable is present, the job will not be created. |
+| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. |
+| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. |
+| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast:container` job. If the variable is present, the job will not be created. |
+| `REVIEW_DISABLED` | From GitLab 11.0, this variable can be used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. |
+| `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. |
+| `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. |
+| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
+| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
+| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, this variable allows specification of the resource type being deployed when using a custom helm chart. Default value is `deployment`. |
+| `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, this variable allows to disable rollout status check because it doesn't support all resource types, for example, `cronjob`. |
+| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. **Tip:** you can use this variable to [customize the Auto Deploy helm chart](#custom-helm-chart) by applying custom override values with `--values my-values.yaml`. |
+| `HELM_RELEASE_NAME` | From GitLab 12.1, this variable allows the `helm` release name to be overridden, this can be used to assign unique release names when deploying multiple projects to a single namespace. |
TIP: **Tip:**
Set up the replica variables using a
@@ -1058,7 +1054,7 @@ planned for a subsequent release.
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
- Auto Deploy will fail if GitLab can not create a Kubernetes namespace and
service account for your project. For help debugging this issue, see
- [Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs).
+ [Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting).
### Disable the banner instance wide
diff --git a/doc/university/README.md b/doc/university/README.md
index f696db2df20..b17f40b91a5 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -9,6 +9,10 @@ GitLab University is a great place to start when learning about version control
If you're looking for a GitLab subscription for _your university_, see our [Education](https://about.gitlab.com/solutions/education/) page.
+CAUTION: **Caution:**
+Some of the content in GitLab University may be out of date and we plan to
+[evaluate](https://gitlab.com/gitlab-org/gitlab-ce/issues/41064) it.
+
The GitLab University curriculum is composed of GitLab videos, screencasts, presentations, projects and external GitLab content hosted on other services and has been organized into the following sections:
1. [GitLab Beginner](#1-gitlab-beginner).
diff --git a/doc/user/admin_area/abuse_reports.md b/doc/user/admin_area/abuse_reports.md
index 8088c33fc9c..0c5d2f81e25 100644
--- a/doc/user/admin_area/abuse_reports.md
+++ b/doc/user/admin_area/abuse_reports.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Abuse reports
View and resolve abuse reports from GitLab users.
@@ -59,3 +63,15 @@ page:
NOTE: **Note:**
Users can be [blocked](../../api/users.md#block-user) and
[unblocked](../../api/users.md#unblock-user) using the GitLab API.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/broadcast_messages.md b/doc/user/admin_area/broadcast_messages.md
index 02445abdb37..01b6558bdbe 100644
--- a/doc/user/admin_area/broadcast_messages.md
+++ b/doc/user/admin_area/broadcast_messages.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Broadcast Messages
GitLab can display messages to all users of a GitLab instance in a banner that appears in the UI.
@@ -51,3 +55,15 @@ Once deleted, the broadcast message is removed from the list of broadcast messag
NOTE: **Note:**
Broadcast messages can be deleted while active.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/custom_project_templates.md b/doc/user/admin_area/custom_project_templates.md
index 427f3103cfc..02c2efaa4f3 100644
--- a/doc/user/admin_area/custom_project_templates.md
+++ b/doc/user/admin_area/custom_project_templates.md
@@ -1,26 +1,49 @@
+---
+type: reference
+---
+
# Custom instance-level project templates **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2.
-When you create a new [project](../project/index.md), creating it based on custom project templates is
-a convenient bootstrap option.
+GitLab administrators can configure the group where all the custom project
+templates are sourced.
-GitLab administrators can configure a GitLab group that serves as template
-source for an entire GitLab instance under **Admin area > Settings > Custom project templates**.
+Every project directly under the group namespace will be
+available to the user if they have access to them. For example:
+
+- Public project in the group will be available to every logged in user.
+- Private projects will be available only if the user is a member of the project.
+
+Repository and database information that are copied over to each new project are
+identical to the data exported with
+[GitLab's Project Import/Export](../project/settings/import_export.md).
NOTE: **Note:**
To set project templates at a group level,
see [Custom group-level project templates](../group/custom_project_templates.md).
-Within this section, you can configure the group where all the custom project
-templates are sourced. Every project directly under the group namespace will be
-available to the user if they have access to them. For example, every public
-project in the group will be available to every logged in user.
+## Configuring
-However, private projects will be available only if the user is a member of the project.
+GitLab administrators can configure a GitLab group that serves as template
+source for an entire GitLab instance by:
+
+1. Navigating to **Admin area > Settings > Templates**.
+1. Expanding **Custom project templates**.
+1. Selecting a group to use.
+1. Pressing **Save changes**.
NOTE: **Note:**
Projects below subgroups of the template group are **not** supported.
-Repository and database information that are copied over to each new project are
-identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index da75684a3fe..86491c7d74e 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Container Scanning **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3672)
@@ -47,7 +51,7 @@ To enable Container Scanning in your pipeline, you need:
your Docker image to your project's [Container Registry](../../project/container_registry.md).
The name of the Docker image should match the following scheme:
- ```
+ ```text
$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
```
@@ -114,7 +118,7 @@ When the GitLab Runner uses the Docker executor and NFS is used
(e.g., `/var/lib/docker` is on an NFS mount), Container Scanning might fail with
an error like the following:
-```
+```text
docker: Error response from daemon: failed to copy xattrs: failed to set xattr "security.selinux" on /path/to/file: operation not supported.
```
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index 4b98dd73d76..88e2d1ef22b 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Dynamic Application Security Testing (DAST) **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4348)
@@ -199,3 +203,15 @@ Once a vulnerability is found, you can interact with it. Read more on how to
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 7473647f129..6a810757a28 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Dependency Scanning **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5105)
@@ -150,7 +154,7 @@ using environment variables.
| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
-| `PIP_INDEX_URL` | Base URL of Python Package Index (default https://pypi.org/simple). |
+| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). |
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
## Reports JSON format
@@ -331,7 +335,7 @@ project's dependencies with their versions. This list can be generated only for
[languages and package managers](#supported-languages-and-package-managers)
supported by Gemnasium.
-To see the generated dependency list, navigate to your project's **Project > Dependency List**.
+To see the generated dependency list, navigate to your project's **Security & Compliance > Dependency List**.
## Versioning and release process
@@ -342,3 +346,15 @@ Please check the [Release Process documentation](https://gitlab.com/gitlab-org/s
You can search the [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) project
to find a vulnerability in the Gemnasium database.
You can also [submit new vulnerabilities](https://gitlab.com/gitlab-org/security-products/gemnasium-db/blob/master/CONTRIBUTING.md).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 56a4cbd26d2..31f0b5a050c 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -1,10 +1,22 @@
+---
+type: reference, howto
+---
+
# GitLab Secure **(ULTIMATE)**
-Check your application for security vulnerabilities that may lead to unauthorized access,
-data leaks, and denial of services. GitLab will perform static and dynamic tests on the
-code of your application, looking for known flaws and report them in the merge request
-so you can fix them before merging. Security teams can use dashboards to get a
-high-level view on projects and groups, and start remediation processes when needed.
+Check your application for security vulnerabilities that may lead to
+unauthorized access, data leaks, and denial of services.
+
+GitLab will perform static and dynamic tests on the code of your application,
+looking for known flaws and report them in the merge request so you can fix
+them before merging.
+
+Security teams can use dashboards to get a high-level view on projects and
+groups, and start remediation processes when needed.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview of application security with GitLab, see
+[Security Deep Dive](https://www.youtube.com/watch?v=k4vEJnGYy84).
## Security scanning tools
@@ -54,7 +66,7 @@ Each security vulnerability in the merge request report or the
entry, a detailed information will pop up with different possible options:
- [Dismiss vulnerability](#dismissing-a-vulnerability): Dismissing a vulnerability
- will place a <s>strikethrough</s> styling on it.
+ will place a ~~strikethrough~~ styling on it.
- [Create issue](#creating-an-issue-for-a-vulnerability): The new issue will
have the title and description pre-populated with the information from the
vulnerability report and will be created as [confidential](../project/issues/confidential_issues.md) by default.
@@ -124,7 +136,7 @@ generated by GitLab. To apply the fix:
#### Creating a merge request from a vulnerability
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9224) in
- [GitLab Ultimate](https://about.gitlab.com/pricing) 11.9.
+> [GitLab Ultimate](https://about.gitlab.com/pricing) 11.9.
In certain cases, GitLab will allow you to create a merge request that will
automatically remediate the vulnerability. Any vulnerability that has a
@@ -135,3 +147,15 @@ If this action is available there will be a **Create merge request** button in t
Clicking on this button will create a merge request to apply the solution onto the source branch.
![Create merge request from vulnerability](img/create_issue_with_list_hover.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/license_management/index.md b/doc/user/application_security/license_management/index.md
index b0eb753938b..c324848c703 100644
--- a/doc/user/application_security/license_management/index.md
+++ b/doc/user/application_security/license_management/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# License Management **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483)
@@ -227,3 +231,15 @@ pipeline ID that has a `license_management` job to see the Licenses tab with the
licenses (if any).
![License Management Pipeline Tab](img/license_management_pipeline_tab.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 7df86eedd18..aac881112ff 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Static Application Security Testing (SAST) **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3775)
@@ -334,3 +338,15 @@ Once a vulnerability is found, you can interact with it. Read more on how to
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/security_dashboard/img/dashboard.png b/doc/user/application_security/security_dashboard/img/dashboard.png
deleted file mode 100644
index a75168b1ce4..00000000000
--- a/doc/user/application_security/security_dashboard/img/dashboard.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard.png
new file mode 100644
index 00000000000..40689861e2a
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/group_security_dashboard.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
index f0dad6c54d0..89b310895d3 100644
--- a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
+++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index 2a2385c00ae..ac8c1ac0354 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# GitLab Security Dashboard **(ULTIMATE)**
The Security Dashboard is a good place to get an overview of all the security
@@ -16,9 +20,9 @@ To benefit from the Security Dashboard you must first configure one of the
The Security Dashboard supports the following reports:
- [Container Scanning](../container_scanning/index.md)
-- [DAST](../dast/index.md)
+- [Dynamic Application Security Testing](../dast/index.md)
- [Dependency Scanning](../dependency_scanning/index.md)
-- [SAST](../sast/index.md)
+- [Static Application Security Testing](../sast/index.md)
## Requirements
@@ -43,13 +47,13 @@ for your project. Use it to find and fix vulnerabilities affecting the
## Group Security Dashboard
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6709) in
- [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5.
+> [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5.
The group Security Dashboard gives an overview of the vulnerabilities of all the
projects in a group and its subgroups.
First, navigate to the Security Dashboard found under your group's
-**Overview > Security Dashboard**.
+**Security** tab.
Once you're on the dashboard, at the top you should see a series of filters for:
@@ -58,7 +62,7 @@ Once you're on the dashboard, at the top you should see a series of filters for:
- Report type
- Project
-![dashboard with action buttons and metrics](img/dashboard.png)
+![dashboard with action buttons and metrics](img/group_security_dashboard.png)
Selecting one or more filters will filter the results in this page.
The first section is an overview of all the vulnerabilities, grouped by severity.
@@ -102,3 +106,15 @@ That way, reports are created even if no code change happens.
When using [Auto DevOps](../../../topics/autodevops/index.md), use
[special environment variables](../../../topics/autodevops/index.md#environment-variables)
to configure daily security scans.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index d5c836554ce..e1a6f8fcf71 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -5,11 +5,12 @@ be added directly to your configured cluster. These applications are
needed for [Review Apps](../../ci/review_apps/index.md) and
[deployments](../../ci/environments.md) when using [Auto DevOps](../../topics/autodevops/index.md).
You can install them after you
-[create a cluster](../project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+[create a cluster](../project/clusters/index.md#add-new-gke-cluster).
## Installing applications
Applications managed by GitLab will be installed onto the `gitlab-managed-apps` namespace.
+
This namespace:
- Is different from the namespace used for project deployments.
diff --git a/doc/user/group/saml_sso/img/scim_attribute_mapping.png b/doc/user/group/saml_sso/img/scim_attribute_mapping.png
index c9f6b71f5b0..dad459d8c28 100644
--- a/doc/user/group/saml_sso/img/scim_attribute_mapping.png
+++ b/doc/user/group/saml_sso/img/scim_attribute_mapping.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png b/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png
new file mode 100644
index 00000000000..85e5648816e
--- /dev/null
+++ b/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_provisioning_status.png b/doc/user/group/saml_sso/img/scim_provisioning_status.png
new file mode 100644
index 00000000000..4b8887b5418
--- /dev/null
+++ b/doc/user/group/saml_sso/img/scim_provisioning_status.png
Binary files differ
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index 55c5a18db7d..bc74725bbc9 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -45,7 +45,7 @@ The following identity providers are supported:
Feature.enable(:group_scim, group)
```
-### GitLab configuration
+## GitLab configuration
Once [Single sign-on](index.md) has been configured, we can:
@@ -55,41 +55,48 @@ Once [Single sign-on](index.md) has been configured, we can:
![SCIM token configuration](img/scim_token.png)
-## SCIM IdP configuration
+## Identity Provider configuration
-### Configuration on Azure
+### Azure
-In the [Single sign-on](index.md) configuration for the group, make sure
-that the **Name identifier value** (NameID) points to a unique identifier, such
-as the `user.objectid`. This will match the `extern_uid` used on GitLab.
+First, double check the [Single sign-on](index.md) configuration for your group and ensure that **Name identifier value** (NameID) points to `user.objectid` or another unique identifier. This will match the `extern_uid` used on GitLab.
-The GitLab app in Azure needs to be configured following
-[Azure's SCIM setup](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#getting-started).
+![Name identifier value mapping](img/scim_name_identifier_mapping.png)
-Note the following:
+#### Set up admin credentials
+
+Next, configure your GitLab application in Azure by following the
+[Provisioning users and groups to applications that support SCIM](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#provisioning-users-and-groups-to-applications-that-support-scim)
+section in Azure's SCIM setup documentation.
+
+During this configuration, note the following:
- The `Tenant URL` and `secret token` are the ones retrieved in the
[previous step](#gitlab-configuration).
- Should there be any problems with the availability of GitLab or similar
errors, the notification email set will get those.
+- It is recommended to set a notification email and check the **Send an email notification when a failure occurs** checkbox.
- For mappings, we will only leave `Synchronize Azure Active Directory Users to AppName` enabled.
-You can then test the connection clicking on `Test Connection`.
+You can then test the connection by clicking on **Test Connection**. If the connection is successful, be sure to save your configuration before moving on.
-### Synchronize Azure Active Directory users
+#### Configure attribute mapping
-1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure
- the attribute mapping.
-1. Select the unique identifier (in the example `objectId`) as the `id` and `externalId`,
- and enable the `Create`, `Update`, and `Delete` actions.
-1. Map the `userPricipalName` to `emails[type eq "work"].value` and `mailNickname` to
- `userName`.
+1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure the attribute mapping.
+1. Click **Delete** next to the `mail` mapping.
+1. Map `userPrincipalName` to `emails[type eq "work"].value` and change it's **Matching precedence** to `2`.
+1. Map `mailNickname` to `userName`.
+1. Create a new mapping by clicking **Add New Mapping** then set **Source attribute** to `objectId`, **Target attribute** to `id`, **Match objects using this attribute** to `Yes`, and **Matching precedence** to `1`.
+1. Create a new mapping by clicking **Add New Mapping** then set **Source attribute** to `objectId`, and **Target attribute** to `externalId`.
+1. Click the `userPrincipalName` mapping and change **Match objects using this attribute** to `No`.
- Example configuration:
+ Save your changes and you should have the following configuration:
![Azure's attribute mapping configuration](img/scim_attribute_mapping.png)
-1. Click on **Show advanced options > Edit attribute list for AppName**.
+ NOTE: **Note:** If you used a unique identifier **other than** `objectId`, be sure to map it instead to both `id` and `externalId`.
+
+1. Below the mapping list click on **Show advanced options > Edit attribute list for AppName**.
1. Leave the `id` as the primary and only required field.
NOTE: **Note:**
@@ -99,12 +106,14 @@ You can then test the connection clicking on `Test Connection`.
![Azure's attribute advanced configuration](img/scim_advanced.png)
1. Save all the screens and, in the **Provisioning** step, set
- the `Provisioning Status` to `ON`.
+ the `Provisioning Status` to `On`.
+
+ ![Provisioning status toggle switch](img/scim_provisioning_status.png)
NOTE: **Note:**
You can control what is actually synced by selecting the `Scope`. For example,
`Sync only assigned users and groups` will only sync the users assigned to
- the application (`Users and groups`), otherwise it will sync the whole Active Directory.
+ the application (`Users and groups`), otherwise, it will sync the whole Active Directory.
Once enabled, the synchronization details and any errors will appear on the
bottom of the **Provisioning** screen, together with a link to the audit logs.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 37f3f21f539..9380dcf2a32 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -136,26 +136,26 @@ Supported formats (named colors are not supported):
Color written inside backticks will be followed by a color "chip":
```markdown
-`#F00`
-`#F00A`
-`#FF0000`
-`#FF0000AA`
-`RGB(0,255,0)`
-`RGB(0%,100%,0%)`
-`RGBA(0,255,0,0.3)`
-`HSL(540,70%,50%)`
-`HSLA(540,70%,50%,0.3)`
-```
-
-`#F00`
-`#F00A`
-`#FF0000`
-`#FF0000AA`
-`RGB(0,255,0)`
-`RGB(0%,100%,0%)`
-`RGBA(0,255,0,0.3)`
-`HSL(540,70%,50%)`
-`HSLA(540,70%,50%,0.3)`
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.3)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.3)`
+```
+
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.3)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.3)`
### Diagrams and flowcharts using Mermaid
@@ -397,6 +397,7 @@ unordered or ordered lists:
- [ ] Sub-task 1
- [x] Sub-task 2
- [ ] Sub-task 3
+
1. [x] Completed task
1. [ ] Incomplete task
1. [ ] Sub-task 1
@@ -408,6 +409,7 @@ unordered or ordered lists:
- [ ] Sub-task 1
- [x] Sub-task 2
- [ ] Sub-task 3
+
1. [x] Completed task
1. [ ] Incomplete task
1. [ ] Sub-task 1
@@ -976,7 +978,7 @@ after the `</summary>` tag and before the `</details>` tag, as shown in the exam
These details _will_ remain **hidden** until expanded.
- PASTE LOGS HERE
+PASTE LOGS HERE
</details>
```
@@ -988,7 +990,7 @@ These details _will_ remain **hidden** until expanded.
These details <em>will</em> remain <b>hidden</b> until expanded.
- PASTE LOGS HERE
+PASTE LOGS HERE
</details>
@@ -1047,14 +1049,14 @@ A new line due to the previous backslash.
First paragraph.
Another line in the same paragraph.
-A third line in the same paragraph, but this time ending with two spaces.
+A third line in the same paragraph, but this time ending with two spaces.
A new line directly under the first paragraph.
<!-- (Do *NOT* remove the two ending whitespaces in the second line) -->
<!-- (They are needed for the Markdown text to render correctly on docs.gitlab.com, the backslash works fine inside GitLab itself) -->
Second paragraph.
-Another line, this time ending with a backslash.
+Another line, this time ending with a backslash.
A new line due to the previous backslash.
### Links
@@ -1135,13 +1137,13 @@ GFM will autolink almost any URL you put into your text:
### Lists
Ordered and unordered lists can be easily created. Add the number you want the list
-to start with, like `1. ` (with a space) at the start of each line for ordered lists.
+to start with, like `1.`, followed by a space, at the start of each line for ordered lists.
After the first number, it does not matter what number you use, ordered lists will be
-numbered automatically by vertical order, so repeating `1. ` for all items in the
-same list is common. If you start with a number other than `1. `, it will use that as the first
+numbered automatically by vertical order, so repeating `1.` for all items in the
+same list is common. If you start with a number other than `1.`, it will use that as the first
number, and count up from there.
-Add a `* `, `- ` or `+ ` (with a space) at the start of each line for unordered lists, but
+Add a `*`, `-` or `+`, followed by a space, at the start of each line for unordered lists, but
you should not use a mix of them.
Examples:
@@ -1156,7 +1158,9 @@ Examples:
4. And another item.
* Unordered lists can use asterisks
+
- Or minuses
+
+ Or pluses
```
@@ -1170,9 +1174,11 @@ Examples:
1. Next ordered sub-list item
1. And another item.
-* Unordered lists can use asterisks
+- Unordered lists can use asterisks
+
- Or minuses
-+ Or pluses
+
+- Or pluses
---
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 61a30a775b0..fc5eb8c7b56 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -147,39 +147,40 @@ You can also set your current status [using the API](../../api/users.md#user-sta
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21598) in GitLab 11.4.
-A commit email, is the email that will be displayed in every Git-related action done through the
-GitLab interface.
+A commit email is an email address displayed in every Git-related action carried out through the GitLab interface.
-You are able to select from the list of your own verified emails which email you want to use as the commit email.
+Any of your own verified email addresses can be used as the commit email.
-To change it:
+To change your commit email:
-1. Open the user menu in the top-right corner of the navigation bar.
-1. Hit **Commit email** selection box.
+1. Click on your avatar at the top-right corner of the navigation bar.
+1. From the menu that appears, click **Settings**.
+1. In the **Main settings** section, locate **Commit email** dropdown.
1. Select any of the verified emails.
-1. Hit **Update profile settings**.
+1. Press **Update profile settings**.
### Private commit email
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22560) in GitLab 11.5.
-GitLab provides the user with an automatically generated private commit email option,
+GitLab provides users with an automatically generated private commit email option,
which allows the user to not make their email information public.
To enable this option:
-1. Open the user menu in the top-right corner of the navigation bar.
-1. Hit **Commit email** selection box.
-1. Select **Use a private email** option.
-1. Hit **Update profile settings**.
+1. Click on your avatar at the top-right corner of the navigation bar.
+1. From the menu that appears, click **Settings**.
+1. In the **Main settings** section, locate **Commit email** dropdown.
+1. Select the "Use a private email" option.
+1. Press **Update profile settings**.
Once this option is enabled, every Git-related action will be performed using the private commit email.
-In order to stay fully annonymous, you can also copy this private commit email
+In order to stay fully anonymous, you can also copy this private commit email
and configure it on your local machine using the following command:
-```
-git config --global user.email "YOUR_PRIVATE_COMMIT_EMAIL"
+```sh
+git config --global user.email <YOUR_PRIVATE_COMMIT_EMAIL>
```
## Troubleshooting
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 35f2976899b..670dde6bb00 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -1,33 +1,111 @@
-# Connecting GitLab with a Kubernetes cluster
+# Kubernetes clusters
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in GitLab 10.1.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) for
+> projects in GitLab 10.1.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) for
+> [groups](../../group/clusters/index.md) in GitLab 11.6.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/39840) for
+> [instances](../../instance/clusters/index.md) in GitLab 11.11.
-Connect your project to Google Kubernetes Engine (GKE) or an existing Kubernetes
-cluster in a few steps.
+GitLab provides many features with a Kubernetes integration. Kubernetes can be
+integrated with projects, but also:
+
+- [Groups](../../group/clusters/index.md).
+- [Instances](../../instance/clusters/index.md).
NOTE: **Scalable app deployment with GitLab and Google Cloud Platform**
[Watch the webcast](https://about.gitlab.com/webcast/scalable-app-deploy/) and learn how to spin up a Kubernetes cluster managed by Google Cloud Platform (GCP) in a few clicks.
## Overview
-With one or more Kubernetes clusters associated to your project, you can use
-[Review Apps](../../../ci/review_apps/index.md), deploy your applications, run
-your pipelines, use it with [Auto DevOps](../../../topics/autodevops/index.md),
-and much more, all from within GitLab.
+Using the GitLab project Kubernetes integration, you can:
+
+- Use [Review Apps](../../../ci/review_apps/index.md).
+- Run [pipelines](../../../ci/pipelines.md).
+- [Deploy](#deploying-to-a-kubernetes-cluster) your applications.
+- Detect and [monitor Kubernetes](#kubernetes-monitoring).
+- Use it with [Auto DevOps](#auto-devops).
+- Use [Web terminals](#web-terminals).
+- Use [Deploy Boards](#deploy-boards-premium). **(PREMIUM)**
+- Use [Canary Deployments](#canary-deployments-premium). **(PREMIUM)**
+- View [Pod logs](#pod-logs-ultimate). **(ULTIMATE)**
+
+You can also:
+
+- Connect and deploy to an [Amazon EKS cluster](eks_and_gitlab/index.html).
+- Run serverless workloads on [Kubernetes with Knative](serverless/index.md).
+
+### Deploy Boards **(PREMIUM)**
+
+GitLab's Deploy Boards offer a consolidated view of the current health and
+status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
+displaying the status of the pods in the deployment. Developers and other
+teammates can view the progress and status of a rollout, pod by pod, in the
+workflow they already use without any need to access Kubernetes.
+
+[Read more about Deploy Boards](../deploy_boards.md)
+
+### Canary Deployments **(PREMIUM)**
+
+Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
+and visualize your canary deployments right inside the Deploy Board, without
+the need to leave GitLab.
+
+[Read more about Canary Deployments](../canary_deployments.md)
+
+### Pod logs **(ULTIMATE)**
+
+GitLab makes it easy to view the logs of running pods in connected Kubernetes clusters. By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
+
+[Read more about Kubernetes pod logs](kubernetes_pod_logs.md)
+
+### Kubernetes monitoring
-There are two options when adding a new cluster to your project; either associate
-your account with Google Kubernetes Engine (GKE) so that you can [create new
-clusters](#adding-and-creating-a-new-gke-cluster-via-gitlab) from within GitLab,
-or provide the credentials to an [existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster).
+Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
+[NGINX ingress](../integrations/prometheus_library/nginx.md) is also supported.
+
+[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
+
+### Auto DevOps
+
+Auto DevOps automatically detects, builds, tests, deploys, and monitors your
+applications.
+
+To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
+you will need the Kubernetes project integration enabled.
+
+[Read more about Auto DevOps](../../../topics/autodevops/index.md)
+
+### Web terminals
NOTE: **Note:**
-From [GitLab 11.6](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) you
-can also associate a Kubernetes cluster to your groups and from
-[GitLab 11.11](https://gitlab.com/gitlab-org/gitlab-ce/issues/39840),
-to the GitLab instance. Learn more about [group-level](../../group/clusters/index.md)
-and [instance-level](../../instance/clusters/index.md) Kubernetes clusters.
+Introduced in GitLab 8.15. You must be the project owner or have `maintainer` permissions
+to use terminals. Support is limited to the first container in the
+first pod of your environment.
+
+When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
+support to your [environments](../../../ci/environments.md). This is based on the `exec` functionality found in
+Docker and Kubernetes, so you get a new shell session within your existing
+containers. To use this integration, you should deploy to Kubernetes using
+the deployment variables above, ensuring any deployments, replica sets, and
+pods are annotated with:
-## Adding and creating a new GKE cluster via GitLab
+- `app.gitlab.com/env: $CI_ENVIRONMENT_SLUG`
+- `app.gitlab.com/app: $CI_PROJECT_PATH_SLUG`
+
+`$CI_ENVIRONMENT_SLUG` and `$CI_PROJECT_PATH_SLUG` are the values of
+the CI variables.
+
+## Adding and removing clusters
+
+There are two options when adding a new cluster to your project:
+
+- Associate your account with Google Kubernetes Engine (GKE) to
+ [create new clusters](#add-new-gke-cluster) from within GitLab.
+- Provide credentials to an
+ [existing Kubernetes cluster](#add-existing-kubernetes-cluster).
+
+### Add new GKE cluster
TIP: **Tip:**
Every new Google Cloud Platform (GCP) account receives [$300 in credit upon sign up](https://console.cloud.google.com/freetrial),
@@ -39,7 +117,7 @@ The [Google authentication integration](../../../integration/google.md) must
be enabled in GitLab at the instance level. If that's not the case, ask your
GitLab administrator to enable it. On GitLab.com, this is enabled.
-### Requirements
+#### Requirements
Before creating your first cluster on Google Kubernetes Engine with GitLab's
integration, make sure the following requirements are met:
@@ -49,7 +127,7 @@ integration, make sure the following requirements are met:
- The Kubernetes Engine API and related service are enabled. It should work immediately but may take up to 10 minutes after you create a project. For more information see the
["Before you begin" section of the Kubernetes Engine docs](https://cloud.google.com/kubernetes-engine/docs/quickstart#before-you-begin).
-### Creating the cluster
+#### Creating the cluster
If all of the above requirements are met, you can proceed to create and add a
new Kubernetes cluster to your project:
@@ -57,7 +135,7 @@ new Kubernetes cluster to your project:
1. Navigate to your project's **Operations > Kubernetes** page.
NOTE: **Note:**
- You need Maintainer [permissions] and above to access the Kubernetes page.
+ You need Maintainer [permissions](../../permissions.md) and above to access the Kubernetes page.
1. Click **Add Kubernetes cluster**.
1. Click **Create with Google Kubernetes Engine**.
@@ -91,14 +169,14 @@ client certificate is enabled.
NOTE: **Note:**
Starting from [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/issues/55902), all GKE clusters created by GitLab are RBAC enabled. Take a look at the [RBAC section](#rbac-cluster-resources) for more information.
-## Adding an existing Kubernetes cluster
+### Add existing Kubernetes cluster
To add an existing Kubernetes cluster to your project:
1. Navigate to your project's **Operations > Kubernetes** page.
NOTE: **Note:**
- You need Maintainer [permissions] and above to access the Kubernetes page.
+ You need Maintainer [permissions](../../permissions.md) and above to access the Kubernetes page.
1. Click **Add Kubernetes cluster**.
1. Click **Add an existing Kubernetes cluster** and fill in the details:
@@ -216,7 +294,36 @@ To add an existing Kubernetes cluster to your project:
After a couple of minutes, your cluster will be ready to go. You can now proceed
to install some [pre-defined applications](#installing-applications).
-## Security implications
+### Enabling or disabling integration
+
+After you have successfully added your cluster information, you can enable the
+Kubernetes cluster integration:
+
+1. Click the **Enabled/Disabled** switch
+1. Hit **Save** for the changes to take effect
+
+To disable the Kubernetes cluster integration, follow the same procedure.
+
+### Removing integration
+
+NOTE: **Note:**
+You need Maintainer [permissions](../../permissions.md) and above to remove a Kubernetes cluster integration.
+
+NOTE: **Note:**
+When you remove a cluster, you only remove its relation to GitLab, not the
+cluster itself. To remove the cluster, you can do so by visiting the GKE
+dashboard or using `kubectl`.
+
+To remove the Kubernetes cluster integration from your project, simply click the
+**Remove integration** button. You will then be able to follow the procedure
+and add a Kubernetes cluster again.
+
+## Cluster configuration
+
+This section covers important considerations for configuring Kubernetes
+clusters with GitLab.
+
+### Security implications
CAUTION: **Important:**
The whole cluster security is based on a model where [developers](../../permissions.md)
@@ -227,7 +334,7 @@ functionalities needed to successfully build and deploy a containerized
application. Bear in mind that the same credentials are used for all the
applications running on the cluster.
-## GitLab-managed clusters
+### GitLab-managed clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5.
> Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11.
@@ -246,7 +353,7 @@ NOTE: **Note:**
If you [install applications](#installing-applications) on your cluster, GitLab will create
the resources required to run these even if you have chosen to manage your own cluster.
-## Base domain
+### Base domain
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
@@ -264,7 +371,7 @@ you can either:
- Create an `A` record that points to the Ingress IP address with your domain provider.
- Enter a wildcard DNS address using a service such as nip.io or xip.io. For example, `192.168.1.1.xip.io`.
-## Access controls
+### Access controls
When creating a cluster in GitLab, you will be asked if you would like to create either:
@@ -294,12 +401,12 @@ Helm will also create additional service accounts and other resources for each
installed application. Consult the documentation of the Helm charts for each application
for details.
-If you are [adding an existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster),
+If you are [adding an existing Kubernetes cluster](#add-existing-kubernetes-cluster),
ensure the token of the account has administrator privileges for the cluster.
The resources created by GitLab differ depending on the type of cluster.
-### ABAC cluster resources
+#### ABAC cluster resources
GitLab creates the following resources for ABAC clusters.
@@ -312,7 +419,7 @@ GitLab creates the following resources for ABAC clusters.
| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
-### RBAC cluster resources
+#### RBAC cluster resources
GitLab creates the following resources for RBAC clusters.
@@ -330,11 +437,12 @@ GitLab creates the following resources for RBAC clusters.
NOTE: **Note:**
Project-specific resources are only created if your cluster is [managed by GitLab](#gitlab-managed-clusters).
-### Security of GitLab Runners
+#### Security of GitLab Runners
GitLab Runners have the [privileged mode](https://docs.gitlab.com/runner/executors/docker.html#the-privileged-mode)
enabled by default, which allows them to execute special commands and running
-Docker in Docker. This functionality is needed to run some of the [Auto DevOps]
+Docker in Docker. This functionality is needed to run some of the
+[Auto DevOps](../../../topics/autodevops/index.md)
jobs. This implies the containers are running in privileged mode and you should,
therefore, be aware of some important details.
@@ -353,6 +461,69 @@ If you don't want to use GitLab Runner in privileged mode, either:
1. Installing a Runner
[using `docker+machine`](https://docs.gitlab.com/runner/executors/docker_machine.html).
+### Setting the environment scope **(PREMIUM)**
+
+When adding more than one Kubernetes cluster to your project, you need to differentiate
+them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work.
+
+The default environment scope is `*`, which means all jobs, regardless of their
+environment, will use that cluster. Each scope can only be used by a single
+cluster in a project, and a validation error will occur if otherwise.
+Also, jobs that don't have an environment keyword set will not be able to access any cluster.
+
+For example, let's say the following Kubernetes clusters exist in a project:
+
+| Cluster | Environment scope |
+| ----------- | ----------------- |
+| Development | `*` |
+| Staging | `staging` |
+| Production | `production` |
+
+And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md):
+
+```yaml
+stages:
+- test
+- deploy
+
+test:
+ stage: test
+ script: sh test
+
+deploy to staging:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: staging
+ url: https://staging.example.com/
+
+deploy to production:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: production
+ url: https://example.com/
+```
+
+The result will then be:
+
+- The development cluster will be used for the "test" job.
+- The staging cluster will be used for the "deploy to staging" job.
+- The production cluster will be used for the "deploy to production" job.
+
+### Multiple Kubernetes clusters **(PREMIUM)**
+
+> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
+
+With GitLab Premium, you can associate more than one Kubernetes cluster to your
+project. That way you can have different clusters for different environments,
+like dev, staging, production, etc.
+
+Simply add another cluster, like you did the first time, and make sure to
+[set an environment scope](#setting-the-environment-scope-premium) that will
+differentiate the new cluster with the rest.
+
## Installing applications
GitLab can install and manage some applications in your project-level
@@ -360,7 +531,7 @@ cluster. For more information on installing, upgrading, uninstalling,
and troubleshooting applications for your project cluster, see
[Gitlab Managed Apps](../../clusters/applications.md).
-## Getting the external endpoint
+### Getting the external endpoint
NOTE: **Note:**
With the following procedure, a load balancer must be installed in your cluster
@@ -371,7 +542,7 @@ to obtain the endpoint. You can use either
In order to publish your web application, you first need to find the endpoint which will be either an IP
address or a hostname associated with your load balancer.
-### Automatically determining the external endpoint
+#### Automatically determining the external endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
@@ -386,7 +557,7 @@ and your cluster runs on Google Kubernetes Engine:
If GitLab is still unable to determine the endpoint of your Ingress or Knative application, you can
manually determine it by following the steps below.
-### Manually determining the external endpoint
+#### Manually determining the external endpoint
If the cluster is on GKE, click the **Google Kubernetes Engine** link in the
**Advanced settings**, or go directly to the
@@ -425,7 +596,7 @@ Otherwise, you can list the IP addresses of all load balancers:
kubectl get svc --all-namespaces -o jsonpath='{range.items[?(@.status.loadBalancer.ingress)]}{.status.loadBalancer.ingress[*].ip} '
```
-### Using a static IP
+#### Using a static IP
By default, an ephemeral external IP address is associated to the cluster's load
balancer. If you associate the ephemeral IP with your DNS and the IP changes,
@@ -435,79 +606,19 @@ reserved IP.
Read how to [promote an ephemeral external IP address in GKE](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#promote_ephemeral_ip).
-### Pointing your DNS at the external endpoint
+#### Pointing your DNS at the external endpoint
Once you've set up the external endpoint, you should associate it with a [wildcard DNS
record](https://en.wikipedia.org/wiki/Wildcard_DNS_record) such as `*.example.com.`
in order to be able to reach your apps. If your external endpoint is an IP address,
use an A record. If your external endpoint is a hostname, use a CNAME record.
-## Multiple Kubernetes clusters **(PREMIUM)**
+## Deploying to a Kubernetes cluster
-> Introduced in [GitLab Premium][ee] 10.3.
+A Kubernetes cluster can be the destination for a deployment job, using
+special [deployment variables](#deployment-variables).
-With GitLab Premium, you can associate more than one Kubernetes clusters to your
-project. That way you can have different clusters for different environments,
-like dev, staging, production, etc.
-
-Simply add another cluster, like you did the first time, and make sure to
-[set an environment scope](#setting-the-environment-scope-premium) that will
-differentiate the new cluster with the rest.
-
-## Setting the environment scope **(PREMIUM)**
-
-When adding more than one Kubernetes cluster to your project, you need to differentiate
-them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
-[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work.
-
-The default environment scope is `*`, which means all jobs, regardless of their
-environment, will use that cluster. Each scope can only be used by a single
-cluster in a project, and a validation error will occur if otherwise.
-Also, jobs that don't have an environment keyword set will not be able to access any cluster.
-
----
-
-For example, let's say the following Kubernetes clusters exist in a project:
-
-| Cluster | Environment scope |
-| ----------- | ----------------- |
-| Development | `*` |
-| Staging | `staging` |
-| Production | `production` |
-
-And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md):
-
-```yaml
-stages:
-- test
-- deploy
-
-test:
- stage: test
- script: sh test
-
-deploy to staging:
- stage: deploy
- script: make deploy
- environment:
- name: staging
- url: https://staging.example.com/
-
-deploy to production:
- stage: deploy
- script: make deploy
- environment:
- name: production
- url: https://example.com/
-```
-
-The result will then be:
-
-- The development cluster will be used for the "test" job.
-- The staging cluster will be used for the "deploy to staging" job.
-- The production cluster will be used for the "deploy to production" job.
-
-## Deployment variables
+### Deployment variables
The Kubernetes cluster integration exposes the following
[deployment variables](../../../ci/variables/README.md#deployment-environment-variables) in the
@@ -527,7 +638,7 @@ NOTE: **NOTE:**
Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
service account of the cluster integration.
-### Troubleshooting failed deployment jobs
+### Troubleshooting
Before the deployment jobs starts, GitLab creates the following specifically for
the deployment job:
@@ -559,105 +670,8 @@ namespaces and service accounts yourself.
## Monitoring your Kubernetes cluster **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6.
When [Prometheus is deployed](#installing-applications), GitLab will automatically monitor the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start.
![Cluster Monitoring](img/k8s_cluster_monitoring.png)
-
-## Enabling or disabling the Kubernetes cluster integration
-
-After you have successfully added your cluster information, you can enable the
-Kubernetes cluster integration:
-
-1. Click the **Enabled/Disabled** switch
-1. Hit **Save** for the changes to take effect
-
-You can now start using your Kubernetes cluster for your deployments.
-
-To disable the Kubernetes cluster integration, follow the same procedure.
-
-## Removing the Kubernetes cluster integration
-
-NOTE: **Note:**
-You need Maintainer [permissions] and above to remove a Kubernetes cluster integration.
-
-NOTE: **Note:**
-When you remove a cluster, you only remove its relation to GitLab, not the
-cluster itself. To remove the cluster, you can do so by visiting the GKE
-dashboard or using `kubectl`.
-
-To remove the Kubernetes cluster integration from your project, simply click the
-**Remove integration** button. You will then be able to follow the procedure
-and add a Kubernetes cluster again.
-
-## What you can get with the Kubernetes integration
-
-Here's what you can do with GitLab if you enable the Kubernetes integration.
-
-### Deploy Boards **(PREMIUM)**
-
-GitLab's Deploy Boards offer a consolidated view of the current health and
-status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
-displaying the status of the pods in the deployment. Developers and other
-teammates can view the progress and status of a rollout, pod by pod, in the
-workflow they already use without any need to access Kubernetes.
-
-[Read more about Deploy Boards](../deploy_boards.md)
-
-### Canary Deployments **(PREMIUM)**
-
-Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
-and visualize your canary deployments right inside the Deploy Board, without
-the need to leave GitLab.
-
-[Read more about Canary Deployments](../canary_deployments.md)
-
-### Pod logs **(ULTIMATE)**
-
-GitLab makes it easy to view the logs of running pods in connected Kubernetes clusters. By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
-
-[Read more about Kubernetes pod logs](kubernetes_pod_logs.md)
-
-### Kubernetes monitoring
-
-Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
-[NGINX ingress](../integrations/prometheus_library/nginx.md) is also supported.
-
-[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
-
-### Auto DevOps
-
-Auto DevOps automatically detects, builds, tests, deploys, and monitors your
-applications.
-
-To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
-you will need the Kubernetes project integration enabled.
-
-[Read more about Auto DevOps](../../../topics/autodevops/index.md)
-
-### Web terminals
-
-NOTE: **Note:**
-Introduced in GitLab 8.15. You must be the project owner or have `maintainer` permissions
-to use terminals. Support is limited to the first container in the
-first pod of your environment.
-
-When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
-support to your [environments](../../../ci/environments.md). This is based on the `exec` functionality found in
-Docker and Kubernetes, so you get a new shell session within your existing
-containers. To use this integration, you should deploy to Kubernetes using
-the deployment variables above, ensuring any pods you create are labelled with
-`app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest!
-
-### Integrating Amazon EKS cluster with GitLab
-
-- Learn how to [connect and deploy to an Amazon EKS cluster](eks_and_gitlab/index.md).
-
-### Serverless
-
-- [Run serverless workloads on Kubernetes with Knative.](serverless/index.md)
-
-[permissions]: ../../permissions.md
-[ee]: https://about.gitlab.com/pricing/
-[Auto DevOps]: ../../../topics/autodevops/index.md
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index c021d059d30..8df27976662 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -35,7 +35,7 @@ for an overview of how this is accomplished in GitLab!**
To create an executable runbook, you will need:
1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#add-new-gke-cluster).
1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install
all the other applications. It is installed in its own pod inside the cluster which
can run the helm CLI in a safe environment.
@@ -60,7 +60,7 @@ the components outlined above and the preloaded demo runbook.
### 1. Add a Kubernetes cluster
-Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab)
+Follow the steps outlined in [Add new GKE cluster](../index.md#add-new-gke-cluster)
to add a Kubernetes cluster to your project.
### 2. Install Helm Tiller, Ingress, and JupyterHub
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index a32759c7bdc..92ad49e9448 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -28,7 +28,7 @@ To run Knative on Gitlab, you will need:
- If you are planning on deploying a serverless application, clone the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#add-new-gke-cluster).
The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory.
1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install
Knative.
@@ -96,7 +96,8 @@ cluster which already has Knative installed.
You must do the following:
1. Follow the steps to
- [add an existing Kubernetes cluster](../index.md#adding-an-existing-kubernetes-cluster).
+ [add an existing Kubernetes
+ cluster](../index.md#add-existing-kubernetes-cluster).
1. Ensure GitLab can manage Knative:
- For a non-GitLab managed cluster, ensure that the service account for the token
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 9368c56004d..c9eb81b990c 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -1,13 +1,8 @@
# GitLab Container Registry
-> **Notes:**
->
> - [Introduced][ce-4040] in GitLab 8.8.
> - Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker
> versions earlier than 1.10.
-> - This document is about the user guide. To learn how to enable GitLab Container
-> Registry across your GitLab instance, visit the
-> [administrator documentation](../../administration/container_registry.md).
> - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
> login to GitLab's Container Registry.
@@ -16,28 +11,33 @@
With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images.
+This document is the user guide. To learn how to enable GitLab Container
+Registry across your GitLab instance, visit the
+[administrator documentation](../../administration/container_registry.md).
+
You can read more about Docker Registry at <https://docs.docker.com/registry/introduction/>.
## Enable the Container Registry for your project
-NOTE: **Note:**
-If you cannot find the Container Registry entry under your project's settings,
-that means that it is not enabled in your GitLab instance. Ask your administrator
-to enable it.
-
-1. First, ask your system administrator to enable GitLab Container Registry
- following the [administration documentation](../../administration/container_registry.md).
- If you are using GitLab.com, this is enabled by default so you can start using
- the Registry immediately. Currently there is a soft (10GB) size restriction for
- registry on GitLab.com, as part of the [repository size limit](repository/index.md).
-1. Go to your [project's General settings](settings/index.md#sharing-and-permissions)
+If you cannot find the **Packages > Container Registry** entry under your
+project's sidebar, it is not enabled in your GitLab instance. Ask your
+administrator to enable GitLab Container Registry following the
+[administration documentation](../../administration/container_registry.md).
+
+If you are using GitLab.com, this is enabled by default so you can start using
+the Registry immediately. Currently there is a soft (10GB) size restriction for
+registry on GitLab.com, as part of the [repository size limit](repository/index.md).
+
+Once enabled for your GitLab instance, to enable Container Registry for your
+project:
+
+1. Go to your project's **Settings > General** page.
+1. Expand the **Visibility, project features, permissions** section
and enable the **Container Registry** feature on your project. For new
projects this might be enabled by default. For existing projects
(prior GitLab 8.8), you will have to explicitly enable it.
-1. Hit **Save changes** for the changes to take effect. You should now be able
- to see the **Registry** link in the sidebar.
-
-![Container Registry](img/container_registry.png)
+1. Press **Save changes** for the changes to take effect. You should now be able
+ to see the **Packages > Container Registry** link in the sidebar.
## Build and push images
@@ -49,14 +49,14 @@ to enable it.
> - To move or rename a repository with a container registry you will have to
> delete all existing images.
-If you visit the **Registry** link under your project's menu, you can see the
-explicit instructions to login to the Container Registry using your GitLab
-credentials.
+If you visit the **Packages > Container Registry** link under your project's
+menu, you can see the explicit instructions to login to the Container Registry
+using your GitLab credentials.
For example if the Registry's URL is `registry.example.com`, then you should be
able to login with:
-```
+```sh
docker login registry.example.com
```
@@ -64,14 +64,14 @@ Building and publishing images should be a straightforward process. Just make
sure that you are using the Registry URL with the namespace and project name
that is hosted on GitLab:
-```
+```sh
docker build -t registry.example.com/group/project/image .
docker push registry.example.com/group/project/image
```
Your image will be named after the following scheme:
-```
+```text
<registry URL>/<namespace>/<project>/<image>
```
@@ -79,7 +79,7 @@ GitLab supports up to three levels of image repository names.
Following examples of image tags are valid:
-```
+```text
registry.example.com/group/project:some-tag
registry.example.com/group/project/image:latest
registry.example.com/group/project/my/image:rc1
@@ -90,7 +90,7 @@ registry.example.com/group/project/my/image:rc1
To download and run a container from images hosted in GitLab Container Registry,
use `docker run`:
-```
+```sh
docker run [options] registry.example.com/group/project/image [arguments]
```
@@ -100,7 +100,7 @@ For more information on running Docker containers, visit the
## Control Container Registry from within GitLab
GitLab offers a simple Container Registry management panel. Go to your project
-and click **Registry** in the project menu.
+and click **Packages > Container Registry** in the project menu.
This view will show you all tags in your project and will easily allow you to
delete them.
@@ -173,9 +173,9 @@ curl localhost:5001/debug/vars
A Docker connection error can occur when there are special characters in either the group,
project or branch name. Special characters can include:
-* Leading underscore
-* Trailing hyphen/dash
-* Double hyphen/dash
+- Leading underscore
+- Trailing hyphen/dash
+- Double hyphen/dash
To get around this, you can [change the group path](../group/index.md#changing-a-groups-path),
[change the project path](../project/settings/index.md#renaming-a-repository) or chanage the branch
@@ -183,7 +183,8 @@ name.
### Advanced Troubleshooting
->**NOTE:** The following section is only recommended for experts.
+NOTE: **Note:**
+The following section is only recommended for experts.
Sometimes it's not obvious what is wrong, and you may need to dive deeper into
the communication between the Docker client and the Registry to find out
@@ -195,7 +196,7 @@ diagnose a problem with the S3 setup.
A user attempted to enable an S3-backed Registry. The `docker login` step went
fine. However, when pushing an image, the output showed:
-```
+```text
The push refers to a repository [s3-testing.myregistry.com:4567/root/docker-test/docker-image]
dc5e59c14160: Pushing [==================================================>] 14.85 kB
03c20c1a019a: Pushing [==================================================>] 2.048 kB
@@ -218,7 +219,7 @@ at the communication between the client and the Registry.
The REST API between the Docker client and Registry is [described
here](https://docs.docker.com/registry/spec/api/). Normally, one would just
use Wireshark or tcpdump to capture the traffic and see where things went
-wrong. However, since all communications between Docker clients and servers
+wrong. However, since all communications between Docker clients and servers
are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even
if you know the private key. What can we do instead?
diff --git a/doc/user/project/img/container_registry.png b/doc/user/project/img/container_registry.png
deleted file mode 100644
index abbaf838538..00000000000
--- a/doc/user/project/img/container_registry.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 61c30e0b0ef..e609fe43507 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -323,8 +323,11 @@ Once enabled, an issue will be opened automatically when an alert is triggered w
- `starts_at`: Alert start time via `startsAt`
- `full_query`: Alert query extracted from `generatorURL`
- Optional list of attached annotations extracted from `annotations/*`
+- Alert [GFM](../../markdown.md): GitLab Flavored Markdown from `annotations/gitlab_incident_markdown`
-To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template.
+To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template, which will apply to all incidents. To limit quick actions or other information to only specific types of alerts, use the `annotations/gitlab_incident_markdown` field.
+
+Since [version 12.2](https://gitlab.com/gitlab-org/gitlab-ce/issues/63373), GitLab will tag each incident issue with the `incident` label automatically. If the label does not yet exist, it will be created automatically as well.
If the metric exceeds the threshold of the alert for over 5 minutes, an email will be sent to all [Maintainers and Owners](../../permissions.md#project-members-permissions) of the project.
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index d5ca853eff5..f78ec9d96e6 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -343,6 +343,30 @@ git push -o merge_request.remove_source_branch
You can also use this push option in addition to the
`merge_request.create` push option.
+### Set merge request title using git push options
+
+To set the title of an existing merge request, use
+the `merge_request.title` push option:
+
+```sh
+git push -o merge_request.title="The title I want"
+```
+
+You can also use this push option in addition to the
+`merge_request.create` push option.
+
+### Set merge request description using git push options
+
+To set the description of an existing merge request, use
+the `merge_request.description` push option:
+
+```sh
+git push -o merge_request.description="The description I want"
+```
+
+You can also use this push option in addition to the
+`merge_request.create` push option.
+
## Find the merge request that introduced a change
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2383) in GitLab 10.5.
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 20a03dff2da..9a3c02e1f50 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -180,6 +180,5 @@ for details about the pipelines security model.
[ce-4892]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4892 "Allow developers to merge into a protected branch without having push access"
[ce-5081]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081 "Allow creating protected branches that can't be pushed to"
[ce-21393]: https://gitlab.com/gitlab-org/gitlab-ce/issues/21393
-[ee-restrict]: https://docs.gitlab.com/ee/user/project/protected_branches.html#restricting-push-and-merge-access-to-certain-users
[perm]: ../permissions.md
[ee]: https://about.gitlab.com/pricing/
diff --git a/lib/api/validations/types/labels_list.rb b/lib/api/validations/types/labels_list.rb
index 47cd83c29cf..60277b99106 100644
--- a/lib/api/validations/types/labels_list.rb
+++ b/lib/api/validations/types/labels_list.rb
@@ -10,7 +10,7 @@ module API
when String
value.split(',').map(&:strip)
when Array
- value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
+ value.flat_map { |v| v.to_s.split(',').map(&:strip) }
when LabelsList
value
else
diff --git a/lib/banzai/reference_redactor.rb b/lib/banzai/reference_redactor.rb
index eb5c35da375..936436982e7 100644
--- a/lib/banzai/reference_redactor.rb
+++ b/lib/banzai/reference_redactor.rb
@@ -33,7 +33,7 @@ module Banzai
#
# data - An Array of a Hashes mapping an HTML document to nodes to redact.
def redact_document_nodes(all_document_nodes)
- all_nodes = all_document_nodes.map { |x| x[:nodes] }.flatten
+ all_nodes = all_document_nodes.flat_map { |x| x[:nodes] }
visible = nodes_visible_to_user(all_nodes)
metadata = []
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index c80f49f5ae0..c3a19af7a94 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -7,7 +7,9 @@ module ContainerRegistry
class Client
attr_accessor :uri
- MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'.freeze
+ DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
+ OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
+ ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
# Taken from: FaradayMiddleware::FollowRedirects
REDIRECT_CODES = Set.new [301, 302, 303, 307]
@@ -60,12 +62,13 @@ module ContainerRegistry
end
def accept_manifest(conn)
- conn.headers['Accept'] = MANIFEST_VERSION
+ conn.headers['Accept'] = ACCEPTED_TYPES
conn.response :json, content_type: 'application/json'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
- conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v2+json'
+ conn.response :json, content_type: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
+ conn.response :json, content_type: OCI_MANIFEST_V1_TYPE
end
def response_body(response, allow_redirect: false)
diff --git a/lib/gitlab/auth/activity.rb b/lib/gitlab/auth/activity.rb
index 558628b5422..988ff196193 100644
--- a/lib/gitlab/auth/activity.rb
+++ b/lib/gitlab/auth/activity.rb
@@ -37,14 +37,17 @@ module Gitlab
def user_authenticated!
self.class.user_authenticated_counter_increment!
+
+ case @opts[:message]
+ when :two_factor_authenticated
+ self.class.user_two_factor_authenticated_counter_increment!
+ end
end
def user_session_override!
self.class.user_session_override_counter_increment!
case @opts[:message]
- when :two_factor_authenticated
- self.class.user_two_factor_authenticated_counter_increment!
when :sessionless_sign_in
self.class.user_sessionless_authentication_counter_increment!
end
diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb
index 72a187377d0..91b9ddc0d00 100644
--- a/lib/gitlab/auth/o_auth/auth_hash.rb
+++ b/lib/gitlab/auth/o_auth/auth_hash.rb
@@ -60,8 +60,7 @@ module Gitlab
def get_info(key)
value = info[key]
- Gitlab::Utils.force_utf8(value) if value
- value
+ value.is_a?(String) ? Gitlab::Utils.force_utf8(value) : value
end
def username_and_email
diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb
index c8d83cc1803..1d9aa050041 100644
--- a/lib/gitlab/background_migration/backfill_project_repositories.rb
+++ b/lib/gitlab/background_migration/backfill_project_repositories.rb
@@ -40,7 +40,7 @@ module Gitlab
end
def reload!
- @shards = Hash[*Shard.all.map { |shard| [shard.name, shard.id] }.flatten]
+ @shards = Hash[*Shard.all.flat_map { |shard| [shard.name, shard.id] }]
end
end
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
index 7d40b459c9a..806c3a7a369 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -55,7 +55,7 @@ module Gitlab
def ensure_temporary_tracking_table_exists
table_name = :untracked_files_for_uploads
- unless ActiveRecord::Base.connection.data_source_exists?(table_name)
+ unless ActiveRecord::Base.connection.table_exists?(table_name)
UntrackedFile.connection.create_table table_name do |t|
t.string :path, limit: 600, null: false
t.index :path, unique: true
diff --git a/lib/gitlab/ci/config/normalizer.rb b/lib/gitlab/ci/config/normalizer.rb
index 191f5d09645..99356226ef9 100644
--- a/lib/gitlab/ci/config/normalizer.rb
+++ b/lib/gitlab/ci/config/normalizer.rb
@@ -46,7 +46,7 @@ module Gitlab
parallelized_job_names = @parallelized_jobs.keys.map(&:to_s)
parallelized_config.each_with_object({}) do |(job_name, config), hash|
if config[:dependencies] && (intersection = config[:dependencies] & parallelized_job_names).any?
- parallelized_deps = intersection.map { |dep| @parallelized_jobs[dep.to_sym].map(&:first) }.flatten
+ parallelized_deps = intersection.flat_map { |dep| @parallelized_jobs[dep.to_sym].map(&:first) }
deps = config[:dependencies] - intersection + parallelized_deps
hash[job_name] = config.merge(dependencies: deps)
else
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index 3446644eff8..2a0bf060c9b 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -34,11 +34,9 @@ module Gitlab
def extended_statuses
return @extended_statuses if defined?(@extended_statuses)
- groups = self.class.extended_statuses.map do |group|
+ @extended_statuses = self.class.extended_statuses.flat_map do |group|
Array(group).find { |status| status.matches?(@subject, @user) }
- end
-
- @extended_statuses = groups.flatten.compact
+ end.compact
end
def self.extended_statuses
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 7b9a169a91f..5c1c0c142e5 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -50,9 +50,6 @@ variables:
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
POSTGRES_VERSION: 9.6.2
- KUBERNETES_VERSION: 1.11.10
- HELM_VERSION: 2.14.0
-
DOCKER_DRIVER: overlay2
ROLLOUT_RESOURCE_TYPE: deployment
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 6ead127e7b6..a8ec2d4781d 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -1,14 +1,17 @@
+.auto-deploy:
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.1.0"
+
review:
+ extends: .auto-deploy
stage: review
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- - persist_environment_url
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy
+ - auto-deploy persist_environment_url
environment:
name: review/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_ID-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -27,13 +30,13 @@ review:
- $REVIEW_DISABLED
stop_review:
+ extends: .auto-deploy
stage: cleanup
variables:
GIT_STRATEGY: none
script:
- - install_dependencies
- - initialize_tiller
- - delete
+ - auto-deploy initialize_tiller
+ - auto-deploy delete
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
@@ -57,15 +60,15 @@ stop_review:
# STAGING_ENABLED.
staging:
+ extends: .auto-deploy
stage: staging
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy
environment:
name: staging
url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_INGRESS_BASE_DOMAIN
@@ -81,15 +84,15 @@ staging:
# CANARY_ENABLED.
canary:
+ extends: .auto-deploy
stage: canary
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy canary
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy canary
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -102,18 +105,18 @@ canary:
- $CANARY_ENABLED
.production: &production_template
+ extends: .auto-deploy
stage: production
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- - delete canary
- - delete rollout
- - persist_environment_url
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy
+ - auto-deploy delete canary
+ - auto-deploy delete rollout
+ - auto-deploy persist_environment_url
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -152,17 +155,17 @@ production_manual:
# This job implements incremental rollout on for every push to `master`.
.rollout: &rollout_template
+ extends: .auto-deploy
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy rollout $ROLLOUT_PERCENTAGE
- - scale stable $((100-ROLLOUT_PERCENTAGE))
- - delete canary
- - persist_environment_url
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy rollout $ROLLOUT_PERCENTAGE
+ - auto-deploy scale stable $((100-ROLLOUT_PERCENTAGE))
+ - auto-deploy delete canary
+ - auto-deploy persist_environment_url
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -240,331 +243,3 @@ rollout 100%:
<<: *manual_rollout_template
<<: *production_template
allow_failure: false
-
-.deploy_helpers: &deploy_helpers |
- [[ "$TRACE" ]] && set -x
- export RELEASE_NAME=${HELM_RELEASE_NAME:-$CI_ENVIRONMENT_SLUG}
- auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${RELEASE_NAME}-postgres:5432/${POSTGRES_DB}
- export DATABASE_URL=${DATABASE_URL-$auto_database_url}
- export TILLER_NAMESPACE=$KUBE_NAMESPACE
-
- function get_replicas() {
- track="${1:-stable}"
- percentage="${2:-100}"
-
- env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' )
- env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' )
-
- if [[ "$track" == "stable" ]] || [[ "$track" == "rollout" ]]; then
- # for stable track get number of replicas from `PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- new_replicas=$REPLICAS
- fi
- else
- # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- eval new_replicas=\${env_track}_REPLICAS
- fi
- fi
-
- replicas="${new_replicas:-1}"
- replicas="$(($replicas * $percentage / 100))"
-
- # always return at least one replicas
- if [[ $replicas -gt 0 ]]; then
- echo "$replicas"
- else
- echo 1
- fi
- }
-
- # Extracts variables prefixed with K8S_SECRET_
- # and creates a Kubernetes secret.
- #
- # e.g. If we have the following environment variables:
- # K8S_SECRET_A=value1
- # K8S_SECRET_B=multi\ word\ value
- #
- # Then we will create a secret with the following key-value pairs:
- # data:
- # A: dmFsdWUxCg==
- # B: bXVsdGkgd29yZCB2YWx1ZQo=
- function create_application_secret() {
- track="${1-stable}"
- export APPLICATION_SECRET_NAME=$(application_secret_name "$track")
-
- env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables
-
- kubectl create secret \
- -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \
- --from-env-file k8s_prefixed_variables -o yaml --dry-run |
- kubectl replace -n "$KUBE_NAMESPACE" --force -f -
-
- export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1)
-
- rm k8s_prefixed_variables
- }
-
- function deploy_name() {
- name="$RELEASE_NAME"
- track="${1-stable}"
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
-
- echo $name
- }
-
- function application_secret_name() {
- track="${1-stable}"
- name=$(deploy_name "$track")
-
- echo "${name}-secret"
- }
-
- function deploy() {
- track="${1-stable}"
- percentage="${2:-100}"
- name=$(deploy_name "$track")
-
- if [[ -z "$CI_COMMIT_TAG" ]]; then
- image_repository=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG}
- image_tag=${CI_APPLICATION_TAG:-$CI_COMMIT_SHA}
- else
- image_repository=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE}
- image_tag=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG}
- fi
-
- service_enabled="true"
- postgres_enabled="$POSTGRES_ENABLED"
-
- # if track is different than stable,
- # re-use all attached resources
- if [[ "$track" != "stable" ]]; then
- service_enabled="false"
- postgres_enabled="false"
- fi
-
- replicas=$(get_replicas "$track" "$percentage")
-
- if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then
- secret_name='gitlab-registry'
- else
- secret_name=''
- fi
-
- create_application_secret "$track"
-
- env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
- eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
- if [ -n "$env_ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$env_ADDITIONAL_HOSTS}"
- elif [ -n "$ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$ADDITIONAL_HOSTS}"
- fi
-
- if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
- echo "Deploying first release with database initialization..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
- --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --set releaseOverride="$RELEASE_NAME" \
- --set image.repository="$image_repository" \
- --set image.tag="$image_tag" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le-$CI_PROJECT_ID.$KUBE_INGRESS_BASE_DOMAIN" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set postgresql.imageTag="$POSTGRES_VERSION" \
- --set application.initializeCommand="$DB_INITIALIZE" \
- $HELM_UPGRADE_EXTRA_ARGS \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
-
- echo "Deploying second release..."
- helm upgrade --reuse-values \
- --wait \
- --set application.initializeCommand="" \
- --set application.migrateCommand="$DB_MIGRATE" \
- $HELM_UPGRADE_EXTRA_ARGS \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- else
- echo "Deploying new release..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
- --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --set releaseOverride="$RELEASE_NAME" \
- --set image.repository="$image_repository" \
- --set image.tag="$image_tag" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le-$CI_PROJECT_ID.$KUBE_INGRESS_BASE_DOMAIN" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set postgresql.imageTag="$POSTGRES_VERSION" \
- --set application.migrateCommand="$DB_MIGRATE" \
- $HELM_UPGRADE_EXTRA_ARGS \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- fi
-
- if [[ -z "$ROLLOUT_STATUS_DISABLED" ]]; then
- kubectl rollout status -n "$KUBE_NAMESPACE" -w "$ROLLOUT_RESOURCE_TYPE/$name"
- fi
- }
-
- function scale() {
- track="${1-stable}"
- percentage="${2-100}"
- name=$(deploy_name "$track")
-
- replicas=$(get_replicas "$track" "$percentage")
-
- if [[ -n "$(helm ls -q "^$name$")" ]]; then
- helm upgrade --reuse-values \
- --wait \
- --set replicaCount="$replicas" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- fi
- }
-
- function install_dependencies() {
- apk add -U openssl curl tar gzip bash ca-certificates git
- curl -sSL -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
- curl -sSL -O https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
- apk add glibc-2.28-r0.apk
- rm glibc-2.28-r0.apk
-
- curl -sS "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
- mv linux-amd64/helm /usr/bin/
- mv linux-amd64/tiller /usr/bin/
- helm version --client
- tiller -version
-
- curl -sSL -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
- chmod +x /usr/bin/kubectl
- kubectl version --client
- }
-
- function download_chart() {
- if [[ ! -d chart ]]; then
- auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app}
- auto_chart_name=$(basename $auto_chart)
- auto_chart_name=${auto_chart_name%.tgz}
- auto_chart_name=${auto_chart_name%.tar.gz}
- else
- auto_chart="chart"
- auto_chart_name="chart"
- fi
-
- helm init --client-only
- helm repo add ${AUTO_DEVOPS_CHART_REPOSITORY_NAME:-gitlab} ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io} ${AUTO_DEVOPS_CHART_REPOSITORY_USERNAME:+"--username" "$AUTO_DEVOPS_CHART_REPOSITORY_USERNAME"} ${AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD:+"--password" "$AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD"}
- if [[ ! -d "$auto_chart" ]]; then
- helm fetch ${auto_chart} --untar
- fi
- if [ "$auto_chart_name" != "chart" ]; then
- mv ${auto_chart_name} chart
- fi
-
- helm dependency update chart/
- helm dependency build chart/
- }
-
- function ensure_namespace() {
- kubectl get namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
- }
-
- function check_kube_domain() {
- if [[ -z "$KUBE_INGRESS_BASE_DOMAIN" ]]; then
- echo "In order to deploy or use Review Apps,"
- echo "KUBE_INGRESS_BASE_DOMAIN variables must be set"
- echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings"
- echo "or by defining a variable at group or project level."
- echo "You can also manually add it in .gitlab-ci.yml"
- false
- else
- true
- fi
- }
-
- function initialize_tiller() {
- echo "Checking Tiller..."
-
- export HELM_HOST="localhost:44134"
- tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
- echo "Tiller is listening on ${HELM_HOST}"
-
- if ! helm version --debug; then
- echo "Failed to init Tiller."
- return 1
- fi
- echo ""
- }
-
- function create_secret() {
- echo "Create secret..."
- if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then
- return
- fi
-
- kubectl create secret -n "$KUBE_NAMESPACE" \
- docker-registry gitlab-registry \
- --docker-server="$CI_REGISTRY" \
- --docker-username="${CI_DEPLOY_USER:-$CI_REGISTRY_USER}" \
- --docker-password="${CI_DEPLOY_PASSWORD:-$CI_REGISTRY_PASSWORD}" \
- --docker-email="$GITLAB_USER_EMAIL" \
- -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
- }
-
- function persist_environment_url() {
- echo $CI_ENVIRONMENT_URL > environment_url.txt
- }
-
- function delete() {
- track="${1-stable}"
- name=$(deploy_name "$track")
-
- if [[ -n "$(helm ls -q "^$name$")" ]]; then
- helm delete --purge "$name"
- fi
-
- secret_name=$(application_secret_name "$track")
- kubectl delete secret --ignore-not-found -n "$KUBE_NAMESPACE" "$secret_name"
- }
-
-before_script:
- - *deploy_helpers
diff --git a/lib/gitlab/database/subquery.rb b/lib/gitlab/database/subquery.rb
index 10971d2b274..2a6f39c6a27 100644
--- a/lib/gitlab/database/subquery.rb
+++ b/lib/gitlab/database/subquery.rb
@@ -6,11 +6,7 @@ module Gitlab
class << self
def self_join(relation)
t = relation.arel_table
- # Work around a bug in Rails 5, where LIMIT causes trouble
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/51729
- r = relation.limit(nil).arel
- r.take(relation.limit_value) if relation.limit_value
- t2 = r.as('t2')
+ t2 = relation.arel.as('t2')
relation.unscoped.joins(t.join(t2).on(t[:id].eq(t2[:id])).join_sources.first)
end
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 890228e5e78..615c0ec374c 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -166,7 +166,7 @@ module Gitlab
[model, times.count, times.sum]
end
- summarised_load_times.sort_by(&:last).reverse.each do |(model, query_count, time)|
+ summarised_load_times.sort_by(&:last).reverse_each do |(model, query_count, time)|
logger.info("#{model} total (#{query_count}): #{time.round(2)}ms")
end
end
diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb
index b96590af08e..682edfc4259 100644
--- a/lib/gitlab/push_options.rb
+++ b/lib/gitlab/push_options.rb
@@ -6,9 +6,11 @@ module Gitlab
merge_request: {
keys: [
:create,
+ :description,
:merge_when_pipeline_succeeds,
:remove_source_branch,
- :target
+ :target,
+ :title
]
},
ci: {
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index e43147a3f37..21614ea003e 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -94,6 +94,12 @@ module Gitlab
}mx
end
+ # Based on Jira's project key format
+ # https://confluence.atlassian.com/adminjiraserver073/changing-the-project-key-format-861253229.html
+ def jira_issue_key_regex
+ @jira_issue_key_regex ||= /[A-Z][A-Z_0-9]+-\d+/
+ end
+
def jira_transition_id_regex
@jira_transition_id_regex ||= /\d+/
end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 671d795ec33..4b10f921ed8 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -14,9 +14,12 @@ module Gitlab
# shut Sidekiq down
MUTEX = Mutex.new
+ attr_reader :worker
+
def call(worker, job, queue)
yield
+ @worker = worker
current_rss = get_rss
return unless MAX_RSS > 0 && current_rss > MAX_RSS
@@ -25,9 +28,11 @@ module Gitlab
# Return if another thread is already waiting to shut Sidekiq down
next unless MUTEX.try_lock
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
- " exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}"
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} will stop fetching new jobs in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later"
+ warn("Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
+ " exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}")
+
+ warn("Sidekiq worker PID-#{pid} will stop fetching new jobs"\
+ " in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later")
# Wait `GRACE_TIME` to give the memory intensive job time to finish.
# Then, tell Sidekiq to stop fetching new jobs.
@@ -59,24 +64,28 @@ module Gitlab
def wait_and_signal_pgroup(time, signal, explanation)
return wait_and_signal(time, signal, explanation) unless Process.getpgrp == pid
- Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})"
+ warn("waiting #{time} seconds before sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})", signal: signal)
sleep(time)
- Sidekiq.logger.warn "sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})"
+ warn("sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})", signal: signal)
Process.kill(signal, 0)
end
def wait_and_signal(time, signal, explanation)
- Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ warn("waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})", signal: signal)
sleep(time)
- Sidekiq.logger.warn "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ warn("sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})", signal: signal)
Process.kill(signal, pid)
end
def pid
Process.pid
end
+
+ def warn(message, signal: nil)
+ Sidekiq.logger.warn(class: worker.class, pid: pid, signal: signal, message: message)
+ end
end
end
end
diff --git a/lib/gitlab/submodule_links.rb b/lib/gitlab/submodule_links.rb
index a6c0369d864..18fd604a3b0 100644
--- a/lib/gitlab/submodule_links.rb
+++ b/lib/gitlab/submodule_links.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def for(submodule, sha)
- submodule_url = submodule_url_for(sha)[submodule.path]
+ submodule_url = submodule_url_for(sha, submodule.path)
SubmoduleHelper.submodule_links_for_url(submodule.id, submodule_url, repository)
end
@@ -17,10 +17,15 @@ module Gitlab
attr_reader :repository
- def submodule_url_for(sha)
- strong_memoize(:"submodule_links_for_#{sha}") do
+ def submodule_urls_for(sha)
+ strong_memoize(:"submodule_urls_for_#{sha}") do
repository.submodule_urls_for(sha)
end
end
+
+ def submodule_url_for(sha, path)
+ urls = submodule_urls_for(sha)
+ urls && urls[path]
+ end
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index 31c9e18c996..c66ce0434a4 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -22,7 +22,7 @@ module Gitlab
end
def force_utf8(str)
- str.force_encoding(Encoding::UTF_8)
+ str.dup.force_encoding(Encoding::UTF_8)
end
def ensure_utf8_size(str, bytes:)
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 5f2ed5274e4..d054959e05e 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -33,7 +33,7 @@ namespace :gitlab do
# Removes the entry from the array
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
- connection.execute('TRUNCATE schema_migrations') if connection.data_source_exists? 'schema_migrations'
+ connection.execute('TRUNCATE schema_migrations') if connection.table_exists? 'schema_migrations'
# Drop tables with cascade to avoid dependent table errors
# PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index cbab027d4a3..114d245b688 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1239,6 +1239,9 @@ msgstr ""
msgid "Are you sure you want to delete this %{typeOfComment}?"
msgstr ""
+msgid "Are you sure you want to delete this board?"
+msgstr ""
+
msgid "Are you sure you want to delete this device? This action cannot be undone."
msgstr ""
@@ -1636,6 +1639,12 @@ msgstr ""
msgid "Blog"
msgstr ""
+msgid "Board name"
+msgstr ""
+
+msgid "Board scope"
+msgstr ""
+
msgid "BoardBlankState|Add default lists"
msgstr ""
@@ -2374,6 +2383,9 @@ msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr ""
+msgid "ClusterIntegration|Choose a prefix to be used for your namespaces. Defaults to your project path."
+msgstr ""
+
msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
msgstr ""
@@ -2611,6 +2623,9 @@ msgstr ""
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
+msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
+msgstr ""
+
msgid "ClusterIntegration|Prometheus"
msgstr ""
@@ -2719,6 +2734,9 @@ msgstr ""
msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
+msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, pod logs, and Web terminals."
+msgstr ""
+
msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
msgstr ""
@@ -3228,6 +3246,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create board"
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -3267,6 +3288,9 @@ msgstr ""
msgid "Create milestone"
msgstr ""
+msgid "Create new board"
+msgstr ""
+
msgid "Create new branch"
msgstr ""
@@ -3495,6 +3519,9 @@ msgstr ""
msgid "Delete Snippet"
msgstr ""
+msgid "Delete board"
+msgstr ""
+
msgid "Delete comment"
msgstr ""
@@ -3887,6 +3914,9 @@ msgstr ""
msgid "Edit application"
msgstr ""
+msgid "Edit board"
+msgstr ""
+
msgid "Edit comment"
msgstr ""
@@ -3911,6 +3941,12 @@ msgstr ""
msgid "Edit public deploy key"
msgstr ""
+msgid "Editor|%{mdLinkStart}Markdown is supported%{mdLinkEnd}"
+msgstr ""
+
+msgid "Editor|%{mdLinkStart}Markdown%{mdLinkEnd} and %{actionsLinkStart}quick actions%{actionsLinkEnd} are supported"
+msgstr ""
+
msgid "Email"
msgstr ""
@@ -4067,6 +4103,9 @@ msgstr ""
msgid "Enter at least three characters to search"
msgstr ""
+msgid "Enter board name"
+msgstr ""
+
msgid "Enter in your Bitbucket Server URL and personal access token below"
msgstr ""
@@ -4583,6 +4622,9 @@ msgstr ""
msgid "Failed to create resources"
msgstr ""
+msgid "Failed to delete board. Please try again."
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
@@ -5382,6 +5424,9 @@ msgstr ""
msgid "IDE|Review"
msgstr ""
+msgid "IDE|Successful commit"
+msgstr ""
+
msgid "IP Address"
msgstr ""
@@ -6343,18 +6388,12 @@ msgstr ""
msgid "Mark to do as done"
msgstr ""
-msgid "Markdown"
-msgstr ""
-
msgid "Markdown Help"
msgstr ""
msgid "Markdown enabled"
msgstr ""
-msgid "Markdown is supported"
-msgstr ""
-
msgid "Marks this issue as a duplicate of %{duplicate_reference}."
msgstr ""
@@ -7409,6 +7448,12 @@ msgstr ""
msgid "PerformanceBar|Gitaly calls"
msgstr ""
+msgid "PerformanceBar|Redis calls"
+msgstr ""
+
+msgid "PerformanceBar|Rugged calls"
+msgstr ""
+
msgid "PerformanceBar|SQL queries"
msgstr ""
@@ -11588,6 +11633,9 @@ msgstr ""
msgid "Unable to resolve"
msgstr ""
+msgid "Unable to save your changes. Please try again."
+msgstr ""
+
msgid "Unable to schedule a pipeline to run immediately"
msgstr ""
@@ -13266,9 +13314,6 @@ msgstr ""
msgid "project avatar"
msgstr ""
-msgid "quick actions"
-msgstr ""
-
msgid "register"
msgstr ""
diff --git a/package.json b/package.json
index 4264064c93d..773918524f9 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.67.0",
- "@gitlab/ui": "^5.7.1",
+ "@gitlab/ui": "^5.9.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -99,7 +99,7 @@
"mermaid": "^8.2.3",
"monaco-editor": "^0.15.6",
"monaco-editor-webpack-plugin": "^1.7.0",
- "mousetrap": "^1.4.6",
+ "mousetrap": "1.4.6",
"pdfjs-dist": "^2.0.943",
"pikaday": "^1.6.1",
"popper.js": "^1.14.7",
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index b59540d0377..507dccb52d0 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -70,7 +70,10 @@ module QA
end
def select_labels_and_refresh(labels)
- click_element(:edit_link_labels)
+ Support::Retrier.retry_until do
+ click_element(:edit_link_labels)
+ has_element?(:dropdown_menu_labels, text: labels.first)
+ end
labels.each do |label|
within_element(:dropdown_menu_labels, text: label) do
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 0918445d119..64aab9be056 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -59,7 +59,7 @@ module QA
end
def set_visibility(visibility)
- choose visibility
+ choose visibility.capitalize
end
def click_github_link
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index c0a6004fe27..93a82094776 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -8,6 +8,7 @@ module QA
include Events::Project
attr_writer :initialize_with_readme
+ attr_writer :visibility
attribute :id
attribute :name
@@ -44,6 +45,7 @@ module QA
@standalone = false
@description = 'My awesome project'
@initialize_with_readme = false
+ @visibility = 'public'
end
def name=(raw_name)
@@ -60,7 +62,7 @@ module QA
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
- page.set_visibility('Public')
+ page.set_visibility(@visibility)
page.enable_initialize_with_readme if @initialize_with_readme
page.create_new_project
end
@@ -88,7 +90,7 @@ module QA
post_body = {
name: name,
description: description,
- visibility: 'public',
+ visibility: @visibility,
initialize_with_readme: @initialize_with_readme
}
diff --git a/scripts/insert-rspec-profiling-data b/scripts/insert-rspec-profiling-data
index b34379764e0..88c9d8c12b1 100755
--- a/scripts/insert-rspec-profiling-data
+++ b/scripts/insert-rspec-profiling-data
@@ -14,10 +14,6 @@ module RspecProfiling
Result.establish_connection(results_url)
end
- def prepared?
- connection.data_source_exists?(table)
- end
-
def results_url
ENV['RSPEC_PROFILING_POSTGRES_URL']
end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index a1f6fb6bc0f..4f617b15a6e 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -69,7 +69,6 @@ function get_pod() {
break
fi
- printf "."
let "elapsed_seconds+=interval"
sleep ${interval}
done
@@ -360,7 +359,6 @@ function wait_for_review_app_to_be_accessible() {
break
fi
- printf "."
let "elapsed_seconds+=interval"
sleep ${interval}
done
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 8fca9e680dd..fcab4d73dca 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -77,6 +77,53 @@ describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(404)
end
end
+
+ describe 'caching' do
+ it 'sets appropriate caching headers' do
+ get_archive
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['ETag']).to be_present
+ expect(response.header['Cache-Control']).to include('max-age=60, private')
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'sets appropriate caching headers' do
+ get_archive
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['ETag']).to be_present
+ expect(response.header['Cache-Control']).to include('max-age=60, public')
+ end
+ end
+
+ context 'when ref is a commit SHA' do
+ it 'max-age is set to 3600 in Cache-Control header' do
+ get_archive('ddd0f15ae83993f5cb66a927a28673882e99100b')
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Cache-Control']).to include('max-age=3600')
+ end
+ end
+
+ context 'when If-None-Modified header is set' do
+ it 'returns a 304 status' do
+ # Get the archive cached first
+ get_archive
+
+ request.headers['If-None-Match'] = response.headers['ETag']
+ get_archive
+
+ expect(response).to have_gitlab_http_status(304)
+ end
+ end
+
+ def get_archive(id = 'feature')
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: id }, format: 'zip'
+ end
+ end
end
end
end
diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb
new file mode 100644
index 00000000000..aa53ac50c78
--- /dev/null
+++ b/spec/features/markdown/metrics_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching do
+ include PrometheusHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:prometheus_project) }
+ let(:environment) { create(:environment, project: project) }
+ let(:issue) { create(:issue, project: project, description: description) }
+ let(:description) { "See [metrics dashboard](#{metrics_url}) for info." }
+ let(:metrics_url) { metrics_project_environment_url(project, environment) }
+
+ before do
+ configure_host
+ import_common_metrics
+ stub_any_prometheus_request_with_response
+
+ project.add_developer(user)
+
+ sign_in(user)
+ end
+
+ after do
+ restore_host
+ end
+
+ context 'with deployments and related deployable present' do
+ it 'shows embedded metrics' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('div.prometheus-graph')
+ expect(page).to have_text('Memory Usage (Total)')
+ expect(page).to have_text('Core Usage (Total)')
+ end
+ end
+
+ def import_common_metrics
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
+ end
+
+ def configure_host
+ @original_default_host = default_url_options[:host]
+ @original_gitlab_url = Gitlab.config.gitlab[:url]
+
+ # Ensure we create a metrics url with the right host.
+ # Configure host for route helpers in specs (also updates root_url):
+ default_url_options[:host] = Capybara.server_host
+
+ # Ensure we identify urls with the appropriate host.
+ # Configure host to include port in app:
+ Gitlab.config.gitlab[:url] = root_url.chomp('/')
+ end
+
+ def restore_host
+ default_url_options[:host] = @original_default_host
+ Gitlab.config.gitlab[:url] = @original_gitlab_url
+ end
+end
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index 86331728f88..54705cd51b0 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -34,6 +34,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
before do
stub_omniauth_config(provider)
+ expect(ActiveSession).to receive(:cleanup).with(user).at_least(:once).and_call_original
end
context 'when two-factor authentication is disabled' do
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index fc74a370e72..818939c1b96 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -13,7 +13,7 @@ describe 'User browses commits' do
it 'renders commit' do
visit project_commit_path(project, sample_commit.id)
- expect(page).to have_content(sample_commit.message.gsub!(/\s+/, ' '))
+ expect(page).to have_content(sample_commit.message.gsub(/\s+/, ' '))
.and have_content("Showing #{sample_commit.files_changed_count} changed files")
.and have_content('Side-by-side')
end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index aac095bfa6b..80741ace5d6 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -132,9 +132,15 @@ describe "User creates wiki page" do
fill_in(:wiki_content, with: ascii_content)
- page.within(".wiki-form") do
- click_button("Create page")
- end
+ # This is the dumbest bug in the world:
+ # When the #wiki_content textarea is filled in, JS captures the `Enter` keydown event in order to do
+ # auto-indentation and manually inserts a newline. However, for whatever reason, when you try to click on the
+ # submit button in Capybara, it will not trigger the `click` event if a \n or \r character has been manually
+ # added to the textarea. It will, however, trigger ALL OTHER EVENTS, including `mouseover`/down/up, focus, and
+ # blur. Just not `click`. But only when you manually insert \n or \r - if you manually insert any other sequence
+ # then `click` is fired normally. And it's only Capybara. Browsers and JSDOM don't have this issue.
+ # So that's why the next line performs the click via JS.
+ page.execute_script("document.querySelector('.qa-create-page-button').click()")
page.within ".md" do
expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4")
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index f380bc122a7..65dbae1c674 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -126,7 +126,7 @@ describe "Private Project Access" do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { project_blob_path(project, File.join(commit.id, '.gitignore'))}
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index efba303033b..1ea88010c89 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -132,7 +132,6 @@ describe 'Login' do
it 'does not show a "You are already signed in." error message' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(user.current_otp)
@@ -144,7 +143,6 @@ describe 'Login' do
it 'allows login with valid code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(user.current_otp)
@@ -170,7 +168,6 @@ describe 'Login' do
it 'allows login with invalid code, then valid code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code('foo')
@@ -179,6 +176,15 @@ describe 'Login' do
enter_code(user.current_otp)
expect(current_path).to eq root_path
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ enter_code(user.current_otp)
+ end
end
context 'using backup code' do
@@ -195,7 +201,6 @@ describe 'Login' do
it 'allows login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(codes.sample)
@@ -206,7 +211,6 @@ describe 'Login' do
it 'invalidates the used code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
expect { enter_code(codes.sample) }
@@ -216,7 +220,6 @@ describe 'Login' do
it 'invalidates backup codes twice in a row' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter).twice
- .and increment(:user_session_override_counter).twice
.and increment(:user_two_factor_authenticated_counter).twice
.and increment(:user_session_destroyed_counter)
@@ -230,6 +233,15 @@ describe 'Login' do
expect { enter_code(codes.sample) }
.to change { user.reload.otp_backup_codes.size }.by(-1)
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ enter_code(codes.sample)
+ end
end
context 'with invalid code' do
@@ -274,7 +286,7 @@ describe 'Login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
sign_in_using_saml!
@@ -287,8 +299,8 @@ describe 'Login' do
it 'shows 2FA prompt after OAuth login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
sign_in_using_saml!
@@ -329,6 +341,14 @@ describe 'Login' do
expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ gitlab_sign_in(user)
+ end
end
context 'with invalid username and password' do
@@ -649,7 +669,6 @@ describe 'Login' do
it 'asks the user to accept the terms' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
visit new_user_session_path
@@ -708,7 +727,6 @@ describe 'Login' do
it 'asks the user to accept the terms before setting an email' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
gitlab_sign_in_via('saml', user, 'my-uid')
diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb
index 6a9b281fb4c..5768f42c888 100644
--- a/spec/features/users/user_browses_projects_on_user_page_spec.rb
+++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb
@@ -53,6 +53,19 @@ describe 'Users > User browses projects on user page', :js do
expect(page).to have_content(project2.name)
end
+ it 'does not have incorrectly interpolated message', :js do
+ project = create(:project, namespace: user.namespace, updated_at: 2.minutes.since)
+
+ sign_in(user)
+ visit user_path(user)
+ click_nav_link('Personal projects')
+
+ wait_for_requests
+
+ expect(page).to have_content(project.name)
+ expect(page).not_to have_content("_('Updated')")
+ end
+
context 'when not signed in' do
it 'renders user public project' do
visit user_path(user)
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
index 1ee1205e29a..5d779a323c2 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
@@ -1,6 +1,10 @@
{
"type": "object",
- "required": ["dashboard", "priority", "panel_groups"],
+ "required": [
+ "dashboard",
+ "priority",
+ "panel_groups"
+ ],
"properties": {
"dashboard": { "type": "string" },
"priority": { "type": "number" },
diff --git a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
index 1e62d020026..8fb66f6652b 100644
--- a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
+++ b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
@@ -7,7 +7,7 @@
"message": "Vulnerabilities in libxml2 in nokogiri",
"description": " The version of libxml2 packaged with Nokogiri contains several vulnerabilities.\r\n Nokogiri has mitigated these issues by upgrading to libxml 2.9.5.\r\n\r\n It was discovered that a type confusion error existed in libxml2. An\r\n attacker could use this to specially construct XML data that\r\n could cause a denial of service or possibly execute arbitrary\r\n code. (CVE-2017-0663)\r\n\r\n It was discovered that libxml2 did not properly validate parsed entity\r\n references. An attacker could use this to specially construct XML\r\n data that could expose sensitive information. (CVE-2017-7375)\r\n\r\n It was discovered that a buffer overflow existed in libxml2 when\r\n handling HTTP redirects. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-7376)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overflow in\r\n libxml2 when handling elements. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-9047)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overread\r\n in libxml2 when handling elements. An attacker could use this\r\n to specially construct XML data that could cause a denial of\r\n service. (CVE-2017-9048)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered multiple buffer overreads\r\n in libxml2 when handling parameter-entity references. An attacker\r\n could use these to specially construct XML data that could cause a\r\n denial of service. (CVE-2017-9049, CVE-2017-9050)",
"cve": "rails/Gemfile.lock:nokogiri:gemnasium:06565b64-486d-4326-b906-890d9915804d",
- "severity": "Unknown",
+ "severity": "High",
"solution": "Upgrade to latest version.",
"scanner": {
"id": "gemnasium",
@@ -48,7 +48,7 @@
"message": "Infinite recursion in parameter entities in nokogiri",
"description": "libxml2 incorrectly handles certain parameter entities. An attacker can leverage this with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.",
"cve": "rails/Gemfile.lock:nokogiri:gemnasium:6a0d56f6-2441-492a-9b14-edb95ac31919",
- "severity": "Unknown",
+ "severity": "High",
"solution": "Upgrade to latest version.",
"scanner": {
"id": "gemnasium",
diff --git a/spec/frontend/helpers/indent_helper_spec.js b/spec/frontend/helpers/indent_helper_spec.js
new file mode 100644
index 00000000000..fca12f0d1ef
--- /dev/null
+++ b/spec/frontend/helpers/indent_helper_spec.js
@@ -0,0 +1,371 @@
+import IndentHelper from '~/helpers/indent_helper';
+
+function createMockTextarea() {
+ const el = document.createElement('textarea');
+ el.setCursor = pos => el.setSelectionRange(pos, pos);
+ el.setCursorToEnd = () => el.setCursor(el.value.length);
+ el.selection = () => [el.selectionStart, el.selectionEnd];
+ el.cursor = () => {
+ const [start, end] = el.selection();
+ return start === end ? start : undefined;
+ };
+ return el;
+}
+
+describe('indent_helper', () => {
+ let element;
+ let ih;
+
+ beforeEach(() => {
+ element = createMockTextarea();
+ ih = new IndentHelper(element);
+ });
+
+ describe('indents', () => {
+ describe('a single line', () => {
+ it('when on an empty line; and cursor follows', () => {
+ element.value = '';
+ ih.indent();
+ expect(element.value).toBe(' ');
+ expect(element.cursor()).toBe(4);
+ ih.indent();
+ expect(element.value).toBe(' ');
+ expect(element.cursor()).toBe(8);
+ });
+
+ it('when at the start of a line; and cursor stays at start', () => {
+ element.value = 'foobar';
+ element.setCursor(0);
+ ih.indent();
+ expect(element.value).toBe(' foobar');
+ expect(element.cursor()).toBe(4);
+ });
+
+ it('when the cursor is in the middle; and cursor follows', () => {
+ element.value = 'foobar';
+ element.setCursor(3);
+ ih.indent();
+ expect(element.value).toBe(' foobar');
+ expect(element.cursor()).toBe(7);
+ });
+ });
+
+ describe('several lines', () => {
+ it('when everything is selected; and everything remains selected', () => {
+ element.value = 'foo\nbar\nbaz';
+ element.setSelectionRange(0, 11);
+ ih.indent();
+ expect(element.value).toBe(' foo\n bar\n baz');
+ expect(element.selection()).toEqual([0, 23]);
+ });
+
+ it('when all lines are partially selected; and the selection adapts', () => {
+ element.value = 'foo\nbar\nbaz';
+ element.setSelectionRange(2, 9);
+ ih.indent();
+ expect(element.value).toBe(' foo\n bar\n baz');
+ expect(element.selection()).toEqual([6, 21]);
+ });
+
+ it('when some lines are entirely selected; and entire lines remain selected', () => {
+ element.value = 'foo\nbar\nbaz';
+ element.setSelectionRange(4, 11);
+ ih.indent();
+ expect(element.value).toBe('foo\n bar\n baz');
+ expect(element.selection()).toEqual([4, 19]);
+ });
+
+ it('when some lines are partially selected; and the selection adapts', () => {
+ element.value = 'foo\nbar\nbaz';
+ element.setSelectionRange(5, 9);
+ ih.indent();
+ expect(element.value).toBe('foo\n bar\n baz');
+ expect(element.selection()).toEqual([5 + 4, 9 + 2 * 4]);
+ });
+
+ it('having different indentation when some lines are entirely selected; and entire lines remain selected', () => {
+ element.value = ' foo\nbar\n baz';
+ element.setSelectionRange(8, 19);
+ ih.indent();
+ expect(element.value).toBe(' foo\n bar\n baz');
+ expect(element.selection()).toEqual([8, 27]);
+ });
+
+ it('having different indentation when some lines are partially selected; and the selection adapts', () => {
+ element.value = ' foo\nbar\n baz';
+ element.setSelectionRange(9, 14);
+ ih.indent();
+ expect(element.value).toBe(' foo\n bar\n baz');
+ expect(element.selection()).toEqual([13, 22]);
+ });
+ });
+ });
+
+ describe('unindents', () => {
+ describe('a single line', () => {
+ it('but does nothing if there is not indent', () => {
+ element.value = 'foobar';
+ element.setCursor(2);
+ ih.unindent();
+ expect(element.value).toBe('foobar');
+ expect(element.cursor()).toBe(2);
+ });
+
+ it('but does nothing if there is a partial indent', () => {
+ element.value = ' foobar';
+ element.setCursor(1);
+ ih.unindent();
+ expect(element.value).toBe(' foobar');
+ expect(element.cursor()).toBe(1);
+ });
+
+ it('when the cursor is in the line text; cursor follows', () => {
+ element.value = ' foobar';
+ element.setCursor(6);
+ ih.unindent();
+ expect(element.value).toBe('foobar');
+ expect(element.cursor()).toBe(2);
+ });
+
+ it('when the cursor is in the indent; and cursor goes to start', () => {
+ element.value = ' foobar';
+ element.setCursor(2);
+ ih.unindent();
+ expect(element.value).toBe('foobar');
+ expect(element.cursor()).toBe(0);
+ });
+
+ it('when the cursor is at line start; and cursor stays at start', () => {
+ element.value = ' foobar';
+ element.setCursor(0);
+ ih.unindent();
+ expect(element.value).toBe('foobar');
+ expect(element.cursor()).toBe(0);
+ });
+
+ it('when a selection includes part of the indent and text', () => {
+ element.value = ' foobar';
+ element.setSelectionRange(2, 8);
+ ih.unindent();
+ expect(element.value).toBe('foobar');
+ expect(element.selection()).toEqual([0, 4]);
+ });
+
+ it('when a selection includes part of the indent only', () => {
+ element.value = ' foobar';
+ element.setSelectionRange(0, 4);
+ ih.unindent();
+ expect(element.value).toBe('foobar');
+ expect(element.cursor()).toBe(0);
+
+ element.value = ' foobar';
+ element.setSelectionRange(1, 3);
+ ih.unindent();
+ expect(element.value).toBe('foobar');
+ expect(element.cursor()).toBe(0);
+ });
+ });
+
+ describe('several lines', () => {
+ it('when everything is selected', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setSelectionRange(0, 27);
+ ih.unindent();
+ expect(element.value).toBe('foo\n bar\nbaz');
+ expect(element.selection()).toEqual([0, 15]);
+ });
+
+ it('when all lines are partially selected', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setSelectionRange(5, 26);
+ ih.unindent();
+ expect(element.value).toBe('foo\n bar\nbaz');
+ expect(element.selection()).toEqual([1, 14]);
+ });
+
+ it('when all lines are entirely selected', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setSelectionRange(8, 27);
+ ih.unindent();
+ expect(element.value).toBe(' foo\n bar\nbaz');
+ expect(element.selection()).toEqual([8, 19]);
+ });
+
+ it('when some lines are entirely selected', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setSelectionRange(8, 27);
+ ih.unindent();
+ expect(element.value).toBe(' foo\n bar\nbaz');
+ expect(element.selection()).toEqual([8, 19]);
+ });
+
+ it('when some lines are partially selected', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setSelectionRange(17, 26);
+ ih.unindent();
+ expect(element.value).toBe(' foo\n bar\nbaz');
+ expect(element.selection()).toEqual([13, 18]);
+ });
+
+ it('when some lines are partially selected within their indents', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setSelectionRange(10, 22);
+ ih.unindent();
+ expect(element.value).toBe(' foo\n bar\nbaz');
+ expect(element.selection()).toEqual([8, 16]);
+ });
+ });
+ });
+
+ describe('newline', () => {
+ describe('on a single line', () => {
+ it('auto-indents the new line', () => {
+ element.value = 'foo\n bar\n baz\n qux';
+
+ element.setCursor(3);
+ ih.newline();
+ expect(element.value).toBe('foo\n\n bar\n baz\n qux');
+ expect(element.cursor()).toBe(4);
+
+ element.setCursor(9);
+ ih.newline();
+ expect(element.value).toBe('foo\n\n bar\n \n baz\n qux');
+ expect(element.cursor()).toBe(11);
+
+ element.setCursor(19);
+ ih.newline();
+ expect(element.value).toBe('foo\n\n bar\n \n baz\n \n qux');
+ expect(element.cursor()).toBe(24);
+
+ element.setCursor(36);
+ ih.newline();
+ expect(element.value).toBe('foo\n\n bar\n \n baz\n \n qux\n ');
+ expect(element.cursor()).toBe(45);
+ });
+
+ it('splits a line and auto-indents', () => {
+ element.value = ' foobar';
+ element.setCursor(7);
+ ih.newline();
+ expect(element.value).toBe(' foo\n bar');
+ expect(element.cursor()).toBe(12);
+ });
+
+ it('replaces selection with an indented newline', () => {
+ element.value = ' foobarbaz';
+ element.setSelectionRange(7, 10);
+ ih.newline();
+ expect(element.value).toBe(' foo\n baz');
+ expect(element.cursor()).toBe(12);
+ });
+ });
+
+ it('on several lines.replaces selection with indented newline', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setSelectionRange(4, 17);
+ ih.newline();
+ expect(element.value).toBe(' fo\n az');
+ expect(element.cursor()).toBe(7);
+ });
+ });
+
+ describe('backspace', () => {
+ let event;
+
+ // This suite tests only the special indent-removing behaviour of the
+ // backspace() method, since non-special cases are handled natively as a
+ // backspace keypress.
+
+ beforeEach(() => {
+ event = { preventDefault: jest.fn() };
+ });
+
+ describe('on a single line', () => {
+ it('does nothing special if in the line text', () => {
+ element.value = ' foobar';
+ element.setCursor(7);
+ ih.backspace(event);
+ expect(event.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('does nothing special if after a non-leading indent', () => {
+ element.value = ' foo bar';
+ element.setCursor(11);
+ ih.backspace(event);
+ expect(event.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('deletes one leading indent', () => {
+ element.value = ' foo';
+ element.setCursor(8);
+ ih.backspace(event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ expect(element.value).toBe(' foo');
+ expect(element.cursor()).toBe(4);
+ });
+
+ it('does nothing if cursor is inside the leading indent', () => {
+ element.value = ' foo';
+ element.setCursor(4);
+ ih.backspace(event);
+ expect(event.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('does nothing if cursor is at the start of the line', () => {
+ element.value = ' foo';
+ element.setCursor(0);
+ ih.backspace(event);
+ expect(event.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('deletes one partial indent', () => {
+ element.value = ' foo';
+ element.setCursor(6);
+ ih.backspace(event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ expect(element.value).toBe(' foo');
+ expect(element.cursor()).toBe(4);
+ });
+
+ it('deletes indents sequentially', () => {
+ element.value = ' foo';
+ element.setCursor(10);
+ ih.backspace(event);
+ ih.backspace(event);
+ ih.backspace(event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ expect(element.value).toBe('foo');
+ expect(element.cursor()).toBe(0);
+ });
+ });
+
+ describe('on several lines', () => {
+ it('deletes indent only on its own line', () => {
+ element.value = ' foo\n bar\n baz';
+ element.setCursor(16);
+ ih.backspace(event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ expect(element.value).toBe(' foo\n bar\n baz');
+ expect(element.cursor()).toBe(12);
+ });
+
+ it('has no special behaviour with any range selection', () => {
+ const text = ' foo\n bar\n baz';
+ for (let start = 0; start < text.length; start += 1) {
+ for (let end = start + 1; end < text.length; end += 1) {
+ element.value = text;
+ element.setSelectionRange(start, end);
+ ih.backspace(event);
+ expect(event.preventDefault).not.toHaveBeenCalled();
+
+ // Ensure that the backspace() method doesn't change state
+ // In reality, these two statements won't hold because the browser
+ // will natively process the backspace event.
+ expect(element.value).toBe(text);
+ expect(element.selection()).toEqual([start, end]);
+ }
+ }
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
new file mode 100644
index 00000000000..e3d3b82d2f3
--- /dev/null
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -0,0 +1,180 @@
+import * as cu from '~/lib/utils/common_utils';
+
+const CMD_ENTITY = '&#8984;';
+
+// Redefine `navigator.platform` because it's unsettable by default in JSDOM.
+let platform;
+Object.defineProperty(navigator, 'platform', {
+ configurable: true,
+ get: () => platform,
+ set: val => {
+ platform = val;
+ },
+});
+
+describe('common_utils', () => {
+ describe('platform leader key helpers', () => {
+ const CTRL_EVENT = { ctrlKey: true };
+ const META_EVENT = { metaKey: true };
+ const BOTH_EVENT = { ctrlKey: true, metaKey: true };
+
+ it('should return "ctrl" if navigator.platform is unset', () => {
+ expect(cu.getPlatformLeaderKey()).toBe('ctrl');
+ expect(cu.getPlatformLeaderKeyHTML()).toBe('Ctrl');
+ expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(true);
+ expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(false);
+ expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true);
+ });
+
+ it('should return "meta" on MacOS', () => {
+ navigator.platform = 'MacIntel';
+ expect(cu.getPlatformLeaderKey()).toBe('meta');
+ expect(cu.getPlatformLeaderKeyHTML()).toBe(CMD_ENTITY);
+ expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(false);
+ expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(true);
+ expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true);
+ });
+
+ it('should return "ctrl" on Linux', () => {
+ navigator.platform = 'Linux is great';
+ expect(cu.getPlatformLeaderKey()).toBe('ctrl');
+ expect(cu.getPlatformLeaderKeyHTML()).toBe('Ctrl');
+ expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(true);
+ expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(false);
+ expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true);
+ });
+
+ it('should return "ctrl" on Windows', () => {
+ navigator.platform = 'Win32';
+ expect(cu.getPlatformLeaderKey()).toBe('ctrl');
+ expect(cu.getPlatformLeaderKeyHTML()).toBe('Ctrl');
+ expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(true);
+ expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(false);
+ expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true);
+ });
+ });
+
+ describe('keystroke', () => {
+ const CODE_BACKSPACE = 8;
+ const CODE_TAB = 9;
+ const CODE_ENTER = 13;
+ const CODE_SPACE = 32;
+ const CODE_4 = 52;
+ const CODE_F = 70;
+ const CODE_Z = 90;
+
+ // Helper function that quickly creates KeyboardEvents
+ const k = (code, modifiers = '') => ({
+ keyCode: code,
+ which: code,
+ altKey: modifiers.includes('a'),
+ ctrlKey: modifiers.includes('c'),
+ metaKey: modifiers.includes('m'),
+ shiftKey: modifiers.includes('s'),
+ });
+
+ const EV_F = k(CODE_F);
+ const EV_ALT_F = k(CODE_F, 'a');
+ const EV_CONTROL_F = k(CODE_F, 'c');
+ const EV_META_F = k(CODE_F, 'm');
+ const EV_SHIFT_F = k(CODE_F, 's');
+ const EV_CONTROL_SHIFT_F = k(CODE_F, 'cs');
+ const EV_ALL_F = k(CODE_F, 'scma');
+ const EV_ENTER = k(CODE_ENTER);
+ const EV_TAB = k(CODE_TAB);
+ const EV_SPACE = k(CODE_SPACE);
+ const EV_BACKSPACE = k(CODE_BACKSPACE);
+ const EV_4 = k(CODE_4);
+ const EV_$ = k(CODE_4, 's');
+
+ const { keystroke } = cu;
+
+ it('short-circuits with bad arguments', () => {
+ expect(keystroke()).toBe(false);
+ expect(keystroke({})).toBe(false);
+ });
+
+ it('handles keystrokes using key codes', () => {
+ // Test a letter key with modifiers
+ expect(keystroke(EV_F, CODE_F)).toBe(true);
+ expect(keystroke(EV_F, CODE_F, '')).toBe(true);
+ expect(keystroke(EV_ALT_F, CODE_F, 'a')).toBe(true);
+ expect(keystroke(EV_CONTROL_F, CODE_F, 'c')).toBe(true);
+ expect(keystroke(EV_META_F, CODE_F, 'm')).toBe(true);
+ expect(keystroke(EV_SHIFT_F, CODE_F, 's')).toBe(true);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'cs')).toBe(true);
+ expect(keystroke(EV_ALL_F, CODE_F, 'acms')).toBe(true);
+
+ // Test non-letter keys
+ expect(keystroke(EV_TAB, CODE_TAB)).toBe(true);
+ expect(keystroke(EV_ENTER, CODE_ENTER)).toBe(true);
+ expect(keystroke(EV_SPACE, CODE_SPACE)).toBe(true);
+ expect(keystroke(EV_BACKSPACE, CODE_BACKSPACE)).toBe(true);
+
+ // Test a number/symbol key
+ expect(keystroke(EV_4, CODE_4)).toBe(true);
+ expect(keystroke(EV_$, CODE_4, 's')).toBe(true);
+
+ // Test wrong input
+ expect(keystroke(EV_F, CODE_Z)).toBe(false);
+ expect(keystroke(EV_SHIFT_F, CODE_F)).toBe(false);
+ expect(keystroke(EV_SHIFT_F, CODE_F, 'c')).toBe(false);
+ });
+
+ it('is case-insensitive', () => {
+ expect(keystroke(EV_ALL_F, CODE_F, 'ACMS')).toBe(true);
+ });
+
+ it('handles bogus inputs', () => {
+ expect(keystroke(EV_F, 'not a keystroke')).toBe(false);
+ expect(keystroke(EV_F, null)).toBe(false);
+ });
+
+ it('handles exact modifier keys, in any order', () => {
+ // Test permutations of modifiers
+ expect(keystroke(EV_ALL_F, CODE_F, 'acms')).toBe(true);
+ expect(keystroke(EV_ALL_F, CODE_F, 'smca')).toBe(true);
+ expect(keystroke(EV_ALL_F, CODE_F, 'csma')).toBe(true);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'cs')).toBe(true);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'sc')).toBe(true);
+
+ // Test wrong modifiers
+ expect(keystroke(EV_ALL_F, CODE_F, 'smca')).toBe(true);
+ expect(keystroke(EV_ALL_F, CODE_F)).toBe(false);
+ expect(keystroke(EV_ALL_F, CODE_F, '')).toBe(false);
+ expect(keystroke(EV_ALL_F, CODE_F, 'c')).toBe(false);
+ expect(keystroke(EV_ALL_F, CODE_F, 'ca')).toBe(false);
+ expect(keystroke(EV_ALL_F, CODE_F, 'ms')).toBe(false);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'cs')).toBe(true);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'c')).toBe(false);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 's')).toBe(false);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'csa')).toBe(false);
+ expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'm')).toBe(false);
+ expect(keystroke(EV_SHIFT_F, CODE_F, 's')).toBe(true);
+ expect(keystroke(EV_SHIFT_F, CODE_F, 'c')).toBe(false);
+ expect(keystroke(EV_SHIFT_F, CODE_F, 'csm')).toBe(false);
+ });
+
+ it('handles the platform-dependent leader key', () => {
+ navigator.platform = 'Win32';
+ let EV_UNDO = k(CODE_Z, 'c');
+ let EV_REDO = k(CODE_Z, 'cs');
+ expect(keystroke(EV_UNDO, CODE_Z, 'l')).toBe(true);
+ expect(keystroke(EV_UNDO, CODE_Z, 'c')).toBe(true);
+ expect(keystroke(EV_UNDO, CODE_Z, 'm')).toBe(false);
+ expect(keystroke(EV_REDO, CODE_Z, 'sl')).toBe(true);
+ expect(keystroke(EV_REDO, CODE_Z, 'sc')).toBe(true);
+ expect(keystroke(EV_REDO, CODE_Z, 'sm')).toBe(false);
+
+ navigator.platform = 'MacIntel';
+ EV_UNDO = k(CODE_Z, 'm');
+ EV_REDO = k(CODE_Z, 'ms');
+ expect(keystroke(EV_UNDO, CODE_Z, 'l')).toBe(true);
+ expect(keystroke(EV_UNDO, CODE_Z, 'c')).toBe(false);
+ expect(keystroke(EV_UNDO, CODE_Z, 'm')).toBe(true);
+ expect(keystroke(EV_REDO, CODE_Z, 'sl')).toBe(true);
+ expect(keystroke(EV_REDO, CODE_Z, 'sc')).toBe(false);
+ expect(keystroke(EV_REDO, CODE_Z, 'sm')).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/undo_stack_spec.js b/spec/frontend/lib/utils/undo_stack_spec.js
new file mode 100644
index 00000000000..31ad0e77d6f
--- /dev/null
+++ b/spec/frontend/lib/utils/undo_stack_spec.js
@@ -0,0 +1,237 @@
+import UndoStack from '~/lib/utils/undo_stack';
+
+import { isEqual } from 'underscore';
+
+describe('UndoStack', () => {
+ let stack;
+
+ beforeEach(() => {
+ stack = new UndoStack();
+ });
+
+ afterEach(() => {
+ // Make sure there's not pending saves
+ const history = Array.from(stack.history);
+ jest.runAllTimers();
+ expect(stack.history).toEqual(history);
+ });
+
+ it('is blank on construction', () => {
+ expect(stack.isEmpty()).toBe(true);
+ expect(stack.history).toEqual([]);
+ expect(stack.cursor).toBe(-1);
+ expect(stack.canUndo()).toBe(false);
+ expect(stack.canRedo()).toBe(false);
+ });
+
+ it('handles simple undo/redo behaviour', () => {
+ stack.save(10);
+ stack.save(11);
+ stack.save(12);
+
+ expect(stack.history).toEqual([10, 11, 12]);
+ expect(stack.cursor).toBe(2);
+ expect(stack.current()).toBe(12);
+ expect(stack.isEmpty()).toBe(false);
+ expect(stack.canUndo()).toBe(true);
+ expect(stack.canRedo()).toBe(false);
+
+ stack.undo();
+ expect(stack.history).toEqual([10, 11, 12]);
+ expect(stack.current()).toBe(11);
+ expect(stack.canUndo()).toBe(true);
+ expect(stack.canRedo()).toBe(true);
+
+ stack.undo();
+ expect(stack.current()).toBe(10);
+ expect(stack.canUndo()).toBe(false);
+ expect(stack.canRedo()).toBe(true);
+
+ stack.redo();
+ expect(stack.current()).toBe(11);
+
+ stack.redo();
+ expect(stack.current()).toBe(12);
+ expect(stack.isEmpty()).toBe(false);
+ expect(stack.canUndo()).toBe(true);
+ expect(stack.canRedo()).toBe(false);
+
+ // Saving should clear the redo stack
+ stack.undo();
+ stack.save(13);
+ expect(stack.history).toEqual([10, 11, 13]);
+ expect(stack.current()).toBe(13);
+ });
+
+ it('clear() should clear the undo history', () => {
+ stack.save(0);
+ stack.save(1);
+ stack.save(2);
+ stack.clear();
+ expect(stack.history).toEqual([]);
+ expect(stack.current()).toBeUndefined();
+ });
+
+ it('undo and redo are no-ops if unavailable', () => {
+ stack.save(10);
+ expect(stack.canRedo()).toBe(false);
+ expect(stack.canUndo()).toBe(false);
+
+ stack.save(11);
+ expect(stack.canRedo()).toBe(false);
+ expect(stack.canUndo()).toBe(true);
+
+ expect(stack.redo()).toBeUndefined();
+ expect(stack.history).toEqual([10, 11]);
+ expect(stack.current()).toBe(11);
+ expect(stack.canRedo()).toBe(false);
+ expect(stack.canUndo()).toBe(true);
+
+ expect(stack.undo()).toBe(10);
+ expect(stack.undo()).toBeUndefined();
+ expect(stack.history).toEqual([10, 11]);
+ expect(stack.current()).toBe(10);
+ expect(stack.canRedo()).toBe(true);
+ expect(stack.canUndo()).toBe(false);
+ });
+
+ it('should not save a duplicate state', () => {
+ stack.save(10);
+ stack.save(11);
+ stack.save(11);
+ stack.save(10);
+ stack.save(10);
+
+ expect(stack.history).toEqual([10, 11, 10]);
+ });
+
+ it('uses the === operator to detect duplicates', () => {
+ stack.save(10);
+ stack.save(10);
+ expect(stack.history).toEqual([10]);
+
+ // eslint-disable-next-line eqeqeq
+ expect(2 == '2' && '2' == 2).toBe(true);
+ stack.clear();
+ stack.save(2);
+ stack.save(2);
+ stack.save('2');
+ stack.save('2');
+ stack.save(2);
+ expect(stack.history).toEqual([2, '2', 2]);
+
+ const obj = {};
+ stack.clear();
+ stack.save(obj);
+ stack.save(obj);
+ stack.save({});
+ stack.save({});
+ expect(stack.history).toEqual([{}, {}, {}]);
+ });
+
+ it('should allow custom comparators', () => {
+ stack.comparator = isEqual;
+ const obj = {};
+ stack.clear();
+ stack.save(obj);
+ stack.save(obj);
+ stack.save({});
+ stack.save({});
+ expect(stack.history).toEqual([{}]);
+ });
+
+ it('should enforce a max number of undo states', () => {
+ // Try 2000 saves. Only the last 1000 should be preserved.
+ const sequence = Array(2000)
+ .fill(0)
+ .map((el, i) => i);
+ sequence.forEach(stack.save.bind(stack));
+ expect(stack.history.length).toBe(1000);
+ expect(stack.history).toEqual(sequence.slice(1000));
+ expect(stack.current()).toBe(1999);
+ expect(stack.canUndo()).toBe(true);
+ expect(stack.canRedo()).toBe(false);
+
+ // Saving drops the oldest elements from the stack
+ stack.save('end');
+ expect(stack.history.length).toBe(1000);
+ expect(stack.current()).toBe('end');
+ expect(stack.history).toEqual([...sequence.slice(1001), 'end']);
+
+ // If states were undone but the history is full, can still add.
+ stack.undo();
+ stack.undo();
+ expect(stack.current()).toBe(1998);
+ stack.save(3000);
+ expect(stack.history.length).toBe(999);
+ // should be [1001, 1002, ..., 1998, 3000]
+ expect(stack.history).toEqual([...sequence.slice(1001, 1999), 3000]);
+
+ // Try a different max length
+ stack = new UndoStack(2);
+ stack.save(0);
+ expect(stack.history).toEqual([0]);
+ stack.save(1);
+ expect(stack.history).toEqual([0, 1]);
+ stack.save(2);
+ expect(stack.history).toEqual([1, 2]);
+ });
+
+ describe('scheduled saves', () => {
+ it('should work', () => {
+ // Schedules 1000 ms ahead by default
+ stack.save(0);
+ stack.scheduleSave(1);
+ expect(stack.history).toEqual([0]);
+ jest.advanceTimersByTime(999);
+ expect(stack.history).toEqual([0]);
+ jest.advanceTimersByTime(1);
+ expect(stack.history).toEqual([0, 1]);
+ });
+
+ it('should have an adjustable delay', () => {
+ stack.scheduleSave(2, 100);
+ jest.advanceTimersByTime(100);
+ expect(stack.history).toEqual([2]);
+ });
+
+ it('should cancel previous scheduled saves', () => {
+ stack.scheduleSave(3);
+ jest.advanceTimersByTime(100);
+ stack.scheduleSave(4);
+ jest.runAllTimers();
+ expect(stack.history).toEqual([4]);
+ });
+
+ it('should be canceled by explicit saves', () => {
+ stack.scheduleSave(5);
+ stack.save(6);
+ jest.runAllTimers();
+ expect(stack.history).toEqual([6]);
+ });
+
+ it('should be canceled by undos and redos', () => {
+ stack.save(1);
+ stack.save(2);
+ stack.scheduleSave(3);
+ stack.undo();
+ jest.runAllTimers();
+ expect(stack.history).toEqual([1, 2]);
+ expect(stack.current()).toBe(1);
+
+ stack.scheduleSave(4);
+ stack.redo();
+ jest.runAllTimers();
+ expect(stack.history).toEqual([1, 2]);
+ expect(stack.current()).toBe(2);
+ });
+
+ it('should be persisted immediately with saveNow()', () => {
+ stack.scheduleSave(7);
+ stack.scheduleSave(8);
+ stack.saveNow();
+ jest.runAllTimers();
+ expect(stack.history).toEqual([8]);
+ });
+ });
+});
diff --git a/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js b/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js
new file mode 100644
index 00000000000..7b8df03d3c3
--- /dev/null
+++ b/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js
@@ -0,0 +1,61 @@
+import initGkeNamespace from '~/projects/gke_cluster_namespace';
+
+describe('GKE cluster namespace', () => {
+ const changeEvent = new Event('change');
+ const isHidden = el => el.classList.contains('hidden');
+ const hasDisabledInput = el => el.querySelector('input').disabled;
+
+ let glManagedCheckbox;
+ let selfManaged;
+ let glManaged;
+
+ beforeEach(() => {
+ setFixtures(`
+ <input class="js-gl-managed" type="checkbox" value="1" checked />
+ <div class="js-namespace">
+ <input type="text" />
+ </div>
+ <div class="js-namespace-prefixed">
+ <input type="text" />
+ </div>
+ `);
+
+ glManagedCheckbox = document.querySelector('.js-gl-managed');
+ selfManaged = document.querySelector('.js-namespace');
+ glManaged = document.querySelector('.js-namespace-prefixed');
+
+ initGkeNamespace();
+ });
+
+ describe('GKE cluster namespace toggles', () => {
+ it('initially displays the GitLab-managed label and input', () => {
+ expect(isHidden(glManaged)).toEqual(false);
+ expect(hasDisabledInput(glManaged)).toEqual(false);
+
+ expect(isHidden(selfManaged)).toEqual(true);
+ expect(hasDisabledInput(selfManaged)).toEqual(true);
+ });
+
+ it('displays the self-managed label and input when the Gitlab-managed checkbox is unchecked', () => {
+ glManagedCheckbox.checked = false;
+ glManagedCheckbox.dispatchEvent(changeEvent);
+
+ expect(isHidden(glManaged)).toEqual(true);
+ expect(hasDisabledInput(glManaged)).toEqual(true);
+
+ expect(isHidden(selfManaged)).toEqual(false);
+ expect(hasDisabledInput(selfManaged)).toEqual(false);
+ });
+
+ it('displays the GitLab-managed label and input when the Gitlab-managed checkbox is checked', () => {
+ glManagedCheckbox.checked = true;
+ glManagedCheckbox.dispatchEvent(changeEvent);
+
+ expect(isHidden(glManaged)).toEqual(false);
+ expect(hasDisabledInput(glManaged)).toEqual(false);
+
+ expect(isHidden(selfManaged)).toEqual(true);
+ expect(hasDisabledInput(selfManaged)).toEqual(true);
+ });
+ });
+});
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index b922b910c89..ab4ef899119 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -229,6 +229,19 @@ describe SubmoduleHelper do
end
end
end
+
+ context 'unknown submodule' do
+ before do
+ # When there is no `.gitmodules` file, or if `.gitmodules` does not
+ # know the submodule at the specified path,
+ # `Repository#submodule_url_for` returns `nil`
+ stub_url(nil)
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
end
context 'as view helpers in view context' do
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
index 2f72c9ed89d..2fa807657de 100644
--- a/spec/javascripts/badges/components/badge_list_spec.js
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -60,7 +60,7 @@ describe('BadgeList component', () => {
Vue.nextTick()
.then(() => {
- const loadingIcon = vm.$el.querySelector('.spinner');
+ const loadingIcon = vm.$el.querySelector('.gl-spinner');
expect(loadingIcon).toBeVisible();
})
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
index 4e4d1ae2e99..c82a03a628a 100644
--- a/spec/javascripts/badges/components/badge_spec.js
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -15,7 +15,7 @@ describe('Badge component', () => {
const buttons = vm.$el.querySelectorAll('button');
return {
badgeImage: vm.$el.querySelector('img.project-badge'),
- loadingIcon: vm.$el.querySelector('.spinner'),
+ loadingIcon: vm.$el.querySelector('.gl-spinner'),
reloadButton: buttons[buttons.length - 1],
};
};
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 9c9b435d7fd..6774a46ed58 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -148,7 +148,7 @@ describe('Board list component', () => {
component.list.loadingMore = true;
Vue.nextTick(() => {
- expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull();
+ expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
done();
});
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 7b9b8d2b039..e7675669f7a 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -106,7 +106,7 @@ describe('Registry List', () => {
it('should render a loading spinner', done => {
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBe(null);
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null);
done();
});
});
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index a17494966a3..1f1e626ed33 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -34,7 +34,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained no changed test results out of 11 total tests',
);
@@ -61,7 +61,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
@@ -81,7 +81,7 @@ describe('Grouped Test Reports App', () => {
it('renders failed summary text + new badge', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results out of 11 total tests',
);
@@ -109,7 +109,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests',
);
@@ -137,7 +137,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 fixed test results out of 11 total tests',
);
@@ -190,7 +190,7 @@ describe('Grouped Test Reports App', () => {
});
it('renders loading summary text with loading icon', done => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
index f622f52a7b9..5aac37d28df 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -18,7 +18,7 @@ describe('MR widget status icon component', () => {
it('renders loading icon', () => {
vm = mountComponent(Component, { status: 'loading' });
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index d93badf8cd3..55a11a72551 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -38,7 +38,9 @@ describe('MRWidgetAutoMergeFailed', () => {
Vue.nextTick(() => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button .loading-container span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('button .loading-container span').classList).toContain(
+ 'gl-spinner',
+ );
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 96e512d222a..70c70eca746 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -20,7 +20,7 @@ describe('MRWidgetChecking', () => {
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
it('renders information about merging', () => {
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
index 5bea8c43da3..1f61e19fa84 100644
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/file_icon_spec.js
@@ -72,7 +72,7 @@ describe('File Icon component', () => {
const { classList } = vm.$el.querySelector('.loading-container span');
- expect(classList.contains('spinner')).toEqual(true);
+ expect(classList.contains('gl-spinner')).toEqual(true);
});
it('should add a special class and a size class', () => {
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index a9c1a67b39b..2b059e5e9f4 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -88,7 +88,7 @@ describe('Header CI Component', () => {
vm.actions[0].isLoading = true;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .spinner').getAttribute('style')).toBeFalsy();
+ expect(vm.$el.querySelector('.btn .gl-spinner').getAttribute('style')).toBeFalsy();
done();
});
});
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 3df33f48adb..ce06377bbbf 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -6,6 +6,42 @@ describe ContainerRegistry::Client do
let(:options) { { token: token } }
let(:client) { described_class.new("http://container-registry", options) }
+ shared_examples '#repository_manifest' do |manifest_type|
+ let(:manifest) do
+ {
+ "schemaVersion" => 2,
+ "config" => {
+ "mediaType" => manifest_type,
+ "digest" =>
+ "sha256:4a3ef0786dd241be6000311e1503869b320be433b9cba84cfafeb512d1720c95",
+ "size" => 6608
+ },
+ "layers" => [
+ {
+ "mediaType" => manifest_type,
+ "digest" =>
+ "sha256:83ef92b73cf4595aa7fe214ec6747228283d585f373d8f6bc08d66bebab531b7",
+ "size" => 2828661
+ }
+ ]
+ }
+ end
+
+ it 'GET /v2/:name/manifests/mytag' do
+ stub_request(:get, "http://container-registry/v2/group/test/manifests/mytag")
+ .with(headers: {
+ 'Accept' => described_class::ACCEPTED_TYPES.join(', '),
+ 'Authorization' => "bearer #{token}"
+ })
+ .to_return(status: 200, body: manifest.to_json, headers: { content_type: manifest_type })
+
+ expect(client.repository_manifest('group/test', 'mytag')).to eq(manifest)
+ end
+ end
+
+ it_behaves_like '#repository_manifest', described_class::DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
+ it_behaves_like '#repository_manifest', described_class::OCI_MANIFEST_V1_TYPE
+
describe '#blob' do
it 'GET /v2/:name/blobs/:digest' do
stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index cb4ae3be525..65090f32f66 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -9,7 +9,7 @@ describe ContainerRegistry::Tag do
end
let(:headers) do
- { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' }
+ { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') }
end
let(:tag) { described_class.new(repository, 'tag') }
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index 40001cea22e..a5436149818 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -40,7 +40,11 @@ describe Gitlab::Auth::OAuth::AuthHash do
last_name: last_name_ascii,
name: name_ascii,
nickname: nickname_ascii,
- uid: uid_ascii
+ uid: uid_ascii,
+ address: {
+ locality: 'some locality',
+ country: 'some country'
+ }
}
end
@@ -51,6 +55,7 @@ describe Gitlab::Auth::OAuth::AuthHash do
it { expect(auth_hash.username).to eql nickname_utf8 }
it { expect(auth_hash.name).to eql name_utf8 }
it { expect(auth_hash.password).not_to be_empty }
+ it { expect(auth_hash.location).to eq 'some locality, some country' }
end
context 'email not provided' do
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index 0dee683350f..0d2074eed22 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -114,7 +114,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq, :migra
it 'does not drop the temporary tracking table after processing the batch, if there are still untracked rows' do
subject.perform(1, untracked_files_for_uploads.last.id - 1)
- expect(ActiveRecord::Base.connection.data_source_exists?(:untracked_files_for_uploads)).to be_truthy
+ expect(ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads)).to be_truthy
end
it 'drops the temporary tracking table after processing the batch, if there are no untracked rows left' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index 1a5a38b5d99..b451844f06c 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -45,6 +45,9 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do
expect(subject).to receive(:sleep).with(10).ordered
expect(Process).to receive(:kill).with('SIGKILL', pid).ordered
+ expect(Sidekiq.logger)
+ .to receive(:warn).with(class: 'TestWorker', message: anything, pid: pid, signal: anything).at_least(:once)
+
run
end
diff --git a/spec/lib/gitlab/submodule_links_spec.rb b/spec/lib/gitlab/submodule_links_spec.rb
new file mode 100644
index 00000000000..a84602cd07d
--- /dev/null
+++ b/spec/lib/gitlab/submodule_links_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SubmoduleLinks do
+ let(:submodule_item) { double(id: 'hash', path: 'gitlab-ce') }
+ let(:repo) { double }
+ let(:links) { described_class.new(repo) }
+
+ describe '#for' do
+ subject { links.for(submodule_item, 'ref') }
+
+ context 'when there is no .gitmodules file' do
+ before do
+ stub_urls(nil)
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+
+ context 'when the submodule is unknown' do
+ before do
+ stub_urls({ 'path' => 'url' })
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+
+ context 'when the submodule is known' do
+ before do
+ stub_urls({ 'gitlab-ce' => 'git@gitlab.com:gitlab-org/gitlab-ce.git' })
+ end
+
+ it 'returns links' do
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ end
+ end
+ end
+
+ def stub_urls(urls)
+ allow(repo).to receive(:submodule_urls_for).and_return(urls)
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
index 41afbbb191c..4e8ae35187e 100644
--- a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
@@ -2,68 +2,13 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::WikiPageCounter, :clean_gitlab_redis_shared_state do
- shared_examples :wiki_page_event do |event|
- describe ".count(#{event})" do
- it "increments the wiki page #{event} counter by 1" do
- expect do
- described_class.count(event)
- end.to change { described_class.read(event) }.by 1
- end
- end
-
- describe ".read(#{event})" do
- event_count = 5
-
- it "returns the total number of #{event} events" do
- event_count.times do
- described_class.count(event)
- end
-
- expect(described_class.read(event)).to eq(event_count)
- end
- end
- end
-
- include_examples :wiki_page_event, :create
- include_examples :wiki_page_event, :update
- include_examples :wiki_page_event, :delete
-
- describe 'totals' do
- creations = 5
- edits = 3
- deletions = 2
-
- before do
- creations.times do
- described_class.count(:create)
- end
- edits.times do
- described_class.count(:update)
- end
- deletions.times do
- described_class.count(:delete)
- end
- end
-
- it 'can report all totals' do
- expect(described_class.totals).to include(
- wiki_pages_update: edits,
- wiki_pages_create: creations,
- wiki_pages_delete: deletions
- )
- end
- end
-
- describe 'unknown events' do
- error = described_class::UnknownEvent
-
- it 'cannot increment' do
- expect { described_class.count(:wibble) }.to raise_error error
- end
-
- it 'cannot read' do
- expect { described_class.read(:wibble) }.to raise_error error
- end
- end
+describe Gitlab::UsageDataCounters::WikiPageCounter do
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :create
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :update
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :delete
+
+ it_behaves_like 'a redis usage counter with totals', :wiki_pages,
+ create: 5,
+ update: 3,
+ delete: 2
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 56bbcc4c306..dcc4b70a382 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -640,7 +640,7 @@ describe Notify do
project.request_access(user)
project.requesters.find_by(user_id: user.id)
end
- subject { described_class.member_access_requested_email('project', project_member.id, recipient.notification_email) }
+ subject { described_class.member_access_requested_email('project', project_member.id, recipient.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
@@ -737,9 +737,9 @@ describe Notify do
describe 'project invitation accepted' do
let(:invited_user) { create(:user, name: 'invited user') }
- let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:recipient) { create(:user).tap { |u| project.add_maintainer(u) } }
let(:project_member) do
- invitee = invite_to_project(project, inviter: maintainer)
+ invitee = invite_to_project(project, inviter: recipient)
invitee.accept_invite!(invited_user)
invitee
end
@@ -747,6 +747,7 @@ describe Notify do
subject { described_class.member_invite_accepted_email('project', project_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -762,16 +763,17 @@ describe Notify do
end
describe 'project invitation declined' do
- let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:recipient) { create(:user).tap { |u| project.add_maintainer(u) } }
let(:project_member) do
- invitee = invite_to_project(project, inviter: maintainer)
+ invitee = invite_to_project(project, inviter: recipient)
invitee.decline_invite!
invitee
end
- subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) }
+ subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, recipient.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1087,9 +1089,10 @@ describe Notify do
group.request_access(user)
group.requesters.find_by(user_id: user.id)
end
- subject { described_class.member_access_requested_email('group', group_member.id, recipient.notification_email) }
+ subject { described_class.member_access_requested_email('group', group_member.id, recipient.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1111,9 +1114,11 @@ describe Notify do
group.request_access(user)
group.requesters.find_by(user_id: user.id)
end
+ let(:recipient) { user }
subject { described_class.member_access_denied_email('group', group.id, user.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1128,10 +1133,12 @@ describe Notify do
describe 'group access changed' do
let(:group_member) { create(:group_member, group: group, user: user) }
+ let(:recipient) { user }
subject { described_class.member_access_granted_email('group', group_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
diff --git a/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
new file mode 100644
index 00000000000..232f6f090c3
--- /dev/null
+++ b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190725012225_change_outbound_local_requests_whitelist_default.rb')
+
+describe ChangeOutboundLocalRequestsWhitelistDefault, :migration do
+ let(:application_settings) { table(:application_settings) }
+
+ it 'defaults to empty array' do
+ setting = application_settings.create!
+ setting_with_value = application_settings.create!(outbound_local_requests_whitelist: '{a,b}')
+
+ expect(application_settings.where(outbound_local_requests_whitelist: nil).count).to eq(1)
+
+ migrate!
+
+ expect(application_settings.where(outbound_local_requests_whitelist: nil).count).to eq(0)
+ expect(setting.reload.outbound_local_requests_whitelist).to eq([])
+ expect(setting_with_value.reload.outbound_local_requests_whitelist).to eq(%w[a b])
+ end
+end
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
deleted file mode 100644
index d0ae45f7871..00000000000
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe RelativePositioning do
- let(:project) { create(:project) }
- let(:issue) { create(:issue, project: project) }
- let(:issue1) { create(:issue, project: project) }
- let(:new_issue) { create(:issue, project: project) }
-
- describe '.move_to_end' do
- it 'moves the object to the end' do
- Issue.move_to_end([issue, issue1])
-
- expect(issue1.prev_relative_position).to eq issue.relative_position
- expect(issue.prev_relative_position).to eq nil
- expect(issue1.next_relative_position).to eq nil
- end
-
- it 'does not perform any moves if all issues have their relative_position set' do
- issue.update!(relative_position: 1)
-
- expect(issue).not_to receive(:save)
-
- Issue.move_to_end([issue])
- end
- end
-
- describe '#max_relative_position' do
- it 'returns maximum position' do
- expect(issue.max_relative_position).to eq issue1.relative_position
- end
- end
-
- describe '#prev_relative_position' do
- it 'returns previous position if there is an issue above' do
- expect(issue1.prev_relative_position).to eq issue.relative_position
- end
-
- it 'returns nil if there is no issue above' do
- expect(issue.prev_relative_position).to eq nil
- end
- end
-
- describe '#next_relative_position' do
- it 'returns next position if there is an issue below' do
- expect(issue.next_relative_position).to eq issue1.relative_position
- end
-
- it 'returns nil if there is no issue below' do
- expect(issue1.next_relative_position).to eq nil
- end
- end
-
- describe '#move_before' do
- it 'moves issue before' do
- [issue1, issue].each(&:move_to_end)
-
- issue.move_before(issue1)
-
- expect(issue.relative_position).to be < issue1.relative_position
- end
- end
-
- describe '#move_after' do
- it 'moves issue after' do
- [issue, issue1].each(&:move_to_end)
-
- issue.move_after(issue1)
-
- expect(issue.relative_position).to be > issue1.relative_position
- end
- end
-
- describe '#move_to_end' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'moves issue to the end' do
- new_issue.move_to_end
-
- expect(new_issue.relative_position).to be > issue1.relative_position
- end
- end
-
- describe '#shift_after?' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'returns true' do
- issue.update(relative_position: issue1.relative_position - 1)
-
- expect(issue.shift_after?).to be_truthy
- end
-
- it 'returns false' do
- issue.update(relative_position: issue1.relative_position - 2)
-
- expect(issue.shift_after?).to be_falsey
- end
- end
-
- describe '#shift_before?' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'returns true' do
- issue.update(relative_position: issue1.relative_position + 1)
-
- expect(issue.shift_before?).to be_truthy
- end
-
- it 'returns false' do
- issue.update(relative_position: issue1.relative_position + 2)
-
- expect(issue.shift_before?).to be_falsey
- end
- end
-
- describe '#move_between' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'positions issue between two other' do
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(new_issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issue between on top' do
- new_issue.move_between(nil, issue)
-
- expect(new_issue.relative_position).to be < issue.relative_position
- end
-
- it 'positions issue between to end' do
- new_issue.move_between(issue1, nil)
-
- expect(new_issue.relative_position).to be > issue1.relative_position
- end
-
- it 'positions issues even when after and before positions are the same' do
- issue1.update relative_position: issue.relative_position
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issues between other two if distance is 1' do
- issue1.update relative_position: issue.relative_position + 1
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issue in the middle of other two if distance is big enough' do
- issue.update relative_position: 6000
- issue1.update relative_position: 10000
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to eq(8000)
- end
-
- it 'positions issue closer to the middle if we are at the very top' do
- issue1.update relative_position: 6000
-
- new_issue.move_between(nil, issue1)
-
- expect(new_issue.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
- end
-
- it 'positions issue closer to the middle if we are at the very bottom' do
- issue.update relative_position: 6000
- issue1.update relative_position: nil
-
- new_issue.move_between(issue, nil)
-
- expect(new_issue.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
- end
-
- it 'positions issue in the middle of other two if distance is not big enough' do
- issue.update relative_position: 100
- issue1.update relative_position: 400
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to eq(250)
- end
-
- it 'positions issue in the middle of other two is there is no place' do
- issue.update relative_position: 100
- issue1.update relative_position: 101
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be_between(issue.relative_position, issue1.relative_position)
- end
-
- it 'uses rebalancing if there is no place' do
- issue.update relative_position: 100
- issue1.update relative_position: 101
- issue2 = create(:issue, relative_position: 102, project: project)
- new_issue.update relative_position: 103
-
- new_issue.move_between(issue1, issue2)
- new_issue.save!
-
- expect(new_issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
- expect(issue.reload.relative_position).not_to eq(100)
- end
-
- it 'positions issue right if we pass none-sequential parameters' do
- issue.update relative_position: 99
- issue1.update relative_position: 101
- issue2 = create(:issue, relative_position: 102, project: project)
- new_issue.update relative_position: 103
-
- new_issue.move_between(issue, issue2)
- new_issue.save!
-
- expect(new_issue.relative_position).to be(100)
- end
- end
-end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 013112d1d51..935838ce294 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -16,7 +16,7 @@ describe ContainerRepository do
host_port: 'registry.gitlab')
stub_request(:get, 'http://registry.gitlab/v2/group/test/my_image/tags/list')
- .with(headers: { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' })
+ .with(headers: { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') })
.to_return(
status: 200,
body: JSON.dump(tags: ['test_tag']),
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index d5b016dc8f6..2e7d78d77a8 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -871,4 +871,12 @@ describe Issue do
expect(issue.labels_hook_attrs).to eq([label.hook_attrs])
end
end
+
+ context "relative positioning" do
+ it_behaves_like "a class that supports relative positioning" do
+ let(:project) { create(:project) }
+ let(:factory) { :issue }
+ let(:default_params) { { project: project } }
+ end
+ end
end
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index ac40cf02c48..a27fea0c90f 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -11,6 +11,8 @@ describe MergeRequests::PushOptionsHandlerService do
let(:service) { described_class.new(project, user, changes, push_options) }
let(:source_branch) { 'fix' }
let(:target_branch) { 'feature' }
+ let(:title) { 'my title' }
+ let(:description) { 'my description' }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
@@ -73,6 +75,26 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ shared_examples_for 'a service that can set the title of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the title' do
+ service.execute
+
+ expect(last_mr.title).to eq(title)
+ end
+ end
+
+ shared_examples_for 'a service that can set the description of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the description' do
+ service.execute
+
+ expect(last_mr.description).to eq(description)
+ end
+ end
+
shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
subject(:last_mr) { MergeRequest.last }
@@ -350,6 +372,138 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ describe '`title` push option' do
+ let(:push_options) { { title: title } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, title: title } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, title: title } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
+ describe '`description` push option' do
+ let(:push_options) { { description: description } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, description: description } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, description: description } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
describe 'multiple pushed branches' do
let(:push_options) { { create: true } }
let(:changes) do
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 95a131e8c86..71c4c3ad0d7 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -574,7 +574,7 @@ describe QuickActions::InterpretService do
context 'Issue' do
it 'populates assignee_ids: [] if content contains /unassign' do
- issue.update(assignee_ids: [developer.id])
+ issue.update!(assignee_ids: [developer.id])
_, updates = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [])
@@ -583,7 +583,7 @@ describe QuickActions::InterpretService do
context 'Merge Request' do
it 'populates assignee_ids: [] if content contains /unassign' do
- merge_request.update(assignee_ids: [developer.id])
+ merge_request.update!(assignee_ids: [developer.id])
_, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_ids: [])
diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb
index 3abb5096a7a..b7aff32460d 100644
--- a/spec/support/api/boards_shared_examples.rb
+++ b/spec/support/api/boards_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'group and project boards' do |route_definition, ee = false|
let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) }
diff --git a/spec/support/api/issues_resolving_discussions_shared_examples.rb b/spec/support/api/issues_resolving_discussions_shared_examples.rb
index d2d6260dfa8..4c44f1bd103 100644
--- a/spec/support/api/issues_resolving_discussions_shared_examples.rb
+++ b/spec/support/api/issues_resolving_discussions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'creating an issue resolving discussions through the API' do
it 'creates a new project issue' do
expect(response).to have_gitlab_http_status(:created)
diff --git a/spec/support/api/members_shared_examples.rb b/spec/support/api/members_shared_examples.rb
index 8d910e52eda..603efd4fc75 100644
--- a/spec/support/api/members_shared_examples.rb
+++ b/spec/support/api/members_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'a 404 response when source is private' do
before do
source.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index 63b719be03e..d6439f77408 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'group and project milestones' do |route_definition|
let(:resource_route) { "#{route}/#{milestone.id}" }
let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
diff --git a/spec/support/api/repositories_shared_context.rb b/spec/support/api/repositories_shared_context.rb
index f1341804e56..346015106e3 100644
--- a/spec/support/api/repositories_shared_context.rb
+++ b/spec/support/api/repositories_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'disabled repository' do
before do
project.project_feature.update!(
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index 4cf34d43117..ebbd57c8115 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SchemaPath
def self.expand(schema, dir = nil)
if Gitlab.ee? && dir.nil?
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
index 683234264a8..3786a8012f9 100644
--- a/spec/support/api/scopes/read_user_shared_examples.rb
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'allows the "read_user" scope' do |api_version|
let(:version) { api_version || 'v4' }
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
index 15037222630..3bd1b145433 100644
--- a/spec/support/api/time_tracking_shared_examples.rb
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'an unauthorized API user' do
it { is_expected.to eq(403) }
end
diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb
index 476d80f3a93..27765652f28 100644
--- a/spec/support/banzai/reference_filter_shared_examples.rb
+++ b/spec/support/banzai/reference_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for reference links containing HTML.
#
# Requires a reference:
diff --git a/spec/support/batch_loader.rb b/spec/support/batch_loader.rb
index bb790e660a6..0eb8f279d29 100644
--- a/spec/support/batch_loader.rb
+++ b/spec/support/batch_loader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.after do
BatchLoader::Executor.clear_current
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 60990879fe2..8accc5c1df5 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable Style/GlobalVars
require 'capybara/rails'
require 'capybara/rspec'
diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb
index b376822d530..8da55514c78 100644
--- a/spec/support/carrierwave.rb
+++ b/spec/support/carrierwave.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
CarrierWave.root = File.expand_path('tmp/tests/public', Rails.root)
RSpec.configure do |config|
diff --git a/spec/support/chunked_io/chunked_io_helpers.rb b/spec/support/chunked_io/chunked_io_helpers.rb
index fec1f951563..278f577f3cb 100644
--- a/spec/support/chunked_io/chunked_io_helpers.rb
+++ b/spec/support/chunked_io/chunked_io_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChunkedIOHelpers
def sample_trace_raw
@sample_trace_raw ||= File.read(expand_fixture_path('trace/sample_trace'))
diff --git a/spec/support/commit_trailers_spec_helper.rb b/spec/support/commit_trailers_spec_helper.rb
index efa317fd2f9..a958e259368 100644
--- a/spec/support/commit_trailers_spec_helper.rb
+++ b/spec/support/commit_trailers_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CommitTrailersSpecHelper
extend ActiveSupport::Concern
diff --git a/spec/support/controllers/githubish_import_controller_shared_context.rb b/spec/support/controllers/githubish_import_controller_shared_context.rb
index e71994edec6..3706178ee34 100644
--- a/spec/support/controllers/githubish_import_controller_shared_context.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'a GitHub-ish import controller' do
let(:user) { create(:user) }
let(:token) { "asdasd12345" }
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index 5bb1269a19d..7ddec49bfdf 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
diff --git a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
index a0c77eecb61..d636c1cf6cd 100644
--- a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
+++ b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_context 'Ldap::OmniauthCallbacksController' do
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
index 355555d9d19..b5149a0fcb1 100644
--- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'authenticates sessionless user' do |path, format, params|
params ||= {}
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index be1c2bc3046..c57abbd96c6 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable Metrics/AbcSize
# Note: The ABC size is large here because we have a method generating test cases with
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 08622dff6d9..041ffa25535 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DbCleaner
def delete_from_all_tables!(except: nil)
DatabaseCleaner.clean_with(:deletion, cache_tables: false, except: except)
diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb
index 79dd9a3d58e..f4214d800cf 100644
--- a/spec/support/external_authorization_service_helpers.rb
+++ b/spec/support/external_authorization_service_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExternalAuthorizationServiceHelpers
def enable_external_authorization_service_check
stub_application_setting(external_authorization_service_enabled: true)
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 7c8e57702ae..5590bf0fb7e 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'thread comments' do |resource_name|
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 5d5a0a7b5d2..2f9208e6ed5 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'reportable note' do |type|
diff --git a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
index 8d0e03134d0..d4f8a87d0d8 100644
--- a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
+++ b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'creating an issue for a thread' do
it 'shows an issue with the title filled in' do
title_field = page.find_field('issue[title]')
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 0de92aedba5..c97eeba87db 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "an autodiscoverable RSS feed with current_user's feed token" do
it "has an RSS autodiscovery link tag with current_user's feed token" do
expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false)
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 01531864c1f..0f8ad2c6536 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'variable list' do
it 'shows list of variables' do
page.within('.js-ci-variable-list-section') do
diff --git a/spec/support/forgery_protection.rb b/spec/support/forgery_protection.rb
index fa87d5fa881..1d6ea013292 100644
--- a/spec/support/forgery_protection.rb
+++ b/spec/support/forgery_protection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ForgeryProtection
def with_forgery_protection
ActionController::Base.allow_forgery_protection = true
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index 2fdbddd40c2..a1328ef0d13 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GoogleApi
module CloudPlatformHelpers
def stub_google_api_validate_token
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index 4a9ce9beb78..aff0f87b6e4 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ApiHelpers
# Public: Prepend a request path with the path to the API
#
@@ -30,11 +32,12 @@ module ApiHelpers
end
if query_string
- full_path << (path.index('?') ? '&' : '?')
- full_path << query_string
- end
+ separator = path.index('?') ? '&' : '?'
- full_path
+ full_path + separator + query_string
+ else
+ full_path
+ end
end
def expect_paginated_array_response(items)
diff --git a/spec/support/helpers/assets_helpers.rb b/spec/support/helpers/assets_helpers.rb
index 09bbf451671..fa24ad9ad2a 100644
--- a/spec/support/helpers/assets_helpers.rb
+++ b/spec/support/helpers/assets_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AssetsHelpers
# In a CI environment the assets are not compiled, as there is a CI job
# `compile-assets` that compiles them in the prepare stage for all following
diff --git a/spec/support/helpers/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb
index 3f4a4243cb6..099610f087d 100644
--- a/spec/support/helpers/bare_repo_operations.rb
+++ b/spec/support/helpers/bare_repo_operations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'zlib'
class BareRepoOperations
diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb
index b85fde222ea..683ee3e4bf2 100644
--- a/spec/support/helpers/board_helpers.rb
+++ b/spec/support/helpers/board_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BoardHelpers
def click_card(card)
within card do
diff --git a/spec/support/helpers/capybara_helpers.rb b/spec/support/helpers/capybara_helpers.rb
index bcc2df44708..5abbc1e2951 100644
--- a/spec/support/helpers/capybara_helpers.rb
+++ b/spec/support/helpers/capybara_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CapybaraHelpers
# Execute a block a certain number of times before considering it a failure
#
diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb
index ef638d59d2d..e02501565a9 100644
--- a/spec/support/helpers/ci_artifact_metadata_generator.rb
+++ b/spec/support/helpers/ci_artifact_metadata_generator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# frozen_sting_literal: true
# This generates fake CI metadata .gz for testing
diff --git a/spec/support/helpers/cookie_helper.rb b/spec/support/helpers/cookie_helper.rb
index 5ff7b0b68c9..ea4be12355b 100644
--- a/spec/support/helpers/cookie_helper.rb
+++ b/spec/support/helpers/cookie_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper for setting cookies in Selenium/WebDriver
#
module CookieHelper
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 100e439ef44..575b2e779c5 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CycleAnalyticsHelpers
include GitHelpers
diff --git a/spec/support/helpers/database_connection_helpers.rb b/spec/support/helpers/database_connection_helpers.rb
index 763329499f0..10ea7b5de91 100644
--- a/spec/support/helpers/database_connection_helpers.rb
+++ b/spec/support/helpers/database_connection_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DatabaseConnectionHelpers
def run_with_new_database_connection
pool = ActiveRecord::Base.connection_pool
diff --git a/spec/support/helpers/devise_helpers.rb b/spec/support/helpers/devise_helpers.rb
index fb2a110422a..70fc6a48414 100644
--- a/spec/support/helpers/devise_helpers.rb
+++ b/spec/support/helpers/devise_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DeviseHelpers
# explicitly tells Devise which mapping to use
# this is needed when we are testing a Devise controller bypassing the router
diff --git a/spec/support/helpers/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb
index 6d53ad0b602..6099f87323f 100644
--- a/spec/support/helpers/drag_to_helper.rb
+++ b/spec/support/helpers/drag_to_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DragTo
def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000)
evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), duration: #{duration}, from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});")
diff --git a/spec/support/helpers/dropzone_helper.rb b/spec/support/helpers/dropzone_helper.rb
index fe72d320fcf..a0f261b312e 100644
--- a/spec/support/helpers/dropzone_helper.rb
+++ b/spec/support/helpers/dropzone_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DropzoneHelper
# Provides a way to perform `attach_file` for a Dropzone-based file input
#
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index ed049daba80..83ba654fab3 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EmailHelpers
def sent_to_user(user, recipients: email_recipients)
recipients.count { |to| to == user.notification_email }
diff --git a/spec/support/helpers/exclusive_lease_helpers.rb b/spec/support/helpers/exclusive_lease_helpers.rb
index 383cc7dee81..77703e20602 100644
--- a/spec/support/helpers/exclusive_lease_helpers.rb
+++ b/spec/support/helpers/exclusive_lease_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExclusiveLeaseHelpers
def stub_exclusive_lease(key = nil, uuid = 'uuid', renew: false, timeout: nil)
key ||= instance_of(String)
diff --git a/spec/support/helpers/expect_next_instance_of.rb b/spec/support/helpers/expect_next_instance_of.rb
index b95046b2b42..749d2cb2a56 100644
--- a/spec/support/helpers/expect_next_instance_of.rb
+++ b/spec/support/helpers/expect_next_instance_of.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExpectNextInstanceOf
def expect_next_instance_of(klass, *new_args)
receive_new = receive(:new)
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
index 35718ba90c5..76301fe19ff 100644
--- a/spec/support/helpers/expect_offense.rb
+++ b/spec/support/helpers/expect_offense.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rubocop/rspec/support'
# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
diff --git a/spec/support/helpers/fake_blob_helpers.rb b/spec/support/helpers/fake_blob_helpers.rb
index 801ca8b7412..ef4740638ff 100644
--- a/spec/support/helpers/fake_blob_helpers.rb
+++ b/spec/support/helpers/fake_blob_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FakeBlobHelpers
class FakeBlob
include BlobLike
diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb
index c7766df7a52..6c066b3b199 100644
--- a/spec/support/helpers/fake_migration_classes.rb
+++ b/spec/support/helpers/fake_migration_classes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2]
include Gitlab::Database::RenameReservedPathsMigration::V1
diff --git a/spec/support/helpers/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb
index 22cd8152d77..f765b277175 100644
--- a/spec/support/helpers/fake_u2f_device.rb
+++ b/spec/support/helpers/fake_u2f_device.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class FakeU2fDevice
attr_reader :name
diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb
index df88fd425c9..2a50b41cb4e 100644
--- a/spec/support/helpers/features/branches_helpers.rb
+++ b/spec/support/helpers/features/branches_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with sorting features.
#
# Usage:
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
index 8a139fafac2..a2d8d71b541 100644
--- a/spec/support/helpers/features/notes_helpers.rb
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with notes.
#
# Usage:
diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb
index 003ecb251fe..a6428bf8573 100644
--- a/spec/support/helpers/features/sorting_helpers.rb
+++ b/spec/support/helpers/features/sorting_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with sorting features.
#
# Usage:
diff --git a/spec/support/helpers/filter_spec_helper.rb b/spec/support/helpers/filter_spec_helper.rb
index 721d359c2ee..95c24d76dcd 100644
--- a/spec/support/helpers/filter_spec_helper.rb
+++ b/spec/support/helpers/filter_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper methods for Banzai filter specs
#
# Must be included into specs manually
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 34ef185ea27..39c818b1763 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FilteredSearchHelpers
def filtered_search
page.find('.filtered-search')
diff --git a/spec/support/helpers/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb
index 611d19f36a0..7b3b8ae5f7a 100644
--- a/spec/support/helpers/fixture_helpers.rb
+++ b/spec/support/helpers/fixture_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FixtureHelpers
def fixture_file(filename, dir: '')
return '' if filename.blank?
diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb
index c83860d7b51..de8bb9ac8e3 100644
--- a/spec/support/helpers/git_http_helpers.rb
+++ b/spec/support/helpers/git_http_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'workhorse_helpers'
module GitHttpHelpers
diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb
index 5df4bf24ec2..9901ce374ed 100644
--- a/spec/support/helpers/gitlab_verify_helpers.rb
+++ b/spec/support/helpers/gitlab_verify_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GitlabVerifyHelpers
def collect_ranges(args = {})
verifier = described_class.new(args.merge(batch_size: 1))
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index ec3c460cd37..ae1b859ae3f 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GraphqlHelpers
MutationDefinition = Struct.new(:query, :variables)
diff --git a/spec/support/helpers/import_spec_helper.rb b/spec/support/helpers/import_spec_helper.rb
index d4eced724fa..d8fb2ba08af 100644
--- a/spec/support/helpers/import_spec_helper.rb
+++ b/spec/support/helpers/import_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'ostruct'
# Helper methods for controller specs in the Import namespace
diff --git a/spec/support/helpers/input_helper.rb b/spec/support/helpers/input_helper.rb
index acbb42274ec..5136f8e9cb8 100644
--- a/spec/support/helpers/input_helper.rb
+++ b/spec/support/helpers/input_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# see app/assets/javascripts/test_utils/simulate_input.js
module InputHelper
diff --git a/spec/support/helpers/inspect_requests.rb b/spec/support/helpers/inspect_requests.rb
index 88ddc5c7f6c..4a1d9cb8539 100644
--- a/spec/support/helpers/inspect_requests.rb
+++ b/spec/support/helpers/inspect_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative './wait_for_requests'
module InspectRequests
diff --git a/spec/support/helpers/issue_helpers.rb b/spec/support/helpers/issue_helpers.rb
index ffd72515f37..82b954a92e2 100644
--- a/spec/support/helpers/issue_helpers.rb
+++ b/spec/support/helpers/issue_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IssueHelpers
def visit_issues(project, opts = {})
visit project_issues_path project, opts
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index e9129bd263e..7ec65318ec5 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'action_dispatch/testing/test_request'
require 'fileutils'
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 7e955f3d593..57c33c81ea3 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module JiraServiceHelper
JIRA_URL = "http://jira.example.net".freeze
JIRA_API = JIRA_URL + "/rest/api/2"
diff --git a/spec/support/helpers/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb
index 768b8c127b9..59c8eeb3692 100644
--- a/spec/support/helpers/key_generator_helper.rb
+++ b/spec/support/helpers/key_generator_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Spec
module Support
module Helpers
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 278264f3df5..538a5b8ef3c 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module KubernetesHelpers
include Gitlab::Kubernetes
diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb
index 66ca5d7f0a3..dce8a3803f5 100644
--- a/spec/support/helpers/ldap_helpers.rb
+++ b/spec/support/helpers/ldap_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module LdapHelpers
def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap))
::Gitlab::Auth::LDAP::Adapter.new(provider, ldap)
diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb
index 911eb48a8ca..d6091035b59 100644
--- a/spec/support/helpers/live_debugger.rb
+++ b/spec/support/helpers/live_debugger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'io/console'
module LiveDebugger
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 0cb99b4e087..2b508ee6f2c 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'devise_helpers'
module LoginHelpers
diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb
index 96401379cf0..eea03fb9325 100644
--- a/spec/support/helpers/markdown_feature.rb
+++ b/spec/support/helpers/markdown_feature.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This is a helper class used by the GitLab Markdown feature spec
#
# Because the feature spec only cares about the output of the Markdown, and the
diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb
index 3b49d0b3319..49beecc6d4b 100644
--- a/spec/support/helpers/merge_request_diff_helpers.rb
+++ b/spec/support/helpers/merge_request_diff_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestDiffHelpers
def click_diff_line(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
diff --git a/spec/support/helpers/merge_request_helpers.rb b/spec/support/helpers/merge_request_helpers.rb
index 772adff4626..3c359bc9353 100644
--- a/spec/support/helpers/merge_request_helpers.rb
+++ b/spec/support/helpers/merge_request_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestHelpers
def visit_merge_requests(project, opts = {})
visit project_merge_requests_path project, opts
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index 272b24f7541..2727ab7fb1e 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MigrationsHelpers
def active_record_base
ActiveRecord::Base
diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb
index 4230d315d9b..94dbd2fb1b7 100644
--- a/spec/support/helpers/mobile_helpers.rb
+++ b/spec/support/helpers/mobile_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MobileHelpers
def resize_screen_xs
resize_window(575, 768)
diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb
index 79a0aa174b1..a4322618cd3 100644
--- a/spec/support/helpers/note_interaction_helpers.rb
+++ b/spec/support/helpers/note_interaction_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NoteInteractionHelpers
def open_more_actions_dropdown(note)
note_element = find("#note_#{note.id}")
diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb
index 44c2051598c..16ecb338f6e 100644
--- a/spec/support/helpers/notification_helpers.rb
+++ b/spec/support/helpers/notification_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NotificationHelpers
extend self
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index bcb11a09b36..b2d22853e4c 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProjectForksHelper
def fork_project(project, user = nil, params = {})
Gitlab::GitalyClient.allow_n_plus_1_calls do
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index db662836013..7c03746a395 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PrometheusHelpers
def prometheus_memory_query(environment_slug)
%{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
@@ -74,6 +76,14 @@ module PrometheusHelpers
WebMock.stub_request(:any, /prometheus.example.com/)
end
+ def stub_any_prometheus_request_with_response(status: 200, headers: {}, body: nil)
+ stub_any_prometheus_request.to_return({
+ status: status,
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
+ body: body || prometheus_values_body.to_json
+ })
+ end
+
def stub_all_prometheus_requests(environment_slug, body: nil, status: 200)
stub_prometheus_request(
prometheus_query_with_time_url(prometheus_memory_query(environment_slug), Time.now.utc),
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index f77b43391dd..d936dc6de41 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveRecord
class QueryRecorder
attr_reader :log, :skip_cached, :cached
diff --git a/spec/support/helpers/quick_actions_helpers.rb b/spec/support/helpers/quick_actions_helpers.rb
index 361190aa352..cb853f5363f 100644
--- a/spec/support/helpers/quick_actions_helpers.rb
+++ b/spec/support/helpers/quick_actions_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QuickActionsHelpers
def write_note(text)
Sidekiq::Testing.fake! do
diff --git a/spec/support/helpers/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb
index 7d8d7750bf3..d8f354a69da 100644
--- a/spec/support/helpers/rake_helpers.rb
+++ b/spec/support/helpers/rake_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RakeHelpers
def run_rake_task(task_name, *args)
Rake::Task[task_name].reenable
diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb
index 528da37e8cf..aa9d3b3a199 100644
--- a/spec/support/helpers/reactive_caching_helpers.rb
+++ b/spec/support/helpers/reactive_caching_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ReactiveCachingHelpers
def reactive_cache_key(subject, *qualifiers)
([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':')
diff --git a/spec/support/helpers/redis_without_keys.rb b/spec/support/helpers/redis_without_keys.rb
index 6220167dee6..e030f1028f7 100644
--- a/spec/support/helpers/redis_without_keys.rb
+++ b/spec/support/helpers/redis_without_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Redis
ForbiddenCommand = Class.new(StandardError)
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index 9f27502aa52..f96a01d15b5 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ReferenceParserHelpers
def empty_html_link
Nokogiri::HTML.fragment('<a></a>').children[0]
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index 44d95a029af..ca4d2acbf2c 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RepoHelpers
extend self
diff --git a/spec/support/helpers/routes_helpers.rb b/spec/support/helpers/routes_helpers.rb
index c4129606418..2a7cd81cbe3 100644
--- a/spec/support/helpers/routes_helpers.rb
+++ b/spec/support/helpers/routes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RoutesHelpers
def fake_routes(&block)
@routes = @routes.dup
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index abbbb636d66..815337f8615 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SearchHelpers
def select_filter(name)
find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click
diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb
index 71f1a86b0c1..20738b45129 100644
--- a/spec/support/helpers/seed_repo.rb
+++ b/spec/support/helpers/seed_repo.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This file is generated by generate-seed-repo-rb. Do not edit this file manually.
#
# Seed repo:
diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb
index 87672c8896d..9c42c2b0d8b 100644
--- a/spec/support/helpers/select2_helper.rb
+++ b/spec/support/helpers/select2_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'wait_for_requests'
# Select2 ajax programmatic helper
diff --git a/spec/support/helpers/selection_helper.rb b/spec/support/helpers/selection_helper.rb
index b4725b137b2..a5f9ca76f6e 100644
--- a/spec/support/helpers/selection_helper.rb
+++ b/spec/support/helpers/selection_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SelectionHelper
def select_element(selector)
find(selector)
diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb
index e505a6b7258..3801d25fb63 100644
--- a/spec/support/helpers/sorting_helper.rb
+++ b/spec/support/helpers/sorting_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper allows you to sort items
#
# Params
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 049702be1f6..c8b2bf040e6 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'active_support/core_ext/hash/transform_values'
require 'active_support/hash_with_indifferent_access'
require 'active_support/dependencies'
diff --git a/spec/support/helpers/stub_env.rb b/spec/support/helpers/stub_env.rb
index 1c2f474a015..8107ffc939f 100644
--- a/spec/support/helpers/stub_env.rb
+++ b/spec/support/helpers/stub_env.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubENV
def stub_env(key_or_hash, value = nil)
diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb
index 48258692304..6c3efff7262 100644
--- a/spec/support/helpers/stub_feature_flags.rb
+++ b/spec/support/helpers/stub_feature_flags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubFeatureFlags
# Stub Feature flags with `flag_name: true/false`
#
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 4cb3b18df85..badea94352a 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubGitlabCalls
def stub_gitlab_calls
stub_user
diff --git a/spec/support/helpers/stub_gitlab_data.rb b/spec/support/helpers/stub_gitlab_data.rb
index fa402f35b95..ed518393c03 100644
--- a/spec/support/helpers/stub_gitlab_data.rb
+++ b/spec/support/helpers/stub_gitlab_data.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubGitlabData
def gitlab_ci_yaml
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
diff --git a/spec/support/helpers/stub_metrics.rb b/spec/support/helpers/stub_metrics.rb
index 64983fdf222..e347955efbb 100644
--- a/spec/support/helpers/stub_metrics.rb
+++ b/spec/support/helpers/stub_metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubMetrics
def authentication_metrics
Gitlab::Auth::Activity
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index d31f9908714..e5b8bb712bb 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubObjectStorage
def stub_object_storage_uploader(
config:,
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
index 5cad35282c0..6eb8007ed26 100644
--- a/spec/support/helpers/stub_requests.rb
+++ b/spec/support/helpers/stub_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubRequests
IP_ADDRESS_STUB = '8.8.8.9'.freeze
diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb
index 58b7ee93dff..cac839ed5fe 100644
--- a/spec/support/helpers/stub_worker.rb
+++ b/spec/support/helpers/stub_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubWorker
def stub_worker(queue:)
diff --git a/spec/support/helpers/terms_helper.rb b/spec/support/helpers/terms_helper.rb
index a00ec14138b..a61bae18f9a 100644
--- a/spec/support/helpers/terms_helper.rb
+++ b/spec/support/helpers/terms_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TermsHelper
def enforce_terms
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index b062631b995..a4acf76e1a3 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rspec/mocks'
require 'toml-rb'
diff --git a/spec/support/helpers/upload_helpers.rb b/spec/support/helpers/upload_helpers.rb
index 5eead80c935..60e14a8673b 100644
--- a/spec/support/helpers/upload_helpers.rb
+++ b/spec/support/helpers/upload_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fileutils'
module UploadHelpers
diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb
index 45b9faa0fea..3bb2f7c5b51 100644
--- a/spec/support/helpers/wait_for_requests.rb
+++ b/spec/support/helpers/wait_for_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WaitForRequests
extend self
diff --git a/spec/support/helpers/wait_helpers.rb b/spec/support/helpers/wait_helpers.rb
index 7e8e25798e8..a8c4408db59 100644
--- a/spec/support/helpers/wait_helpers.rb
+++ b/spec/support/helpers/wait_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WaitHelpers
extend self
diff --git a/spec/support/helpers/wiki_helpers.rb b/spec/support/helpers/wiki_helpers.rb
index 8165403cb60..06cea728b42 100644
--- a/spec/support/helpers/wiki_helpers.rb
+++ b/spec/support/helpers/wiki_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WikiHelpers
extend self
diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb
index ef1f9f68671..4488e5f227e 100644
--- a/spec/support/helpers/workhorse_helpers.rb
+++ b/spec/support/helpers/workhorse_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WorkhorseHelpers
extend self
diff --git a/spec/support/http_io/http_io_helpers.rb b/spec/support/http_io/http_io_helpers.rb
index 42144870eb5..0193db81fa9 100644
--- a/spec/support/http_io/http_io_helpers.rb
+++ b/spec/support/http_io/http_io_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HttpIOHelpers
def stub_remote_url_206(url, file_path)
WebMock.stub_request(:get, url)
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index 2542a59bb00..ac6840dbcfc 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ImportExport
module CommonUtil
def setup_symlink(tmpdir, symlink_name)
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
index b4164cff922..122df7f27f0 100644
--- a/spec/support/import_export/configuration_helper.rb
+++ b/spec/support/import_export/configuration_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ConfigurationHelper
# Returns a list of models from hashes/arrays contained in +project_tree+
def names_from_tree(project_tree)
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index 388b88f0331..f862a9bc1a4 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require './spec/support/import_export/configuration_helper'
module ExportFileHelper
diff --git a/spec/support/inspect_squelch.rb b/spec/support/inspect_squelch.rb
index 8ee6732370b..90475204889 100644
--- a/spec/support/inspect_squelch.rb
+++ b/spec/support/inspect_squelch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class can generate a lot of output if it fails,
# so squelch the instance variable output.
class ActiveSupport::Cache::NullStore
diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb
index 71bcc82ee58..ee25df00dfb 100644
--- a/spec/support/issuables_requiring_filter_shared_examples.rb
+++ b/spec/support/issuables_requiring_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuables requiring filter' do |action|
it "doesn't load any issuables if no filter is set" do
expect_any_instance_of(described_class).not_to receive(:issuables_collection)
diff --git a/spec/support/json_response.rb b/spec/support/json_response.rb
index 43d8ab73dde..55bdce0cfe9 100644
--- a/spec/support/json_response.rb
+++ b/spec/support/json_response.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include_context 'JSON response', type: :controller
config.include_context 'JSON response', type: :request
diff --git a/spec/support/matchers/abort_matcher.rb b/spec/support/matchers/abort_matcher.rb
index ce1dd140210..64fed2ca069 100644
--- a/spec/support/matchers/abort_matcher.rb
+++ b/spec/support/matchers/abort_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :abort_execution do
match do |code_block|
@captured_stderr = StringIO.new
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index e6899e2d23c..c9ff777f604 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# AccessMatchers
#
# The custom matchers contained in this module are used to test a user's access
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
index 429401a5da8..401bf6c196e 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# AccessMatchersForController
#
# For testing authorize_xxx in controller.
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index f4127efc6ae..c38aa7ad6a6 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
match do |migration|
BackgroundMigrationWorker.jobs.any? do |job|
diff --git a/spec/support/matchers/be_a_binary_string.rb b/spec/support/matchers/be_a_binary_string.rb
index f041ae76167..6195c6c7554 100644
--- a/spec/support/matchers/be_a_binary_string.rb
+++ b/spec/support/matchers/be_a_binary_string.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_a_binary_string do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('ASCII-8BIT')
diff --git a/spec/support/matchers/be_like_time.rb b/spec/support/matchers/be_like_time.rb
index 1f27390eab7..b449f7a7ffb 100644
--- a/spec/support/matchers/be_like_time.rb
+++ b/spec/support/matchers/be_like_time.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_like_time do |expected|
match do |actual|
expect(actual).to be_within(1.second).of(expected)
diff --git a/spec/support/matchers/be_url.rb b/spec/support/matchers/be_url.rb
index f8096af1b22..7bd0e7fada4 100644
--- a/spec/support/matchers/be_url.rb
+++ b/spec/support/matchers/be_url.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_url do |_|
match do |actual|
URI.parse(actual) rescue false
diff --git a/spec/support/matchers/be_utf8.rb b/spec/support/matchers/be_utf8.rb
index ea806352422..4fa55539a49 100644
--- a/spec/support/matchers/be_utf8.rb
+++ b/spec/support/matchers/be_utf8.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_utf8 do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8')
diff --git a/spec/support/matchers/be_valid_commit.rb b/spec/support/matchers/be_valid_commit.rb
index 3696e4d5f03..b59339de622 100644
--- a/spec/support/matchers/be_valid_commit.rb
+++ b/spec/support/matchers/be_valid_commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_valid_commit do
match do |actual|
actual &&
diff --git a/spec/support/matchers/disallow_request_matchers.rb b/spec/support/matchers/disallow_request_matchers.rb
index db4d90e4fd0..a161e3660cd 100644
--- a/spec/support/matchers/disallow_request_matchers.rb
+++ b/spec/support/matchers/disallow_request_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :disallow_request do
match do |middleware|
alert = middleware.env['rack.session'].to_hash
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index cd042401f3a..40cf85eb8e5 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExceedQueryLimitHelpers
def with_threshold(threshold)
@threshold = threshold
diff --git a/spec/support/matchers/execute_check.rb b/spec/support/matchers/execute_check.rb
index 7232fad52fb..d3c0751f0dc 100644
--- a/spec/support/matchers/execute_check.rb
+++ b/spec/support/matchers/execute_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :execute_check do |expected|
match do |actual|
expect(actual).to eq(SystemCheck)
diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb
index 933ed22b5d0..c2b3ebe3422 100644
--- a/spec/support/matchers/gitaly_matchers.rb
+++ b/spec/support/matchers/gitaly_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :gitaly_request_with_path do |storage_name, relative_path|
match do |actual|
repository = actual.repository
diff --git a/spec/support/matchers/gitlab_git_matchers.rb b/spec/support/matchers/gitlab_git_matchers.rb
index c840cd4bf2d..aea1603db05 100644
--- a/spec/support/matchers/gitlab_git_matchers.rb
+++ b/spec/support/matchers/gitlab_git_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :gitlab_git_repository_with do |values|
match do |actual|
actual.is_a?(Gitlab::Git::Repository) &&
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 7894484f590..4d48b4b5389 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :require_graphql_authorizations do |*expected|
match do |field|
expect(field.metadata[:authorize]).to eq(*expected)
diff --git a/spec/support/matchers/have_emoji.rb b/spec/support/matchers/have_emoji.rb
index 23fb8e9c1c4..273bd0b7f40 100644
--- a/spec/support/matchers/have_emoji.rb
+++ b/spec/support/matchers/have_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_emoji do |emoji_name|
match do |actual|
expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']")
diff --git a/spec/support/matchers/have_gitlab_http_status.rb b/spec/support/matchers/have_gitlab_http_status.rb
index e7e418cdde4..13a64a58218 100644
--- a/spec/support/matchers/have_gitlab_http_status.rb
+++ b/spec/support/matchers/have_gitlab_http_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_gitlab_http_status do |expected|
match do |actual|
expect(actual).to have_http_status(expected)
diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb
index 92cf3de5448..049cfc022fb 100644
--- a/spec/support/matchers/have_issuable_counts.rb
+++ b/spec/support/matchers/have_issuable_counts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_issuable_counts do |opts|
expected_counts = opts.map do |state, count|
"#{state.to_s.humanize} #{count}"
diff --git a/spec/support/matchers/include_module.rb b/spec/support/matchers/include_module.rb
index 0a78af1e90e..9b6970cf061 100644
--- a/spec/support/matchers/include_module.rb
+++ b/spec/support/matchers/include_module.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_module do |expected|
match do
described_class.included_modules.include?(expected)
diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb
index 62f510b0fbd..743f0b8c932 100644
--- a/spec/support/matchers/issuable_matchers.rb
+++ b/spec/support/matchers/issuable_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"|
match do |actual|
node = find("#{parent} h#{level} a#user-content-#{id}")
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index ec4ec6f4038..12e8fa83a60 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# MarkdownMatchers
#
# Custom matchers for our custom HTML::Pipeline filters. These are used to test
diff --git a/spec/support/matchers/match_file.rb b/spec/support/matchers/match_file.rb
index d1888b3376a..4e522b52912 100644
--- a/spec/support/matchers/match_file.rb
+++ b/spec/support/matchers/match_file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :match_file do |expected|
match do |actual|
expect(Digest::MD5.hexdigest(actual)).to eq(Digest::MD5.hexdigest(File.read(expected)))
diff --git a/spec/support/matchers/match_ids.rb b/spec/support/matchers/match_ids.rb
index 1cb6b74acac..7bc41949937 100644
--- a/spec/support/matchers/match_ids.rb
+++ b/spec/support/matchers/match_ids.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :match_ids do |*expected|
match do |actual|
actual_ids = map_ids(actual)
diff --git a/spec/support/matchers/metric_counter_matcher.rb b/spec/support/matchers/metric_counter_matcher.rb
index 22d5cd17e3f..f0d52b9b149 100644
--- a/spec/support/matchers/metric_counter_matcher.rb
+++ b/spec/support/matchers/metric_counter_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :increment do |counter|
match do |adapter|
expect(adapter.send(counter))
diff --git a/spec/support/matchers/navigation_matcher.rb b/spec/support/matchers/navigation_matcher.rb
index 63f59b9654c..ad73c96031e 100644
--- a/spec/support/matchers/navigation_matcher.rb
+++ b/spec/support/matchers/navigation_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_active_navigation do |expected|
match do |page|
expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb
index 9a7697e2bfc..a3e9c3b8474 100644
--- a/spec/support/matchers/pagination_matcher.rb
+++ b/spec/support/matchers/pagination_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_pagination_headers do |expected|
match do |actual|
expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link')
diff --git a/spec/support/matchers/query_matcher.rb b/spec/support/matchers/query_matcher.rb
index bb0feca7c43..3e47fe241bc 100644
--- a/spec/support/matchers/query_matcher.rb
+++ b/spec/support/matchers/query_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil|
supports_block_expectations
diff --git a/spec/support/matchers/satisfy_matchers.rb b/spec/support/matchers/satisfy_matchers.rb
index 585915bac93..dd1920ee2b2 100644
--- a/spec/support/matchers/satisfy_matchers.rb
+++ b/spec/support/matchers/satisfy_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These matchers are a syntactic hack to provide more readable expectations for
# an Enumerable object.
#
diff --git a/spec/support/matchers/security_header_matcher.rb b/spec/support/matchers/security_header_matcher.rb
index f8518d13ebb..760b1fddd06 100644
--- a/spec/support/matchers/security_header_matcher.rb
+++ b/spec/support/matchers/security_header_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_security_headers do |expected|
match do |actual|
expect(actual.headers).to include('X-Content-Type-Options')
diff --git a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
index 016bcfa9b1b..656be3b6d4d 100644
--- a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
+++ b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MigrationsHelpers
module TrackUntrackedUploadsHelpers
PUBLIC_DIR = File.join(Rails.root, 'tmp', 'tests', 'public')
diff --git a/spec/support/omni_auth.rb b/spec/support/omni_auth.rb
index 0b1af4052ff..64aa3855a6f 100644
--- a/spec/support/omni_auth.rb
+++ b/spec/support/omni_auth.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
OmniAuth.config.test_mode = true
diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb
index de21e808932..82582630dee 100644
--- a/spec/support/prometheus/additional_metrics_shared_examples.rb
+++ b/spec/support/prometheus/additional_metrics_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'additional metrics query' do
include Prometheus::MetricBuilders
diff --git a/spec/support/prometheus/metric_builders.rb b/spec/support/prometheus/metric_builders.rb
index c8d056d3fc8..512e32a44d0 100644
--- a/spec/support/prometheus/metric_builders.rb
+++ b/spec/support/prometheus/metric_builders.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Prometheus
module MetricBuilders
def simple_query(suffix = 'a', **opts)
diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 71eec9f3217..8666c19481c 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "protected tags > access control > CE" do
ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)|
it "allows creating protected tags that #{access_type_name} can create" do
diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb
index 0457e8487d8..7c571738a01 100644
--- a/spec/support/redis/redis_helpers.rb
+++ b/spec/support/redis/redis_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RedisHelpers
# config/README.md
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index 6aa59960092..7e47cdae866 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "redis_shared_examples" do
include StubENV
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index b38c5dfe60b..1c9f9e5161e 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "helpers/stub_configuration"
require_relative "helpers/stub_metrics"
require_relative "helpers/stub_object_storage"
diff --git a/spec/support/seed.rb b/spec/support/seed.rb
index bea2e9c3044..36cb819763b 100644
--- a/spec/support/seed.rb
+++ b/spec/support/seed.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include SeedHelper, :seed_helper
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index b0bf942aa09..6ec8750ce87 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'valid cluster create params' do
let(:params) do
{
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index 8b4cffaac19..4c3644e6724 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with executable attributes.
# It can take a `default_params`.
diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb
index ffbce6c42bf..5e5acd0e40a 100644
--- a/spec/support/services/issuable_update_service_shared_examples.rb
+++ b/spec/support/services/issuable_update_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable update service' do
def update_issuable(opts)
described_class.new(project, user, opts).execute(open_issuable)
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 1284415da1f..65236f13e27 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields|
diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb
index 1d2a4856724..bbe442f07b0 100644
--- a/spec/support/setup_builds_storage.rb
+++ b/spec/support/setup_builds_storage.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
def builds_path
Rails.root.join('tmp/tests/builds')
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 4f5d53f9317..b4d061a8215 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context :email_shared_context do
let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
let(:receiver) { Gitlab::Email::Receiver.new(email_raw) }
diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
index a0d994c4d8d..38f6011646e 100644
--- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'GroupProjectsFinder context' do
diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
index b8a9554f55f..26ab6fbd400 100644
--- a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'IssuesFinder context' do
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index ab6687f1d07..ef1e65d2577 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests context' do
diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
index 9e1f89ee0ed..d6404b2ee4b 100644
--- a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'UsersFinder#execute filter by project context' do
diff --git a/spec/support/shared_contexts/json_response_shared_context.rb b/spec/support/shared_contexts/json_response_shared_context.rb
index df5fc288089..bd37c97ed35 100644
--- a/spec/support/shared_contexts/json_response_shared_context.rb
+++ b/spec/support/shared_contexts/json_response_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'JSON response' do
let(:json_response) { JSON.parse(response.body) }
end
diff --git a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
index 05424d08b9d..276ebf973c8 100644
--- a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
+++ b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'merge request allowing collaboration' do
include ProjectForksHelper
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index 0c3a24d206f..4d176ab5fca 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Service.available_services_names.each do |service|
shared_context service do
let(:dashed_service) { service.dasherize }
diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb
index 1b1f67daac3..560cd500ecd 100644
--- a/spec/support/shared_contexts/url_shared_context.rb
+++ b/spec/support/shared_contexts/url_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'invalid urls' do
let(:urls_with_CRLF) do
["http://127.0.0.1:333/pa\rth",
diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
index dc97a39f051..82975027e5b 100644
--- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'chat slash commands service' do
describe "Associations" do
it { is_expected.to respond_to :token }
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index 68c2b6e10e2..6fd4b14d51d 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'common trace features' do
describe '#html' do
before do
diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb
index da5a4f3e319..75f93a32d78 100644
--- a/spec/support/shared_examples/common_system_notes_examples.rb
+++ b/spec/support/shared_examples/common_system_notes_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'system note creation' do |update_params, note_text|
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
index 8dd78fd0a25..2faa0cf8c1c 100644
--- a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'disabled when using an external authorization service' do
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index f4b02dc5350..fb22498f84f 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable notes filter' do
let(:params) do
if issuable_parent.is_a?(Project)
diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
index eb051166a69..1cd14ea2251 100644
--- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'set sort order from user preference' do
describe '#set_sort_order_from_user_preference' do
# There is no issuable_sorting_field defined in any CE controllers yet,
diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb
index bafd9bac8d0..f3f9abb7da2 100644
--- a/spec/support/shared_examples/controllers/todos_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'todos actions' do
context 'when authorized' do
before do
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 59708173716..97b2a01576c 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'handle uploads' do
let(:user) { create(:user) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb
index e80722857ec..78666e677ef 100644
--- a/spec/support/shared_examples/controllers/variables_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'GET #show lists all variables' do
it 'renders the variables as json' do
subject
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
index 4e45e2921e7..60c8899d349 100644
--- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
diff --git a/spec/support/shared_examples/email_format_shared_examples.rb b/spec/support/shared_examples/email_format_shared_examples.rb
index b924a208e71..22d6c2b38e3 100644
--- a/spec/support/shared_examples/email_format_shared_examples.rb
+++ b/spec/support/shared_examples/email_format_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
diff --git a/spec/support/shared_examples/fast_destroy_all.rb b/spec/support/shared_examples/fast_destroy_all.rb
index a8079b6d864..a64259c03f2 100644
--- a/spec/support/shared_examples/fast_destroy_all.rb
+++ b/spec/support/shared_examples/fast_destroy_all.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'fast destroyable' do
describe 'Forbid #destroy and #destroy_all' do
it 'does not delete database rows and associted external data' do
diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
index 2b36955a3c4..f24e47f4638 100644
--- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'comment on merge request file' do
it 'adds a comment' do
click_diff_line(find("[id='#{sample_commit.line_code}']"))
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index ec1b1754cf0..c0db4cdde72 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'a creatable merge request' do
include WaitForRequests
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index a6121fcc50a..964c80007b0 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'an editable merge request' do
it 'updates merge request', :js do
find('.js-assignee-search').click
diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
index 96c821b26f7..09a48533ee3 100644
--- a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issue sidebar stays collapsed on mobile' do
before do
resize_screen_xs
diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
index c92c7f603d6..63ed37cde03 100644
--- a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable user dropdown behaviors' do
include FilteredSearchHelpers
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index d87e5fcaa88..8e1d24c4be2 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'Maintainer manages access requests' do
let(:user) { create(:user) }
let(:maintainer) { create(:user) }
diff --git a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
index 64c3b80136d..51559c0b110 100644
--- a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'project features apply to issuables' do |klass|
let(:described_class) { klass }
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index a8f2c2e7a5a..867a1774aa9 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "protected branches > access control > CE" do
ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can push to" do
diff --git a/spec/support/shared_examples/features/search_shared_examples.rb b/spec/support/shared_examples/features/search_shared_examples.rb
index 25ebbf011d5..e27d6700cbf 100644
--- a/spec/support/shared_examples/features/search_shared_examples.rb
+++ b/spec/support/shared_examples/features/search_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'top right search form' do
it 'does not show top right search form' do
expect(page).not_to have_selector('.search')
diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb
index 0dc351b5149..984a06ccd1a 100644
--- a/spec/support/shared_examples/file_finder.rb
+++ b/spec/support/shared_examples/file_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'file finder' do
let(:query) { 'files' }
let(:search_results) { subject.find(query) }
diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
index d7e17cc0b70..b8b0079e36d 100644
--- a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
+++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'a finder with external authorization service' do
diff --git a/spec/support/shared_examples/gitlab_verify.rb b/spec/support/shared_examples/gitlab_verify.rb
index 560913ca92f..721ea3b4c88 100644
--- a/spec/support/shared_examples/gitlab_verify.rb
+++ b/spec/support/shared_examples/gitlab_verify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do
describe 'batching' do
let(:first_batch) { objects[0].id..objects[0].id }
diff --git a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
index 713f0a879c1..145c476c7f7 100644
--- a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'issuable state' do
it 'exposes all the existing issuable states' do
expect(described_class.values.keys).to include(*%w[opened closed locked])
diff --git a/spec/support/shared_examples/group_members_shared_example.rb b/spec/support/shared_examples/group_members_shared_example.rb
index 547c83c7955..4f7d496741d 100644
--- a/spec/support/shared_examples/group_members_shared_example.rb
+++ b/spec/support/shared_examples/group_members_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'members and requesters associations' do
describe '#members_and_requesters' do
it 'includes members and requesters' do
diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb
index 01bee603274..17f495ebe46 100644
--- a/spec/support/shared_examples/helm_generated_script.rb
+++ b/spec/support/shared_examples/helm_generated_script.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'helm commands' do
describe '#generate_script' do
let(:helm_setup) do
diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb
index d97b21f71cd..3460a8ba297 100644
--- a/spec/support/shared_examples/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/issuable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cache counters invalidator' do
it 'invalidates counter cache for assignees' do
expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts)
diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
index 244f4766a84..52d90b5f183 100644
--- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
include ProjectForksHelper
diff --git a/spec/support/shared_examples/issue_tracker_service_shared_example.rb b/spec/support/shared_examples/issue_tracker_service_shared_example.rb
index a6ab03cb808..0a483fd30ba 100644
--- a/spec/support/shared_examples/issue_tracker_service_shared_example.rb
+++ b/spec/support/shared_examples/issue_tracker_service_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
it { is_expected.to allow_value('https://example.com').for(url_attr) }
diff --git a/spec/support/shared_examples/ldap_shared_examples.rb b/spec/support/shared_examples/ldap_shared_examples.rb
index 52c34e78965..0a70ce7ea0c 100644
--- a/spec/support/shared_examples/ldap_shared_examples.rb
+++ b/spec/support/shared_examples/ldap_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'normalizes a DN' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
index f326e502092..22e5698825d 100644
--- a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
+++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'redirecting a legacy path' do |source, target|
include RSpec::Rails::RequestExampleGroup
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
index dcf7c1a90c2..2cbc0c2bdf2 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'backfill migration for project repositories' do |storage|
describe '#perform' do
let(:storage_versions) { storage == :legacy ? [nil, 0] : [1, 2] }
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb
new file mode 100644
index 00000000000..91bf804978d
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+shared_examples 'a redis usage counter' do |thing, event|
+ describe ".count(#{event})", :clean_gitlab_redis_shared_state do
+ it "increments the #{thing} #{event} counter by 1" do
+ expect do
+ described_class.count(event)
+ end.to change { described_class.read(event) }.by 1
+ end
+ end
+
+ describe ".read(#{event})", :clean_gitlab_redis_shared_state do
+ event_count = 5
+
+ it "returns the total number of #{event} events" do
+ event_count.times do
+ described_class.count(event)
+ end
+
+ expect(described_class.read(event)).to eq(event_count)
+ end
+ end
+end
+
+shared_examples 'a redis usage counter with totals' do |prefix, events|
+ describe 'totals', :clean_gitlab_redis_shared_state do
+ before do
+ events.each do |k, n|
+ n.times do
+ described_class.count(k)
+ end
+ end
+ end
+
+ let(:expected_totals) do
+ events.transform_keys { |k| "#{prefix}_#{k}".to_sym }
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(expected_totals)
+ end
+ end
+
+ # Override these let-bindings to adjust the unknown events tests
+ let(:unknown_event) { described_class::UnknownEvent }
+ let(:bad_event) { :wibble }
+
+ describe 'unknown events' do
+ it 'cannot increment' do
+ expect { described_class.count(bad_event) }.to raise_error unknown_event
+ end
+
+ it 'cannot read' do
+ expect { described_class.read(bad_event) }.to raise_error unknown_event
+ end
+ end
+end
diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
index a86050e2cf2..96c02260d53 100644
--- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb
+++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'timeout'
shared_examples 'malicious regexp' do
diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb
index fea52c2eeb2..93a8c4709a6 100644
--- a/spec/support/shared_examples/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/mentionable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all Mentionable implementations.
# Requires a shared context containing:
# - subject { "the mentionable implementation" }
diff --git a/spec/support/shared_examples/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb
index 8b757586941..bda4b978737 100644
--- a/spec/support/shared_examples/milestone_tabs_examples.rb
+++ b/spec/support/shared_examples/milestone_tabs_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'milestone tabs' do
def go(path, extra_params = {})
params =
diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
index a248f60d23e..b837ca87256 100644
--- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples_for 'AtomicInternalId' do |validate_presence: true|
diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
index 0a302e7d030..71c0c57f302 100644
--- a/spec/support/shared_examples/models/chat_service_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
shared_examples_for "chat service" do |service_name|
diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
index d6490a808ce..8e58cc7ba22 100644
--- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application core specs' do |application_name|
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
index bd3661471f8..7ddb3b11c85 100644
--- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application helm specs' do |application_name|
let(:application) { create(application_name) }
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 4525c03837f..5341aacb445 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application status specs' do |application_name|
describe '#status' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
index a4762b68858..7ea2bb265cc 100644
--- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
+++ b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This shared example requires a `builder` and `user` variable
shared_examples 'issuable hook data' do |kind|
let(:data) { builder.build(user: user) }
diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb
index ef5cea3f2a5..050d710f1de 100644
--- a/spec/support/shared_examples/models/members_notifications_shared_example.rb
+++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'members notifications' do |entity_type|
let(:notification_service) { double('NotificationService').as_null_object }
diff --git a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
index f0264878811..03d10c10e3c 100644
--- a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
+++ b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'project hook data with deprecateds' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
index 43033a2d256..eb1ade03017 100644
--- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb
+++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples_for 'model with uploads' do |supports_fileuploads|
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index 4452b1c82cb..ca031df000e 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'gitlab email notification' do
set(:group) { create(:group) }
set(:subgroup) { create(:group, parent: group) }
@@ -50,7 +52,7 @@ shared_examples 'an email sent to a user' do
it 'is sent to user\'s group notification email' do
group_notification_email = 'user+group@example.com'
- create(:notification_setting, user: recipient, source: project.group, notification_email: group_notification_email)
+ create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email)
expect(subject).to deliver_to(group_notification_email)
end
diff --git a/spec/support/shared_examples/position_formatters.rb b/spec/support/shared_examples/position_formatters.rb
index ffc9456dbc7..30b6b8d24f0 100644
--- a/spec/support/shared_examples/position_formatters.rb
+++ b/spec/support/shared_examples/position_formatters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for "position formatter" do
let(:formatter) { described_class.new(attrs) }
diff --git a/spec/support/shared_examples/reference_parser_shared_examples.rb b/spec/support/shared_examples/reference_parser_shared_examples.rb
index baf8bcc04b8..d903c0f10e0 100644
--- a/spec/support/shared_examples/reference_parser_shared_examples.rb
+++ b/spec/support/shared_examples/reference_parser_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "referenced feature visibility" do |*related_features|
let(:feature_fields) do
related_features.map { |feature| (feature + "_access_level").to_sym }
diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb
new file mode 100644
index 00000000000..5ee62644c54
--- /dev/null
+++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb
@@ -0,0 +1,253 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "a class that supports relative positioning" do
+ let(:item1) { create(factory, default_params) }
+ let(:item2) { create(factory, default_params) }
+ let(:new_item) { create(factory, default_params) }
+
+ def create_item(params)
+ create(factory, params.merge(default_params))
+ end
+
+ describe '.move_to_end' do
+ it 'moves the object to the end' do
+ item1.update(relative_position: 5)
+ item2.update(relative_position: 15)
+
+ described_class.move_to_end([item1, item2])
+
+ expect(item2.prev_relative_position).to eq item1.relative_position
+ expect(item1.prev_relative_position).to eq nil
+ expect(item2.next_relative_position).to eq nil
+ end
+
+ it 'does not perform any moves if all items have their relative_position set' do
+ item1.update!(relative_position: 1)
+
+ expect(item1).not_to receive(:save)
+
+ described_class.move_to_end([item1])
+ end
+ end
+
+ describe '#max_relative_position' do
+ it 'returns maximum position' do
+ expect(item1.max_relative_position).to eq item2.relative_position
+ end
+ end
+
+ describe '#prev_relative_position' do
+ it 'returns previous position if there is an item above' do
+ item1.update(relative_position: 5)
+ item2.update(relative_position: 15)
+
+ expect(item2.prev_relative_position).to eq item1.relative_position
+ end
+
+ it 'returns nil if there is no item above' do
+ expect(item1.prev_relative_position).to eq nil
+ end
+ end
+
+ describe '#next_relative_position' do
+ it 'returns next position if there is an item below' do
+ item1.update(relative_position: 5)
+ item2.update(relative_position: 15)
+
+ expect(item1.next_relative_position).to eq item2.relative_position
+ end
+
+ it 'returns nil if there is no item below' do
+ expect(item2.next_relative_position).to eq nil
+ end
+ end
+
+ describe '#move_before' do
+ it 'moves item before' do
+ [item2, item1].each(&:move_to_end)
+
+ item1.move_before(item2)
+
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+ end
+
+ describe '#move_after' do
+ it 'moves item after' do
+ [item1, item2].each(&:move_to_end)
+
+ item1.move_after(item2)
+
+ expect(item1.relative_position).to be > item2.relative_position
+ end
+ end
+
+ describe '#move_to_end' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'moves item to the end' do
+ new_item.move_to_end
+
+ expect(new_item.relative_position).to be > item2.relative_position
+ end
+ end
+
+ describe '#shift_after?' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'returns true' do
+ item1.update(relative_position: item2.relative_position - 1)
+
+ expect(item1.shift_after?).to be_truthy
+ end
+
+ it 'returns false' do
+ item1.update(relative_position: item2.relative_position - 2)
+
+ expect(item1.shift_after?).to be_falsey
+ end
+ end
+
+ describe '#shift_before?' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'returns true' do
+ item1.update(relative_position: item2.relative_position + 1)
+
+ expect(item1.shift_before?).to be_truthy
+ end
+
+ it 'returns false' do
+ item1.update(relative_position: item2.relative_position + 2)
+
+ expect(item1.shift_before?).to be_falsey
+ end
+ end
+
+ describe '#move_between' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'positions item between two other' do
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(new_item.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions item between on top' do
+ new_item.move_between(nil, item1)
+
+ expect(new_item.relative_position).to be < item1.relative_position
+ end
+
+ it 'positions item between to end' do
+ new_item.move_between(item2, nil)
+
+ expect(new_item.relative_position).to be > item2.relative_position
+ end
+
+ it 'positions items even when after and before positions are the same' do
+ item2.update relative_position: item1.relative_position
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions items between other two if distance is 1' do
+ item2.update relative_position: item1.relative_position + 1
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions item in the middle of other two if distance is big enough' do
+ item1.update relative_position: 6000
+ item2.update relative_position: 10000
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to eq(8000)
+ end
+
+ it 'positions item closer to the middle if we are at the very top' do
+ item2.update relative_position: 6000
+
+ new_item.move_between(nil, item2)
+
+ expect(new_item.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions item closer to the middle if we are at the very bottom' do
+ new_item.update relative_position: 1
+ item1.update relative_position: 6000
+ item2.destroy
+
+ new_item.move_between(item1, nil)
+
+ expect(new_item.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions item in the middle of other two if distance is not big enough' do
+ item1.update relative_position: 100
+ item2.update relative_position: 400
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to eq(250)
+ end
+
+ it 'positions item in the middle of other two is there is no place' do
+ item1.update relative_position: 100
+ item2.update relative_position: 101
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be_between(item1.relative_position, item2.relative_position)
+ end
+
+ it 'uses rebalancing if there is no place' do
+ item1.update relative_position: 100
+ item2.update relative_position: 101
+ item3 = create_item(relative_position: 102)
+ new_item.update relative_position: 103
+
+ new_item.move_between(item2, item3)
+ new_item.save!
+
+ expect(new_item.relative_position).to be_between(item2.relative_position, item3.relative_position)
+ expect(item1.reload.relative_position).not_to eq(100)
+ end
+
+ it 'positions item right if we pass none-sequential parameters' do
+ item1.update relative_position: 99
+ item2.update relative_position: 101
+ item3 = create_item(relative_position: 102)
+ new_item.update relative_position: 103
+
+ new_item.move_between(item1, item3)
+ new_item.save!
+
+ expect(new_item.relative_position).to be(100)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
index 8a7fcf856a1..776a0bdd29e 100644
--- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'custom attributes endpoints' do |attributable_name|
let!(:custom_attribute1) { attributable.custom_attributes.create key: 'foo', value: 'foo' }
let!(:custom_attribute2) { attributable.custom_attributes.create key: 'bar', value: 'bar' }
diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb
index 366c2955359..76c6c93964a 100644
--- a/spec/support/shared_examples/requests/api/diff_discussions.rb
+++ b/spec/support/shared_examples/requests/api/diff_discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "includes diff discussions" do
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
index c3132c41f5b..fc72287f265 100644
--- a/spec/support/shared_examples/requests/api/discussions.rb
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "returns an array of discussions" do
diff --git a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
index 96d59e0c472..9fe6288d53f 100644
--- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
+++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable participants endpoint' do
let(:area) { entity.class.name.underscore.pluralize }
diff --git a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
index 5f4e178f2e5..90c1ed8d09b 100644
--- a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
def get_issue
json_response.is_a?(Array) ? json_response.detect {|issue| issue['id'] == target_issue.id} : json_response
end
diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb
index 57eefd5ef01..354ae7288b1 100644
--- a/spec/support/shared_examples/requests/api/notes.rb
+++ b/spec/support/shared_examples/requests/api/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
context 'sorting' do
diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
index 7e2416b23f3..42054a273f3 100644
--- a/spec/support/shared_examples/requests/api/resolvable_discussions.rb
+++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_name|
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do
it "resolves discussion if resolved is true" do
diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb
index ebfc5fed3bb..eebed7e42c1 100644
--- a/spec/support/shared_examples/requests/api/status_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for status checking.
#
# Requires an API request:
diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb
index 04140cad3f0..2a38d56141a 100644
--- a/spec/support/shared_examples/requests/graphql_shared_examples.rb
+++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'a working graphql query' do
diff --git a/spec/support/shared_examples/serializers/note_entity_examples.rb b/spec/support/shared_examples/serializers/note_entity_examples.rb
index ec208aba2a9..bfcaa2f1bd5 100644
--- a/spec/support/shared_examples/serializers/note_entity_examples.rb
+++ b/spec/support/shared_examples/serializers/note_entity_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'note entity' do
subject { entity.as_json }
diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb
index 5bdc04f660f..19818a6091b 100644
--- a/spec/support/shared_examples/services/boards/boards_create_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_create_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'boards create service' do
context 'when parent does not have a board' do
it 'creates a new board' do
diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb
index e0d5a7c61f2..566e5050f8e 100644
--- a/spec/support/shared_examples/services/boards/boards_list_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'boards list service' do
context 'when parent does not have a board' do
it 'creates a new parent board' do
diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb
index 8b879cef084..75733c774ef 100644
--- a/spec/support/shared_examples/services/boards/issues_list_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issues list service' do
it 'delegates search to IssuesFinder' do
params = { board_id: board.id, id: list1.id }
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
index 5359831f8f8..d3fa8084185 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issues move service' do |group|
shared_examples 'updating timestamps' do
it 'updates updated_at' do
diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
index 62b6ffe1836..95725078f9d 100644
--- a/spec/support/shared_examples/services/boards/lists_destroy_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists destroy service' do
context 'when list type is label' do
it 'removes list from board' do
diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb
index 0a8220111ab..29784f6da08 100644
--- a/spec/support/shared_examples/services/boards/lists_list_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists list service' do
context 'when the board has a backlog list' do
let!(:backlog_list) { create(:backlog_list, board: board) }
diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb
index 2cdb968a45d..0b3bfd8e2a8 100644
--- a/spec/support/shared_examples/services/boards/lists_move_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_move_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists move service' do
let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) }
diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
index 02de47a96dd..1e0ac8b7615 100644
--- a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'check ingress ip executions' do |app_name|
describe '#execute' do
let(:application) { create(app_name, :installed) }
diff --git a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
index b8db35a6ef9..1c3fa5644d3 100644
--- a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'gitlab projects import validations' do
context 'with an invalid path' do
let(:path) { '/invalid-path/' }
diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
index 36c486dbdd6..52fef1c13e1 100644
--- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
RSpec.shared_examples 'slack or mattermost notifications' do
diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
index 833c31a57cb..b5321c6db34 100644
--- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb
+++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'snippet visibility' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/shared_examples/snippets_shared_examples.rb b/spec/support/shared_examples/snippets_shared_examples.rb
index 85f0facd5c3..5c35617bd36 100644
--- a/spec/support/shared_examples/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/snippets_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These shared examples expect a `snippets` array of snippets
RSpec.shared_examples 'paginated snippets' do |remote: false|
it "is limited to #{Snippet.default_per_page} items per page" do
diff --git a/spec/support/shared_examples/taskable_shared_examples.rb b/spec/support/shared_examples/taskable_shared_examples.rb
index 4a1df1ce380..f04f509f3d2 100644
--- a/spec/support/shared_examples/taskable_shared_examples.rb
+++ b/spec/support/shared_examples/taskable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for task state functionality for issues and merge requests.
#
# Requires a context containing:
diff --git a/spec/support/shared_examples/throttled_touch.rb b/spec/support/shared_examples/throttled_touch.rb
index eba990d4037..aaaa590862d 100644
--- a/spec/support/shared_examples/throttled_touch.rb
+++ b/spec/support/shared_examples/throttled_touch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'throttled touch' do
describe '#touch' do
it 'updates the updated_at timestamp' do
diff --git a/spec/support/shared_examples/unique_ip_check_shared_examples.rb b/spec/support/shared_examples/unique_ip_check_shared_examples.rb
index e5c8ac6a004..65d86ddee9e 100644
--- a/spec/support/shared_examples/unique_ip_check_shared_examples.rb
+++ b/spec/support/shared_examples/unique_ip_check_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'unique ips sign in limit' do
include StubENV
before do
diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb
index 4cb6d001b9b..b7ac08372f9 100644
--- a/spec/support/shared_examples/update_invalid_issuable.rb
+++ b/spec/support/shared_examples/update_invalid_issuable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'update invalid issuable' do |klass|
let(:params) do
{
diff --git a/spec/support/shared_examples/updating_mentions_shared_examples.rb b/spec/support/shared_examples/updating_mentions_shared_examples.rb
index 5e3f19ba19e..ef385f94cc2 100644
--- a/spec/support/shared_examples/updating_mentions_shared_examples.rb
+++ b/spec/support/shared_examples/updating_mentions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'updating mentions' do |service_class|
let(:mentioned_user) { create(:user) }
let(:service_class) { service_class }
diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
index 1190863d88e..9263aaff89a 100644
--- a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "matches the method pattern" do |method|
let(:target) { subject }
let(:args) { nil }
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
index 1bd176280c5..5d605dd811b 100644
--- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'with storage' do |store, **stub_params|
before do
subject.object_store = store
diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb
index 25277ccd9aa..16fceddb605 100644
--- a/spec/support/shared_examples/url_validator_examples.rb
+++ b/spec/support/shared_examples/url_validator_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'url validator examples' do |schemes|
let(:validator) { described_class.new(attributes: [:link_url], **options) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index d1a765f27b9..585c458a64e 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'sidekiq/testing/inline'
# If Sidekiq::Testing.inline! is used, SQL transactions done inside
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index 55212355daa..95f0f971787 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.before(:each, :broken_storage) do
allow(Gitlab::GitalyClient).to receive(:call) do
diff --git a/spec/support/test_reports/test_reports_helper.rb b/spec/support/test_reports/test_reports_helper.rb
index 6840fb9a860..6ba50c83b25 100644
--- a/spec/support/test_reports/test_reports_helper.rb
+++ b/spec/support/test_reports/test_reports_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TestReportsHelper
def create_test_case_rspec_success(name = 'test_spec')
Gitlab::Ci::Reports::TestCase.new(
diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/trace/trace_helpers.rb
index c7802bbcb94..9255715ff71 100644
--- a/spec/support/trace/trace_helpers.rb
+++ b/spec/support/trace/trace_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TraceHelpers
def create_legacy_trace(build, content)
File.open(legacy_trace_path(build), 'wb') { |stream| stream.write(content) }
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
index 9ac7e7fc515..32b88edc2df 100644
--- a/spec/support/webmock.rb
+++ b/spec/support/webmock.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'webmock'
require 'webmock/rspec'
diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index 0206928a211..88c4b52b3a6 100644
--- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -12,6 +12,7 @@ describe 'projects/merge_requests/creations/_new_submit.html.haml' do
assign(:hidden_commit_count, 0)
assign(:total_commit_count, merge_request.commits.count)
assign(:project, merge_request.target_project)
+ assign(:target_project, merge_request.target_project)
assign(:mr_presenter, merge_request.present(current_user: merge_request.author))
allow(view).to receive(:can?).and_return(true)
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 529afa03f9c..0a3a46210ed 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -23,6 +23,7 @@ describe 'projects/merge_requests/edit.html.haml' do
before do
assign(:project, project)
+ assign(:target_project, project)
assign(:merge_request, closed_merge_request)
assign(:mr_presenter, closed_merge_request.present(current_user: user))
diff --git a/yarn.lock b/yarn.lock
index 4fa7665b000..7095ff706c2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -996,10 +996,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579"
integrity sha512-hJOmWEs6RkjzyKkb1vc9wwKGZIBIP0coHkxu/KgOoxhBVudpGk4CH7xJ6UuB2TKpb0SEh5CC1CzRZfBYaFhsaA==
-"@gitlab/ui@^5.7.1":
- version "5.7.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.7.1.tgz#e55d04052dd6e50ed1e90676aacc64290d62c0b6"
- integrity sha512-F06/6z6/69LbKIK0PYRDTB/teSPUnF7LijHl4JiuYHXn7Y2/iVoLsAMikhT89RVR84orHPGnw16vtCPjSjBDrA==
+"@gitlab/ui@^5.9.0":
+ version "5.9.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.9.0.tgz#a38b1b57c365608b100b95969ae7a57ce1707542"
+ integrity sha512-cgvEPWVerYZNLqkHjg5dd0VhEDBWj8aNoISZCaGOWI9K4yVtpMPVRUv19o/xYm4vUexfFsG9vg9lBgd+4ZU6Yw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
@@ -8340,7 +8340,7 @@ monaco-editor@^0.15.6:
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.15.6.tgz#d63b3b06f86f803464f003b252627c3eb4a09483"
integrity sha512-JoU9V9k6KqT9R9Tiw1RTU8ohZ+Xnf9DMg6Ktqqw5hILumwmq7xqa/KLXw513uTUsWbhtnHoSJYYR++u3pkyxJg==
-mousetrap@^1.4.6:
+mousetrap@1.4.6:
version "1.4.6"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.4.6.tgz#eaca72e22e56d5b769b7555873b688c3332e390a"
integrity sha1-6spy4i5W1bdpt1VYc7aIwzMuOQo=