summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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/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/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/application_controller.rb2
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb4
-rw-r--r--app/controllers/concerns/group_tree.rb20
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb5
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb11
-rw-r--r--app/finders/group_descendants_finder.rb2
-rw-r--r--app/graphql/types/group_type.rb8
-rw-r--r--app/helpers/groups_helper.rb4
-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/ci/pipeline.rb10
-rw-r--r--app/models/concerns/descendant.rb11
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/deployment_metrics.rb13
-rw-r--r--app/models/group.rb3
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/project.rb25
-rw-r--r--app/policies/group_policy.rb6
-rw-r--r--app/serializers/stage_entity.rb8
-rw-r--r--app/services/ci/process_pipeline_service.rb4
-rw-r--r--app/services/groups/nested_create_service.rb4
-rw-r--r--app/services/groups/transfer_service.rb1
-rw-r--r--app/services/members/destroy_service.rb2
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb17
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb8
-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/groups/settings/_advanced.html.haml25
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml8
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml6
-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/59325-units-are-not-shown-on-the-performance-dashboard-2.yml5
-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/64972-fix-unicorn-workers-metric.yml5
-rw-r--r--changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.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/remove_deployment_metrics_deployment_platform_fallback.yml6
-rw-r--r--config/prometheus/cluster_metrics.yml16
-rw-r--r--config/prometheus/common_metrics.yml42
-rw-r--r--db/migrate/20190709220014_import_common_metrics_y_axis.rb13
-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/administration/logs.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/database_review.md11
-rw-r--r--doc/development/documentation/styleguide.md2
-rw-r--r--doc/development/filtering_by_label.md166
-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/topics/autodevops/index.md86
-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/entities.rb5
-rw-r--r--lib/api/groups.rb5
-rw-r--r--lib/api/job_artifacts.rb4
-rw-r--r--lib/api/validations/types/labels_list.rb2
-rw-r--r--lib/banzai/reference_redactor.rb2
-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/badge/coverage/report.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/object_hierarchy.rb17
-rw-r--r--lib/gitlab/project_authorizations.rb121
-rw-r--r--lib/gitlab/project_authorizations/with_nested_groups.rb125
-rw-r--r--lib/gitlab/project_authorizations/without_nested_groups.rb35
-rw-r--r--lib/gitlab/push_options.rb4
-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/prometheus/pid_provider.rb35
-rw-r--r--locale/gitlab.pot24
-rw-r--r--package.json4
-rw-r--r--qa/qa/page/project/issue/show.rb5
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb21
-rw-r--r--qa/qa/page/project/sub_menus/project.rb4
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb2
-rw-r--r--spec/controllers/concerns/group_tree_spec.rb4
-rw-r--r--spec/controllers/dashboard/groups_controller_spec.rb2
-rw-r--r--spec/controllers/groups/children_controller_spec.rb4
-rw-r--r--spec/controllers/groups/labels_controller_spec.rb4
-rw-r--r--spec/controllers/groups_controller_spec.rb6
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb6
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb6
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb5
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb2
-rw-r--r--spec/factories/abuse_reports.rb2
-rw-r--r--spec/factories/appearances.rb2
-rw-r--r--spec/factories/application_settings.rb2
-rw-r--r--spec/factories/award_emoji.rb2
-rw-r--r--spec/factories/badge.rb2
-rw-r--r--spec/factories/boards.rb2
-rw-r--r--spec/factories/broadcast_messages.rb2
-rw-r--r--spec/factories/chat_names.rb2
-rw-r--r--spec/factories/chat_teams.rb2
-rw-r--r--spec/factories/ci/bridge.rb2
-rw-r--r--spec/factories/ci/build_trace_chunks.rb2
-rw-r--r--spec/factories/ci/build_trace_section_names.rb2
-rw-r--r--spec/factories/ci/builds.rb2
-rw-r--r--spec/factories/ci/group_variables.rb2
-rw-r--r--spec/factories/ci/job_artifacts.rb2
-rw-r--r--spec/factories/ci/pipeline_schedule.rb2
-rw-r--r--spec/factories/ci/pipeline_schedule_variables.rb2
-rw-r--r--spec/factories/ci/pipeline_variables.rb2
-rw-r--r--spec/factories/ci/pipelines.rb2
-rw-r--r--spec/factories/ci/runner_projects.rb2
-rw-r--r--spec/factories/ci/runners.rb2
-rw-r--r--spec/factories/ci/stages.rb2
-rw-r--r--spec/factories/ci/trigger_requests.rb2
-rw-r--r--spec/factories/ci/triggers.rb2
-rw-r--r--spec/factories/ci/variables.rb2
-rw-r--r--spec/factories/clusters/applications/helm.rb2
-rw-r--r--spec/factories/clusters/clusters.rb2
-rw-r--r--spec/factories/clusters/platforms/kubernetes.rb2
-rw-r--r--spec/factories/clusters/providers/gcp.rb2
-rw-r--r--spec/factories/commit_statuses.rb2
-rw-r--r--spec/factories/commits.rb2
-rw-r--r--spec/factories/container_repositories.rb2
-rw-r--r--spec/factories/conversational_development_index_metrics.rb2
-rw-r--r--spec/factories/deploy_keys_projects.rb2
-rw-r--r--spec/factories/deploy_tokens.rb2
-rw-r--r--spec/factories/deployments.rb2
-rw-r--r--spec/factories/emails.rb2
-rw-r--r--spec/factories/environments.rb2
-rw-r--r--spec/factories/events.rb2
-rw-r--r--spec/factories/file_uploaders.rb2
-rw-r--r--spec/factories/fork_network_members.rb2
-rw-r--r--spec/factories/fork_networks.rb2
-rw-r--r--spec/factories/gitaly/commit.rb2
-rw-r--r--spec/factories/gitaly/commit_author.rb2
-rw-r--r--spec/factories/gitaly/tag.rb2
-rw-r--r--spec/factories/gpg_key_subkeys.rb2
-rw-r--r--spec/factories/gpg_keys.rb2
-rw-r--r--spec/factories/gpg_signature.rb2
-rw-r--r--spec/factories/group_custom_attributes.rb2
-rw-r--r--spec/factories/group_members.rb2
-rw-r--r--spec/factories/groups.rb2
-rw-r--r--spec/factories/identities.rb2
-rw-r--r--spec/factories/import_export_uploads.rb2
-rw-r--r--spec/factories/import_states.rb2
-rw-r--r--spec/factories/instance_configuration.rb2
-rw-r--r--spec/factories/internal_ids.rb2
-rw-r--r--spec/factories/issues.rb2
-rw-r--r--spec/factories/keys.rb2
-rw-r--r--spec/factories/label_links.rb2
-rw-r--r--spec/factories/label_priorities.rb2
-rw-r--r--spec/factories/labels.rb2
-rw-r--r--spec/factories/lfs_file_locks.rb2
-rw-r--r--spec/factories/lfs_objects.rb2
-rw-r--r--spec/factories/lfs_objects_projects.rb2
-rw-r--r--spec/factories/lists.rb2
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/factories/merge_requests_closing_issues.rb2
-rw-r--r--spec/factories/milestones.rb2
-rw-r--r--spec/factories/namespaces.rb2
-rw-r--r--spec/factories/notes.rb2
-rw-r--r--spec/factories/notification_settings.rb2
-rw-r--r--spec/factories/oauth_access_grants.rb2
-rw-r--r--spec/factories/oauth_access_tokens.rb2
-rw-r--r--spec/factories/oauth_applications.rb2
-rw-r--r--spec/factories/pages_domains.rb2
-rw-r--r--spec/factories/personal_access_tokens.rb2
-rw-r--r--spec/factories/pool_repositories.rb2
-rw-r--r--spec/factories/programming_languages.rb2
-rw-r--r--spec/factories/project_auto_devops.rb2
-rw-r--r--spec/factories/project_custom_attributes.rb2
-rw-r--r--spec/factories/project_deploy_tokens.rb2
-rw-r--r--spec/factories/project_group_links.rb2
-rw-r--r--spec/factories/project_hooks.rb2
-rw-r--r--spec/factories/project_members.rb2
-rw-r--r--spec/factories/project_statistics.rb2
-rw-r--r--spec/factories/project_wikis.rb2
-rw-r--r--spec/factories/projects.rb2
-rw-r--r--spec/factories/protected_branches.rb2
-rw-r--r--spec/factories/protected_tags.rb2
-rw-r--r--spec/factories/redirect_routes.rb2
-rw-r--r--spec/factories/releases.rb2
-rw-r--r--spec/factories/remote_mirrors.rb2
-rw-r--r--spec/factories/repository_languages.rb2
-rw-r--r--spec/factories/sent_notifications.rb2
-rw-r--r--spec/factories/sequences.rb2
-rw-r--r--spec/factories/service_hooks.rb2
-rw-r--r--spec/factories/services.rb2
-rw-r--r--spec/factories/shards.rb2
-rw-r--r--spec/factories/snippets.rb2
-rw-r--r--spec/factories/spam_logs.rb2
-rw-r--r--spec/factories/subscriptions.rb2
-rw-r--r--spec/factories/system_hooks.rb2
-rw-r--r--spec/factories/system_note_metadata.rb2
-rw-r--r--spec/factories/term_agreements.rb2
-rw-r--r--spec/factories/terms.rb2
-rw-r--r--spec/factories/timelogs.rb2
-rw-r--r--spec/factories/todos.rb2
-rw-r--r--spec/factories/trending_project.rb2
-rw-r--r--spec/factories/u2f_registrations.rb2
-rw-r--r--spec/factories/uploads.rb2
-rw-r--r--spec/factories/user_agent_details.rb2
-rw-r--r--spec/factories/user_callouts.rb2
-rw-r--r--spec/factories/user_custom_attributes.rb2
-rw-r--r--spec/factories/users.rb2
-rw-r--r--spec/factories/users_star_projects.rb2
-rw-r--r--spec/factories/web_hook_log.rb2
-rw-r--r--spec/factories/wiki_directories.rb2
-rw-r--r--spec/factories/wiki_pages.rb2
-rw-r--r--spec/features/dashboard/groups_list_spec.rb6
-rw-r--r--spec/features/groups/empty_states_spec.rb2
-rw-r--r--spec/features/groups/group_settings_spec.rb2
-rw-r--r--spec/features/groups/issues_spec.rb2
-rw-r--r--spec/features/groups/members/list_members_spec.rb4
-rw-r--r--spec/features/groups/share_lock_spec.rb2
-rw-r--r--spec/features/groups/show_spec.rb38
-rw-r--r--spec/features/groups_spec.rb4
-rw-r--r--spec/features/ide_spec.rb2
-rw-r--r--spec/features/import/manifest_import_spec.rb2
-rw-r--r--spec/features/labels_hierarchy_spec.rb2
-rw-r--r--spec/features/markdown/metrics_spec.rb60
-rw-r--r--spec/features/oauth_login_spec.rb1
-rw-r--r--spec/features/projects/artifacts/user_downloads_artifacts_spec.rb4
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb2
-rw-r--r--spec/features/projects/members/invite_group_spec.rb4
-rw-r--r--spec/features/projects/new_project_spec.rb2
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/user_transfers_a_project_spec.rb2
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb2
-rw-r--r--spec/features/projects/user_creates_project_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb12
-rw-r--r--spec/features/projects_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/finders/autocomplete/users_finder_spec.rb2
-rw-r--r--spec/finders/cluster_ancestors_finder_spec.rb4
-rw-r--r--spec/finders/group_descendants_finder_spec.rb6
-rw-r--r--spec/finders/group_members_finder_spec.rb4
-rw-r--r--spec/finders/group_projects_finder_spec.rb12
-rw-r--r--spec/finders/groups_finder_spec.rb2
-rw-r--r--spec/finders/issues_finder_spec.rb2
-rw-r--r--spec/finders/labels_finder_spec.rb6
-rw-r--r--spec/finders/members_finder_spec.rb8
-rw-r--r--spec/finders/merge_requests_finder_spec.rb2
-rw-r--r--spec/finders/todos_finder_spec.rb2
-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/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb6
-rw-r--r--spec/helpers/groups_helper_spec.rb6
-rw-r--r--spec/helpers/namespaces_helper_spec.rb4
-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/banzai/filter/milestone_reference_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb7
-rw-r--r--spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb4
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb16
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/grant_spec.rb2
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb6
-rw-r--r--spec/lib/gitlab/manifest_import/manifest_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb4
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb38
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb3
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb2
-rw-r--r--spec/lib/gitlab/submodule_links_spec.rb47
-rw-r--r--spec/lib/prometheus/pid_provider_spec.rb66
-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/ci/pipeline_spec.rb17
-rw-r--r--spec/models/ci/runner_spec.rb2
-rw-r--r--spec/models/clusters/cluster_spec.rb2
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb2
-rw-r--r--spec/models/concerns/group_descendant_spec.rb6
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/deployment_metrics_spec.rb12
-rw-r--r--spec/models/group_spec.rb28
-rw-r--r--spec/models/members/group_member_spec.rb2
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/models/namespace_spec.rb20
-rw-r--r--spec/models/notification_recipient_spec.rb2
-rw-r--r--spec/models/postgresql/replication_slot_spec.rb2
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_spec.rb101
-rw-r--r--spec/models/todo_spec.rb6
-rw-r--r--spec/models/user_spec.rb38
-rw-r--r--spec/policies/group_member_policy_spec.rb2
-rw-r--r--spec/policies/group_policy_spec.rb59
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb2
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb2
-rw-r--r--spec/requests/api/group_labels_spec.rb4
-rw-r--r--spec/requests/api/groups_spec.rb10
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb2
-rw-r--r--spec/requests/api/members_spec.rb6
-rw-r--r--spec/requests/api/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/openid_connect_spec.rb2
-rw-r--r--spec/serializers/group_child_entity_spec.rb2
-rw-r--r--spec/serializers/group_child_serializer_spec.rb4
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/services/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/groups/auto_devops_service_spec.rb2
-rw-r--r--spec/services/groups/create_service_spec.rb46
-rw-r--r--spec/services/groups/nested_create_service_spec.rb30
-rw-r--r--spec/services/groups/transfer_service_spec.rb17
-rw-r--r--spec/services/groups/update_service_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb4
-rw-r--r--spec/services/members/destroy_service_spec.rb6
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb154
-rw-r--r--spec/services/notification_service_spec.rb50
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb2
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb4
-rw-r--r--spec/spec_helper.rb8
-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.rb8
-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/policies/group_policy_shared_context.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/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/project_latest_successful_build_for_examples.rb63
-rw-r--r--spec/support/shared_examples/reference_parser_shared_examples.rb2
-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/groups/edit.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb2
-rw-r--r--yarn.lock10
619 files changed, 3871 insertions, 1494 deletions
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/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/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/application_controller.rb b/app/controllers/application_controller.rb
index 0c80a276fce..1d55a073f3b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -421,7 +421,7 @@ class ApplicationController < ActionController::Base
end
def manifest_import_enabled?
- Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
+ Gitlab::CurrentSettings.import_sources.include?('manifest')
end
def phabricator_import_enabled?
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/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb
index e9a7d6a3152..d076c62c707 100644
--- a/app/controllers/concerns/group_tree.rb
+++ b/app/controllers/concerns/group_tree.rb
@@ -32,18 +32,14 @@ module GroupTree
def filtered_groups_with_ancestors(groups)
filtered_groups = groups.search(params[:filter]).page(params[:page])
- if Group.supports_nested_objects?
- # We find the ancestors by ID of the search results here.
- # Otherwise the ancestors would also have filters applied,
- # which would cause them not to be preloaded.
- #
- # Pagination needs to be applied before loading the ancestors to
- # make sure ancestors are not cut off by pagination.
- Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
- .base_and_ancestors
- else
- filtered_groups
- end
+ # We find the ancestors by ID of the search results here.
+ # Otherwise the ancestors would also have filters applied,
+ # which would cause them not to be preloaded.
+ #
+ # Pagination needs to be applied before loading the ancestors to
+ # make sure ancestors are not cut off by pagination.
+ Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
+ .base_and_ancestors
end
# rubocop: enable CodeReuse/ActiveRecord
end
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/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 2ef18d900f2..da8a371acaa 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -92,7 +92,10 @@ class Projects::ArtifactsController < Projects::ApplicationController
def build_from_ref
return unless @ref_name
- project.latest_successful_build_for(params[:job], @ref_name)
+ commit = project.commit(@ref_name)
+ return unless commit
+
+ project.latest_successful_build_for_sha(params[:job], commit.id)
end
def artifacts_file
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index 4274c356227..99f4524eec5 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -51,6 +51,6 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
def job_from_ref
return unless @ref_name
- project.latest_successful_build_for(params[:job], @ref_name)
+ project.latest_successful_build_for_ref(params[:job], @ref_name)
end
end
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/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index ec340f38450..4e489a9c930 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -132,8 +132,6 @@ class GroupDescendantsFinder
end
def subgroups
- return Group.none unless Group.supports_nested_objects?
-
# When filtering subgroups, we want to find all matches within the tree of
# descendants to show to the user
groups = if params[:filter]
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 530aecc2bf9..66b41919914 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -14,10 +14,8 @@ module Types
group.avatar_url(only_path: false)
end
- if ::Group.supports_nested_objects?
- field :parent, GroupType,
- null: true,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
- end
+ field :parent, GroupType,
+ null: true,
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 5aed7e313e6..160f9ac4793 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -127,10 +127,6 @@ module GroupsHelper
groups.to_json
end
- def supports_nested_groups?
- Group.supports_nested_objects?
- end
-
private
def get_group_sidebar_links
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/ci/pipeline.rb b/app/models/ci/pipeline.rb
index c2eb51ba100..1c76f401690 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -229,10 +229,12 @@ module Ci
#
# ref - The name (or names) of the branch(es)/tag(s) to limit the list of
# pipelines to.
+ # sha - The commit SHA (or mutliple SHAs) to limit the list of pipelines to.
# limit - This limits a backlog search, default to 100.
- def self.newest_first(ref: nil, limit: 100)
+ def self.newest_first(ref: nil, sha: nil, limit: 100)
relation = order(id: :desc)
relation = relation.where(ref: ref) if ref
+ relation = relation.where(sha: sha) if sha
if limit
ids = relation.limit(limit).select(:id)
@@ -246,10 +248,14 @@ module Ci
newest_first(ref: ref).pluck(:status).first
end
- def self.latest_successful_for(ref)
+ def self.latest_successful_for_ref(ref)
newest_first(ref: ref).success.take
end
+ def self.latest_successful_for_sha(sha)
+ newest_first(sha: sha).success.take
+ end
+
def self.latest_successful_for_refs(refs)
relation = newest_first(ref: refs).success
diff --git a/app/models/concerns/descendant.rb b/app/models/concerns/descendant.rb
deleted file mode 100644
index 4c436522122..00000000000
--- a/app/models/concerns/descendant.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module Descendant
- extend ActiveSupport::Concern
-
- class_methods do
- def supports_nested_objects?
- Gitlab::Database.postgresql?
- end
- 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/deployment_metrics.rb b/app/models/deployment_metrics.rb
index cfe762ca25e..2056c8bc59c 100644
--- a/app/models/deployment_metrics.rb
+++ b/app/models/deployment_metrics.rb
@@ -44,18 +44,7 @@ class DeploymentMetrics
end
end
- # TODO remove fallback case to deployment_platform_cluster.
- # Otherwise we will continue to pay the performance penalty described in
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/63475
- #
- # Removal issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/64105
def cluster_prometheus
- cluster_with_fallback = cluster || deployment_platform_cluster
-
- cluster_with_fallback.application_prometheus if cluster_with_fallback&.application_prometheus_available?
- end
-
- def deployment_platform_cluster
- deployment.environment.deployment_platform&.cluster
+ cluster.application_prometheus if cluster&.application_prometheus_available?
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 26ce2957e9b..74eb556b1b5 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -10,7 +10,6 @@ class Group < Namespace
include Referable
include SelectForProjectAuthorization
include LoadedInGroupList
- include Descendant
include GroupDescendant
include TokenAuthenticatable
include WithUploads
@@ -388,7 +387,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/namespace.rb b/app/models/namespace.rb
index b8d7348268a..058350b16ce 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -332,8 +332,6 @@ class Namespace < ApplicationRecord
end
def force_share_with_group_lock_on_descendants
- return unless Group.supports_nested_objects?
-
# We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
# different table aliases, hence we're just using WHERE IN. Since we have a
diff --git a/app/models/project.rb b/app/models/project.rb
index 0020e423628..cca7da8c49a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -719,16 +719,27 @@ class Project < ApplicationRecord
repository.commits_by(oids: oids)
end
- # ref can't be HEAD, can only be branch/tag name or SHA
- def latest_successful_build_for(job_name, ref = default_branch)
- latest_pipeline = ci_pipelines.latest_successful_for(ref)
+ # ref can't be HEAD, can only be branch/tag name
+ def latest_successful_build_for_ref(job_name, ref = default_branch)
+ return unless ref
+
+ latest_pipeline = ci_pipelines.latest_successful_for_ref(ref)
+ return unless latest_pipeline
+
+ latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name)
+ end
+
+ def latest_successful_build_for_sha(job_name, sha)
+ return unless sha
+
+ latest_pipeline = ci_pipelines.latest_successful_for_sha(sha)
return unless latest_pipeline
latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name)
end
- def latest_successful_build_for!(job_name, ref = default_branch)
- latest_successful_build_for(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
+ def latest_successful_build_for_ref!(job_name, ref = default_branch)
+ latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
end
def merge_base_commit(first_commit_id, second_commit_id)
@@ -1503,12 +1514,12 @@ class Project < ApplicationRecord
end
@latest_successful_pipeline_for_default_branch =
- ci_pipelines.latest_successful_for(default_branch)
+ ci_pipelines.latest_successful_for_ref(default_branch)
end
def latest_successful_pipeline_for(ref = nil)
if ref && ref != default_branch
- ci_pipelines.latest_successful_for(ref)
+ ci_pipelines.latest_successful_for_ref(ref)
else
latest_successful_pipeline_for_default_branch
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 0add8bfad31..84b1873c05d 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -16,8 +16,6 @@ class GroupPolicy < BasePolicy
condition(:maintainer) { access_level >= GroupMember::MAINTAINER }
condition(:reporter) { access_level >= GroupMember::REPORTER }
- condition(:nested_groups_supported, scope: :global) { Group.supports_nested_objects? }
-
condition(:has_parent, scope: :subject) { @subject.has_parent? }
condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? }
condition(:parent_share_with_group_locked, scope: :subject) { @subject.parent&.share_with_group_lock? }
@@ -108,8 +106,8 @@ class GroupPolicy < BasePolicy
enable :read_nested_project_resources
end
- rule { owner & nested_groups_supported }.enable :create_subgroup
- rule { maintainer & maintainer_can_create_group & nested_groups_supported }.enable :create_subgroup
+ rule { owner }.enable :create_subgroup
+ rule { maintainer & maintainer_can_create_group }.enable :create_subgroup
rule { public_group | logged_in_viewable }.enable :view_globally
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/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb
index 01bd685712b..a51ac9aa593 100644
--- a/app/services/groups/nested_create_service.rb
+++ b/app/services/groups/nested_create_service.rb
@@ -18,10 +18,6 @@ module Groups
return namespace
end
- if group_path.include?('/') && !Group.supports_nested_objects?
- raise 'Nested groups are not supported on MySQL'
- end
-
create_group_path
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 98e7c311572..fe7e07ef9f0 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -43,7 +43,6 @@ module Groups
def ensure_allowed_transfer
raise_transfer_error(:group_is_already_root) if group_is_already_root?
- raise_transfer_error(:database_not_supported) unless Group.supports_nested_objects?
raise_transfer_error(:same_parent_as_current) if same_parent?
raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index c8d5e563cd8..0164760920f 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -31,7 +31,7 @@ module Members
return unless member.is_a?(GroupMember) && member.user && member.group
delete_project_members(member)
- delete_subgroup_members(member) if Group.supports_nested_objects?
+ delete_subgroup_members(member)
end
def delete_project_members(member)
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/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 4a26d2be2af..ae67b4f5256 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -102,13 +102,7 @@ module Users
end
def fresh_authorizations
- klass = if Group.supports_nested_objects?
- Gitlab::ProjectAuthorizations::WithNestedGroups
- else
- Gitlab::ProjectAuthorizations::WithoutNestedGroups
- end
-
- klass.new(user).calculate
+ Gitlab::ProjectAuthorizations.new(user).calculate
end
end
end
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/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml
index 5d211d0e186..d1eb6478997 100644
--- a/app/views/groups/settings/_advanced.html.haml
+++ b/app/views/groups/settings/_advanced.html.haml
@@ -23,20 +23,19 @@
= f.submit 'Change group path', class: 'btn btn-warning'
-- if supports_nested_groups?
- .sub-section
- %h4.warning-title Transfer group
- = form_for @group, url: transfer_group_path(@group), method: :put do |f|
- .form-group
- = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group) } })
- = hidden_field_tag 'new_parent_group_id'
+.sub-section
+ %h4.warning-title Transfer group
+ = form_for @group, url: transfer_group_path(@group), method: :put do |f|
+ .form-group
+ = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group) } })
+ = hidden_field_tag 'new_parent_group_id'
- %ul
- %li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}.
- %li You can only transfer the group to a group you manage.
- %li You will need to update your local repositories to point to the new location.
- %li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
- = f.submit 'Transfer group', class: 'btn btn-warning'
+ %ul
+ %li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}.
+ %li You can only transfer the group to a group you manage.
+ %li You will need to update your local repositories to point to the new location.
+ %li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
+ = f.submit 'Transfer group', class: 'btn btn-warning'
.sub-section
%h4.danger-title= _('Remove group')
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index d1634eb62c0..92dd558885a 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -9,7 +9,7 @@
= @project.name
%ul.sidebar-top-level-items
= nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do
- = link_to project_path(@project), class: 'shortcuts-project qa-link-project' do
+ = link_to project_path(@project), class: 'shortcuts-project', data: { qa_selector: 'project_link' } do
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
@@ -26,7 +26,7 @@
%span= _('Details')
= nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity qa-activity-link' do
+ = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity', data: { qa_selector: 'activity_link' } do
%span= _('Activity')
- if project_nav_tab?(:releases)
@@ -146,7 +146,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
- = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests qa-merge-requests-link' do
+ = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests', data: { qa_selector: 'merge_requests_link' } do
.nav-icon-container
= sprite_icon('git-merge')
%span.nav-item-name#js-onboarding-mr-link
@@ -277,7 +277,7 @@
- if project_nav_tab? :wiki
- wiki_url = project_wiki_path(@project, :home)
= nav_link(controller: :wikis) do
- = link_to wiki_url, class: 'shortcuts-wiki qa-wiki-link' do
+ = link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do
.nav-icon-container
= sprite_icon('book')
%span.nav-item-name
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index fe74dc122c3..04b77fb987a 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -8,7 +8,7 @@
.card.auto-devops-card
.card-body
.form-check
- = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: auto_devops_enabled
+ = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: auto_devops_enabled, data: { qa_selector: 'enable_autodevops_checkbox' }
= form.label :enabled, class: 'form-check-label' do
%strong= s_('CICD|Default to Auto DevOps pipeline')
- if auto_devops_enabled
@@ -42,4 +42,4 @@
= s_('CICD|Automatic deployment to staging, manual deployment to production')
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production'), target: '_blank'
- = f.submit _('Save changes'), class: "btn btn-success prepend-top-15"
+ = f.submit _('Save changes'), class: "btn btn-success prepend-top-15", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 5e3e1076c2c..293f0a241eb 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -16,7 +16,7 @@
.settings-content
= render 'form'
-%section.qa-autodevops-settings.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded) }
+%section.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded), data: { qa_selector: 'autodevops_settings_content' } }
.settings-header
%h4
= s_('CICD|Auto DevOps')
@@ -30,7 +30,7 @@
= render_if_exists 'projects/settings/ci_cd/protected_environments', expanded: expanded
-%section.qa-runners-settings.settings.no-animate#js-runners-settings{ class: ('expanded' if expanded) }
+%section.settings.no-animate#js-runners-settings{ class: ('expanded' if expanded), data: { qa_selector: 'runners_settings_content' } }
.settings-header
%h4
= _("Runners")
@@ -41,7 +41,7 @@
.settings-content
= render 'projects/runners/index'
-%section.qa-variables-settings.settings.no-animate{ class: ('expanded' if expanded) }
+%section.settings.no-animate{ class: ('expanded' if expanded), data: { qa_selector: 'variables_settings_content' } }
.settings-header
= render 'ci/variables/header', expanded: expanded
.settings-content
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/59325-units-are-not-shown-on-the-performance-dashboard-2.yml b/changelogs/unreleased/59325-units-are-not-shown-on-the-performance-dashboard-2.yml
new file mode 100644
index 00000000000..38cfa0f273e
--- /dev/null
+++ b/changelogs/unreleased/59325-units-are-not-shown-on-the-performance-dashboard-2.yml
@@ -0,0 +1,5 @@
+---
+title: 'fix: updates to include units for the y axis label'
+merge_request: 30330
+author:
+type: fixed
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/64972-fix-unicorn-workers-metric.yml b/changelogs/unreleased/64972-fix-unicorn-workers-metric.yml
new file mode 100644
index 00000000000..f80111f0bd9
--- /dev/null
+++ b/changelogs/unreleased/64972-fix-unicorn-workers-metric.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pid discovery for Unicorn processes in `PidProvider`
+merge_request: 31056
+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/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/remove_deployment_metrics_deployment_platform_fallback.yml b/changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml
new file mode 100644
index 00000000000..d32cbd1d8e0
--- /dev/null
+++ b/changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml
@@ -0,0 +1,6 @@
+---
+title: Remove incorrect fallback when determining which cluster to use when retrieving
+ MR performance metrics
+merge_request: 31126
+author:
+type: changed
diff --git a/config/prometheus/cluster_metrics.yml b/config/prometheus/cluster_metrics.yml
index 3df76b0974f..f2a41e4c337 100644
--- a/config/prometheus/cluster_metrics.yml
+++ b/config/prometheus/cluster_metrics.yml
@@ -2,12 +2,12 @@
priority: 1
metrics:
- title: "CPU Usage"
- y_label: "CPU"
+ y_label: "CPU (cores)"
required_metrics: ['container_cpu_usage_seconds_total']
weight: 1
queries:
- query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
- label: Usage
+ label: Usage (cores)
unit: "cores"
appearance:
line:
@@ -15,7 +15,7 @@
area:
opacity: 0
- query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
- label: Requested
+ label: Requested (cores)
unit: "cores"
appearance:
line:
@@ -23,7 +23,7 @@
area:
opacity: 0
- query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
- label: Capacity
+ label: Capacity (cores)
unit: "cores"
appearance:
line:
@@ -32,12 +32,12 @@
area:
opacity: 0
- title: "Memory usage"
- y_label: "Memory"
+ y_label: "Memory (GiB)"
required_metrics: ['container_memory_usage_bytes']
weight: 1
queries:
- query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
- label: Usage
+ label: Usage (GiB)
unit: "GiB"
appearance:
line:
@@ -45,7 +45,7 @@
area:
opacity: 0
- query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
- label: Requested
+ label: Requested (GiB)
unit: "GiB"
appearance:
line:
@@ -53,7 +53,7 @@
area:
opacity: 0
- query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
- label: Capacity
+ label: Capacity (GiB)
unit: "GiB"
appearance:
line:
diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml
index f9ce5a6f365..32475ef8380 100644
--- a/config/prometheus/common_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -21,16 +21,16 @@ panel_groups:
metrics:
- id: response_metrics_nginx_ingress_latency_pod_average
query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
- label: Pod average
+ label: Pod average (ms)
unit: ms
- title: "HTTP Error Rate"
type: "area-chart"
- y_label: "HTTP Errors"
+ y_label: "HTTP Errors (%)"
weight: 1
metrics:
- id: response_metrics_nginx_ingress_http_error_rate
query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100'
- label: 5xx Errors
+ label: 5xx Errors (%)
unit: "%"
# NGINX Ingress metrics for post-0.16.0 versions
- group: Response metrics (NGINX Ingress)
@@ -52,16 +52,16 @@ panel_groups:
metrics:
- id: response_metrics_nginx_ingress_16_latency_pod_average
query_range: 'sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000'
- label: Pod average
+ label: Pod average (ms)
unit: ms
- title: "HTTP Error Rate"
type: "area-chart"
- y_label: "HTTP Errors"
+ y_label: "HTTP Errors (%)"
weight: 1
metrics:
- id: response_metrics_nginx_ingress_16_http_error_rate
query_range: 'sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100'
- label: 5xx Errors
+ label: 5xx Errors (%)
unit: "%"
- group: Response metrics (HA Proxy)
priority: 10
@@ -82,7 +82,7 @@ panel_groups:
metrics:
- id: response_metrics_ha_proxy_http_error_rate
query_range: 'sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m]))'
- label: HTTP Errors
+ label: HTTP Errors (%)
unit: "%"
- group: Response metrics (AWS ELB)
priority: 10
@@ -94,7 +94,7 @@ panel_groups:
metrics:
- id: response_metrics_aws_elb_throughput_requests
query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
- label: Total
+ label: Total (req/sec)
unit: req / sec
- title: "Latency"
type: "area-chart"
@@ -103,7 +103,7 @@ panel_groups:
metrics:
- id: response_metrics_aws_elb_latency_average
query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000'
- label: Average
+ label: Average (ms)
unit: ms
- title: "HTTP Error Rate"
type: "area-chart"
@@ -112,7 +112,7 @@ panel_groups:
metrics:
- id: response_metrics_aws_elb_http_error_rate
query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})'
- label: HTTP Errors
+ label: HTTP Errors (%)
unit: "%"
- group: Response metrics (NGINX)
priority: 10
@@ -133,9 +133,9 @@ panel_groups:
metrics:
- id: response_metrics_nginx_latency
query_range: 'avg(nginx_server_requestMsec{%{environment_filter}})'
- label: Upstream
+ label: Upstream (ms)
unit: ms
- - title: "HTTP Error Rate"
+ - title: "HTTP Error Rate (Errors / Sec)"
type: "area-chart"
y_label: "HTTP 500 Errors / Sec"
weight: 1
@@ -149,12 +149,12 @@ panel_groups:
panels:
- title: "Memory Usage (Total)"
type: "area-chart"
- y_label: "Total Memory Used"
+ y_label: "Total Memory Used (GB)"
weight: 4
metrics:
- id: system_metrics_kubernetes_container_memory_total
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024'
- label: Total
+ label: Total (GB)
unit: GB
- title: "Core Usage (Total)"
type: "area-chart"
@@ -163,25 +163,25 @@ panel_groups:
metrics:
- id: system_metrics_kubernetes_container_cores_total
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)'
- label: Total
+ label: Total (cores)
unit: "cores"
- title: "Memory Usage (Pod average)"
type: "area-chart"
- y_label: "Memory Used per Pod"
+ y_label: "Memory Used per Pod (MB)"
weight: 2
metrics:
- id: system_metrics_kubernetes_container_memory_average
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
- label: Pod average
+ label: Pod average (MB)
unit: MB
- title: "Canary: Memory Usage (Pod Average)"
type: "area-chart"
- y_label: "Memory Used per Pod"
+ y_label: "Memory Used per Pod (MB)"
weight: 2
metrics:
- id: system_metrics_kubernetes_container_memory_average_canary
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
- label: Pod average
+ label: Pod average (MB)
unit: MB
track: canary
- title: "Core Usage (Pod Average)"
@@ -191,7 +191,7 @@ panel_groups:
metrics:
- id: system_metrics_kubernetes_container_core_usage
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
- label: Pod average
+ label: Pod average (cores)
unit: "cores"
- title: "Canary: Core Usage (Pod Average)"
type: "area-chart"
@@ -200,7 +200,7 @@ panel_groups:
metrics:
- id: system_metrics_kubernetes_container_core_usage_canary
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
- label: Pod average
+ label: Pod average (cores)
unit: "cores"
track: canary
- title: "Knative function invocations"
diff --git a/db/migrate/20190709220014_import_common_metrics_y_axis.rb b/db/migrate/20190709220014_import_common_metrics_y_axis.rb
new file mode 100644
index 00000000000..89ecf32ecc1
--- /dev/null
+++ b/db/migrate/20190709220014_import_common_metrics_y_axis.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ImportCommonMetricsYAxis < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def up
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
+ end
+
+ def down
+ # no-op
+ 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/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/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/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/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/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/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 296ab63166f..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:
@@ -721,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
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/entities.rb b/lib/api/entities.rb
index 10b4f8934d7..2e78331df6c 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -366,10 +366,7 @@ module API
end
expose :request_access_enabled
expose :full_name, :full_path
-
- if ::Group.supports_nested_objects?
- expose :parent_id
- end
+ expose :parent_id
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index ec1020c7c78..f545f33c06b 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -114,10 +114,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
-
- if ::Group.supports_nested_objects?
- optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
- end
+ optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
use :optional_params
end
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index e7fed55170e..b35aa952f81 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -27,7 +27,7 @@ module API
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- latest_build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
+ latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
present_carrierwave_file!(latest_build.artifacts_file)
end
@@ -45,7 +45,7 @@ module API
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
+ build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
path = Gitlab::Ci::Build::Artifacts::Path
.new(params[:artifact_path])
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/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/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
index 7f7cc62c8ef..15cccc6f287 100644
--- a/lib/gitlab/badge/coverage/report.rb
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -14,7 +14,7 @@ module Gitlab
@ref = ref
@job = job
- @pipeline = @project.ci_pipelines.latest_successful_for(@ref)
+ @pipeline = @project.ci_pipelines.latest_successful_for_ref(@ref)
end
def entity
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/object_hierarchy.rb b/lib/gitlab/object_hierarchy.rb
index 38b32770e90..c06f106ffe1 100644
--- a/lib/gitlab/object_hierarchy.rb
+++ b/lib/gitlab/object_hierarchy.rb
@@ -32,11 +32,6 @@ module Gitlab
# Returns the maximum depth starting from the base
# A base object with no children has a maximum depth of `1`
def max_descendants_depth
- unless hierarchy_supported?
- # This makes the return value consistent with the case where hierarchy is supported
- return descendants_base.exists? ? 1 : nil
- end
-
base_and_descendants(with_depth: true).maximum(DEPTH_COLUMN)
end
@@ -66,8 +61,6 @@ module Gitlab
# each parent.
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors(upto: nil, hierarchy_order: nil)
- return ancestors_base unless hierarchy_supported?
-
recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(model.all)
recursive_query = recursive_query.order(depth: hierarchy_order) if hierarchy_order
@@ -81,10 +74,6 @@ module Gitlab
# When `with_depth` is `true`, a `depth` column is included where it starts with `1` for the base objects
# and incremented as we go down the descendant tree
def base_and_descendants(with_depth: false)
- unless hierarchy_supported?
- return with_depth ? descendants_base.select("1 as #{DEPTH_COLUMN}", objects_table[Arel.star]) : descendants_base
- end
-
read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(model.all))
end
@@ -112,8 +101,6 @@ module Gitlab
# If nested objects are not supported, ancestors_base is returned.
# rubocop: disable CodeReuse/ActiveRecord
def all_objects
- return ancestors_base unless hierarchy_supported?
-
ancestors = base_and_ancestors_cte
descendants = base_and_descendants_cte
@@ -135,10 +122,6 @@ module Gitlab
private
- def hierarchy_supported?
- Gitlab::Database.postgresql?
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors)
diff --git a/lib/gitlab/project_authorizations.rb b/lib/gitlab/project_authorizations.rb
new file mode 100644
index 00000000000..a9270cd536e
--- /dev/null
+++ b/lib/gitlab/project_authorizations.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+# This class relies on Common Table Expressions to efficiently get all data,
+# including data for nested groups.
+module Gitlab
+ class ProjectAuthorizations
+ attr_reader :user
+
+ # user - The User object for which to calculate the authorizations.
+ def initialize(user)
+ @user = user
+ end
+
+ def calculate
+ cte = recursive_cte
+ cte_alias = cte.table.alias(Group.table_name)
+ projects = Project.arel_table
+ links = ProjectGroupLink.arel_table
+
+ relations = [
+ # The project a user has direct access to.
+ user.projects.select_for_project_authorization,
+
+ # The personal projects of the user.
+ user.personal_projects.select_as_maintainer_for_project_authorization,
+
+ # Projects that belong directly to any of the groups the user has
+ # access to.
+ Namespace
+ .unscoped
+ .select([alias_as_column(projects[:id], 'project_id'),
+ cte_alias[:access_level]])
+ .from(cte_alias)
+ .joins(:projects),
+
+ # Projects shared with any of the namespaces the user has access to.
+ Namespace
+ .unscoped
+ .select([
+ links[:project_id],
+ least(cte_alias[:access_level], links[:group_access], 'access_level')
+ ])
+ .from(cte_alias)
+ .joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
+ .joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
+ .joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
+ .where('p_ns.share_with_group_lock IS FALSE')
+ ]
+
+ ProjectAuthorization
+ .unscoped
+ .with
+ .recursive(cte.to_arel)
+ .select_from_union(relations)
+ end
+
+ private
+
+ # Builds a recursive CTE that gets all the groups the current user has
+ # access to, including any nested groups.
+ def recursive_cte
+ cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
+ members = Member.arel_table
+ namespaces = Namespace.arel_table
+
+ # Namespaces the user is a member of.
+ cte << user.groups
+ .select([namespaces[:id], members[:access_level]])
+ .except(:order)
+
+ # Sub groups of any groups the user is a member of.
+ cte << Group.select([
+ namespaces[:id],
+ greatest(members[:access_level], cte.table[:access_level], 'access_level')
+ ])
+ .joins(join_cte(cte))
+ .joins(join_members)
+ .except(:order)
+
+ cte
+ end
+
+ # Builds a LEFT JOIN to join optional memberships onto the CTE.
+ def join_members
+ members = Member.arel_table
+ namespaces = Namespace.arel_table
+
+ cond = members[:source_id]
+ .eq(namespaces[:id])
+ .and(members[:source_type].eq('Namespace'))
+ .and(members[:requested_at].eq(nil))
+ .and(members[:user_id].eq(user.id))
+
+ Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
+ end
+
+ # Builds an INNER JOIN to join namespaces onto the CTE.
+ def join_cte(cte)
+ namespaces = Namespace.arel_table
+ cond = cte.table[:id].eq(namespaces[:parent_id])
+
+ Arel::Nodes::InnerJoin.new(cte.table, Arel::Nodes::On.new(cond))
+ end
+
+ def greatest(left, right, column_alias)
+ sql_function('GREATEST', [left, right], column_alias)
+ end
+
+ def least(left, right, column_alias)
+ sql_function('LEAST', [left, right], column_alias)
+ end
+
+ def sql_function(name, args, column_alias)
+ alias_as_column(Arel::Nodes::NamedFunction.new(name, args), column_alias)
+ end
+
+ def alias_as_column(value, alias_to)
+ Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to))
+ end
+ end
+end
diff --git a/lib/gitlab/project_authorizations/with_nested_groups.rb b/lib/gitlab/project_authorizations/with_nested_groups.rb
deleted file mode 100644
index 2372a316ab0..00000000000
--- a/lib/gitlab/project_authorizations/with_nested_groups.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module ProjectAuthorizations
- # Calculating new project authorizations when supporting nested groups.
- #
- # This class relies on Common Table Expressions to efficiently get all data,
- # including data for nested groups. As a result this class can only be used
- # on PostgreSQL.
- class WithNestedGroups
- attr_reader :user
-
- # user - The User object for which to calculate the authorizations.
- def initialize(user)
- @user = user
- end
-
- def calculate
- cte = recursive_cte
- cte_alias = cte.table.alias(Group.table_name)
- projects = Project.arel_table
- links = ProjectGroupLink.arel_table
-
- relations = [
- # The project a user has direct access to.
- user.projects.select_for_project_authorization,
-
- # The personal projects of the user.
- user.personal_projects.select_as_maintainer_for_project_authorization,
-
- # Projects that belong directly to any of the groups the user has
- # access to.
- Namespace
- .unscoped
- .select([alias_as_column(projects[:id], 'project_id'),
- cte_alias[:access_level]])
- .from(cte_alias)
- .joins(:projects),
-
- # Projects shared with any of the namespaces the user has access to.
- Namespace
- .unscoped
- .select([links[:project_id],
- least(cte_alias[:access_level],
- links[:group_access],
- 'access_level')])
- .from(cte_alias)
- .joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
- .joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
- .joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
- .where('p_ns.share_with_group_lock IS FALSE')
- ]
-
- ProjectAuthorization
- .unscoped
- .with
- .recursive(cte.to_arel)
- .select_from_union(relations)
- end
-
- private
-
- # Builds a recursive CTE that gets all the groups the current user has
- # access to, including any nested groups.
- def recursive_cte
- cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
- members = Member.arel_table
- namespaces = Namespace.arel_table
-
- # Namespaces the user is a member of.
- cte << user.groups
- .select([namespaces[:id], members[:access_level]])
- .except(:order)
-
- # Sub groups of any groups the user is a member of.
- cte << Group.select([namespaces[:id],
- greatest(members[:access_level],
- cte.table[:access_level], 'access_level')])
- .joins(join_cte(cte))
- .joins(join_members)
- .except(:order)
-
- cte
- end
-
- # Builds a LEFT JOIN to join optional memberships onto the CTE.
- def join_members
- members = Member.arel_table
- namespaces = Namespace.arel_table
-
- cond = members[:source_id]
- .eq(namespaces[:id])
- .and(members[:source_type].eq('Namespace'))
- .and(members[:requested_at].eq(nil))
- .and(members[:user_id].eq(user.id))
-
- Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
- end
-
- # Builds an INNER JOIN to join namespaces onto the CTE.
- def join_cte(cte)
- namespaces = Namespace.arel_table
- cond = cte.table[:id].eq(namespaces[:parent_id])
-
- Arel::Nodes::InnerJoin.new(cte.table, Arel::Nodes::On.new(cond))
- end
-
- def greatest(left, right, column_alias)
- sql_function('GREATEST', [left, right], column_alias)
- end
-
- def least(left, right, column_alias)
- sql_function('LEAST', [left, right], column_alias)
- end
-
- def sql_function(name, args, column_alias)
- alias_as_column(Arel::Nodes::NamedFunction.new(name, args), column_alias)
- end
-
- def alias_as_column(value, alias_to)
- Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to))
- end
- end
- end
-end
diff --git a/lib/gitlab/project_authorizations/without_nested_groups.rb b/lib/gitlab/project_authorizations/without_nested_groups.rb
deleted file mode 100644
index 50b41b17649..00000000000
--- a/lib/gitlab/project_authorizations/without_nested_groups.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module ProjectAuthorizations
- # Calculating new project authorizations when not supporting nested groups.
- class WithoutNestedGroups
- attr_reader :user
-
- # user - The User object for which to calculate the authorizations.
- def initialize(user)
- @user = user
- end
-
- def calculate
- relations = [
- # Projects the user is a direct member of
- user.projects.select_for_project_authorization,
-
- # Personal projects
- user.personal_projects.select_as_maintainer_for_project_authorization,
-
- # Projects of groups the user is a member of
- user.groups_projects.select_for_project_authorization,
-
- # Projects shared with groups the user is a member of
- user.groups.joins(:shared_projects).select_for_project_authorization
- ]
-
- ProjectAuthorization
- .unscoped
- .select_from_union(relations)
- end
- end
- 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/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/prometheus/pid_provider.rb b/lib/prometheus/pid_provider.rb
index c92522c73c5..e0f7e7e0a9e 100644
--- a/lib/prometheus/pid_provider.rb
+++ b/lib/prometheus/pid_provider.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require 'prometheus/client/support/unicorn'
-
module Prometheus
module PidProvider
extend self
@@ -10,29 +8,38 @@ module Prometheus
if Sidekiq.server?
'sidekiq'
elsif defined?(Unicorn::Worker)
- "unicorn_#{unicorn_worker_id}"
+ unicorn_worker_id
elsif defined?(::Puma)
- "puma_#{puma_worker_id}"
+ puma_worker_id
else
- "process_#{Process.pid}"
+ unknown_process_id
end
end
private
- # This is not fully accurate as we don't really know if the nil returned
- # is actually means we're on master or not.
- # Follow up issue was created to address this problem and
- # to introduce more structrured approach to a current process discovery:
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/64740
def unicorn_worker_id
- ::Prometheus::Client::Support::Unicorn.worker_id || 'master'
+ if matches = process_name.match(/unicorn.*worker\[([0-9]+)\]/)
+ "unicorn_#{matches[1]}"
+ elsif process_name =~ /unicorn/
+ "unicorn_master"
+ else
+ unknown_process_id
+ end
end
- # See the comment for #unicorn_worker_id
def puma_worker_id
- match = process_name.match(/cluster worker ([0-9]+):/)
- match ? match[1] : 'master'
+ if matches = process_name.match(/puma.*cluster worker ([0-9]+):/)
+ "puma_#{matches[1]}"
+ elsif process_name =~ /puma/
+ "puma_master"
+ else
+ unknown_process_id
+ end
+ end
+
+ def unknown_process_id
+ "process_#{Process.pid}"
end
def process_name
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 996f082f197..114d245b688 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2383,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 ""
@@ -2620,6 +2623,9 @@ msgstr ""
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
+msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
+msgstr ""
+
msgid "ClusterIntegration|Prometheus"
msgstr ""
@@ -2728,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 ""
@@ -3932,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 ""
@@ -6373,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 ""
@@ -13305,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/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index b8c5c563da6..ae826fb3a32 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -8,33 +8,32 @@ module QA
include Common
view 'app/views/projects/settings/ci_cd/show.html.haml' do
- element :autodevops_settings
- element :runners_settings
- element :variables_settings
+ element :autodevops_settings_content
+ element :runners_settings_content
+ element :variables_settings_content
end
view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
- element :enable_auto_devops_field, 'check_box :enabled' # rubocop:disable QA/ElementWithPattern
- element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')" # rubocop:disable QA/ElementWithPattern
- element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern
+ element :enable_autodevops_checkbox
+ element :save_changes_button
end
def expand_runners_settings(&block)
- expand_section(:runners_settings) do
+ expand_section(:runners_settings_content) do
Settings::Runners.perform(&block)
end
end
def expand_ci_variables(&block)
- expand_section(:variables_settings) do
+ expand_section(:variables_settings_content) do
Settings::CiVariables.perform(&block)
end
end
def enable_auto_devops
- expand_section(:autodevops_settings) do
- check 'Default to Auto DevOps pipeline'
- click_on 'Save changes'
+ expand_section(:autodevops_settings_content) do
+ check_element :enable_autodevops_checkbox
+ click_element :save_changes_button
end
end
end
diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb
index 5e0ee3c274a..6f1bc131f84 100644
--- a/qa/qa/page/project/sub_menus/project.rb
+++ b/qa/qa/page/project/sub_menus/project.rb
@@ -10,7 +10,7 @@ module QA
def self.included(base)
base.class_eval do
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
- element :link_project
+ element :project_link
end
end
end
@@ -18,7 +18,7 @@ module QA
def click_project
retry_on_exception do
within_sidebar do
- click_element(:link_project)
+ click_element(:project_link)
end
end
end
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index 0db58fbefc1..d54f7ad33cf 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -85,7 +85,7 @@ describe Boards::IssuesController do
expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
end
- it 'avoids N+1 database queries when adding a subgroup, project, and issue', :nested_groups do
+ it 'avoids N+1 database queries when adding a subgroup, project, and issue' do
create(:project, group: sub_group_1)
create(:labeled_issue, project: project, labels: [development])
control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb
index aa3cd690e3f..835c3d9b3af 100644
--- a/spec/controllers/concerns/group_tree_spec.rb
+++ b/spec/controllers/concerns/group_tree_spec.rb
@@ -30,7 +30,7 @@ describe GroupTree do
expect(assigns(:groups)).to contain_exactly(other_group)
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
it 'only renders root groups when no parent was given' do
create(:group, :public, parent: group)
@@ -85,7 +85,7 @@ describe GroupTree do
expect(json_response.first['id']).to eq(group.id)
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
it 'expands the tree when filtering' do
subgroup = create(:group, :public, parent: group, name: 'filter')
diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb
index 48373d29412..20a0951423b 100644
--- a/spec/controllers/dashboard/groups_controller_spec.rb
+++ b/spec/controllers/dashboard/groups_controller_spec.rb
@@ -26,7 +26,7 @@ describe Dashboard::GroupsController do
expect(assigns(:groups)).to contain_exactly(member_of_group)
end
- context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do
+ context 'when rendering an expanded hierarchy with public groups you are not a member of' do
let!(:top_level_result) { create(:group, name: 'chef-top') }
let!(:top_level_a) { create(:group, name: 'top-a') }
let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) }
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index 02fb971bd9a..bced300a24c 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -46,7 +46,7 @@ describe Groups::ChildrenController do
end
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
let!(:public_subgroup) { create(:group, :public, parent: group) }
let!(:private_subgroup) { create(:group, :private, parent: group) }
let!(:public_project) { create(:project, :public, namespace: group) }
@@ -292,7 +292,7 @@ describe Groups::ChildrenController do
end
end
- context 'with subgroups and projects', :nested_groups do
+ context 'with subgroups and projects' do
let!(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) }
let!(:other_subgroup) { create(:group, :public, parent: group) }
let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) }
diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb
index 3cc6fc6f066..98a4c50fc49 100644
--- a/spec/controllers/groups/labels_controller_spec.rb
+++ b/spec/controllers/groups/labels_controller_spec.rb
@@ -24,7 +24,7 @@ describe Groups::LabelsController do
expect(label_ids).to match_array([label_1.title, group_label_1.title])
end
- context 'with ancestor group', :nested_groups do
+ context 'with ancestor group' do
set(:subgroup) { create(:group, parent: group) }
set(:subgroup_label_1) { create(:group_label, group: subgroup, title: 'subgroup_label_1') }
@@ -32,7 +32,7 @@ describe Groups::LabelsController do
subgroup.add_owner(user)
end
- it 'returns ancestor group labels', :nested_groups do
+ it 'returns ancestor group labels' do
get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json
label_ids = json_response.map {|label| label['title']}
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index d2faef5b12b..404e61c5271 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -89,7 +89,7 @@ describe GroupsController do
end
describe 'GET #new' do
- context 'when creating subgroups', :nested_groups do
+ context 'when creating subgroups' do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
before do
@@ -166,7 +166,7 @@ describe GroupsController do
end
end
- context 'when creating subgroups', :nested_groups do
+ context 'when creating subgroups' do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
context 'and logged in as Owner' do
@@ -584,7 +584,7 @@ describe GroupsController do
end
end
- describe 'PUT transfer', :postgresql do
+ describe 'PUT transfer' do
before do
sign_in(user)
end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 64a66502732..38388c21749 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -231,7 +231,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen an existing nested namespace and name for the project', :postgresql do
+ context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
@@ -250,7 +250,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -281,7 +281,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 5af7572e74e..e465eca6c71 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -197,7 +197,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen an existing nested namespace for the project', :postgresql do
+ context 'user has chosen an existing nested namespace for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
@@ -215,7 +215,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen a non-existent nested namespaces for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -246,7 +246,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index f11880122b1..fa71d9b61b1 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -885,10 +885,9 @@ describe Projects::MergeRequestsController do
environment2 = create(:environment, project: forked)
create(:deployment, :succeed, environment: environment2, sha: sha, ref: 'master', deployable: build)
- # TODO address the last 11 queries
+ # TODO address the last 5 queries
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/63952 (5 queries)
- # And https://gitlab.com/gitlab-org/gitlab-ce/issues/64105 (6 queries)
- leeway = 11
+ leeway = 5
expect { get_ci_environments_status }.not_to exceed_all_query_limit(control_count + leeway)
end
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 767cee7d54a..9b2025b836c 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -115,7 +115,7 @@ describe Projects::MilestonesController do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:subgroup) { create(:group, :public, parent: group) }
let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index 021971ac421..578af9ed895 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :abuse_report do
reporter factory: :user
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
index dd5129229d4..bdd5964fb93 100644
--- a/spec/factories/appearances.rb
+++ b/spec/factories/appearances.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Read about factories at https://github.com/thoughtbot/factory_bot
FactoryBot.define do
diff --git a/spec/factories/application_settings.rb b/spec/factories/application_settings.rb
index 00c063c49f8..90b6b9e648a 100644
--- a/spec/factories/application_settings.rb
+++ b/spec/factories/application_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :application_setting do
default_projects_limit 42
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
index 43753fa650c..a8bb806381e 100644
--- a/spec/factories/award_emoji.rb
+++ b/spec/factories/award_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :award_emoji do
name "thumbsup"
diff --git a/spec/factories/badge.rb b/spec/factories/badge.rb
index b87ece946cb..1d4e29014cc 100644
--- a/spec/factories/badge.rb
+++ b/spec/factories/badge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
trait :base_badge do
link_url { generate(:url) }
diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb
index 6524bb4830c..a5aff5c7504 100644
--- a/spec/factories/boards.rb
+++ b/spec/factories/boards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :board do
transient do
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index 1a2be5e9552..2a30e2034b1 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :broadcast_message do
message "MyText"
diff --git a/spec/factories/chat_names.rb b/spec/factories/chat_names.rb
index 56993e5da18..07bf990162f 100644
--- a/spec/factories/chat_names.rb
+++ b/spec/factories/chat_names.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :chat_name, class: ChatName do
user factory: :user
diff --git a/spec/factories/chat_teams.rb b/spec/factories/chat_teams.rb
index d048c46d6b6..52628e6d53d 100644
--- a/spec/factories/chat_teams.rb
+++ b/spec/factories/chat_teams.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :chat_team, class: ChatTeam do
sequence(:team_id) { |n| "abcdefghijklm#{n}" }
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
index 7cb5900f2b7..6491b9dca19 100644
--- a/spec/factories/ci/bridge.rb
+++ b/spec/factories/ci/bridge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_bridge, class: Ci::Bridge do
name 'bridge'
diff --git a/spec/factories/ci/build_trace_chunks.rb b/spec/factories/ci/build_trace_chunks.rb
index 3e8e2736423..492dc47f083 100644
--- a/spec/factories/ci/build_trace_chunks.rb
+++ b/spec/factories/ci/build_trace_chunks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_build_trace_chunk, class: Ci::BuildTraceChunk do
build factory: :ci_build
diff --git a/spec/factories/ci/build_trace_section_names.rb b/spec/factories/ci/build_trace_section_names.rb
index ce07e716dde..e52694ef3dc 100644
--- a/spec/factories/ci/build_trace_section_names.rb
+++ b/spec/factories/ci/build_trace_section_names.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_build_trace_section_name, class: Ci::BuildTraceSectionName do
sequence(:name) { |n| "section_#{n}" }
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 5f7c75a3a92..e3b7c64176a 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb
index 9bf520a2c0a..13c2b78e61b 100644
--- a/spec/factories/ci/group_variables.rb
+++ b/spec/factories/ci/group_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_group_variable, class: Ci::GroupVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 542fa9775cd..2d68a8e9fe3 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb
index 4b83ba2ac1b..8fae6986869 100644
--- a/spec/factories/ci/pipeline_schedule.rb
+++ b/spec/factories/ci/pipeline_schedule.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_schedule, class: Ci::PipelineSchedule do
cron '0 1 * * *'
diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb
index c85b97fbfc7..fd7cfada65b 100644
--- a/spec/factories/ci/pipeline_schedule_variables.rb
+++ b/spec/factories/ci/pipeline_schedule_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/pipeline_variables.rb b/spec/factories/ci/pipeline_variables.rb
index b18055d7b3a..af0982124d7 100644
--- a/spec/factories/ci/pipeline_variables.rb
+++ b/spec/factories/ci/pipeline_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_variable, class: Ci::PipelineVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index aa5ccbda6cd..9652b0000a9 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_empty_pipeline, class: Ci::Pipeline do
source :push
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index ec15972c423..bc28544a839 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_runner_project, class: Ci::RunnerProject do
runner factory: [:ci_runner, :project]
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 24e70913b87..1e4344b814d 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_runner, class: Ci::Runner do
sequence(:description) { |n| "My runner#{n}" }
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
index ce61e6bf759..88ff8d7dc53 100644
--- a/spec/factories/ci/stages.rb
+++ b/spec/factories/ci/stages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_stage, class: Ci::LegacyStage do
skip_create
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 0e9fc3d0014..d63bf9868c9 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
trigger factory: :ci_trigger
diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb
index 742d9efba2d..6f628ed5435 100644
--- a/spec/factories/ci/triggers.rb
+++ b/spec/factories/ci/triggers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_trigger_without_token, class: Ci::Trigger do
owner
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 97a7c9ba252..55d11085040 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index d78f01828d7..7fe9ea6801a 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :clusters_applications_helm, class: Clusters::Applications::Helm do
cluster factory: %i(cluster provided_by_gcp)
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 171f5256d2b..b0d14b672f4 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster, class: Clusters::Cluster do
user
diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb
index bf30a9c3a61..d5dc288fddb 100644
--- a/spec/factories/clusters/platforms/kubernetes.rb
+++ b/spec/factories/clusters/platforms/kubernetes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster_platform_kubernetes, class: Clusters::Platforms::Kubernetes do
cluster
diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb
index 186c7c8027c..22462651b6a 100644
--- a/spec/factories/clusters/providers/gcp.rb
+++ b/spec/factories/clusters/providers/gcp.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster_provider_gcp, class: Clusters::Providers::Gcp do
cluster
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 848a31e96c1..a76da30217e 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :commit_status, class: CommitStatus do
name 'default'
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index 2bcc4b6cf52..d1554426a76 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/repo_helpers'
FactoryBot.define do
diff --git a/spec/factories/container_repositories.rb b/spec/factories/container_repositories.rb
index 00fad7975c9..a9771200d6e 100644
--- a/spec/factories/container_repositories.rb
+++ b/spec/factories/container_repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :container_repository do
name 'test_image'
diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb
index abf37fb861e..ea5816684c6 100644
--- a/spec/factories/conversational_development_index_metrics.rb
+++ b/spec/factories/conversational_development_index_metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do
leader_issues 9.256
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
index 4350652fb79..7f82902dee7 100644
--- a/spec/factories/deploy_keys_projects.rb
+++ b/spec/factories/deploy_keys_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deploy_keys_project do
deploy_key
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
index 017e866e69c..a96258f5cbe 100644
--- a/spec/factories/deploy_tokens.rb
+++ b/spec/factories/deploy_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deploy_token do
token { SecureRandom.hex(50) }
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 1c7787bc1a6..89ff1c527df 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deployment, class: Deployment do
sha 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
index feacd5ccf15..284ba631c37 100644
--- a/spec/factories/emails.rb
+++ b/spec/factories/emails.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :email do
user
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 9d9e3d693b8..b5c8f0ca4f0 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :environment, class: Environment do
sequence(:name) { |n| "environment#{n}" }
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index bf8411b1894..b15eb1592fc 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :event do
project
diff --git a/spec/factories/file_uploaders.rb b/spec/factories/file_uploaders.rb
index 8404985bfea..ec8f5c9af2d 100644
--- a/spec/factories/file_uploaders.rb
+++ b/spec/factories/file_uploaders.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :file_uploader do
skip_create
diff --git a/spec/factories/fork_network_members.rb b/spec/factories/fork_network_members.rb
index 850e3f158f1..d34de72ab60 100644
--- a/spec/factories/fork_network_members.rb
+++ b/spec/factories/fork_network_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :fork_network_member do
association :project
diff --git a/spec/factories/fork_networks.rb b/spec/factories/fork_networks.rb
index 813b1943eb2..12ea688bbce 100644
--- a/spec/factories/fork_networks.rb
+++ b/spec/factories/fork_networks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :fork_network do
association :root_project, factory: :project
diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb
index 5034c3d0e48..954b5338846 100644
--- a/spec/factories/gitaly/commit.rb
+++ b/spec/factories/gitaly/commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
sequence(:gitaly_commit_id) { Digest::SHA1.hexdigest(Time.now.to_f.to_s) }
diff --git a/spec/factories/gitaly/commit_author.rb b/spec/factories/gitaly/commit_author.rb
index aaf634ce08b..51dcd8a623b 100644
--- a/spec/factories/gitaly/commit_author.rb
+++ b/spec/factories/gitaly/commit_author.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gitaly_commit_author, class: Gitaly::CommitAuthor do
skip_create
diff --git a/spec/factories/gitaly/tag.rb b/spec/factories/gitaly/tag.rb
index e99776d524a..a7a84753090 100644
--- a/spec/factories/gitaly/tag.rb
+++ b/spec/factories/gitaly/tag.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gitaly_tag, class: Gitaly::Tag do
skip_create
diff --git a/spec/factories/gpg_key_subkeys.rb b/spec/factories/gpg_key_subkeys.rb
index 6c7db5379a9..9daf26d930d 100644
--- a/spec/factories/gpg_key_subkeys.rb
+++ b/spec/factories/gpg_key_subkeys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gpg_key_subkey do
gpg_key
diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb
index 3c0f43cc1b6..9f321643174 100644
--- a/spec/factories/gpg_keys.rb
+++ b/spec/factories/gpg_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/gpg_helpers'
FactoryBot.define do
diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb
index b89e6ffc4b3..a0fc1740d77 100644
--- a/spec/factories/gpg_signature.rb
+++ b/spec/factories/gpg_signature.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gpg_signature do
commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) }
diff --git a/spec/factories/group_custom_attributes.rb b/spec/factories/group_custom_attributes.rb
index d2f45d5d3ce..895bc42c84b 100644
--- a/spec/factories/group_custom_attributes.rb
+++ b/spec/factories/group_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group_custom_attribute do
group
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 077c6ddc5ae..3bf9cdef253 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group_member do
access_level { GroupMember::OWNER }
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index b67ab955ffc..334c0f369cd 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group, class: Group, parent: :namespace do
sequence(:name) { |n| "group#{n}" }
diff --git a/spec/factories/identities.rb b/spec/factories/identities.rb
index 122d0d42938..21cfe7fe623 100644
--- a/spec/factories/identities.rb
+++ b/spec/factories/identities.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :identity do
provider 'ldapmain'
diff --git a/spec/factories/import_export_uploads.rb b/spec/factories/import_export_uploads.rb
index 7750d49b1d0..8521411e0e8 100644
--- a/spec/factories/import_export_uploads.rb
+++ b/spec/factories/import_export_uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :import_export_upload do
project { create(:project) }
diff --git a/spec/factories/import_states.rb b/spec/factories/import_states.rb
index d6de26dccbc..8e778200389 100644
--- a/spec/factories/import_states.rb
+++ b/spec/factories/import_states.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :import_state, class: ProjectImportState do
status :none
diff --git a/spec/factories/instance_configuration.rb b/spec/factories/instance_configuration.rb
index 31866a9c221..daa0be3f404 100644
--- a/spec/factories/instance_configuration.rb
+++ b/spec/factories/instance_configuration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :instance_configuration do
skip_create
diff --git a/spec/factories/internal_ids.rb b/spec/factories/internal_ids.rb
index fbde07a391a..df5c5beeb42 100644
--- a/spec/factories/internal_ids.rb
+++ b/spec/factories/internal_ids.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :internal_id do
project
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index ab27fee33dc..434225f7022 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :issue do
title { generate(:title) }
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index 3f0c60f32b7..087d2521836 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/key_generator_helper'
FactoryBot.define do
diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb
index 007847d9cf4..9b7c0a158bd 100644
--- a/spec/factories/label_links.rb
+++ b/spec/factories/label_links.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :label_link do
label
diff --git a/spec/factories/label_priorities.rb b/spec/factories/label_priorities.rb
index c4824faad53..2f9f2ddfa89 100644
--- a/spec/factories/label_priorities.rb
+++ b/spec/factories/label_priorities.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :label_priority do
project
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index f759b6d499d..3eed750be03 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
trait :base_label do
title { generate(:label_title) }
diff --git a/spec/factories/lfs_file_locks.rb b/spec/factories/lfs_file_locks.rb
index b9d24f82b65..73675d076ab 100644
--- a/spec/factories/lfs_file_locks.rb
+++ b/spec/factories/lfs_file_locks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :lfs_file_lock do
user
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 08ac7fb69e7..631d87cfb12 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 4804d0bb884..7b55cc57f75 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :lfs_objects_project do
lfs_object
diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb
index 210c58b21e9..e68611ec518 100644
--- a/spec/factories/lists.rb
+++ b/spec/factories/lists.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :list do
board
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 0b6a43b13a9..3d12ff98257 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :merge_request do
title { generate(:title) }
diff --git a/spec/factories/merge_requests_closing_issues.rb b/spec/factories/merge_requests_closing_issues.rb
index ee0606a72b6..94f8b27358c 100644
--- a/spec/factories/merge_requests_closing_issues.rb
+++ b/spec/factories/merge_requests_closing_issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :merge_requests_closing_issues do
issue
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index 027349b2f8e..7d623000fc9 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :milestone do
title
diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb
index 0cfc6e3aa46..09dbe16ef9e 100644
--- a/spec/factories/namespaces.rb
+++ b/spec/factories/namespaces.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :namespace do
sequence(:name) { |n| "namespace#{n}" }
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index c51f2f958f9..5b9a7e6f864 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/repo_helpers'
include ActionDispatch::TestProcess
diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb
index 5116ef33f5d..c16b0e456ba 100644
--- a/spec/factories/notification_settings.rb
+++ b/spec/factories/notification_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :notification_setting do
source factory: :project
diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb
index 02c51cd9899..7a46ae6d71d 100644
--- a/spec/factories/oauth_access_grants.rb
+++ b/spec/factories/oauth_access_grants.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_access_grant do
resource_owner_id { create(:user).id }
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index eabfd6cd830..8d1075dacbb 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_access_token do
resource_owner
diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb
index 4427da1d6c7..4748b320298 100644
--- a/spec/factories/oauth_applications.rb
+++ b/spec/factories/oauth_applications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do
sequence(:name) { |n| "OAuth App #{n}" }
diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb
index e441dfcf229..ee5be82cd19 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :pages_domain, class: 'PagesDomain' do
sequence(:domain) { |n| "my#{n}.domain.com" }
diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb
index e7fd22a96b2..cc9b2328705 100644
--- a/spec/factories/personal_access_tokens.rb
+++ b/spec/factories/personal_access_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :personal_access_token do
user
diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb
index 8cac666069c..7a8946d47b0 100644
--- a/spec/factories/pool_repositories.rb
+++ b/spec/factories/pool_repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :pool_repository do
shard { Shard.by_name("default") }
diff --git a/spec/factories/programming_languages.rb b/spec/factories/programming_languages.rb
index d3511d42b6c..ee8e7765ec9 100644
--- a/spec/factories/programming_languages.rb
+++ b/spec/factories/programming_languages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :programming_language do
name 'Ruby'
diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb
index 1de42512402..4cc59c7095c 100644
--- a/spec/factories/project_auto_devops.rb
+++ b/spec/factories/project_auto_devops.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_auto_devops do
project
diff --git a/spec/factories/project_custom_attributes.rb b/spec/factories/project_custom_attributes.rb
index 099d2d7ff19..c9ed86cc0ac 100644
--- a/spec/factories/project_custom_attributes.rb
+++ b/spec/factories/project_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_custom_attribute do
project
diff --git a/spec/factories/project_deploy_tokens.rb b/spec/factories/project_deploy_tokens.rb
index 4866cb58d88..fbf5454b0fe 100644
--- a/spec/factories/project_deploy_tokens.rb
+++ b/spec/factories/project_deploy_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_deploy_token do
project
diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb
index 59c77627ee5..b02d167a950 100644
--- a/spec/factories/project_group_links.rb
+++ b/spec/factories/project_group_links.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_group_link do
project
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index a448d565e4b..96c9742c7d0 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_hook do
url { generate(:url) }
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index c72e0487895..6dcac0400ca 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_member do
user
diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb
index 3d4174eb852..f084d9d5cbf 100644
--- a/spec/factories/project_statistics.rb
+++ b/spec/factories/project_statistics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_statistics do
project
diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb
index 4d21ed47f39..401402614f4 100644
--- a/spec/factories/project_wikis.rb
+++ b/spec/factories/project_wikis.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_wiki do
skip_create
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 0e8810b73a1..afe27aaf1fb 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/test_env'
FactoryBot.define do
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
index 5457c0d2a8f..741615bc0d3 100644
--- a/spec/factories/protected_branches.rb
+++ b/spec/factories/protected_branches.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :protected_branch do
name
diff --git a/spec/factories/protected_tags.rb b/spec/factories/protected_tags.rb
index 2b81d089549..6ff2a245b58 100644
--- a/spec/factories/protected_tags.rb
+++ b/spec/factories/protected_tags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :protected_tag do
name
diff --git a/spec/factories/redirect_routes.rb b/spec/factories/redirect_routes.rb
index 774232d0b34..8fa0aec007c 100644
--- a/spec/factories/redirect_routes.rb
+++ b/spec/factories/redirect_routes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :redirect_route do
sequence(:path) { |n| "redirect#{n}" }
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 4cacc77c182..34794f57284 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :release do
tag "v1.1.0"
diff --git a/spec/factories/remote_mirrors.rb b/spec/factories/remote_mirrors.rb
index adc7da27522..ff1d751c86c 100644
--- a/spec/factories/remote_mirrors.rb
+++ b/spec/factories/remote_mirrors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :remote_mirror, class: 'RemoteMirror' do
association :project, :repository
diff --git a/spec/factories/repository_languages.rb b/spec/factories/repository_languages.rb
index 1757ba6766c..b2b17ebbce2 100644
--- a/spec/factories/repository_languages.rb
+++ b/spec/factories/repository_languages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :repository_language do
project
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
index b0174dd06b7..11116af7dbb 100644
--- a/spec/factories/sent_notifications.rb
+++ b/spec/factories/sent_notifications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :sent_notification do
project
diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb
index f2b6e7a11f9..b6f2d6d8389 100644
--- a/spec/factories/sequences.rb
+++ b/spec/factories/sequences.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
sequence(:username) { |n| "user#{n}" }
sequence(:name) { |n| "John Doe#{n}" }
diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb
index c907862b4f6..ff819f4f8d0 100644
--- a/spec/factories/service_hooks.rb
+++ b/spec/factories/service_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :service_hook do
url { generate(:url) }
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index daf842e3075..5ef39b3e818 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :service do
project
diff --git a/spec/factories/shards.rb b/spec/factories/shards.rb
index c095fa5f0a0..c30a38180e8 100644
--- a/spec/factories/shards.rb
+++ b/spec/factories/shards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :shard do
name "default"
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index dc12b562108..9c3a0fbe9b3 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :snippet do
author
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
index a467f850a80..42a856832e7 100644
--- a/spec/factories/spam_logs.rb
+++ b/spec/factories/spam_logs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :spam_log do
user
diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb
index 8f7ab74ec70..2b652cd57bf 100644
--- a/spec/factories/subscriptions.rb
+++ b/spec/factories/subscriptions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :subscription do
project
diff --git a/spec/factories/system_hooks.rb b/spec/factories/system_hooks.rb
index 9e00eeb6ef1..a2d163a060f 100644
--- a/spec/factories/system_hooks.rb
+++ b/spec/factories/system_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :system_hook do
url { generate(:url) }
diff --git a/spec/factories/system_note_metadata.rb b/spec/factories/system_note_metadata.rb
index e913068da40..8cd4b77799c 100644
--- a/spec/factories/system_note_metadata.rb
+++ b/spec/factories/system_note_metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :system_note_metadata do
note
diff --git a/spec/factories/term_agreements.rb b/spec/factories/term_agreements.rb
index 3c4eebd0196..b7e259bd44b 100644
--- a/spec/factories/term_agreements.rb
+++ b/spec/factories/term_agreements.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :term_agreement do
term
diff --git a/spec/factories/terms.rb b/spec/factories/terms.rb
index 5ffca365a5f..b890261d293 100644
--- a/spec/factories/terms.rb
+++ b/spec/factories/terms.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :term, class: ApplicationSetting::Term do
terms "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb
index b45f06b9a0a..056a8833c46 100644
--- a/spec/factories/timelogs.rb
+++ b/spec/factories/timelogs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Read about factories at https://github.com/thoughtbot/factory_bot
FactoryBot.define do
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index ed3d87eb76b..2ff024112a4 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :todo do
project
diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb
index f7c634fd21f..354b8c1e002 100644
--- a/spec/factories/trending_project.rb
+++ b/spec/factories/trending_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
# TrendingProject
factory :trending_project, class: 'TrendingProject' do
diff --git a/spec/factories/u2f_registrations.rb b/spec/factories/u2f_registrations.rb
index 26090b08966..c968468834b 100644
--- a/spec/factories/u2f_registrations.rb
+++ b/spec/factories/u2f_registrations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :u2f_registration do
certificate { FFaker::BaconIpsum.characters(728) }
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 426abdc2a6c..2ede92c8af0 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :upload do
model { build(:project) }
diff --git a/spec/factories/user_agent_details.rb b/spec/factories/user_agent_details.rb
index 7183a8e1140..055aea50585 100644
--- a/spec/factories/user_agent_details.rb
+++ b/spec/factories/user_agent_details.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_agent_detail do
ip_address '127.0.0.1'
diff --git a/spec/factories/user_callouts.rb b/spec/factories/user_callouts.rb
index 528e442c14b..c4a217fd357 100644
--- a/spec/factories/user_callouts.rb
+++ b/spec/factories/user_callouts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_callout do
feature_name :gke_cluster_integration
diff --git a/spec/factories/user_custom_attributes.rb b/spec/factories/user_custom_attributes.rb
index a184a2e0f17..7bd5c06f4ef 100644
--- a/spec/factories/user_custom_attributes.rb
+++ b/spec/factories/user_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_custom_attribute do
user
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 4f3392cdcbf..b2c8bdab013 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user, aliases: [:author, :assignee, :recipient, :owner, :resource_owner] do
email { generate(:email) }
diff --git a/spec/factories/users_star_projects.rb b/spec/factories/users_star_projects.rb
index 6afd08a2084..a1299024fe4 100644
--- a/spec/factories/users_star_projects.rb
+++ b/spec/factories/users_star_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :users_star_project do
project
diff --git a/spec/factories/web_hook_log.rb b/spec/factories/web_hook_log.rb
index 17837260a4b..5750af85662 100644
--- a/spec/factories/web_hook_log.rb
+++ b/spec/factories/web_hook_log.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :web_hook_log do
web_hook factory: :project_hook
diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb
index b105c82b19d..de23cf110b5 100644
--- a/spec/factories/wiki_directories.rb
+++ b/spec/factories/wiki_directories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :wiki_directory do
skip_create
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index ae257d769e8..761ba58edb2 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'ostruct'
FactoryBot.define do
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index fb76e2b0014..bdab3c63244 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -27,7 +27,7 @@ describe 'Dashboard Groups page', :js do
expect(page).not_to have_content(another_group.name)
end
- it 'shows subgroups the user is member of', :nested_groups do
+ it 'shows subgroups the user is member of' do
group.add_owner(user)
nested_group.add_owner(user)
@@ -40,7 +40,7 @@ describe 'Dashboard Groups page', :js do
expect(page).to have_content(nested_group.name)
end
- context 'when filtering groups', :nested_groups do
+ context 'when filtering groups' do
before do
group.add_owner(user)
nested_group.add_owner(user)
@@ -79,7 +79,7 @@ describe 'Dashboard Groups page', :js do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let!(:subgroup) { create(:group, :public, parent: group) }
before do
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index e4eb0d355d1..3fbc0f78d38 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -110,7 +110,7 @@ describe 'Group empty states' do
end
context 'group without a project' do
- context 'group has a subgroup', :nested_groups do
+ context 'group has a subgroup' do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, namespace: subgroup) }
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 676769c25fe..257aeae7e23 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -121,7 +121,7 @@ describe 'Edit group settings' do
expect(find(:css, '.group-root-path').text).to eq(root_url)
end
- it 'has a parent group URL label for a subgroup group', :postgresql do
+ it 'has a parent group URL label for a subgroup group' do
subgroup = create(:group, parent: group)
visit edit_group_path(subgroup)
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 0ada530781c..a16004c79ca 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -50,7 +50,7 @@ describe 'Group issues page' do
end
end
- context 'issues list', :nested_groups do
+ context 'issues list' do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, :public, group: subgroup)}
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb
index 4ba7161601e..ffff755793f 100644
--- a/spec/features/groups/members/list_members_spec.rb
+++ b/spec/features/groups/members/list_members_spec.rb
@@ -13,7 +13,7 @@ describe 'Groups > Members > List members' do
sign_in(user1)
end
- it 'show members from current group and parent', :nested_groups do
+ it 'show members from current group and parent' do
group.add_developer(user1)
nested_group.add_developer(user2)
@@ -23,7 +23,7 @@ describe 'Groups > Members > List members' do
expect(second_row.text).to include(user2.name)
end
- it 'show user once if member of both current group and parent', :nested_groups do
+ it 'show user once if member of both current group and parent' do
group.add_developer(user1)
nested_group.add_developer(user1)
diff --git a/spec/features/groups/share_lock_spec.rb b/spec/features/groups/share_lock_spec.rb
index 704d9f12888..86b523fbdd9 100644
--- a/spec/features/groups/share_lock_spec.rb
+++ b/spec/features/groups/share_lock_spec.rb
@@ -9,7 +9,7 @@ describe 'Group share with group lock' do
sign_in(root_owner)
end
- context 'with a subgroup', :nested_groups do
+ context 'with a subgroup' do
let!(:subgroup) { create(:group, parent: root_group) }
context 'when enabling the parent group share with group lock' do
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index bed998a0859..0d78bd23973 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -75,11 +75,7 @@ describe 'Group show page' do
sign_in(owner)
end
- context 'when subgroups are supported', :nested_groups do
- before do
- allow(Group).to receive(:supports_nested_objects?) { true }
- end
-
+ context 'when subgroups are supported' do
it 'allows creating subgroups' do
visit path
@@ -87,19 +83,6 @@ describe 'Group show page' do
.to have_css("li[data-text='New subgroup']", visible: false)
end
end
-
- context 'when subgroups are not supported' do
- before do
- allow(Group).to receive(:supports_nested_objects?) { false }
- end
-
- it 'does not allow creating subgroups' do
- visit path
-
- expect(page)
- .not_to have_selector("li[data-text='New subgroup']", visible: false)
- end
- end
end
context 'for maintainers' do
@@ -107,11 +90,7 @@ describe 'Group show page' do
sign_in(maintainer)
end
- context 'when subgroups are supported', :nested_groups do
- before do
- allow(Group).to receive(:supports_nested_objects?) { true }
- end
-
+ context 'when subgroups are supported' do
context 'when subgroup_creation_level is set to maintainers' do
before do
relaxed_group.add_maintainer(maintainer)
@@ -141,19 +120,6 @@ describe 'Group show page' do
end
end
end
-
- context 'when subgroups are not supported' do
- before do
- allow(Group).to receive(:supports_nested_objects?) { false }
- end
-
- it 'does not allow creating subgroups' do
- visit path
-
- expect(page)
- .not_to have_selector("li[data-text='New subgroup']", visible: false)
- end
- end
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 8e7f78cab81..ad645643919 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -105,7 +105,7 @@ describe 'Group' do
end
end
- describe 'create a nested group', :nested_groups, :js do
+ describe 'create a nested group', :js do
let(:group) { create(:group, path: 'foo') }
context 'as admin' do
@@ -231,7 +231,7 @@ describe 'Group' do
end
end
- describe 'group page with nested groups', :nested_groups, :js do
+ describe 'group page with nested groups', :js do
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
diff --git a/spec/features/ide_spec.rb b/spec/features/ide_spec.rb
index 6eb59ef72c2..65989c36c1e 100644
--- a/spec/features/ide_spec.rb
+++ b/spec/features/ide_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'IDE', :js do
- describe 'sub-groups', :nested_groups do
+ describe 'sub-groups' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb
index a90cdd8d920..90b499efd72 100644
--- a/spec/features/import/manifest_import_spec.rb
+++ b/spec/features/import/manifest_import_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Import multiple repositories by uploading a manifest file', :js, :postgresql do
+describe 'Import multiple repositories by uploading a manifest file', :js do
include Select2Helper
let(:user) { create(:admin) }
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 489651fea15..45bd2218ee6 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Labels Hierarchy', :js, :nested_groups do
+describe 'Labels Hierarchy', :js do
include FilteredSearchHelpers
let!(:user) { create(:user) }
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/artifacts/user_downloads_artifacts_spec.rb b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
index 5cb015e80be..69296ef00fd 100644
--- a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
@@ -1,8 +1,8 @@
require "spec_helper"
describe "User downloads artifacts" do
- set(:project) { create(:project, :public) }
- set(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project) }
+ set(:project) { create(:project, :repository, :public) }
+ set(:pipeline) { create(:ci_empty_pipeline, status: :success, sha: project.commit.id, project: project) }
set(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) }
shared_examples "downloading" 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/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index 7432c600c1e..c5423ec4662 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -58,7 +58,7 @@ describe 'Project > Members > Invite group', :js do
end
end
- context 'for a project in a subgroup', :nested_groups do
+ context 'for a project in a subgroup' do
let!(:group_to_share_with) { create(:group) }
let(:root_group) { create(:group) }
let(:subgroup) { create(:group, parent: root_group) }
@@ -181,7 +181,7 @@ describe 'Project > Members > Invite group', :js do
group_to_share_with.add_maintainer(maintainer)
end
- it 'the groups dropdown does not show ancestors', :nested_groups do
+ it 'the groups dropdown does not show ancestors' do
visit project_settings_members_path(project)
click_on 'invite-group-tab'
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 7602935b47e..8cdaed43fbc 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -294,7 +294,7 @@ describe 'New project' do
end
end
- context 'from manifest file', :postgresql do
+ context 'from manifest file' do
before do
first('.import_manifest').click
end
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index bf0c0de89b2..31fdd308292 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -126,7 +126,7 @@ describe "Projects > Settings > Pipelines settings" do
end
end
- context 'when auto devops is turned on group parent level', :nested_groups do
+ context 'when auto devops is turned on group parent level' do
before do
group = create(:group, parent: create(:group, :auto_devops_enabled))
project.update!(namespace: group)
diff --git a/spec/features/projects/settings/user_transfers_a_project_spec.rb b/spec/features/projects/settings/user_transfers_a_project_spec.rb
index 2fdbc04fa62..9af2e47a334 100644
--- a/spec/features/projects/settings/user_transfers_a_project_spec.rb
+++ b/spec/features/projects/settings/user_transfers_a_project_spec.rb
@@ -68,7 +68,7 @@ describe 'Projects > Settings > User transfers a project', :js do
end
end
- context 'when nested groups are available', :nested_groups do
+ context 'when nested groups are available' do
it 'allows transferring a project to a subgroup' do
subgroup = create(:group, parent: group)
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index 50e7e934cf6..8afbcf72ca7 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Subgroup Issuables', :js, :nested_groups do
+describe 'Subgroup Issuables', :js do
let!(:group) { create(:group, name: 'group') }
let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
let!(:project) { create(:project, namespace: subgroup, name: 'project') }
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index c0932539131..19262a635ac 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -26,7 +26,7 @@ describe 'User creates a project', :js do
expect(page).to have_content(project.url_to_repo)
end
- context 'in a subgroup they do not own', :nested_groups do
+ context 'in a subgroup they do not own' do
let(:parent) { create(:group) }
let!(:subgroup) { create(:group, parent: parent) }
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/projects_spec.rb b/spec/features/projects_spec.rb
index b5112758475..d1e2d17e9cc 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -314,7 +314,7 @@ describe 'Project' do
end
end
- context 'for subgroups', :js, :nested_groups do
+ context 'for subgroups', :js do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:project) { create(:project, :repository, group: subgroup) }
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/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb
index bcde115b1a6..f3b54ca0461 100644
--- a/spec/finders/autocomplete/users_finder_spec.rb
+++ b/spec/finders/autocomplete/users_finder_spec.rb
@@ -50,7 +50,7 @@ describe Autocomplete::UsersFinder do
it { is_expected.to match_array([user1]) }
end
- context 'when passed a subgroup', :nested_groups do
+ context 'when passed a subgroup' do
let(:grandparent) { create(:group, :public) }
let(:parent) { create(:group, :public, parent: grandparent) }
let(:child) { create(:group, :public, parent: parent) }
diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb
index 750042b6b54..4aedb41d446 100644
--- a/spec/finders/cluster_ancestors_finder_spec.rb
+++ b/spec/finders/cluster_ancestors_finder_spec.rb
@@ -32,7 +32,7 @@ describe ClusterAncestorsFinder, '#execute' do
is_expected.to eq([project_cluster, group_cluster, instance_cluster])
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) }
@@ -65,7 +65,7 @@ describe ClusterAncestorsFinder, '#execute' do
is_expected.to eq([group_cluster, instance_cluster])
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) }
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index c28fd7cad11..5fb6739d6e2 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -19,7 +19,7 @@ describe GroupDescendantsFinder do
expect(finder.has_children?).to be_truthy
end
- context 'when there are subgroups', :nested_groups do
+ context 'when there are subgroups' do
it 'is true when there are projects' do
create(:group, parent: group)
@@ -99,7 +99,7 @@ describe GroupDescendantsFinder do
)
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:subgroup1) { create(:group, parent: group, name: 'a', path: 'sub-a') }
let!(:subgroup2) { create(:group, parent: group, name: 'z', path: 'sub-z') }
@@ -126,7 +126,7 @@ describe GroupDescendantsFinder do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:project) { create(:project, namespace: group) }
let!(:subgroup) { create(:group, :private, parent: group) }
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 8975ea0f063..49b0e14241e 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -18,7 +18,7 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to match_array([member3, member2, member1])
end
- it 'returns members for nested group', :nested_groups do
+ it 'returns members for nested group' do
group.add_developer(user2)
nested_group.request_access(user4)
member1 = group.add_maintainer(user1)
@@ -30,7 +30,7 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member3, member4])
end
- it 'returns members for descendant groups if requested', :nested_groups do
+ it 'returns members for descendant groups if requested' do
member1 = group.add_maintainer(user2)
member2 = group.add_maintainer(user1)
nested_group.add_maintainer(user2)
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index f8fcc2d0e40..f4bd8a3f6ba 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -19,7 +19,7 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -33,7 +33,7 @@ describe GroupProjectsFinder do
end
context "all" do
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -78,7 +78,7 @@ describe GroupProjectsFinder do
subgroup_private_project.add_maintainer(current_user)
end
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -96,7 +96,7 @@ describe GroupProjectsFinder do
current_user.update(external: true)
end
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -111,7 +111,7 @@ describe GroupProjectsFinder do
end
context "all" do
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -153,7 +153,7 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index 367ca43bdfe..c8875d1f92d 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -65,7 +65,7 @@ describe GroupsFinder do
end
end
- context 'subgroups', :nested_groups do
+ context 'subgroups' do
let(:user) { create(:user) }
let!(:parent_group) { create(:group, :public) }
let!(:public_subgroup) { create(:group, :public, parent: parent_group) }
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index bf38d083ca6..7fbe27c1939 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -51,7 +51,7 @@ describe IssuesFinder do
end
end
- context 'when include_subgroup param is true', :nested_groups do
+ context 'when include_subgroup param is true' do
before do
params[:include_subgroups] = true
end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 98b4933fef6..ba41ded112a 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -89,7 +89,7 @@ describe LabelsFinder do
end
end
- context 'when including labels from group ancestors', :nested_groups do
+ context 'when including labels from group ancestors' do
it 'returns labels from group and its ancestors' do
private_group_1.add_developer(user)
private_subgroup_1.add_developer(user)
@@ -108,7 +108,7 @@ describe LabelsFinder do
end
end
- context 'when including labels from group descendants', :nested_groups do
+ context 'when including labels from group descendants' do
it 'returns labels from group and its descendants' do
private_group_1.add_developer(user)
private_subgroup_1.add_developer(user)
@@ -128,7 +128,7 @@ describe LabelsFinder do
end
end
- context 'filtering by project_id', :nested_groups do
+ context 'filtering by project_id' do
context 'when include_ancestor_groups is true' do
let!(:sub_project) { create(:project, namespace: private_subgroup_1 ) }
let!(:project_label) { create(:label, project: sub_project, title: 'Label 5') }
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 83348457caa..4203f58fe81 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -9,7 +9,7 @@ describe MembersFinder, '#execute' do
set(:user3) { create(:user) }
set(:user4) { create(:user) }
- it 'returns members for project and parent groups', :nested_groups do
+ it 'returns members for project and parent groups' do
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
member2 = nested_group.add_maintainer(user3)
@@ -20,7 +20,7 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
- it 'includes nested group members if asked', :nested_groups do
+ it 'includes nested group members if asked' do
project = create(:project, namespace: group)
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
@@ -32,7 +32,7 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
- context 'when include_invited_groups_members == true', :nested_groups do
+ context 'when include_invited_groups_members == true' do
subject { described_class.new(project, user2).execute(include_invited_groups_members: true) }
set(:linked_group) { create(:group, :public, :access_requestable) }
@@ -40,7 +40,7 @@ describe MembersFinder, '#execute' do
set(:linked_group_member) { linked_group.add_developer(user1) }
set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) }
- it 'includes all the invited_groups members including members inherited from ancestor groups', :nested_groups do
+ it 'includes all the invited_groups members including members inherited from ancestor groups' do
create(:project_group_link, project: project, group: nested_linked_group)
expect(subject).to contain_exactly(linked_group_member, nested_linked_group_member)
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index da5e9dab058..78224f0b9da 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -42,7 +42,7 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1, merge_request2)
end
- it 'filters by group including subgroups', :nested_groups do
+ it 'filters by group including subgroups' do
params = { group_id: group.id, include_subgroups: true }
merge_requests = described_class.new(user, params).execute
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 22318a9946a..f7b35e76925 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -36,7 +36,7 @@ describe TodosFinder do
expect(todos).to match_array([todo1])
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:subgroup) { create(:group, parent: group) }
let!(:todo3) { create(:todo, user: user, group: subgroup, target: issue) }
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/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 20e197e9f73..47591445fc0 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::NamespaceProjectsResolver, :nested_groups do
+describe Resolvers::NamespaceProjectsResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index d2540696b17..e80388f9ea7 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -118,7 +118,7 @@ describe AutoDevopsHelper do
it { is_expected.to eq('instance enabled') }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
before do
receiver.update(parent: parent)
end
@@ -170,7 +170,7 @@ describe AutoDevopsHelper do
it { is_expected.to eq('instance enabled') }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
let(:receiver) { create(:project, :repository, namespace: group) }
before do
@@ -203,7 +203,7 @@ describe AutoDevopsHelper do
it { is_expected.to be_nil }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
let(:receiver) { create(:project, :repository, namespace: group) }
context 'when auto devops is disabled on group level' do
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 1763c46389a..037b16c90ed 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -86,7 +86,7 @@ describe GroupsHelper do
end
end
- describe 'group_title', :nested_groups do
+ describe 'group_title' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -99,7 +99,7 @@ describe GroupsHelper do
end
# rubocop:disable Layout/SpaceBeforeComma
- describe '#share_with_group_lock_help_text', :nested_groups do
+ describe '#share_with_group_lock_help_text' do
let!(:root_group) { create(:group) }
let!(:subgroup) { create(:group, parent: root_group) }
let!(:sub_subgroup) { create(:group, parent: subgroup) }
@@ -230,7 +230,7 @@ describe GroupsHelper do
end
end
- describe 'parent_group_options', :nested_groups do
+ describe 'parent_group_options' do
let(:current_user) { create(:user) }
let(:group) { create(:group, name: 'group') }
let(:group2) { create(:group, name: 'group2') }
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index 601f864ef36..e38513f6d94 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe NamespacesHelper, :postgresql do
+describe NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_project_creation_level) { nil }
let!(:admin_group) do
@@ -109,7 +109,7 @@ describe NamespacesHelper, :postgresql do
expect(options).to include(user_group.name)
end
- context 'when nested groups are available', :nested_groups do
+ context 'when nested groups are available' do
it 'includes groups nested in groups the user can administer' do
allow(helper).to receive(:current_user).and_return(user)
child_group = create(:group, :private, parent: user_group)
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/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index f0a5dc8d0d7..91edadfa234 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -363,7 +363,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
expect(doc.css('a')).to be_empty
end
- it 'supports parent group references', :nested_groups do
+ it 'supports parent group references' do
milestone.update!(group: parent_group)
doc = reference_filter("See #{reference}")
@@ -396,7 +396,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
context 'when group milestone' do
let(:group_milestone) { create(:milestone, title: 'group_milestone', group: group) }
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
let(:sub_group) { create(:group, parent: group) }
let(:sub_group_milestone) { create(:milestone, title: 'sub_group_milestone', group: sub_group) }
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/fix_cross_project_label_links_spec.rb b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
index 20af63bc6c8..84ccd57a50b 100644
--- a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
@@ -75,7 +75,7 @@ describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, sch
create_resource(target_type, 1, 2)
end
- it 'ignores label links referencing ancestor group labels', :nested_groups do
+ it 'ignores label links referencing ancestor group labels' do
labels_table.create(id: 4, title: 'bug', color: 'red', project_id: 2, type: 'ProjectLabel')
label_links_table.create(label_id: 4, target_type: target_type, target_id: 1)
link = label_links_table.create(label_id: 1, target_type: target_type, target_id: 1)
@@ -85,7 +85,7 @@ describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, sch
expect(link.reload.label_id).to eq(1)
end
- it 'checks also issues and MRs in subgroups', :nested_groups do
+ it 'checks also issues and MRs in subgroups' do
link = label_links_table.create(label_id: 2, target_type: target_type, target_id: 1)
subject.perform(1, 100)
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index f4759b69538..ebd3c28f130 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -103,7 +103,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:project_path) { 'a-group/a-sub-group/a-project' }
let(:existing_group) do
@@ -188,20 +188,6 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
end
end
- context 'when subgroups are not available' do
- let(:project_path) { 'a-group/a-sub-group/a-project' }
-
- before do
- expect(Group).to receive(:supports_nested_objects?) { false }
- end
-
- describe '#create_project_if_needed' do
- it 'raises an error' do
- expect { importer.create_project_if_needed }.to raise_error('Nested groups are not supported on MySQL')
- end
- end
- end
-
def prepare_repository(project_path, source_project)
repo_path = File.join(base_dir, project_path)
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index a528707c9dc..959f3fdc289 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
subject { described_class.new(models).count }
- describe '#count', :postgresql do
+ describe '#count' do
let(:models) { [Project, Identity] }
context 'when reltuples is up to date' do
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index a57f033b5ed..cc9d778e579 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
subject { strategy.count }
- describe '#count', :postgresql do
+ describe '#count' do
let(:estimates) do
{
Project => threshold + 1,
diff --git a/spec/lib/gitlab/database/grant_spec.rb b/spec/lib/gitlab/database/grant_spec.rb
index 5ebf3f399b6..9e62dc14d77 100644
--- a/spec/lib/gitlab/database/grant_spec.rb
+++ b/spec/lib/gitlab/database/grant_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::Database::Grant do
expect(described_class.create_and_execute_trigger?('users')).to eq(true)
end
- it 'returns false when the user can not create and/or execute a trigger', :postgresql do
+ it 'returns false when the user can not create and/or execute a trigger' do
# In case of MySQL the user may have SUPER permissions, making it
# impossible to have `false` returned when running tests; hence we only
# run these tests on PostgreSQL.
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 2734fcef0a0..53a91a35ec9 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'returns the user belonging to the subgroup matching the search query', :nested_groups do
+ it 'returns the user belonging to the subgroup matching the search query' do
user1 = create(:user, username: 'gob_bluth')
subgroup = create(:group, parent: group)
create(:group_member, :developer, user: user1, group: subgroup)
@@ -32,7 +32,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'returns the user belonging to the parent group matching the search query', :nested_groups do
+ it 'returns the user belonging to the parent group matching the search query' do
user1 = create(:user, username: 'gob_bluth')
parent_group = create(:group, children: [group])
create(:group_member, :developer, user: user1, group: parent_group)
@@ -44,7 +44,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'does not return the user belonging to the private subgroup', :nested_groups do
+ it 'does not return the user belonging to the private subgroup' do
user1 = create(:user, username: 'gob_bluth')
subgroup = create(:group, :private, parent: group)
create(:group_member, :developer, user: user1, group: subgroup)
diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb
index ab305fb2316..ded93e23c08 100644
--- a/spec/lib/gitlab/manifest_import/manifest_spec.rb
+++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::Manifest, :postgresql do
+describe Gitlab::ManifestImport::Manifest do
let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) }
let(:manifest) { described_class.new(file) }
diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
index 1d01d437535..a7487972f51 100644
--- a/spec/lib/gitlab/manifest_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::ProjectCreator, :postgresql do
+describe Gitlab::ManifestImport::ProjectCreator do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:repository) do
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index e6e9ae3223e..bfd456cdd7e 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ObjectHierarchy, :postgresql do
+describe Gitlab::ObjectHierarchy do
let!(:parent) { create(:group) }
let!(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) }
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index ee3c571c9c0..71c109db1f1 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -96,7 +96,7 @@ describe Gitlab::PerformanceBar do
end
end
- context 'when allowed group is nested', :nested_groups do
+ context 'when allowed group is nested' do
let!(:nested_my_group) { create(:group, parent: create(:group, path: 'my-org'), path: 'my-group') }
before do
@@ -110,7 +110,7 @@ describe Gitlab::PerformanceBar do
end
end
- context 'when a nested group has the same path', :nested_groups do
+ context 'when a nested group has the same path' do
before do
create(:group, :nested, path: 'my-group').add_developer(user)
end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index bd0bc2c9044..75e2d5e1319 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -20,13 +20,7 @@ describe Gitlab::ProjectAuthorizations do
end
let(:authorizations) do
- klass = if Group.supports_nested_objects?
- Gitlab::ProjectAuthorizations::WithNestedGroups
- else
- Gitlab::ProjectAuthorizations::WithoutNestedGroups
- end
-
- klass.new(user).calculate
+ described_class.new(user).calculate
end
it 'returns the correct number of authorizations' do
@@ -46,28 +40,26 @@ describe Gitlab::ProjectAuthorizations do
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
end
- if Group.supports_nested_objects?
- context 'with nested groups' do
- let!(:nested_group) { create(:group, parent: group) }
- let!(:nested_project) { create(:project, namespace: nested_group) }
+ context 'with nested groups' do
+ let!(:nested_group) { create(:group, parent: group) }
+ let!(:nested_project) { create(:project, namespace: nested_group) }
- it 'includes nested groups' do
- expect(authorizations.pluck(:project_id)).to include(nested_project.id)
- end
+ it 'includes nested groups' do
+ expect(authorizations.pluck(:project_id)).to include(nested_project.id)
+ end
- it 'inherits access levels when the user is not a member of a nested group' do
- mapping = map_access_levels(authorizations)
+ it 'inherits access levels when the user is not a member of a nested group' do
+ mapping = map_access_levels(authorizations)
- expect(mapping[nested_project.id]).to eq(Gitlab::Access::DEVELOPER)
- end
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::DEVELOPER)
+ end
- it 'uses the greatest access level when a user is a member of a nested group' do
- nested_group.add_maintainer(user)
+ it 'uses the greatest access level when a user is a member of a nested group' do
+ nested_group.add_maintainer(user)
- mapping = map_access_levels(authorizations)
+ mapping = map_access_levels(authorizations)
- expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER)
- end
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER)
end
end
end
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/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index d6763c7b2e1..5d2164491b5 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SQL::CTE, :postgresql do
+describe Gitlab::SQL::CTE do
describe '#to_arel' do
it 'generates an Arel relation for the CTE body' do
relation = User.where(id: 1)
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index 7fe39dd5a96..407a4d8a247 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SQL::RecursiveCTE, :postgresql do
+describe Gitlab::SQL::RecursiveCTE do
let(:cte) { described_class.new(:cte_name) }
describe '#to_arel' do
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/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb
index e7d500612b1..ba843b27254 100644
--- a/spec/lib/prometheus/pid_provider_spec.rb
+++ b/spec/lib/prometheus/pid_provider_spec.rb
@@ -25,22 +25,60 @@ describe Prometheus::PidProvider do
before do
stub_const('Unicorn::Worker', Class.new)
hide_const('Puma')
+
+ expect(described_class).to receive(:process_name)
+ .at_least(:once)
+ .and_return(process_name)
end
- context 'when `Prometheus::Client::Support::Unicorn` provides worker_id' do
- before do
- expect(::Prometheus::Client::Support::Unicorn).to receive(:worker_id).and_return(1)
+ context 'when unicorn master is specified in process name' do
+ context 'when running in Omnibus' do
+ context 'before the process was renamed' do
+ let(:process_name) { "/opt/gitlab/embedded/bin/unicorn"}
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+
+ context 'after the process was renamed' do
+ let(:process_name) { "unicorn master -D -E production -c /var/opt/gitlab/gitlab-rails/etc/unicorn.rb /opt/gitlab/embedded/service/gitlab-rails/config.ru" }
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
end
- it { is_expected.to eq 'unicorn_1' }
+ context 'when in development env' do
+ context 'before the process was renamed' do
+ let(:process_name) { "path_to_bindir/bin/unicorn_rails"}
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+
+ context 'after the process was renamed' do
+ let(:process_name) { "unicorn_rails master -c /gitlab_dir/config/unicorn.rb -E development" }
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+ end
end
- context 'when no worker_id is provided from `Prometheus::Client::Support::Unicorn`' do
- before do
- expect(::Prometheus::Client::Support::Unicorn).to receive(:worker_id).and_return(nil)
+ context 'when unicorn worker id is specified in process name' do
+ context 'when running in Omnibus' do
+ let(:process_name) { "unicorn worker[1] -D -E production -c /var/opt/gitlab/gitlab-rails/etc/unicorn.rb /opt/gitlab/embedded/service/gitlab-rails/config.ru" }
+
+ it { is_expected.to eq 'unicorn_1' }
end
- it { is_expected.to eq 'unicorn_master' }
+ context 'when in development env' do
+ let(:process_name) { "unicorn_rails worker[1] -c gitlab_dir/config/unicorn.rb -E development" }
+
+ it { is_expected.to eq 'unicorn_1' }
+ end
+ end
+
+ context 'when no specified unicorn master or worker id in process name' do
+ let(:process_name) { "bin/unknown_process"}
+
+ it { is_expected.to eq "process_#{Process.pid}" }
end
end
@@ -48,20 +86,20 @@ describe Prometheus::PidProvider do
before do
stub_const('Puma', Module.new)
hide_const('Unicorn::Worker')
+
+ expect(described_class).to receive(:process_name)
+ .at_least(:once)
+ .and_return(process_name)
end
context 'when cluster worker id is specified in process name' do
- before do
- expect(described_class).to receive(:process_name).and_return('puma: cluster worker 1: 17483 [gitlab-puma-worker]')
- end
+ let(:process_name) { 'puma: cluster worker 1: 17483 [gitlab-puma-worker]' }
it { is_expected.to eq 'puma_1' }
end
context 'when no worker id is specified in process name' do
- before do
- expect(described_class).to receive(:process_name).and_return('bin/puma')
- end
+ let(:process_name) { 'bin/puma' }
it { is_expected.to eq 'puma_master' }
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/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index e24bbc39761..1fb83fbb088 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1799,7 +1799,7 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '.latest_successful_for' do
+ describe '.latest_successful_for_ref' do
include_context 'with some outdated pipelines'
let!(:latest_successful_pipeline) do
@@ -1807,7 +1807,20 @@ describe Ci::Pipeline, :mailer do
end
it 'returns the latest successful pipeline' do
- expect(described_class.latest_successful_for('ref'))
+ expect(described_class.latest_successful_for_ref('ref'))
+ .to eq(latest_successful_pipeline)
+ end
+ end
+
+ describe '.latest_successful_for_sha' do
+ include_context 'with some outdated pipelines'
+
+ let!(:latest_successful_pipeline) do
+ create_pipeline(:success, 'ref', 'awesomesha', project)
+ end
+
+ it 'returns the latest successful pipeline' do
+ expect(described_class.latest_successful_for_sha('awesomesha'))
.to eq(latest_successful_pipeline)
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 24ea059e871..78b151631c1 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -146,7 +146,7 @@ describe Ci::Runner do
expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
end
- context 'with a parent group with a runner', :nested_groups do
+ context 'with a parent group with a runner' do
let(:runner) { create(:ci_runner, :group, groups: [parent_group]) }
let(:project) { create(:project, group: group) }
let(:group) { create(:group, parent: parent_group) }
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 52661178d76..8f2f1b200e4 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -342,7 +342,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- context 'when sub-group has configured kubernetes cluster', :nested_groups do
+ context 'when sub-group has configured kubernetes cluster' do
let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:sub_group) { sub_group_cluster.group }
let(:project) { create(:project, group: sub_group) }
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index c4f9f62ece5..27f535487c8 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -45,7 +45,7 @@ describe DeploymentPlatform do
is_expected.to eq(group_cluster.platform_kubernetes)
end
- context 'when child group has configured kubernetes cluster', :nested_groups do
+ context 'when child group has configured kubernetes cluster' do
let(:child_group1) { create(:group, parent: group) }
let!(:child_group1_cluster) { create(:cluster_for_group, groups: [child_group1]) }
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 194caac3fce..192e884f3e8 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDescendant, :nested_groups do
+describe GroupDescendant do
let(:parent) { create(:group) }
let(:subgroup) { create(:group, parent: parent) }
let(:subsub_group) { create(:group, parent: subgroup) }
@@ -84,7 +84,7 @@ describe GroupDescendant, :nested_groups do
it 'tracks the exception when a parent was not preloaded' do
expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
- expect { GroupDescendant.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
+ expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
end
it 'recovers if a parent was not reloaded by querying for the parent' do
@@ -93,7 +93,7 @@ describe GroupDescendant, :nested_groups do
# this does not raise in production, so stubbing it here.
allow(Gitlab::Sentry).to receive(:track_exception)
- expect(GroupDescendant.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
+ expect(described_class.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
end
it 'raises an error if not all elements were preloaded' do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index e19da41c3fe..39680c0e51a 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -128,7 +128,7 @@ describe Issuable do
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
end
- it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do
+ it 'returns true with a milestone from the the parent of the issue project group' do
parent = create(:group)
group.update(parent: parent)
milestone = create(:milestone, group: parent)
diff --git a/spec/models/deployment_metrics_spec.rb b/spec/models/deployment_metrics_spec.rb
index 0aadb1f3a5e..7c574a8b6c8 100644
--- a/spec/models/deployment_metrics_spec.rb
+++ b/spec/models/deployment_metrics_spec.rb
@@ -49,18 +49,6 @@ describe DeploymentMetrics do
it { is_expected.to be_truthy }
end
-
- context 'fallback deployment platform' do
- let(:cluster) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [deployment.project]) }
- let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
-
- before do
- expect(deployment.project).to receive(:deployment_platform).and_return(cluster.platform)
- expect(cluster.application_prometheus).to receive(:can_query?).and_return(true)
- end
-
- it { is_expected.to be_truthy }
- end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 90e0900445e..7e9bbf5a407 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -71,7 +71,7 @@ describe Group do
end
end
- describe '#notification_settings', :nested_groups do
+ describe '#notification_settings' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:sub_group) { create(:group, parent_id: group.id) }
@@ -237,7 +237,7 @@ describe Group do
it { is_expected.to match_array([private_group, internal_group, group]) }
end
- context 'when user is a member of private subgroup', :postgresql do
+ context 'when user is a member of private subgroup' do
let!(:private_subgroup) { create(:group, :private, parent: private_group) }
before do
@@ -416,7 +416,7 @@ describe Group do
it { expect(group.last_owner?(@members[:owner])).to be_falsy }
end
- context 'with owners from a parent', :postgresql do
+ context 'with owners from a parent' do
before do
parent_group = create(:group)
create(:group_member, :owner, group: parent_group)
@@ -524,7 +524,7 @@ describe Group do
it { expect(subject.parent).to be_kind_of(described_class) }
end
- describe '#members_with_parents', :nested_groups do
+ describe '#members_with_parents' do
let!(:group) { create(:group, :nested) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
@@ -535,7 +535,7 @@ describe Group do
end
end
- describe '#direct_and_indirect_members', :nested_groups do
+ describe '#direct_and_indirect_members' do
let!(:group) { create(:group, :nested) }
let!(:sub_group) { create(:group, parent: group) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
@@ -552,7 +552,7 @@ describe Group do
end
end
- describe '#users_with_descendants', :nested_groups do
+ describe '#users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
@@ -571,7 +571,7 @@ describe Group do
end
end
- describe '#direct_and_indirect_users', :nested_groups do
+ describe '#direct_and_indirect_users' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
let(:user_c) { create(:user) }
@@ -601,7 +601,7 @@ describe Group do
end
end
- describe '#project_users_with_descendants', :nested_groups do
+ describe '#project_users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
let(:user_c) { create(:user) }
@@ -678,7 +678,7 @@ describe Group do
end
end
- context 'sub groups and projects', :nested_groups do
+ context 'sub groups and projects' do
it 'enables two_factor_requirement for group member' do
group.add_user(user, GroupMember::OWNER)
@@ -687,7 +687,7 @@ describe Group do
expect(user.reload.require_two_factor_authentication_from_group).to be_truthy
end
- context 'expanded group members', :nested_groups do
+ context 'expanded group members' do
let(:indirect_user) { create(:user) }
it 'enables two_factor_requirement for subgroup member' do
@@ -720,7 +720,7 @@ describe Group do
expect(user.reload.require_two_factor_authentication_from_group).to be_falsey
end
- it 'does not enable two_factor_requirement for subgroup child project member', :nested_groups do
+ it 'does not enable two_factor_requirement for subgroup child project member' do
subgroup = create(:group, :nested, parent: group)
project = create(:project, group: subgroup)
project.add_maintainer(user)
@@ -820,7 +820,7 @@ describe Group do
it_behaves_like 'ref is protected'
end
- context 'when group has children', :postgresql do
+ context 'when group has children' do
let(:group_child) { create(:group, parent: group) }
let(:group_child_2) { create(:group, parent: group_child) }
let(:group_child_3) { create(:group, parent: group_child_2) }
@@ -843,7 +843,7 @@ describe Group do
end
end
- describe '#highest_group_member', :nested_groups do
+ describe '#highest_group_member' do
let(:nested_group) { create(:group, parent: group) }
let(:nested_group_2) { create(:group, parent: nested_group) }
let(:user) { create(:user) }
@@ -932,7 +932,7 @@ describe Group do
it { is_expected.to eq(config) }
end
- context 'with parent groups', :nested_groups do
+ context 'with parent groups' do
where(:instance_value, :parent_value, :group_value, :config) do
# Instance level enabled
true | nil | nil | { status: true, scope: :instance }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index f227abd3dae..ebb0bfca369 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -69,7 +69,7 @@ describe GroupMember do
end
end
- context 'access levels', :nested_groups do
+ context 'access levels' do
context 'with parent group' do
it_behaves_like 'inherited access level as a member of entity' do
let(:entity) { create(:group, parent: parent_entity) }
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 497764b6825..79c39b81196 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -130,7 +130,7 @@ describe ProjectMember do
end
end
- context 'with parent group and a subgroup', :nested_groups do
+ context 'with parent group and a subgroup' do
it_behaves_like 'inherited access level as a member of entity' do
let(:subgroup) { create(:group, parent: parent_entity) }
let(:entity) { create(:project, group: subgroup) }
diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb
index 3229a32234e..5341278db7c 100644
--- a/spec/models/namespace/root_storage_statistics_spec.rb
+++ b/spec/models/namespace/root_storage_statistics_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
it_behaves_like 'data refresh'
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:subgroup1) { create(:group, parent: namespace)}
let(:subgroup2) { create(:group, parent: subgroup1)}
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index f908f3504e0..2b9c3c43af9 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -191,7 +191,7 @@ describe Namespace do
end
end
- describe '#ancestors_upto', :nested_groups do
+ describe '#ancestors_upto' do
let(:parent) { create(:group) }
let(:child) { create(:group, parent: parent) }
let(:child2) { create(:group, parent: child) }
@@ -271,7 +271,7 @@ describe Namespace do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
@@ -475,7 +475,7 @@ describe Namespace do
end
end
- describe '#self_and_hierarchy', :nested_groups do
+ describe '#self_and_hierarchy' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -490,7 +490,7 @@ describe Namespace do
end
end
- describe '#ancestors', :nested_groups do
+ describe '#ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -504,7 +504,7 @@ describe Namespace do
end
end
- describe '#self_and_ancestors', :nested_groups do
+ describe '#self_and_ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -518,7 +518,7 @@ describe Namespace do
end
end
- describe '#descendants', :nested_groups do
+ describe '#descendants' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -534,7 +534,7 @@ describe Namespace do
end
end
- describe '#self_and_descendants', :nested_groups do
+ describe '#self_and_descendants' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -550,7 +550,7 @@ describe Namespace do
end
end
- describe '#users_with_descendants', :nested_groups do
+ describe '#users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
@@ -597,7 +597,7 @@ describe Namespace do
it { expect(group.all_pipelines.to_a).to match_array([pipeline1, pipeline2]) }
end
- describe '#share_with_group_lock with subgroups', :nested_groups do
+ describe '#share_with_group_lock with subgroups' do
context 'when creating a subgroup' do
let(:subgroup) { create(:group, parent: root_group )}
@@ -738,7 +738,7 @@ describe Namespace do
end
describe '#root_ancestor' do
- it 'returns the top most ancestor', :nested_groups do
+ it 'returns the top most ancestor' do
root_group = create(:group)
nested_group = create(:group, parent: root_group)
deep_nested_group = create(:group, parent: nested_group)
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 20278d81f6d..4122736c148 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -49,7 +49,7 @@ describe NotificationRecipient do
end
context '#notification_setting' do
- context 'for child groups', :nested_groups do
+ context 'for child groups' do
let!(:moved_group) { create(:group) }
let(:group) { create(:group) }
let(:sub_group_1) { create(:group, parent: group) }
diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb
index 95ae204a8a8..d435fccc09a 100644
--- a/spec/models/postgresql/replication_slot_spec.rb
+++ b/spec/models/postgresql/replication_slot_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Postgresql::ReplicationSlot, :postgresql do
+describe Postgresql::ReplicationSlot do
describe '.in_use?' do
it 'returns true when replication slots are present' do
expect(described_class).to receive(:exists?).and_return(true)
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index dad5506900b..cd997224122 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -25,7 +25,7 @@ describe ProjectGroupLink do
expect(project_group_link).not_to be_valid
end
- it "doesn't allow a project to be shared with an ancestor of the group it is in", :nested_groups do
+ it "doesn't allow a project to be shared with an ancestor of the group it is in" do
project_group_link.group = parent_group
expect(project_group_link).not_to be_valid
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 7d458324c20..24dfd0c5605 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2019,62 +2019,33 @@ describe Project do
end
end
- describe '#latest_successful_build_for' do
+ describe '#latest_successful_build_for_ref' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create_pipeline(project) }
- context 'with many builds' do
- it 'gives the latest builds from latest pipeline' do
- pipeline1 = create_pipeline(project)
- pipeline2 = create_pipeline(project)
- create_build(pipeline1, 'test')
- create_build(pipeline1, 'test2')
- build1_p2 = create_build(pipeline2, 'test')
- create_build(pipeline2, 'test2')
-
- expect(project.latest_successful_build_for(build1_p2.name))
- .to eq(build1_p2)
- end
- end
+ it_behaves_like 'latest successful build for sha or ref'
- context 'with succeeded pipeline' do
- let!(:build) { create_build }
+ subject { project.latest_successful_build_for_ref(build_name) }
- context 'standalone pipeline' do
- it 'returns builds for ref for default_branch' do
- expect(project.latest_successful_build_for(build.name))
- .to eq(build)
- end
+ context 'with a specified ref' do
+ let(:build) { create_build }
- it 'returns empty relation if the build cannot be found' do
- expect(project.latest_successful_build_for('TAIL'))
- .to be_nil
- end
- end
-
- context 'with some pending pipeline' do
- before do
- create_build(create_pipeline(project, 'pending'))
- end
+ subject { project.latest_successful_build_for_ref(build.name, project.default_branch) }
- it 'gives the latest build from latest pipeline' do
- expect(project.latest_successful_build_for(build.name))
- .to eq(build)
- end
- end
+ it { is_expected.to eq(build) }
end
+ end
- context 'with pending pipeline' do
- it 'returns empty relation' do
- pipeline.update(status: 'pending')
- pending_build = create_build(pipeline)
+ describe '#latest_successful_build_for_sha' do
+ let(:project) { create(:project, :repository) }
+ let(:pipeline) { create_pipeline(project) }
- expect(project.latest_successful_build_for(pending_build.name)).to be_nil
- end
- end
+ it_behaves_like 'latest successful build for sha or ref'
+
+ subject { project.latest_successful_build_for_sha(build_name, project.commit.sha) }
end
- describe '#latest_successful_build_for!' do
+ describe '#latest_successful_build_for_ref!' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create_pipeline(project) }
@@ -2087,7 +2058,7 @@ describe Project do
build1_p2 = create_build(pipeline2, 'test')
create_build(pipeline2, 'test2')
- expect(project.latest_successful_build_for(build1_p2.name))
+ expect(project.latest_successful_build_for_ref!(build1_p2.name))
.to eq(build1_p2)
end
end
@@ -2097,12 +2068,12 @@ describe Project do
context 'standalone pipeline' do
it 'returns builds for ref for default_branch' do
- expect(project.latest_successful_build_for!(build.name))
+ expect(project.latest_successful_build_for_ref!(build.name))
.to eq(build)
end
it 'returns exception if the build cannot be found' do
- expect { project.latest_successful_build_for!(build.name, 'TAIL') }
+ expect { project.latest_successful_build_for_ref!(build.name, 'TAIL') }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -2113,7 +2084,7 @@ describe Project do
end
it 'gives the latest build from latest pipeline' do
- expect(project.latest_successful_build_for!(build.name))
+ expect(project.latest_successful_build_for_ref!(build.name))
.to eq(build)
end
end
@@ -2124,7 +2095,7 @@ describe Project do
pipeline.update(status: 'pending')
pending_build = create_build(pipeline)
- expect { project.latest_successful_build_for!(pending_build.name) }
+ expect { project.latest_successful_build_for_ref!(pending_build.name) }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -2292,7 +2263,7 @@ describe Project do
end
end
- describe '#ancestors_upto', :nested_groups do
+ describe '#ancestors_upto' do
let(:parent) { create(:group) }
let(:child) { create(:group, parent: parent) }
let(:child2) { create(:group, parent: child) }
@@ -2331,7 +2302,7 @@ describe Project do
it { is_expected.to eq(group) }
end
- context 'in a nested group', :nested_groups do
+ context 'in a nested group' do
let(:root) { create(:group) }
let(:child) { create(:group, parent: root) }
let(:project) { create(:project, group: child) }
@@ -2479,7 +2450,7 @@ describe Project do
expect(forked_project.in_fork_network_of?(project)).to be_truthy
end
- it 'is true for a fork of a fork', :postgresql do
+ it 'is true for a fork of a fork' do
other_fork = fork_project(forked_project)
expect(other_fork.in_fork_network_of?(project)).to be_truthy
@@ -3801,7 +3772,7 @@ describe Project do
end
end
- context 'when enabled on root parent', :nested_groups do
+ context 'when enabled on root parent' do
let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) }
context 'when auto devops instance enabled' do
@@ -3821,7 +3792,7 @@ describe Project do
end
end
- context 'when disabled on root parent', :nested_groups do
+ context 'when disabled on root parent' do
let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) }
context 'when auto devops instance enabled' do
@@ -4033,7 +4004,7 @@ describe Project do
context 'with a ref that is not the default branch' do
it 'returns the latest successful pipeline for the given ref' do
- expect(project.ci_pipelines).to receive(:latest_successful_for).with('foo')
+ expect(project.ci_pipelines).to receive(:latest_successful_for_ref).with('foo')
project.latest_successful_pipeline_for('foo')
end
@@ -4061,7 +4032,7 @@ describe Project do
it 'memoizes and returns the latest successful pipeline for the default branch' do
pipeline = double(:pipeline)
- expect(project.ci_pipelines).to receive(:latest_successful_for)
+ expect(project.ci_pipelines).to receive(:latest_successful_for_ref)
.with(project.default_branch)
.and_return(pipeline)
.once
@@ -4264,18 +4235,16 @@ describe Project do
expect(project.badges.count).to eq 3
end
- if Group.supports_nested_objects?
- context 'with nested_groups' do
- let(:parent_group) { create(:group) }
+ context 'with nested_groups' do
+ let(:parent_group) { create(:group) }
- before do
- create_list(:group_badge, 2, group: project_group)
- project_group.update(parent: parent_group)
- end
+ before do
+ create_list(:group_badge, 2, group: project_group)
+ project_group.update(parent: parent_group)
+ end
- it 'returns the project and the project nested groups badges' do
- expect(project.badges.count).to eq 5
- end
+ it 'returns the project and the project nested groups badges' do
+ expect(project.badges.count).to eq 5
end
end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index b5bf294790a..9aeef7c3b4b 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -262,11 +262,7 @@ describe Todo do
todo2 = create(:todo, group: child_group)
todos = described_class.for_group_and_descendants(parent_group)
- expect(todos).to include(todo1)
-
- # Nested groups only work on PostgreSQL, so on MySQL todo2 won't be
- # present.
- expect(todos).to include(todo2) if Gitlab::Database.postgresql?
+ expect(todos).to contain_exactly(todo1, todo2)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 2d20f8c78cc..35c335c5b5c 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -985,7 +985,7 @@ describe User do
it { expect(user.namespaces).to contain_exactly(user.namespace, group) }
it { expect(user.manageable_namespaces).to contain_exactly(user.namespace, group) }
- context 'with child groups', :nested_groups do
+ context 'with child groups' do
let!(:subgroup) { create(:group, parent: group) }
describe '#manageable_namespaces' do
@@ -2082,11 +2082,7 @@ describe User do
subject { user.membership_groups }
- if Group.supports_nested_objects?
- it { is_expected.to contain_exactly parent_group, child_group }
- else
- it { is_expected.to contain_exactly parent_group }
- end
+ it { is_expected.to contain_exactly parent_group, child_group }
end
describe '#authorizations_for_projects' do
@@ -2386,7 +2382,7 @@ describe User do
it_behaves_like :member
end
- context 'with subgroup with different owner for project runner', :nested_groups do
+ context 'with subgroup with different owner for project runner' do
let(:group) { create(:group) }
let(:another_user) { create(:user) }
let(:subgroup) { create(:group, parent: group) }
@@ -2490,22 +2486,16 @@ describe User do
group.add_owner(user)
end
- if Group.supports_nested_objects?
- it 'returns all groups' do
- is_expected.to match_array [
- group,
- nested_group_1, nested_group_1_1,
- nested_group_2, nested_group_2_1
- ]
- end
- else
- it 'returns the top-level groups' do
- is_expected.to match_array [group]
- end
+ it 'returns all groups' do
+ is_expected.to match_array [
+ group,
+ nested_group_1, nested_group_1_1,
+ nested_group_2, nested_group_2_1
+ ]
end
end
- context 'user is member of the first child (internal node), branch 1', :nested_groups do
+ context 'user is member of the first child (internal node), branch 1' do
before do
nested_group_1.add_owner(user)
end
@@ -2518,7 +2508,7 @@ describe User do
end
end
- context 'user is member of the first child (internal node), branch 2', :nested_groups do
+ context 'user is member of the first child (internal node), branch 2' do
before do
nested_group_2.add_owner(user)
end
@@ -2531,7 +2521,7 @@ describe User do
end
end
- context 'user is member of the last child (leaf node)', :nested_groups do
+ context 'user is member of the last child (leaf node)' do
before do
nested_group_1_1.add_owner(user)
end
@@ -2687,7 +2677,7 @@ describe User do
end
end
- context 'with 2FA requirement from expanded groups', :nested_groups do
+ context 'with 2FA requirement from expanded groups' do
let!(:group1) { create :group, require_two_factor_authentication: true }
let!(:group1a) { create :group, parent: group1 }
@@ -2702,7 +2692,7 @@ describe User do
end
end
- context 'with 2FA requirement on nested child group', :nested_groups do
+ context 'with 2FA requirement on nested child group' do
let!(:group1) { create :group, require_two_factor_authentication: false }
let!(:group1a) { create :group, require_two_factor_authentication: true, parent: group1 }
diff --git a/spec/policies/group_member_policy_spec.rb b/spec/policies/group_member_policy_spec.rb
index 7bd7184cffe..a4f3301a064 100644
--- a/spec/policies/group_member_policy_spec.rb
+++ b/spec/policies/group_member_policy_spec.rb
@@ -58,7 +58,7 @@ describe GroupMemberPolicy do
end
end
- context 'with the group parent', :postgresql do
+ context 'with the group parent' do
let(:current_user) { create :user }
let(:subgroup) { create(:group, :private, parent: group)}
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index dc3675a7b9e..be55d94daec 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -51,7 +51,7 @@ describe GroupPolicy do
it { expect_allowed(:read_label, :read_list) }
- context 'in subgroups', :nested_groups do
+ context 'in subgroups' do
let(:subgroup) { create(:group, :private, parent: group) }
let(:project) { create(:project, namespace: subgroup) }
@@ -104,8 +104,6 @@ describe GroupPolicy do
end
it 'allows every maintainer permission plus creating subgroups' do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
create_subgroup_permission = [:create_subgroup]
updated_maintainer_permissions =
maintainer_permissions + create_subgroup_permission
@@ -122,8 +120,6 @@ describe GroupPolicy do
context 'with subgroup_creation_level set to owner' do
it 'allows every maintainer permission' do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -137,8 +133,6 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -151,8 +145,6 @@ describe GroupPolicy do
let(:current_user) { admin }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -161,52 +153,7 @@ describe GroupPolicy do
end
end
- describe 'when nested group support feature is disabled' do
- before do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
- end
-
- context 'admin' do
- let(:current_user) { admin }
-
- it 'allows every owner permission except creating subgroups' do
- create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions =
- owner_permissions - create_subgroup_permission
-
- expect_disallowed(*create_subgroup_permission)
- expect_allowed(*updated_owner_permissions)
- end
- end
-
- context 'owner' do
- let(:current_user) { owner }
-
- it 'allows every owner permission except creating subgroups' do
- create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions =
- owner_permissions - create_subgroup_permission
-
- expect_disallowed(*create_subgroup_permission)
- expect_allowed(*updated_owner_permissions)
- end
- end
-
- context 'maintainer' do
- let(:current_user) { maintainer }
-
- it 'allows every maintainer permission except creating subgroups' do
- create_subgroup_permission = [:create_subgroup]
- updated_maintainer_permissions =
- maintainer_permissions - create_subgroup_permission
-
- expect_disallowed(*create_subgroup_permission)
- expect_allowed(*updated_maintainer_permissions)
- end
- end
- end
-
- describe 'private nested group use the highest access level from the group and inherited permissions', :nested_groups do
+ describe 'private nested group use the highest access level from the group and inherited permissions' do
let(:nested_group) do
create(:group, :private, :owner_subgroup_creation_only, parent: group)
end
@@ -289,8 +236,6 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 7054a70e2ed..6b988e2645b 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -43,7 +43,7 @@ describe Clusters::ClusterPresenter do
end
shared_examples 'ancestor clusters' do
- context 'ancestor clusters', :nested_groups do
+ context 'ancestor clusters' do
let(:root_group) { create(:group, name: 'Root Group') }
let(:parent) { create(:group, name: 'parent', parent: root_group) }
let(:child) { create(:group, name: 'child', parent: parent) }
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index de79e8c4c5c..0b9c0c2ebe9 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -63,7 +63,7 @@ describe API::Boards do
end
end
- describe "POST /groups/:id/boards/lists", :nested_groups do
+ describe "POST /groups/:id/boards/lists" do
set(:group) { create(:group) }
set(:board_parent) { create(:group, parent: group ) }
let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 63fa16c79ca..815e9531ecf 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting projects', :nested_groups do
+describe 'getting projects' do
include GraphqlHelpers
let(:group) { create(:group) }
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 3769f8b78e4..fcea57d9df7 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -94,7 +94,7 @@ describe API::GroupLabels do
expect(response).to have_gitlab_http_status(400)
end
- it "does not delete parent's group labels", :nested_groups do
+ it "does not delete parent's group labels" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
@@ -127,7 +127,7 @@ describe API::GroupLabels do
expect(json_response['description']).to eq('test')
end
- it "does not update parent's group label", :nested_groups do
+ it "does not update parent's group label" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 52d926d5484..50f36141aed 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -530,7 +530,7 @@ describe API::Groups do
expect(json_response.length).to eq(2)
end
- it "returns projects including those in subgroups", :nested_groups do
+ it "returns projects including those in subgroups" do
subgroup = create(:group, parent: group1)
create(:project, group: subgroup)
create(:project, group: subgroup)
@@ -642,7 +642,7 @@ describe API::Groups do
end
end
- describe 'GET /groups/:id/subgroups', :nested_groups do
+ describe 'GET /groups/:id/subgroups' do
let!(:subgroup1) { create(:group, parent: group1) }
let!(:subgroup2) { create(:group, :private, parent: group1) }
let!(:subgroup3) { create(:group, :private, parent: group2) }
@@ -786,7 +786,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(403)
end
- context 'as owner', :nested_groups do
+ context 'as owner' do
before do
group2.add_owner(user1)
end
@@ -798,7 +798,7 @@ describe API::Groups do
end
end
- context 'as maintainer', :nested_groups do
+ context 'as maintainer' do
before do
group2.add_maintainer(user1)
end
@@ -825,7 +825,7 @@ describe API::Groups do
expect(json_response["visibility"]).to eq(Gitlab::VisibilityLevel.string_level(Gitlab::CurrentSettings.current_application_settings.default_group_visibility))
end
- it "creates a nested group", :nested_groups do
+ it "creates a nested group" do
parent = create(:group)
parent.add_owner(user3)
group = attributes_for(:group, { parent_id: parent.id })
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index 9a41d790945..5916bb11516 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -82,7 +82,7 @@ describe API::Issues do
end
end
- context 'when group has subgroups', :nested_groups do
+ context 'when group has subgroups' do
let(:subgroup_1) { create(:group, parent: group) }
let(:subgroup_2) { create(:group, parent: subgroup_1) }
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 55f38079b1f..26f6e705528 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -99,7 +99,7 @@ describe API::Members do
end
end
- describe 'GET /:source_type/:id/members/all', :nested_groups do
+ describe 'GET /:source_type/:id/members/all' do
let(:nested_user) { create(:user) }
let(:project_user) { create(:user) }
let(:linked_group_user) { create(:user) }
@@ -238,7 +238,7 @@ describe API::Members do
end
context 'access levels' do
- it 'does not create the member if group level is higher', :nested_groups do
+ it 'does not create the member if group level is higher' do
parent = create(:group)
group.update(parent: parent)
@@ -252,7 +252,7 @@ describe API::Members do
expect(json_response['message']['access_level']).to eq(["should be greater than or equal to Developer inherited membership from group #{parent.name}"])
end
- it 'creates the member if group level is lower', :nested_groups do
+ it 'creates the member if group level is lower' do
parent = create(:group)
group.update(parent: parent)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index ced853caab4..7a6f1cd548c 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -723,7 +723,7 @@ describe API::MergeRequests do
it_behaves_like 'merge requests list'
- context 'when have subgroups', :nested_groups do
+ context 'when have subgroups' do
let!(:group) { create(:group, :public) }
let!(:subgroup) { create(:group, parent: group) }
let!(:project) { create(:project, :public, :repository, creator: user, namespace: subgroup, only_allow_merge_if_pipeline_succeeds: false) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fee300e9d7a..5b3a2412aff 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1357,7 +1357,7 @@ describe API::Projects do
end
end
- context 'nested group project', :nested_groups do
+ context 'nested group project' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:project2) { create(:project, group: nested_group) }
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 86e41cbdf00..025568d8bea 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -104,7 +104,7 @@ describe 'OpenID Connect requests' do
expect(json_response).to match(id_token_claims.merge(user_info_claims))
expected_groups = [group1.full_path, group3.full_path]
- expected_groups << group4.full_path if Group.supports_nested_objects?
+ expected_groups << group4.full_path
expect(json_response['groups']).to match_array(expected_groups)
end
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index b58d95ccb43..00e2f931549 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -62,7 +62,7 @@ describe GroupChildEntity do
it_behaves_like 'group child json'
end
- describe 'for a group', :nested_groups do
+ describe 'for a group' do
let(:description) { 'Awesomeness' }
let(:object) do
create(:group, :nested, :with_avatar,
diff --git a/spec/serializers/group_child_serializer_spec.rb b/spec/serializers/group_child_serializer_spec.rb
index 5541ada3750..c9e8535585b 100644
--- a/spec/serializers/group_child_serializer_spec.rb
+++ b/spec/serializers/group_child_serializer_spec.rb
@@ -16,7 +16,7 @@ describe GroupChildSerializer do
end
end
- context 'with a hierarchy', :nested_groups do
+ context 'with a hierarchy' do
let(:parent) { create(:group) }
subject(:serializer) do
@@ -75,7 +75,7 @@ describe GroupChildSerializer do
expect(serializer.represent(build_list(:project, 2))).to be_kind_of(Array)
end
- context 'with a hierarchy', :nested_groups do
+ context 'with a hierarchy' do
let(:parent) { create(:group) }
subject(:serializer) do
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 54e6abc2d3a..7f9827329b3 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -126,7 +126,7 @@ describe PipelineSerializer do
expect(subject.all? { |entry| entry[:merge_request].present? }).to be_truthy
end
- it 'preloads related merge requests', :postgresql do
+ it 'preloads related merge requests' do
recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.log)
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 40878e24cb4..931b67b2950 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -112,7 +112,7 @@ describe Boards::Issues::ListService do
it_behaves_like 'issues list service'
end
- context 'and group is an ancestor', :nested_groups do
+ context 'and group is an ancestor' do
let(:parent) { create(:group) }
let(:group) { create(:group, parent: parent) }
let!(:backlog) { create(:backlog_list, board: board) }
diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb
index 7f8ab517cef..7591b2f6f12 100644
--- a/spec/services/groups/auto_devops_service_spec.rb
+++ b/spec/services/groups/auto_devops_service_spec.rb
@@ -47,7 +47,7 @@ describe Groups::AutoDevopsService, '#execute' do
expect(subgroup_1.auto_devops_enabled?).to eq(false)
end
- context 'when subgroups have projects', :nested_groups do
+ context 'when subgroups have projects' do
it 'reflects changes on projects' do
subgroup_1 = create(:group, parent: group)
project_1 = create(:project, namespace: subgroup_1)
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index a7c95428485..0f9f20de586 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -44,7 +44,7 @@ describe Groups::CreateService, '#execute' do
end
end
- describe 'creating subgroup', :nested_groups do
+ describe 'creating subgroup' do
let!(:group) { create(:group) }
let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) }
@@ -54,47 +54,31 @@ describe Groups::CreateService, '#execute' do
end
it { is_expected.to be_persisted }
+ end
- context 'when nested groups feature is disabled' do
- it 'does not save group and returns an error' do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
+ context 'as guest' do
+ it 'does not save group and returns an error' do
+ is_expected.not_to be_persisted
- is_expected.not_to be_persisted
- expect(subject.errors[:parent_id]).to include('You don’t have permission to create a subgroup in this group.')
- expect(subject.parent_id).to be_nil
- end
+ expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.')
+ expect(subject.parent_id).to be_nil
end
end
- context 'when nested groups feature is enabled' do
+ context 'as owner' do
before do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
- end
-
- context 'as guest' do
- it 'does not save group and returns an error' do
- is_expected.not_to be_persisted
-
- expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.')
- expect(subject.parent_id).to be_nil
- end
+ group.add_owner(user)
end
- context 'as owner' do
- before do
- group.add_owner(user)
- end
+ it { is_expected.to be_persisted }
+ end
- it { is_expected.to be_persisted }
+ context 'as maintainer' do
+ before do
+ group.add_maintainer(user)
end
- context 'as maintainer' do
- before do
- group.add_maintainer(user)
- end
-
- it { is_expected.to be_persisted }
- end
+ it { is_expected.to be_persisted }
end
end
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index 13acf9e055b..b30392c1b12 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -28,35 +28,7 @@ describe Groups::NestedCreateService do
end
end
- describe 'without subgroups' do
- let(:params) { { group_path: 'a-group' } }
-
- before do
- allow(Group).to receive(:supports_nested_objects?) { false }
- end
-
- it 'creates the group' do
- group = service.execute
-
- expect(group).to be_persisted
- end
-
- it 'returns the group if it already existed' do
- existing_group = create(:group, path: 'a-group')
-
- expect(service.execute).to eq(existing_group)
- end
-
- it 'raises an error when tring to create a subgroup' do
- service = described_class.new(user, group_path: 'a-group/a-sub-group')
-
- expect { service.execute }.to raise_error('Nested groups are not supported on MySQL')
- end
-
- it_behaves_like 'with a visibility level'
- end
-
- describe 'with subgroups', :nested_groups do
+ describe 'with subgroups' do
let(:params) { { group_path: 'a-group/a-sub-group' } }
describe "#execute" do
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index b5708ebba76..f3af8cf5f3b 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -2,28 +2,13 @@
require 'rails_helper'
-describe Groups::TransferService, :postgresql do
+describe Groups::TransferService do
let(:user) { create(:user) }
let(:new_parent_group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
let(:transfer_service) { described_class.new(group, user) }
shared_examples 'ensuring allowed transfer for a group' do
- context 'with other database than PostgreSQL' do
- before do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
- end
-
- it 'returns false' do
- expect(transfer_service.execute(new_parent_group)).to be_falsy
- end
-
- it 'adds an error on group' do
- transfer_service.execute(new_parent_group)
- expect(transfer_service.error).to eq('Transfer failed: Database is not supported.')
- end
- end
-
context "when there's an exception on GitLab shell directories" do
let(:new_parent_group) { create(:group, :public) }
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index d081c20f669..5d4576139f7 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -133,7 +133,7 @@ describe Groups::UpdateService do
end
end
- context 'for a subgroup', :nested_groups do
+ context 'for a subgroup' do
let(:subgroup) { create(:group, :private, parent: private_group) }
context 'when the parent group share_with_group_lock is enabled' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 68b79132096..3ae7c7b1c1d 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -116,7 +116,7 @@ describe Issues::UpdateService, :mailer do
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
- context 'when moving issue between issues from different projects', :nested_groups do
+ context 'when moving issue between issues from different projects' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
@@ -703,7 +703,7 @@ describe Issues::UpdateService, :mailer do
end
end
- context 'when moving an issue ', :nested_groups do
+ context 'when moving an issue ' do
it 'raises an error for invalid move ids within a project' do
opts = { move_between_ids: [9000, 9999] }
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 52f9a305d8f..7dce7f035d4 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -267,15 +267,15 @@ describe Members::DestroyService do
expect(group.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subgroup membership', :postgresql do
+ it 'removes the subgroup membership' do
expect(subgroup.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subsubgroup membership', :postgresql do
+ it 'removes the subsubgroup membership' do
expect(subsubgroup.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subsubproject membership', :postgresql do
+ it 'removes the subsubproject membership' do
expect(subsubproject.members.map(&:user)).not_to include(member_user)
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/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index c20de1fd079..1dcade1de0d 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -111,7 +111,7 @@ describe NotificationService, :mailer do
should_email(participant)
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
before do
build_group(project)
end
@@ -337,7 +337,7 @@ describe NotificationService, :mailer do
it_behaves_like 'new note notifications'
- context 'which is a subgroup', :nested_groups do
+ context 'which is a subgroup' do
let!(:parent) { create(:group) }
let!(:group) { create(:group, parent: parent) }
@@ -388,7 +388,7 @@ describe NotificationService, :mailer do
should_email(admin)
end
- context 'on project that belongs to subgroup', :nested_groups do
+ context 'on project that belongs to subgroup' do
let(:group_reporter) { create(:user) }
let(:group_guest) { create(:user) }
let(:parent_group) { create(:group) }
@@ -458,7 +458,7 @@ describe NotificationService, :mailer do
should_not_email_nested_group_user(@pg_disabled)
end
- it 'notifies parent group members with mention level', :nested_groups do
+ it 'notifies parent group members with mention level' do
note = create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: "@#{@pg_mention.username}")
notification.new_note(note)
@@ -2410,52 +2410,40 @@ describe NotificationService, :mailer do
group
end
- # Creates a nested group only if supported
- # to avoid errors on MySQL
def create_nested_group(visibility)
- if Group.supports_nested_objects?
- parent_group = create(:group, visibility)
- child_group = create(:group, visibility, parent: parent_group)
+ parent_group = create(:group, visibility)
+ child_group = create(:group, visibility, parent: parent_group)
- # Parent group member: global=disabled, parent_group=watch, child_group=global
- @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group)
- @pg_watcher.notification_settings_for(nil).disabled!
+ # Parent group member: global=disabled, parent_group=watch, child_group=global
+ @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group)
+ @pg_watcher.notification_settings_for(nil).disabled!
- # Parent group member: global=global, parent_group=disabled, child_group=global
- @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group)
- @pg_disabled.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=disabled, child_group=global
+ @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group)
+ @pg_disabled.notification_settings_for(nil).global!
- # Parent group member: global=global, parent_group=mention, child_group=global
- @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group)
- @pg_mention.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=mention, child_group=global
+ @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
- # Parent group member: global=global, parent_group=participating, child_group=global
- @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group)
- @pg_mention.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=participating, child_group=global
+ @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
- child_group
- else
- create(:group, visibility)
- end
+ child_group
end
def add_member_for_parent_group(user, project)
- return unless Group.supports_nested_objects?
-
project.reload
project.group.parent.add_maintainer(user)
end
def should_email_nested_group_user(user, times: 1, recipients: email_recipients)
- return unless Group.supports_nested_objects?
-
should_email(user, times: 1, recipients: email_recipients)
end
def should_not_email_nested_group_user(user, recipients: email_recipients)
- return unless Group.supports_nested_objects?
-
should_not_email(user, recipients: email_recipients)
end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 2f70c8ea94d..b625653bc77 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -118,7 +118,7 @@ describe Projects::AutocompleteService do
expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:subgroup) { create(:group, :public, parent: group) }
let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 2a553e18807..ce809bbf6c5 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -176,7 +176,7 @@ describe Todos::Destroy::EntityLeaveService do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) }
diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb
index a1798686d7c..7dd495847b3 100644
--- a/spec/services/todos/destroy/group_private_service_spec.rb
+++ b/spec/services/todos/destroy/group_private_service_spec.rb
@@ -35,7 +35,7 @@ describe Todos::Destroy::GroupPrivateService do
expect(project_member.todos).to match_array([todo_project_member])
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:parent_group) { create(:group) }
let(:subgroup) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) }
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 0287a24808d..f5a914bb482 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -135,7 +135,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects of subgroups of groups the user is a member of', :nested_groups do
+ context 'projects of subgroups of groups the user is a member of' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let!(:other_project) { create(:project, group: nested_group) }
@@ -163,7 +163,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects shared with subgroups of groups the user is a member of', :nested_groups do
+ context 'projects shared with subgroups of groups the user is a member of' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:other_project) { create(:project) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a44b5069ade..6ef5f46c81b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -258,14 +258,6 @@ RSpec.configure do |config|
Gitlab::CurrentSettings.clear_in_memory_application_settings!
end
- config.around(:each, :nested_groups) do |example|
- example.run if Group.supports_nested_objects?
- end
-
- config.around(:each, :postgresql) do |example|
- example.run if Gitlab::Database.postgresql?
- end
-
# This makes sure the `ApplicationController#can?` method is stubbed with the
# original implementation for all view specs.
config.before(:each, type: :view) do
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..718d9857b18 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" }
@@ -321,7 +323,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen an existing nested namespace and name for the project', :postgresql do
+ context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
@@ -340,7 +342,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -371,7 +373,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
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/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index 74389c4d82b..c11725c63d2 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -31,7 +31,7 @@ RSpec.shared_context 'GroupPolicy context' do
:admin_group_member,
:change_visibility_level,
:set_note_created_at,
- (Gitlab::Database.postgresql? ? :create_subgroup : nil)
+ :create_subgroup
].compact
end
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/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/project_latest_successful_build_for_examples.rb b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb
new file mode 100644
index 00000000000..a9bd23e9fc9
--- /dev/null
+++ b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+shared_examples 'latest successful build for sha or ref' do
+ context 'with many builds' do
+ let(:other_pipeline) { create_pipeline(project) }
+ let(:other_build) { create_build(other_pipeline, 'test') }
+ let(:build_name) { other_build.name }
+
+ before do
+ pipeline1 = create_pipeline(project)
+ pipeline2 = create_pipeline(project)
+ create_build(pipeline1, 'test')
+ create_build(pipeline1, 'test2')
+ create_build(pipeline2, 'test2')
+ end
+
+ it 'gives the latest builds from latest pipeline' do
+ expect(subject).to eq(other_build)
+ end
+ end
+
+ context 'with succeeded pipeline' do
+ let!(:build) { create_build }
+ let(:build_name) { build.name }
+
+ context 'standalone pipeline' do
+ it 'returns builds for ref for default_branch' do
+ expect(subject).to eq(build)
+ end
+
+ context 'with nonexistent build' do
+ let(:build_name) { 'TAIL' }
+
+ it 'returns empty relation if the build cannot be found' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ context 'with some pending pipeline' do
+ before do
+ create_build(create_pipeline(project, 'pending'))
+ end
+
+ it 'gives the latest build from latest pipeline' do
+ expect(subject).to eq(build)
+ end
+ end
+ end
+
+ context 'with pending pipeline' do
+ let!(:pending_build) { create_build(pipeline) }
+ let(:build_name) { pending_build.name }
+
+ before do
+ pipeline.update(status: 'pending')
+ end
+
+ it 'returns empty relation' do
+ expect(subject).to be_nil
+ end
+ end
+end
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/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/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 29e15960fb8..47804411b9d 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -35,7 +35,7 @@ describe 'groups/edit.html.haml' do
it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
end
- context 'for a subgroup', :nested_groups do
+ context 'for a subgroup' do
let!(:subgroup) { create(:group, parent: root_group) }
let(:sub_owner) { create(:user) }
let(:test_group) { subgroup }
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index 2e19d0cec26..26e429ac5d0 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'layouts/header/_new_dropdown' do
)
end
- it 'has a "New subgroup" link', :nested_groups do
+ it 'has a "New subgroup" link' do
render
expect(rendered).to have_link(
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=