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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/verify-lockfile.gitlab-ci.yml11
-rw-r--r--.rubocop_manual_todo.yml1269
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue12
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_details.js4
-rw-r--r--app/helpers/invite_members_helper.rb10
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/deployment.rb7
-rw-r--r--app/services/deployments/create_service.rb8
-rw-r--r--app/views/groups/_invite_members_modal.html.haml2
-rw-r--r--app/views/groups/_invite_members_side_nav_link.html.haml3
-rw-r--r--app/views/groups/group_members/index.html.haml6
-rw-r--r--app/views/groups/show.html.haml7
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_invite_members_link.html.haml4
-rw-r--r--app/views/projects/_invite_members_modal.html.haml2
-rw-r--r--app/views/projects/_invite_members_side_nav_link.html.haml3
-rw-r--r--app/views/projects/empty.html.haml3
-rw-r--r--app/views/projects/no_repo.html.haml2
-rw-r--r--app/views/projects/project_members/index.html.haml6
-rw-r--r--app/views/projects/show.html.haml3
-rw-r--r--app/views/shared/deploy_tokens/_table.html.haml2
-rw-r--r--app/workers/jira_connect/sync_builds_worker.rb1
-rw-r--r--changelogs/unreleased/239172-add-entity-columns-to-vulnerability-occurrences.yml5
-rw-r--r--changelogs/unreleased/294004-enable-jira-sync-builds.yml5
-rw-r--r--changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsindeploytokens_table-html-haml.yml5
-rw-r--r--changelogs/unreleased/zj-no-duplicate-deployments.yml5
-rw-r--r--config/feature_flags/development/jira_sync_builds.yml8
-rw-r--r--config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml2
-rw-r--r--db/migrate/20210105153342_add_entity_columns_to_vulnerability_occurrences.rb16
-rw-r--r--db/migrate/20210105154321_add_text_limit_to_vulnerability_occurrences_entity_columns.rb23
-rw-r--r--db/schema_migrations/202101051533421
-rw-r--r--db/schema_migrations/202101051543211
-rw-r--r--db/structure.sql11
-rw-r--r--doc/api/merge_requests.md56
-rw-r--r--doc/development/gemfile.md13
-rw-r--r--doc/development/testing_guide/best_practices.md176
-rw-r--r--doc/raketasks/backup_restore.md6
-rw-r--r--doc/raketasks/cleanup.md4
-rw-r--r--doc/raketasks/features.md4
-rw-r--r--doc/raketasks/import.md4
-rw-r--r--doc/raketasks/list_repos.md4
-rw-r--r--doc/raketasks/spdx.md4
-rw-r--r--doc/raketasks/user_management.md4
-rw-r--r--doc/raketasks/web_hooks.md4
-rw-r--r--doc/topics/autodevops/index.md10
-rw-r--r--doc/topics/autodevops/requirements.md9
-rw-r--r--doc/user/clusters/applications.md49
-rw-r--r--doc/user/group/saml_sso/index.md27
-rw-r--r--doc/user/packages/nuget_repository/index.md2
-rw-r--r--doc/user/profile/notifications.md1
-rw-r--r--doc/user/project/clusters/index.md86
-rw-r--r--lib/atlassian/jira_connect/client.rb2
-rw-r--r--lib/gitlab/experimentation.rb42
-rw-r--r--lib/gitlab/experimentation/controller_concern.rb6
-rw-r--r--lib/gitlab/experimentation/experiment.rb3
-rw-r--r--lib/gitlab/experimentation_logger.rb9
-rw-r--r--rubocop/cop/gitlab/namespaced_class.rb39
-rw-r--r--spec/features/admin/admin_projects_spec.rb1
-rw-r--r--spec/features/groups/navbar_spec.rb8
-rw-r--r--spec/features/projects/members/invite_group_spec.rb4
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb2
-rw-r--r--spec/features/projects/navbar_spec.rb19
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb2
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js13
-rw-r--r--spec/helpers/invite_members_helper_spec.rb84
-rw-r--r--spec/helpers/projects_helper_spec.rb34
-rw-r--r--spec/lib/atlassian/jira_connect/client_spec.rb18
-rw-r--r--spec/lib/gitlab/experimentation/controller_concern_spec.rb27
-rw-r--r--spec/lib/gitlab/experimentation/experiment_spec.rb3
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb62
-rw-r--r--spec/models/ci/pipeline_spec.rb20
-rw-r--r--spec/rubocop/cop/gitlab/namespaced_class_spec.rb73
-rw-r--r--spec/services/deployments/create_service_spec.rb21
-rw-r--r--spec/views/groups/show.html.haml_spec.rb52
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb6
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb2
-rw-r--r--spec/views/projects/empty.html.haml_spec.rb37
-rw-r--r--spec/views/projects/show.html.haml_spec.rb51
-rw-r--r--spec/workers/jira_connect/sync_builds_worker_spec.rb24
-rw-r--r--yarn.lock2
84 files changed, 2254 insertions, 333 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b41d7bcd34f..38426bae943 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -111,3 +111,4 @@ include:
- local: .gitlab/ci/dast.gitlab-ci.yml
- local: .gitlab/ci/workhorse.gitlab-ci.yml
- local: .gitlab/ci/graphql.gitlab-ci.yml
+ - local: .gitlab/ci/verify-lockfile.gitlab-ci.yml
diff --git a/.gitlab/ci/verify-lockfile.gitlab-ci.yml b/.gitlab/ci/verify-lockfile.gitlab-ci.yml
new file mode 100644
index 00000000000..edf204f2f80
--- /dev/null
+++ b/.gitlab/ci/verify-lockfile.gitlab-ci.yml
@@ -0,0 +1,11 @@
+verify_lockfile:
+ stage: test
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.29-lfs-2.9-node-12.18-yarn-1.22-graphicsmagick-1.3.34
+ needs: []
+ rules:
+ - changes:
+ - yarn.lock
+ script:
+ - npm config set @dappelt:registry https://gitlab.com/api/v4/projects/22564149/packages/npm/
+ - npx lockfile-lint@4.3.7 --path yarn.lock --allowed-hosts yarn --validate-https
+ - npx @dappelt/untamper-my-lockfile --lockfile yarn.lock
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml
index cdf7f79412b..ffc7a663db0 100644
--- a/.rubocop_manual_todo.yml
+++ b/.rubocop_manual_todo.yml
@@ -1468,3 +1468,1272 @@ RSpec/AnyInstanceOf:
- 'spec/workers/stuck_ci_jobs_worker_spec.rb'
- 'spec/workers/wait_for_cluster_creation_worker_spec.rb'
- 'ee/spec/workers/security/auto_fix_worker_spec.rb'
+
+Gitlab/NamespacedClass:
+ Exclude:
+ - 'config/**/*.rb'
+ - 'db/**/*.rb'
+ - 'ee/bin/**/*'
+ - 'ee/db/**/*.rb'
+ - 'ee/elastic/**/*.rb'
+ - 'scripts/**/*'
+ - 'spec/migrations/**/*.rb'
+ # The list above represents the permanent exclusions for this rule
+ # due to the fact these files are related to infrastructure code.
+ # This list should eventually be moved to .rubocop.yml after all TODOs
+ # are addressed.
+ #
+ # The list below represents the classes that require
+ # a namespace as they make the domain related code.
+ - 'app/channels/issues_channel.rb'
+ - 'app/controllers/abuse_reports_controller.rb'
+ - 'app/controllers/acme_challenges_controller.rb'
+ - 'app/controllers/application_controller.rb'
+ - 'app/controllers/autocomplete_controller.rb'
+ - 'app/controllers/chaos_controller.rb'
+ - 'app/controllers/confirmations_controller.rb'
+ - 'app/controllers/dashboard_controller.rb'
+ - 'app/controllers/graphql_controller.rb'
+ - 'app/controllers/groups_controller.rb'
+ - 'app/controllers/health_check_controller.rb'
+ - 'app/controllers/health_controller.rb'
+ - 'app/controllers/help_controller.rb'
+ - 'app/controllers/ide_controller.rb'
+ - 'app/controllers/invites_controller.rb'
+ - 'app/controllers/jwks_controller.rb'
+ - 'app/controllers/jwt_controller.rb'
+ - 'app/controllers/metrics_controller.rb'
+ - 'app/controllers/notification_settings_controller.rb'
+ - 'app/controllers/omniauth_callbacks_controller.rb'
+ - 'app/controllers/passwords_controller.rb'
+ - 'app/controllers/profiles_controller.rb'
+ - 'app/controllers/projects_controller.rb'
+ - 'app/controllers/registrations_controller.rb'
+ - 'app/controllers/root_controller.rb'
+ - 'app/controllers/runner_setup_controller.rb'
+ - 'app/controllers/search_controller.rb'
+ - 'app/controllers/sent_notifications_controller.rb'
+ - 'app/controllers/sessions_controller.rb'
+ - 'app/controllers/snippets_controller.rb'
+ - 'app/controllers/uploads_controller.rb'
+ - 'app/controllers/user_callouts_controller.rb'
+ - 'app/controllers/users_controller.rb'
+ - 'app/controllers/whats_new_controller.rb'
+ - 'app/experiments/application_experiment.rb'
+ - 'app/finders/abuse_reports_finder.rb'
+ - 'app/finders/access_requests_finder.rb'
+ - 'app/finders/admin/projects_finder.rb'
+ - 'app/finders/applications_finder.rb'
+ - 'app/finders/award_emojis_finder.rb'
+ - 'app/finders/branches_finder.rb'
+ - 'app/finders/cluster_ancestors_finder.rb'
+ - 'app/finders/clusters_finder.rb'
+ - 'app/finders/container_repositories_finder.rb'
+ - 'app/finders/context_commits_finder.rb'
+ - 'app/finders/contributed_projects_finder.rb'
+ - 'app/finders/deployments_finder.rb'
+ - 'app/finders/environment_names_finder.rb'
+ - 'app/finders/environments_finder.rb'
+ - 'app/finders/events_finder.rb'
+ - 'app/finders/feature_flags_finder.rb'
+ - 'app/finders/feature_flags_user_lists_finder.rb'
+ - 'app/finders/fork_projects_finder.rb'
+ - 'app/finders/fork_targets_finder.rb'
+ - 'app/finders/freeze_periods_finder.rb'
+ - 'app/finders/git_refs_finder.rb'
+ - 'app/finders/group_descendants_finder.rb'
+ - 'app/finders/group_finder.rb'
+ - 'app/finders/group_members_finder.rb'
+ - 'app/finders/group_projects_finder.rb'
+ - 'app/finders/groups_finder.rb'
+ - 'app/finders/issuable_finder.rb'
+ - 'app/finders/issuable_finder/params.rb'
+ - 'app/finders/issues_finder.rb'
+ - 'app/finders/issues_finder/params.rb'
+ - 'app/finders/joined_groups_finder.rb'
+ - 'app/finders/keys_finder.rb'
+ - 'app/finders/labels_finder.rb'
+ - 'app/finders/license_template_finder.rb'
+ - 'app/finders/members_finder.rb'
+ - 'app/finders/merge_request_target_project_finder.rb'
+ - 'app/finders/merge_requests_finder.rb'
+ - 'app/finders/merge_requests_finder/params.rb'
+ - 'app/finders/milestones_finder.rb'
+ - 'app/finders/notes_finder.rb'
+ - 'app/finders/pending_todos_finder.rb'
+ - 'app/finders/personal_access_tokens_finder.rb'
+ - 'app/finders/personal_projects_finder.rb'
+ - 'app/finders/projects_finder.rb'
+ - 'app/finders/prometheus_metrics_finder.rb'
+ - 'app/finders/protected_branches_finder.rb'
+ - 'app/finders/releases_finder.rb'
+ - 'app/finders/resource_milestone_event_finder.rb'
+ - 'app/finders/resource_state_event_finder.rb'
+ - 'app/finders/sentry_issue_finder.rb'
+ - 'app/finders/serverless_domain_finder.rb'
+ - 'app/finders/snippets_finder.rb'
+ - 'app/finders/starred_projects_finder.rb'
+ - 'app/finders/tags_finder.rb'
+ - 'app/finders/template_finder.rb'
+ - 'app/finders/todos_finder.rb'
+ - 'app/finders/union_finder.rb'
+ - 'app/finders/uploader_finder.rb'
+ - 'app/finders/user_finder.rb'
+ - 'app/finders/user_group_notification_settings_finder.rb'
+ - 'app/finders/user_groups_counter.rb'
+ - 'app/finders/user_recent_events_finder.rb'
+ - 'app/finders/users_finder.rb'
+ - 'app/finders/users_star_projects_finder.rb'
+ - 'app/finders/users_with_pending_todos_finder.rb'
+ - 'app/graphql/gitlab_schema.rb'
+ - 'app/mailers/abuse_report_mailer.rb'
+ - 'app/mailers/application_mailer.rb'
+ - 'app/mailers/devise_mailer.rb'
+ - 'app/mailers/email_rejection_mailer.rb'
+ - 'app/mailers/notify.rb'
+ - 'app/mailers/previews/devise_mailer_preview.rb'
+ - 'app/mailers/previews/email_rejection_mailer_preview.rb'
+ - 'app/mailers/previews/notify_preview.rb'
+ - 'app/mailers/previews/repository_check_mailer_preview.rb'
+ - 'app/mailers/repository_check_mailer.rb'
+ - 'app/models/ability.rb'
+ - 'app/models/abuse_report.rb'
+ - 'app/models/active_session.rb'
+ - 'app/models/appearance.rb'
+ - 'app/models/application_record.rb'
+ - 'app/models/application_setting.rb'
+ - 'app/models/application_setting/term.rb'
+ - 'app/models/approval.rb'
+ - 'app/models/audit_event.rb'
+ - 'app/models/audit_event_archived.rb'
+ - 'app/models/authentication_event.rb'
+ - 'app/models/award_emoji.rb'
+ - 'app/models/badge.rb'
+ - 'app/models/badges/group_badge.rb'
+ - 'app/models/badges/project_badge.rb'
+ - 'app/models/blob.rb'
+ - 'app/models/board.rb'
+ - 'app/models/board_group_recent_visit.rb'
+ - 'app/models/board_project_recent_visit.rb'
+ - 'app/models/broadcast_message.rb'
+ - 'app/models/bulk_import.rb'
+ - 'app/models/chat_name.rb'
+ - 'app/models/chat_team.rb'
+ - 'app/models/ci_platform_metric.rb'
+ - 'app/models/commit.rb'
+ - 'app/models/commit_collection.rb'
+ - 'app/models/commit_range.rb'
+ - 'app/models/commit_status.rb'
+ - 'app/models/commit_user_mention.rb'
+ - 'app/models/commit_with_pipeline.rb'
+ - 'app/models/compare.rb'
+ - 'app/models/concerns/uniquify.rb'
+ - 'app/models/container_expiration_policy.rb'
+ - 'app/models/container_repository.rb'
+ - 'app/models/custom_emoji.rb'
+ - 'app/models/data_list.rb'
+ - 'app/models/deploy_key.rb'
+ - 'app/models/deploy_keys_project.rb'
+ - 'app/models/deploy_token.rb'
+ - 'app/models/deployment.rb'
+ - 'app/models/deployment_cluster.rb'
+ - 'app/models/deployment_merge_request.rb'
+ - 'app/models/deployment_metrics.rb'
+ - 'app/models/description_version.rb'
+ - 'app/models/design_user_mention.rb'
+ - 'app/models/diff_discussion.rb'
+ - 'app/models/diff_note.rb'
+ - 'app/models/diff_note_position.rb'
+ - 'app/models/directly_addressed_user.rb'
+ - 'app/models/discussion.rb'
+ - 'app/models/discussion_note.rb'
+ - 'app/models/draft_note.rb'
+ - 'app/models/email.rb'
+ - 'app/models/environment.rb'
+ - 'app/models/environment_status.rb'
+ - 'app/models/epic.rb'
+ - 'app/models/event.rb'
+ - 'app/models/event_collection.rb'
+ - 'app/models/experiment.rb'
+ - 'app/models/experiment_subject.rb'
+ - 'app/models/experiment_user.rb'
+ - 'app/models/exported_protected_branch.rb'
+ - 'app/models/external_issue.rb'
+ - 'app/models/external_pull_request.rb'
+ - 'app/models/fork_network.rb'
+ - 'app/models/fork_network_member.rb'
+ - 'app/models/generic_commit_status.rb'
+ - 'app/models/gpg_key.rb'
+ - 'app/models/gpg_key_subkey.rb'
+ - 'app/models/gpg_signature.rb'
+ - 'app/models/grafana_integration.rb'
+ - 'app/models/group.rb'
+ - 'app/models/group_custom_attribute.rb'
+ - 'app/models/group_deploy_key.rb'
+ - 'app/models/group_deploy_keys_group.rb'
+ - 'app/models/group_deploy_token.rb'
+ - 'app/models/group_group_link.rb'
+ - 'app/models/group_import_state.rb'
+ - 'app/models/group_label.rb'
+ - 'app/models/guest.rb'
+ - 'app/models/hooks/active_hook_filter.rb'
+ - 'app/models/hooks/project_hook.rb'
+ - 'app/models/hooks/service_hook.rb'
+ - 'app/models/hooks/system_hook.rb'
+ - 'app/models/hooks/web_hook.rb'
+ - 'app/models/hooks/web_hook_log.rb'
+ - 'app/models/identity.rb'
+ - 'app/models/identity/uniqueness_scopes.rb'
+ - 'app/models/import_export_upload.rb'
+ - 'app/models/import_failure.rb'
+ - 'app/models/individual_note_discussion.rb'
+ - 'app/models/instance_configuration.rb'
+ - 'app/models/instance_metadata.rb'
+ - 'app/models/internal_id.rb'
+ - 'app/models/issuable_severity.rb'
+ - 'app/models/issue.rb'
+ - 'app/models/issue/metrics.rb'
+ - 'app/models/issue_assignee.rb'
+ - 'app/models/issue_collection.rb'
+ - 'app/models/issue_email_participant.rb'
+ - 'app/models/issue_link.rb'
+ - 'app/models/issue_user_mention.rb'
+ - 'app/models/iteration.rb'
+ - 'app/models/jira_connect_installation.rb'
+ - 'app/models/jira_connect_subscription.rb'
+ - 'app/models/jira_import_state.rb'
+ - 'app/models/key.rb'
+ - 'app/models/label.rb'
+ - 'app/models/label_link.rb'
+ - 'app/models/label_note.rb'
+ - 'app/models/label_priority.rb'
+ - 'app/models/legacy_diff_discussion.rb'
+ - 'app/models/legacy_diff_note.rb'
+ - 'app/models/lfs_download_object.rb'
+ - 'app/models/lfs_file_lock.rb'
+ - 'app/models/lfs_object.rb'
+ - 'app/models/lfs_objects_project.rb'
+ - 'app/models/license_template.rb'
+ - 'app/models/list.rb'
+ - 'app/models/list_user_preference.rb'
+ - 'app/models/member.rb'
+ - 'app/models/members/group_member.rb'
+ - 'app/models/members/project_member.rb'
+ - 'app/models/members_preloader.rb'
+ - 'app/models/merge_request.rb'
+ - 'app/models/merge_request_assignee.rb'
+ - 'app/models/merge_request_context_commit.rb'
+ - 'app/models/merge_request_context_commit_diff_file.rb'
+ - 'app/models/merge_request_diff.rb'
+ - 'app/models/merge_request_diff_commit.rb'
+ - 'app/models/merge_request_diff_file.rb'
+ - 'app/models/merge_request_reviewer.rb'
+ - 'app/models/merge_request_user_mention.rb'
+ - 'app/models/merge_requests_closing_issues.rb'
+ - 'app/models/milestone.rb'
+ - 'app/models/milestone_note.rb'
+ - 'app/models/milestone_release.rb'
+ - 'app/models/namespace.rb'
+ - 'app/models/namespace/traversal_hierarchy.rb'
+ - 'app/models/namespace_onboarding_action.rb'
+ - 'app/models/namespace_setting.rb'
+ - 'app/models/note.rb'
+ - 'app/models/note_diff_file.rb'
+ - 'app/models/notification_reason.rb'
+ - 'app/models/notification_recipient.rb'
+ - 'app/models/notification_setting.rb'
+ - 'app/models/oauth_access_grant.rb'
+ - 'app/models/oauth_access_token.rb'
+ - 'app/models/out_of_context_discussion.rb'
+ - 'app/models/onboarding_progress.rb'
+ - 'app/models/pages_deployment.rb'
+ - 'app/models/pages_domain.rb'
+ - 'app/models/pages_domain_acme_order.rb'
+ - 'app/models/personal_access_token.rb'
+ - 'app/models/personal_snippet.rb'
+ - 'app/models/plan.rb'
+ - 'app/models/plan_limits.rb'
+ - 'app/models/pool_repository.rb'
+ - 'app/models/product_analytics_event.rb'
+ - 'app/models/programming_language.rb'
+ - 'app/models/project.rb'
+ - 'app/models/project_authorization.rb'
+ - 'app/models/project_auto_devops.rb'
+ - 'app/models/project_ci_cd_setting.rb'
+ - 'app/models/project_custom_attribute.rb'
+ - 'app/models/project_daily_statistic.rb'
+ - 'app/models/project_deploy_token.rb'
+ - 'app/models/project_export_job.rb'
+ - 'app/models/project_feature.rb'
+ - 'app/models/project_feature_usage.rb'
+ - 'app/models/project_group_link.rb'
+ - 'app/models/project_import_data.rb'
+ - 'app/models/project_import_state.rb'
+ - 'app/models/project_label.rb'
+ - 'app/models/project_metrics_setting.rb'
+ - 'app/models/project_pages_metadatum.rb'
+ - 'app/models/project_repository.rb'
+ - 'app/models/project_repository_storage_move.rb'
+ - 'app/models/project_services/alerts_service.rb'
+ - 'app/models/project_services/alerts_service_data.rb'
+ - 'app/models/project_services/asana_service.rb'
+ - 'app/models/project_services/assembla_service.rb'
+ - 'app/models/project_services/bamboo_service.rb'
+ - 'app/models/project_services/bugzilla_service.rb'
+ - 'app/models/project_services/buildkite_service.rb'
+ - 'app/models/project_services/builds_email_service.rb'
+ - 'app/models/project_services/campfire_service.rb'
+ - 'app/models/project_services/chat_notification_service.rb'
+ - 'app/models/project_services/ci_service.rb'
+ - 'app/models/project_services/confluence_service.rb'
+ - 'app/models/project_services/custom_issue_tracker_service.rb'
+ - 'app/models/project_services/datadog_service.rb'
+ - 'app/models/project_services/discord_service.rb'
+ - 'app/models/project_services/drone_ci_service.rb'
+ - 'app/models/project_services/emails_on_push_service.rb'
+ - 'app/models/project_services/ewm_service.rb'
+ - 'app/models/project_services/external_wiki_service.rb'
+ - 'app/models/project_services/flowdock_service.rb'
+ - 'app/models/project_services/hangouts_chat_service.rb'
+ - 'app/models/project_services/hipchat_service.rb'
+ - 'app/models/project_services/irker_service.rb'
+ - 'app/models/project_services/issue_tracker_data.rb'
+ - 'app/models/project_services/issue_tracker_service.rb'
+ - 'app/models/project_services/jenkins_service.rb'
+ - 'app/models/project_services/jira_service.rb'
+ - 'app/models/project_services/jira_tracker_data.rb'
+ - 'app/models/project_services/mattermost_service.rb'
+ - 'app/models/project_services/mattermost_slash_commands_service.rb'
+ - 'app/models/project_services/microsoft_teams_service.rb'
+ - 'app/models/project_services/mock_ci_service.rb'
+ - 'app/models/project_services/mock_deployment_service.rb'
+ - 'app/models/project_services/mock_monitoring_service.rb'
+ - 'app/models/project_services/monitoring_service.rb'
+ - 'app/models/project_services/open_project_service.rb'
+ - 'app/models/project_services/open_project_tracker_data.rb'
+ - 'app/models/project_services/packagist_service.rb'
+ - 'app/models/project_services/pipelines_email_service.rb'
+ - 'app/models/project_services/pivotaltracker_service.rb'
+ - 'app/models/project_services/prometheus_service.rb'
+ - 'app/models/project_services/pushover_service.rb'
+ - 'app/models/project_services/redmine_service.rb'
+ - 'app/models/project_services/slack_service.rb'
+ - 'app/models/project_services/slack_slash_commands_service.rb'
+ - 'app/models/project_services/slash_commands_service.rb'
+ - 'app/models/project_services/teamcity_service.rb'
+ - 'app/models/project_services/unify_circuit_service.rb'
+ - 'app/models/project_services/webex_teams_service.rb'
+ - 'app/models/project_services/youtrack_service.rb'
+ - 'app/models/project_setting.rb'
+ - 'app/models/project_snippet.rb'
+ - 'app/models/project_statistics.rb'
+ - 'app/models/project_team.rb'
+ - 'app/models/project_tracing_setting.rb'
+ - 'app/models/project_wiki.rb'
+ - 'app/models/prometheus_alert.rb'
+ - 'app/models/prometheus_alert_event.rb'
+ - 'app/models/prometheus_metric.rb'
+ - 'app/models/protectable_dropdown.rb'
+ - 'app/models/protected_branch.rb'
+ - 'app/models/protected_tag.rb'
+ - 'app/models/push_event.rb'
+ - 'app/models/push_event_payload.rb'
+ - 'app/models/raw_usage_data.rb'
+ - 'app/models/readme_blob.rb'
+ - 'app/models/redirect_route.rb'
+ - 'app/models/ref_matcher.rb'
+ - 'app/models/release.rb'
+ - 'app/models/release_highlight.rb'
+ - 'app/models/remote_mirror.rb'
+ - 'app/models/repository.rb'
+ - 'app/models/repository_language.rb'
+ - 'app/models/resource_event.rb'
+ - 'app/models/resource_label_event.rb'
+ - 'app/models/resource_milestone_event.rb'
+ - 'app/models/resource_state_event.rb'
+ - 'app/models/resource_timebox_event.rb'
+ - 'app/models/review.rb'
+ - 'app/models/route.rb'
+ - 'app/models/self_managed_prometheus_alert_event.rb'
+ - 'app/models/sent_notification.rb'
+ - 'app/models/sentry_issue.rb'
+ - 'app/models/service.rb'
+ - 'app/models/service_desk_setting.rb'
+ - 'app/models/service_list.rb'
+ - 'app/models/shard.rb'
+ - 'app/models/snippet.rb'
+ - 'app/models/snippet_blob.rb'
+ - 'app/models/snippet_input_action.rb'
+ - 'app/models/snippet_input_action_collection.rb'
+ - 'app/models/snippet_repository.rb'
+ - 'app/models/snippet_repository_storage_move.rb'
+ - 'app/models/snippet_statistics.rb'
+ - 'app/models/snippet_user_mention.rb'
+ - 'app/models/spam_log.rb'
+ - 'app/models/ssh_host_key.rb'
+ - 'app/models/state_note.rb'
+ - 'app/models/subscription.rb'
+ - 'app/models/suggestion.rb'
+ - 'app/models/synthetic_note.rb'
+ - 'app/models/system_note_metadata.rb'
+ - 'app/models/term_agreement.rb'
+ - 'app/models/timelog.rb'
+ - 'app/models/todo.rb'
+ - 'app/models/tree.rb'
+ - 'app/models/trending_project.rb'
+ - 'app/models/u2f_registration.rb'
+ - 'app/models/upload.rb'
+ - 'app/models/user.rb'
+ - 'app/models/user_agent_detail.rb'
+ - 'app/models/user_callout.rb'
+ - 'app/models/user_canonical_email.rb'
+ - 'app/models/user_custom_attribute.rb'
+ - 'app/models/user_detail.rb'
+ - 'app/models/user_highest_role.rb'
+ - 'app/models/user_interacted_project.rb'
+ - 'app/models/user_mention.rb'
+ - 'app/models/user_preference.rb'
+ - 'app/models/user_status.rb'
+ - 'app/models/user_synced_attributes_metadata.rb'
+ - 'app/models/users_star_project.rb'
+ - 'app/models/users_statistics.rb'
+ - 'app/models/vulnerability.rb'
+ - 'app/models/web_ide_terminal.rb'
+ - 'app/models/webauthn_registration.rb'
+ - 'app/models/wiki.rb'
+ - 'app/models/wiki_directory.rb'
+ - 'app/models/wiki_page.rb'
+ - 'app/models/wiki_page/meta.rb'
+ - 'app/models/wiki_page/slug.rb'
+ - 'app/models/x509_certificate.rb'
+ - 'app/models/x509_commit_signature.rb'
+ - 'app/models/x509_issuer.rb'
+ - 'app/models/zoom_meeting.rb'
+ - 'app/policies/application_setting/term_policy.rb'
+ - 'app/policies/award_emoji_policy.rb'
+ - 'app/policies/base_policy.rb'
+ - 'app/policies/blob_policy.rb'
+ - 'app/policies/board_policy.rb'
+ - 'app/policies/commit_policy.rb'
+ - 'app/policies/commit_status_policy.rb'
+ - 'app/policies/container_expiration_policy_policy.rb'
+ - 'app/policies/container_repository_policy.rb'
+ - 'app/policies/custom_emoji_policy.rb'
+ - 'app/policies/deploy_key_policy.rb'
+ - 'app/policies/deploy_keys_project_policy.rb'
+ - 'app/policies/deploy_token_policy.rb'
+ - 'app/policies/deployment_policy.rb'
+ - 'app/policies/draft_note_policy.rb'
+ - 'app/policies/environment_policy.rb'
+ - 'app/policies/external_issue_policy.rb'
+ - 'app/policies/global_policy.rb'
+ - 'app/policies/grafana_integration_policy.rb'
+ - 'app/policies/group_deploy_key_policy.rb'
+ - 'app/policies/group_deploy_keys_group_policy.rb'
+ - 'app/policies/group_label_policy.rb'
+ - 'app/policies/group_member_policy.rb'
+ - 'app/policies/group_policy.rb'
+ - 'app/policies/identity_provider_policy.rb'
+ - 'app/policies/instance_metadata_policy.rb'
+ - 'app/policies/issuable_policy.rb'
+ - 'app/policies/issue_policy.rb'
+ - 'app/policies/merge_request_policy.rb'
+ - 'app/policies/milestone_policy.rb'
+ - 'app/policies/namespace_policy.rb'
+ - 'app/policies/nil_policy.rb'
+ - 'app/policies/note_policy.rb'
+ - 'app/policies/personal_access_token_policy.rb'
+ - 'app/policies/personal_snippet_policy.rb'
+ - 'app/policies/project_ci_cd_setting_policy.rb'
+ - 'app/policies/project_label_policy.rb'
+ - 'app/policies/project_member_policy.rb'
+ - 'app/policies/project_policy.rb'
+ - 'app/policies/project_snippet_policy.rb'
+ - 'app/policies/project_statistics_policy.rb'
+ - 'app/policies/prometheus_alert_policy.rb'
+ - 'app/policies/protected_branch_policy.rb'
+ - 'app/policies/release_policy.rb'
+ - 'app/policies/repository_policy.rb'
+ - 'app/policies/resource_label_event_policy.rb'
+ - 'app/policies/service_policy.rb'
+ - 'app/policies/suggestion_policy.rb'
+ - 'app/policies/timebox_policy.rb'
+ - 'app/policies/todo_policy.rb'
+ - 'app/policies/user_policy.rb'
+ - 'app/policies/wiki_page_policy.rb'
+ - 'app/policies/wiki_policy.rb'
+ - 'app/presenters/award_emoji_presenter.rb'
+ - 'app/presenters/blob_presenter.rb'
+ - 'app/presenters/board_presenter.rb'
+ - 'app/presenters/clusterable_presenter.rb'
+ - 'app/presenters/commit_presenter.rb'
+ - 'app/presenters/commit_status_presenter.rb'
+ - 'app/presenters/environment_presenter.rb'
+ - 'app/presenters/event_presenter.rb'
+ - 'app/presenters/generic_commit_status_presenter.rb'
+ - 'app/presenters/group_clusterable_presenter.rb'
+ - 'app/presenters/group_member_presenter.rb'
+ - 'app/presenters/instance_clusterable_presenter.rb'
+ - 'app/presenters/invitation_presenter.rb'
+ - 'app/presenters/issue_presenter.rb'
+ - 'app/presenters/label_presenter.rb'
+ - 'app/presenters/member_presenter.rb'
+ - 'app/presenters/members_presenter.rb'
+ - 'app/presenters/merge_request_presenter.rb'
+ - 'app/presenters/milestone_presenter.rb'
+ - 'app/presenters/pages_domain_presenter.rb'
+ - 'app/presenters/project_clusterable_presenter.rb'
+ - 'app/presenters/project_hook_presenter.rb'
+ - 'app/presenters/project_member_presenter.rb'
+ - 'app/presenters/project_presenter.rb'
+ - 'app/presenters/prometheus_alert_presenter.rb'
+ - 'app/presenters/release_presenter.rb'
+ - 'app/presenters/search_service_presenter.rb'
+ - 'app/presenters/sentry_error_presenter.rb'
+ - 'app/presenters/service_hook_presenter.rb'
+ - 'app/presenters/snippet_blob_presenter.rb'
+ - 'app/presenters/snippet_presenter.rb'
+ - 'app/presenters/todo_presenter.rb'
+ - 'app/presenters/tree_entry_presenter.rb'
+ - 'app/presenters/user_presenter.rb'
+ - 'app/presenters/web_hook_log_presenter.rb'
+ - 'app/serializers/accessibility_error_entity.rb'
+ - 'app/serializers/accessibility_reports_comparer_entity.rb'
+ - 'app/serializers/accessibility_reports_comparer_serializer.rb'
+ - 'app/serializers/analytics_build_entity.rb'
+ - 'app/serializers/analytics_build_serializer.rb'
+ - 'app/serializers/analytics_commit_entity.rb'
+ - 'app/serializers/analytics_commit_serializer.rb'
+ - 'app/serializers/analytics_generic_serializer.rb'
+ - 'app/serializers/analytics_issue_entity.rb'
+ - 'app/serializers/analytics_issue_serializer.rb'
+ - 'app/serializers/analytics_merge_request_entity.rb'
+ - 'app/serializers/analytics_merge_request_serializer.rb'
+ - 'app/serializers/analytics_stage_entity.rb'
+ - 'app/serializers/analytics_stage_serializer.rb'
+ - 'app/serializers/analytics_summary_entity.rb'
+ - 'app/serializers/analytics_summary_serializer.rb'
+ - 'app/serializers/award_emoji_entity.rb'
+ - 'app/serializers/base_discussion_entity.rb'
+ - 'app/serializers/base_serializer.rb'
+ - 'app/serializers/blob_entity.rb'
+ - 'app/serializers/board_serializer.rb'
+ - 'app/serializers/board_simple_entity.rb'
+ - 'app/serializers/build_action_entity.rb'
+ - 'app/serializers/build_artifact_entity.rb'
+ - 'app/serializers/build_coverage_entity.rb'
+ - 'app/serializers/build_details_entity.rb'
+ - 'app/serializers/build_metadata_entity.rb'
+ - 'app/serializers/build_serializer.rb'
+ - 'app/serializers/build_trace_entity.rb'
+ - 'app/serializers/build_trace_serializer.rb'
+ - 'app/serializers/cluster_application_entity.rb'
+ - 'app/serializers/cluster_entity.rb'
+ - 'app/serializers/cluster_error_entity.rb'
+ - 'app/serializers/cluster_serializer.rb'
+ - 'app/serializers/codequality_degradation_entity.rb'
+ - 'app/serializers/codequality_reports_comparer_entity.rb'
+ - 'app/serializers/codequality_reports_comparer_serializer.rb'
+ - 'app/serializers/cohort_activity_month_entity.rb'
+ - 'app/serializers/cohort_entity.rb'
+ - 'app/serializers/cohorts_entity.rb'
+ - 'app/serializers/cohorts_serializer.rb'
+ - 'app/serializers/commit_entity.rb'
+ - 'app/serializers/container_repositories_serializer.rb'
+ - 'app/serializers/container_repository_entity.rb'
+ - 'app/serializers/container_tag_entity.rb'
+ - 'app/serializers/container_tags_serializer.rb'
+ - 'app/serializers/current_board_entity.rb'
+ - 'app/serializers/current_board_serializer.rb'
+ - 'app/serializers/current_user_entity.rb'
+ - 'app/serializers/deploy_key_entity.rb'
+ - 'app/serializers/deploy_key_serializer.rb'
+ - 'app/serializers/deploy_keys_project_entity.rb'
+ - 'app/serializers/deployment_cluster_entity.rb'
+ - 'app/serializers/deployment_entity.rb'
+ - 'app/serializers/deployment_serializer.rb'
+ - 'app/serializers/detailed_status_entity.rb'
+ - 'app/serializers/diff_file_base_entity.rb'
+ - 'app/serializers/diff_file_entity.rb'
+ - 'app/serializers/diff_file_metadata_entity.rb'
+ - 'app/serializers/diff_line_entity.rb'
+ - 'app/serializers/diff_line_parallel_entity.rb'
+ - 'app/serializers/diff_line_serializer.rb'
+ - 'app/serializers/diff_viewer_entity.rb'
+ - 'app/serializers/diffs_entity.rb'
+ - 'app/serializers/diffs_metadata_entity.rb'
+ - 'app/serializers/diffs_metadata_serializer.rb'
+ - 'app/serializers/diffs_serializer.rb'
+ - 'app/serializers/discussion_diff_file_entity.rb'
+ - 'app/serializers/discussion_entity.rb'
+ - 'app/serializers/discussion_serializer.rb'
+ - 'app/serializers/draft_note_entity.rb'
+ - 'app/serializers/draft_note_serializer.rb'
+ - 'app/serializers/entity_request.rb'
+ - 'app/serializers/environment_entity.rb'
+ - 'app/serializers/environment_serializer.rb'
+ - 'app/serializers/environment_status_entity.rb'
+ - 'app/serializers/environment_status_serializer.rb'
+ - 'app/serializers/feature_flag_entity.rb'
+ - 'app/serializers/feature_flag_scope_entity.rb'
+ - 'app/serializers/feature_flag_serializer.rb'
+ - 'app/serializers/feature_flag_summary_entity.rb'
+ - 'app/serializers/feature_flag_summary_serializer.rb'
+ - 'app/serializers/feature_flags_client_entity.rb'
+ - 'app/serializers/feature_flags_client_serializer.rb'
+ - 'app/serializers/fork_namespace_entity.rb'
+ - 'app/serializers/fork_namespace_serializer.rb'
+ - 'app/serializers/group_analytics_stage_entity.rb'
+ - 'app/serializers/group_analytics_stage_serializer.rb'
+ - 'app/serializers/group_basic_entity.rb'
+ - 'app/serializers/group_child_entity.rb'
+ - 'app/serializers/group_child_serializer.rb'
+ - 'app/serializers/group_deploy_key_entity.rb'
+ - 'app/serializers/group_deploy_key_serializer.rb'
+ - 'app/serializers/group_deploy_keys_group_entity.rb'
+ - 'app/serializers/group_entity.rb'
+ - 'app/serializers/group_group_link_entity.rb'
+ - 'app/serializers/group_group_link_serializer.rb'
+ - 'app/serializers/group_serializer.rb'
+ - 'app/serializers/issuable_entity.rb'
+ - 'app/serializers/issuable_sidebar_basic_entity.rb'
+ - 'app/serializers/issuable_sidebar_extras_entity.rb'
+ - 'app/serializers/issuable_sidebar_todo_entity.rb'
+ - 'app/serializers/issue_board_entity.rb'
+ - 'app/serializers/issue_entity.rb'
+ - 'app/serializers/issue_serializer.rb'
+ - 'app/serializers/issue_sidebar_basic_entity.rb'
+ - 'app/serializers/issue_sidebar_extras_entity.rb'
+ - 'app/serializers/job_artifact_report_entity.rb'
+ - 'app/serializers/job_entity.rb'
+ - 'app/serializers/job_group_entity.rb'
+ - 'app/serializers/label_entity.rb'
+ - 'app/serializers/label_serializer.rb'
+ - 'app/serializers/lfs_file_lock_entity.rb'
+ - 'app/serializers/lfs_file_lock_serializer.rb'
+ - 'app/serializers/linked_issue_entity.rb'
+ - 'app/serializers/linked_project_issue_entity.rb'
+ - 'app/serializers/linked_project_issue_serializer.rb'
+ - 'app/serializers/member_entity.rb'
+ - 'app/serializers/member_serializer.rb'
+ - 'app/serializers/member_user_entity.rb'
+ - 'app/serializers/merge_request_basic_entity.rb'
+ - 'app/serializers/merge_request_create_entity.rb'
+ - 'app/serializers/merge_request_create_serializer.rb'
+ - 'app/serializers/merge_request_current_user_entity.rb'
+ - 'app/serializers/merge_request_diff_entity.rb'
+ - 'app/serializers/merge_request_for_pipeline_entity.rb'
+ - 'app/serializers/merge_request_metrics_entity.rb'
+ - 'app/serializers/merge_request_noteable_entity.rb'
+ - 'app/serializers/merge_request_poll_cached_widget_entity.rb'
+ - 'app/serializers/merge_request_poll_widget_entity.rb'
+ - 'app/serializers/merge_request_serializer.rb'
+ - 'app/serializers/merge_request_sidebar_basic_entity.rb'
+ - 'app/serializers/merge_request_sidebar_extras_entity.rb'
+ - 'app/serializers/merge_request_user_entity.rb'
+ - 'app/serializers/merge_request_widget_commit_entity.rb'
+ - 'app/serializers/merge_request_widget_entity.rb'
+ - 'app/serializers/move_to_project_entity.rb'
+ - 'app/serializers/move_to_project_serializer.rb'
+ - 'app/serializers/namespace_basic_entity.rb'
+ - 'app/serializers/namespace_serializer.rb'
+ - 'app/serializers/note_attachment_entity.rb'
+ - 'app/serializers/note_entity.rb'
+ - 'app/serializers/note_user_entity.rb'
+ - 'app/serializers/paginated_diff_entity.rb'
+ - 'app/serializers/paginated_diff_serializer.rb'
+ - 'app/serializers/pipeline_details_entity.rb'
+ - 'app/serializers/pipeline_entity.rb'
+ - 'app/serializers/pipeline_serializer.rb'
+ - 'app/serializers/project_entity.rb'
+ - 'app/serializers/project_import_entity.rb'
+ - 'app/serializers/project_mirror_entity.rb'
+ - 'app/serializers/project_mirror_serializer.rb'
+ - 'app/serializers/project_note_entity.rb'
+ - 'app/serializers/project_note_serializer.rb'
+ - 'app/serializers/project_serializer.rb'
+ - 'app/serializers/prometheus_alert_entity.rb'
+ - 'app/serializers/prometheus_alert_serializer.rb'
+ - 'app/serializers/prometheus_metric_entity.rb'
+ - 'app/serializers/prometheus_metric_serializer.rb'
+ - 'app/serializers/release_entity.rb'
+ - 'app/serializers/release_serializer.rb'
+ - 'app/serializers/remote_mirror_entity.rb'
+ - 'app/serializers/review_app_setup_entity.rb'
+ - 'app/serializers/review_app_setup_serializer.rb'
+ - 'app/serializers/rollout_status_entity.rb'
+ - 'app/serializers/route_entity.rb'
+ - 'app/serializers/route_serializer.rb'
+ - 'app/serializers/runner_entity.rb'
+ - 'app/serializers/service_event_entity.rb'
+ - 'app/serializers/service_event_serializer.rb'
+ - 'app/serializers/service_field_entity.rb'
+ - 'app/serializers/service_field_serializer.rb'
+ - 'app/serializers/stage_entity.rb'
+ - 'app/serializers/stage_serializer.rb'
+ - 'app/serializers/suggestion_entity.rb'
+ - 'app/serializers/suggestion_serializer.rb'
+ - 'app/serializers/test_case_entity.rb'
+ - 'app/serializers/test_report_entity.rb'
+ - 'app/serializers/test_report_serializer.rb'
+ - 'app/serializers/test_report_summary_entity.rb'
+ - 'app/serializers/test_report_summary_serializer.rb'
+ - 'app/serializers/test_reports_comparer_entity.rb'
+ - 'app/serializers/test_reports_comparer_serializer.rb'
+ - 'app/serializers/test_suite_comparer_entity.rb'
+ - 'app/serializers/test_suite_entity.rb'
+ - 'app/serializers/test_suite_serializer.rb'
+ - 'app/serializers/test_suite_summary_entity.rb'
+ - 'app/serializers/trigger_variable_entity.rb'
+ - 'app/serializers/triggered_pipeline_entity.rb'
+ - 'app/serializers/user_entity.rb'
+ - 'app/serializers/user_preference_entity.rb'
+ - 'app/serializers/user_serializer.rb'
+ - 'app/serializers/web_ide_terminal_entity.rb'
+ - 'app/serializers/web_ide_terminal_serializer.rb'
+ - 'app/services/access_token_validation_service.rb'
+ - 'app/services/audit_event_service.rb'
+ - 'app/services/auto_merge_service.rb'
+ - 'app/services/base_container_service.rb'
+ - 'app/services/base_count_service.rb'
+ - 'app/services/base_renderer.rb'
+ - 'app/services/base_service.rb'
+ - 'app/services/bulk_create_integration_service.rb'
+ - 'app/services/bulk_import_service.rb'
+ - 'app/services/bulk_push_event_payload_service.rb'
+ - 'app/services/bulk_update_integration_service.rb'
+ - 'app/services/cohorts_service.rb'
+ - 'app/services/compare_service.rb'
+ - 'app/services/container_expiration_policy_service.rb'
+ - 'app/services/event_create_service.rb'
+ - 'app/services/gravatar_service.rb'
+ - 'app/services/import_export_clean_up_service.rb'
+ - 'app/services/issuable_base_service.rb'
+ - 'app/services/issue_rebalancing_service.rb'
+ - 'app/services/markdown_content_rewriter_service.rb'
+ - 'app/services/merge_request_metrics_service.rb'
+ - 'app/services/metrics_service.rb'
+ - 'app/services/note_summary.rb'
+ - 'app/services/notification_service.rb'
+ - 'app/services/onboarding_progress_service.rb'
+ - 'app/services/post_receive_service.rb'
+ - 'app/services/preview_markdown_service.rb'
+ - 'app/services/push_event_payload_service.rb'
+ - 'app/services/repository_archive_clean_up_service.rb'
+ - 'app/services/reset_project_cache_service.rb'
+ - 'app/services/search_service.rb'
+ - 'app/services/service_response.rb'
+ - 'app/services/submit_usage_ping_service.rb'
+ - 'app/services/system_hooks_service.rb'
+ - 'app/services/task_list_toggle_service.rb'
+ - 'app/services/todo_service.rb'
+ - 'app/services/update_container_registry_info_service.rb'
+ - 'app/services/upload_service.rb'
+ - 'app/services/user_agent_detail_service.rb'
+ - 'app/services/user_project_access_changed_service.rb'
+ - 'app/services/verify_pages_domain_service.rb'
+ - 'app/services/web_hook_service.rb'
+ - 'app/services/x509_certificate_revoke_service.rb'
+ - 'app/uploaders/attachment_uploader.rb'
+ - 'app/uploaders/avatar_uploader.rb'
+ - 'app/uploaders/deleted_object_uploader.rb'
+ - 'app/uploaders/dependency_proxy/file_uploader.rb'
+ - 'app/uploaders/external_diff_uploader.rb'
+ - 'app/uploaders/favicon_uploader.rb'
+ - 'app/uploaders/file_mover.rb'
+ - 'app/uploaders/file_uploader.rb'
+ - 'app/uploaders/gitlab_uploader.rb'
+ - 'app/uploaders/import_export_uploader.rb'
+ - 'app/uploaders/job_artifact_uploader.rb'
+ - 'app/uploaders/lfs_object_uploader.rb'
+ - 'app/uploaders/namespace_file_uploader.rb'
+ - 'app/uploaders/personal_file_uploader.rb'
+ - 'app/validators/abstract_path_validator.rb'
+ - 'app/validators/addressable_url_validator.rb'
+ - 'app/validators/array_members_validator.rb'
+ - 'app/validators/branch_filter_validator.rb'
+ - 'app/validators/certificate_fingerprint_validator.rb'
+ - 'app/validators/certificate_key_validator.rb'
+ - 'app/validators/certificate_validator.rb'
+ - 'app/validators/cluster_name_validator.rb'
+ - 'app/validators/color_validator.rb'
+ - 'app/validators/cron_freeze_period_timezone_validator.rb'
+ - 'app/validators/cron_timezone_validator.rb'
+ - 'app/validators/cron_validator.rb'
+ - 'app/validators/devise_email_validator.rb'
+ - 'app/validators/duration_validator.rb'
+ - 'app/validators/feature_flag_strategies_validator.rb'
+ - 'app/validators/feature_flag_user_xids_validator.rb'
+ - 'app/validators/future_date_validator.rb'
+ - 'app/validators/html_safety_validator.rb'
+ - 'app/validators/ip_address_validator.rb'
+ - 'app/validators/js_regex_validator.rb'
+ - 'app/validators/json_schema_validator.rb'
+ - 'app/validators/key_restriction_validator.rb'
+ - 'app/validators/line_code_validator.rb'
+ - 'app/validators/named_ecdsa_key_validator.rb'
+ - 'app/validators/namespace_path_validator.rb'
+ - 'app/validators/project_path_validator.rb'
+ - 'app/validators/public_url_validator.rb'
+ - 'app/validators/qualified_domain_array_validator.rb'
+ - 'app/validators/rsa_key_validator.rb'
+ - 'app/validators/same_project_association_validator.rb'
+ - 'app/validators/sha_validator.rb'
+ - 'app/validators/system_hook_url_validator.rb'
+ - 'app/validators/top_level_group_validator.rb'
+ - 'app/validators/untrusted_regexp_validator.rb'
+ - 'app/validators/variable_duplicates_validator.rb'
+ - 'app/validators/x509_certificate_credentials_validator.rb'
+ - 'app/validators/zoom_url_validator.rb'
+ - 'app/workers/admin_email_worker.rb'
+ - 'app/workers/approve_blocked_pending_approval_users_worker.rb'
+ - 'app/workers/archive_trace_worker.rb'
+ - 'app/workers/authorized_keys_worker.rb'
+ - 'app/workers/authorized_projects_worker.rb'
+ - 'app/workers/auto_merge_process_worker.rb'
+ - 'app/workers/background_migration_worker.rb'
+ - 'app/workers/build_coverage_worker.rb'
+ - 'app/workers/build_finished_worker.rb'
+ - 'app/workers/build_hooks_worker.rb'
+ - 'app/workers/build_queue_worker.rb'
+ - 'app/workers/build_success_worker.rb'
+ - 'app/workers/build_trace_sections_worker.rb'
+ - 'app/workers/bulk_import_worker.rb'
+ - 'app/workers/chat_notification_worker.rb'
+ - 'app/workers/ci_platform_metrics_update_cron_worker.rb'
+ - 'app/workers/cleanup_container_repository_worker.rb'
+ - 'app/workers/cluster_configure_istio_worker.rb'
+ - 'app/workers/cluster_install_app_worker.rb'
+ - 'app/workers/cluster_patch_app_worker.rb'
+ - 'app/workers/cluster_provision_worker.rb'
+ - 'app/workers/cluster_update_app_worker.rb'
+ - 'app/workers/cluster_upgrade_app_worker.rb'
+ - 'app/workers/cluster_wait_for_app_installation_worker.rb'
+ - 'app/workers/cluster_wait_for_app_update_worker.rb'
+ - 'app/workers/cluster_wait_for_ingress_ip_address_worker.rb'
+ - 'app/workers/container_expiration_policy_worker.rb'
+ - 'app/workers/create_commit_signature_worker.rb'
+ - 'app/workers/create_note_diff_file_worker.rb'
+ - 'app/workers/create_pipeline_worker.rb'
+ - 'app/workers/delete_container_repository_worker.rb'
+ - 'app/workers/delete_diff_files_worker.rb'
+ - 'app/workers/delete_merged_branches_worker.rb'
+ - 'app/workers/delete_stored_files_worker.rb'
+ - 'app/workers/delete_user_worker.rb'
+ - 'app/workers/destroy_pages_deployments_worker.rb'
+ - 'app/workers/detect_repository_languages_worker.rb'
+ - 'app/workers/disallow_two_factor_for_group_worker.rb'
+ - 'app/workers/disallow_two_factor_for_subgroups_worker.rb'
+ - 'app/workers/email_receiver_worker.rb'
+ - 'app/workers/emails_on_push_worker.rb'
+ - 'app/workers/error_tracking_issue_link_worker.rb'
+ - 'app/workers/expire_build_artifacts_worker.rb'
+ - 'app/workers/expire_build_instance_artifacts_worker.rb'
+ - 'app/workers/expire_job_cache_worker.rb'
+ - 'app/workers/expire_pipeline_cache_worker.rb'
+ - 'app/workers/export_csv_worker.rb'
+ - 'app/workers/external_service_reactive_caching_worker.rb'
+ - 'app/workers/file_hook_worker.rb'
+ - 'app/workers/flush_counter_increments_worker.rb'
+ - 'app/workers/git_garbage_collect_worker.rb'
+ - 'app/workers/gitlab_performance_bar_stats_worker.rb'
+ - 'app/workers/gitlab_shell_worker.rb'
+ - 'app/workers/gitlab_usage_ping_worker.rb'
+ - 'app/workers/group_destroy_worker.rb'
+ - 'app/workers/group_export_worker.rb'
+ - 'app/workers/group_import_worker.rb'
+ - 'app/workers/import_export_project_cleanup_worker.rb'
+ - 'app/workers/import_issues_csv_worker.rb'
+ - 'app/workers/invalid_gpg_signature_update_worker.rb'
+ - 'app/workers/irker_worker.rb'
+ - 'app/workers/issuable_export_csv_worker.rb'
+ - 'app/workers/issue_due_scheduler_worker.rb'
+ - 'app/workers/issue_placement_worker.rb'
+ - 'app/workers/issue_rebalancing_worker.rb'
+ - 'app/workers/member_invitation_reminder_emails_worker.rb'
+ - 'app/workers/merge_request_cleanup_refs_worker.rb'
+ - 'app/workers/merge_request_mergeability_check_worker.rb'
+ - 'app/workers/merge_worker.rb'
+ - 'app/workers/migrate_external_diffs_worker.rb'
+ - 'app/workers/namespaceless_project_destroy_worker.rb'
+ - 'app/workers/new_issue_worker.rb'
+ - 'app/workers/new_merge_request_worker.rb'
+ - 'app/workers/new_note_worker.rb'
+ - 'app/workers/pages_domain_removal_cron_worker.rb'
+ - 'app/workers/pages_domain_ssl_renewal_cron_worker.rb'
+ - 'app/workers/pages_domain_ssl_renewal_worker.rb'
+ - 'app/workers/pages_domain_verification_cron_worker.rb'
+ - 'app/workers/pages_domain_verification_worker.rb'
+ - 'app/workers/pages_remove_worker.rb'
+ - 'app/workers/pages_transfer_worker.rb'
+ - 'app/workers/pages_update_configuration_worker.rb'
+ - 'app/workers/pages_worker.rb'
+ - 'app/workers/partition_creation_worker.rb'
+ - 'app/workers/pipeline_hooks_worker.rb'
+ - 'app/workers/pipeline_metrics_worker.rb'
+ - 'app/workers/pipeline_notification_worker.rb'
+ - 'app/workers/pipeline_process_worker.rb'
+ - 'app/workers/pipeline_schedule_worker.rb'
+ - 'app/workers/pipeline_update_worker.rb'
+ - 'app/workers/post_receive.rb'
+ - 'app/workers/process_commit_worker.rb'
+ - 'app/workers/project_cache_worker.rb'
+ - 'app/workers/project_daily_statistics_worker.rb'
+ - 'app/workers/project_destroy_worker.rb'
+ - 'app/workers/project_export_worker.rb'
+ - 'app/workers/project_schedule_bulk_repository_shard_moves_worker.rb'
+ - 'app/workers/project_service_worker.rb'
+ - 'app/workers/project_update_repository_storage_worker.rb'
+ - 'app/workers/propagate_integration_group_worker.rb'
+ - 'app/workers/propagate_integration_inherit_descendant_worker.rb'
+ - 'app/workers/propagate_integration_inherit_worker.rb'
+ - 'app/workers/propagate_integration_project_worker.rb'
+ - 'app/workers/propagate_integration_worker.rb'
+ - 'app/workers/propagate_service_template_worker.rb'
+ - 'app/workers/prune_old_events_worker.rb'
+ - 'app/workers/prune_web_hook_logs_worker.rb'
+ - 'app/workers/purge_dependency_proxy_cache_worker.rb'
+ - 'app/workers/reactive_caching_worker.rb'
+ - 'app/workers/rebase_worker.rb'
+ - 'app/workers/remote_mirror_notification_worker.rb'
+ - 'app/workers/remove_expired_group_links_worker.rb'
+ - 'app/workers/remove_expired_members_worker.rb'
+ - 'app/workers/remove_unaccepted_member_invites_worker.rb'
+ - 'app/workers/remove_unreferenced_lfs_objects_worker.rb'
+ - 'app/workers/repository_archive_cache_worker.rb'
+ - 'app/workers/repository_cleanup_worker.rb'
+ - 'app/workers/repository_fork_worker.rb'
+ - 'app/workers/repository_import_worker.rb'
+ - 'app/workers/repository_remove_remote_worker.rb'
+ - 'app/workers/repository_update_remote_mirror_worker.rb'
+ - 'app/workers/requests_profiles_worker.rb'
+ - 'app/workers/run_pipeline_schedule_worker.rb'
+ - 'app/workers/schedule_merge_request_cleanup_refs_worker.rb'
+ - 'app/workers/schedule_migrate_external_diffs_worker.rb'
+ - 'app/workers/self_monitoring_project_create_worker.rb'
+ - 'app/workers/self_monitoring_project_delete_worker.rb'
+ - 'app/workers/service_desk_email_receiver_worker.rb'
+ - 'app/workers/snippet_schedule_bulk_repository_shard_moves_worker.rb'
+ - 'app/workers/snippet_update_repository_storage_worker.rb'
+ - 'app/workers/stage_update_worker.rb'
+ - 'app/workers/stuck_ci_jobs_worker.rb'
+ - 'app/workers/stuck_export_jobs_worker.rb'
+ - 'app/workers/stuck_merge_jobs_worker.rb'
+ - 'app/workers/system_hook_push_worker.rb'
+ - 'app/workers/trending_projects_worker.rb'
+ - 'app/workers/update_container_registry_info_worker.rb'
+ - 'app/workers/update_external_pull_requests_worker.rb'
+ - 'app/workers/update_head_pipeline_for_merge_request_worker.rb'
+ - 'app/workers/update_highest_role_worker.rb'
+ - 'app/workers/update_merge_requests_worker.rb'
+ - 'app/workers/update_project_statistics_worker.rb'
+ - 'app/workers/upload_checksum_worker.rb'
+ - 'app/workers/wait_for_cluster_creation_worker.rb'
+ - 'app/workers/web_hook_worker.rb'
+ - 'app/workers/x509_certificate_revoke_worker.rb'
+ - 'app/workers/x509_issuer_crl_check_worker.rb'
+ - 'ee/app/controllers/countries_controller.rb'
+ - 'ee/app/controllers/country_states_controller.rb'
+ - 'ee/app/controllers/omniauth_kerberos_spnego_controller.rb'
+ - 'ee/app/controllers/operations_controller.rb'
+ - 'ee/app/controllers/sitemap_controller.rb'
+ - 'ee/app/controllers/smartcard_controller.rb'
+ - 'ee/app/controllers/subscriptions_controller.rb'
+ - 'ee/app/controllers/survey_responses_controller.rb'
+ - 'ee/app/controllers/trial_registrations_controller.rb'
+ - 'ee/app/controllers/trials_controller.rb'
+ - 'ee/app/controllers/unsubscribes_controller.rb'
+ - 'ee/app/controllers/usernames_controller.rb'
+ - 'ee/app/finders/audit_log_finder.rb'
+ - 'ee/app/finders/billed_users_finder.rb'
+ - 'ee/app/finders/custom_project_templates_finder.rb'
+ - 'ee/app/finders/dast_scanner_profiles_finder.rb'
+ - 'ee/app/finders/dast_site_profiles_finder.rb'
+ - 'ee/app/finders/dast_site_validations_finder.rb'
+ - 'ee/app/finders/epics_finder.rb'
+ - 'ee/app/finders/geo_node_finder.rb'
+ - 'ee/app/finders/gpg_keys_finder.rb'
+ - 'ee/app/finders/group_saml_identity_finder.rb'
+ - 'ee/app/finders/groups_with_templates_finder.rb'
+ - 'ee/app/finders/iterations_finder.rb'
+ - 'ee/app/finders/licenses_finder.rb'
+ - 'ee/app/finders/merge_requests_compliance_finder.rb'
+ - 'ee/app/finders/merge_trains_finder.rb'
+ - 'ee/app/finders/productivity_analytics_finder.rb'
+ - 'ee/app/finders/scim_finder.rb'
+ - 'ee/app/finders/software_license_policies_finder.rb'
+ - 'ee/app/mailers/ci_minutes_usage_mailer.rb'
+ - 'ee/app/mailers/credentials_inventory_mailer.rb'
+ - 'ee/app/mailers/license_mailer.rb'
+ - 'ee/app/mailers/previews/ci_minutes_usage_mailer_preview.rb'
+ - 'ee/app/mailers/previews/license_mailer_preview.rb'
+ - 'ee/app/models/allowed_email_domain.rb'
+ - 'ee/app/models/approval_merge_request_rule.rb'
+ - 'ee/app/models/approval_merge_request_rule_source.rb'
+ - 'ee/app/models/approval_project_rule.rb'
+ - 'ee/app/models/approval_project_rules_protected_branch.rb'
+ - 'ee/app/models/approval_state.rb'
+ - 'ee/app/models/approval_wrapped_any_approver_rule.rb'
+ - 'ee/app/models/approval_wrapped_code_owner_rule.rb'
+ - 'ee/app/models/approval_wrapped_rule.rb'
+ - 'ee/app/models/approver.rb'
+ - 'ee/app/models/approver_group.rb'
+ - 'ee/app/models/board_assignee.rb'
+ - 'ee/app/models/board_label.rb'
+ - 'ee/app/models/board_user_preference.rb'
+ - 'ee/app/models/burndown.rb'
+ - 'ee/app/models/dast_scanner_profile.rb'
+ - 'ee/app/models/dast_site.rb'
+ - 'ee/app/models/dast_site_profile.rb'
+ - 'ee/app/models/dast_site_token.rb'
+ - 'ee/app/models/dast_site_validation.rb'
+ - 'ee/app/models/elasticsearch_indexed_namespace.rb'
+ - 'ee/app/models/elasticsearch_indexed_project.rb'
+ - 'ee/app/models/epic_issue.rb'
+ - 'ee/app/models/epic_user_mention.rb'
+ - 'ee/app/models/feature_flag_issue.rb'
+ - 'ee/app/models/geo_node.rb'
+ - 'ee/app/models/geo_node_namespace_link.rb'
+ - 'ee/app/models/geo_node_status.rb'
+ - 'ee/app/models/gitlab_subscription.rb'
+ - 'ee/app/models/gitlab_subscription_history.rb'
+ - 'ee/app/models/group_deletion_schedule.rb'
+ - 'ee/app/models/group_merge_request_approval_setting.rb'
+ - 'ee/app/models/group_wiki.rb'
+ - 'ee/app/models/group_wiki_repository.rb'
+ - 'ee/app/models/historical_data.rb'
+ - 'ee/app/models/hooks/group_hook.rb'
+ - 'ee/app/models/index_status.rb'
+ - 'ee/app/models/insight.rb'
+ - 'ee/app/models/instance_security_dashboard.rb'
+ - 'ee/app/models/ip_restriction.rb'
+ - 'ee/app/models/issuable_metric_image.rb'
+ - 'ee/app/models/issuable_sla.rb'
+ - 'ee/app/models/issuables_analytics.rb'
+ - 'ee/app/models/iteration_note.rb'
+ - 'ee/app/models/ldap_group_link.rb'
+ - 'ee/app/models/ldap_key.rb'
+ - 'ee/app/models/license.rb'
+ - 'ee/app/models/merge_request_block.rb'
+ - 'ee/app/models/merge_request_diff_detail.rb'
+ - 'ee/app/models/merge_train.rb'
+ - 'ee/app/models/namespace_limit.rb'
+ - 'ee/app/models/namespace_statistics.rb'
+ - 'ee/app/models/path_lock.rb'
+ - 'ee/app/models/pg_replication_slot.rb'
+ - 'ee/app/models/productivity_analytics.rb'
+ - 'ee/app/models/project_alias.rb'
+ - 'ee/app/models/project_repository_state.rb'
+ - 'ee/app/models/project_security_setting.rb'
+ - 'ee/app/models/project_services/github_service.rb'
+ - 'ee/app/models/project_services/github_service/remote_project.rb'
+ - 'ee/app/models/project_services/github_service/status_message.rb'
+ - 'ee/app/models/project_services/github_service/status_notifier.rb'
+ - 'ee/app/models/project_services/gitlab_slack_application_service.rb'
+ - 'ee/app/models/protected_environment.rb'
+ - 'ee/app/models/push_rule.rb'
+ - 'ee/app/models/resource_iteration_event.rb'
+ - 'ee/app/models/resource_weight_event.rb'
+ - 'ee/app/models/saml_group_link.rb'
+ - 'ee/app/models/saml_provider.rb'
+ - 'ee/app/models/scim_identity.rb'
+ - 'ee/app/models/scim_oauth_access_token.rb'
+ - 'ee/app/models/scoped_label_set.rb'
+ - 'ee/app/models/slack_integration.rb'
+ - 'ee/app/models/smartcard_identity.rb'
+ - 'ee/app/models/software_license.rb'
+ - 'ee/app/models/software_license_policy.rb'
+ - 'ee/app/models/storage_shard.rb'
+ - 'ee/app/models/user_permission_export_upload.rb'
+ - 'ee/app/models/users_ops_dashboard_project.rb'
+ - 'ee/app/models/users_security_dashboard_project.rb'
+ - 'ee/app/models/vulnerability_user_mention.rb'
+ - 'ee/app/models/weight_note.rb'
+ - 'ee/app/policies/approval_merge_request_rule_policy.rb'
+ - 'ee/app/policies/approval_project_rule_policy.rb'
+ - 'ee/app/policies/dast_scanner_profile_policy.rb'
+ - 'ee/app/policies/dast_site_profile_policy.rb'
+ - 'ee/app/policies/dast_site_validation_policy.rb'
+ - 'ee/app/policies/epic_policy.rb'
+ - 'ee/app/policies/geo_node_policy.rb'
+ - 'ee/app/policies/instance_security_dashboard_policy.rb'
+ - 'ee/app/policies/issuable_metric_image_policy.rb'
+ - 'ee/app/policies/iteration_policy.rb'
+ - 'ee/app/policies/saml_provider_policy.rb'
+ - 'ee/app/policies/timelog_policy.rb'
+ - 'ee/app/policies/vulnerability_policy.rb'
+ - 'ee/app/presenters/approval_rule_presenter.rb'
+ - 'ee/app/presenters/audit_event_presenter.rb'
+ - 'ee/app/presenters/epic_issue_presenter.rb'
+ - 'ee/app/presenters/epic_presenter.rb'
+ - 'ee/app/presenters/iteration_presenter.rb'
+ - 'ee/app/presenters/merge_request_approver_presenter.rb'
+ - 'ee/app/presenters/subscription_presenter.rb'
+ - 'ee/app/presenters/vulnerability_presenter.rb'
+ - 'ee/app/serializers/audit_event_entity.rb'
+ - 'ee/app/serializers/audit_event_serializer.rb'
+ - 'ee/app/serializers/blocking_merge_request_entity.rb'
+ - 'ee/app/serializers/board_assignee_entity.rb'
+ - 'ee/app/serializers/board_label_entity.rb'
+ - 'ee/app/serializers/board_milestone_entity.rb'
+ - 'ee/app/serializers/dashboard_environment_entity.rb'
+ - 'ee/app/serializers/dashboard_environments_project_entity.rb'
+ - 'ee/app/serializers/dashboard_environments_serializer.rb'
+ - 'ee/app/serializers/dashboard_operations_project_entity.rb'
+ - 'ee/app/serializers/dashboard_operations_serializer.rb'
+ - 'ee/app/serializers/dependency_entity.rb'
+ - 'ee/app/serializers/dependency_list_entity.rb'
+ - 'ee/app/serializers/dependency_list_serializer.rb'
+ - 'ee/app/serializers/epic_base_entity.rb'
+ - 'ee/app/serializers/epic_entity.rb'
+ - 'ee/app/serializers/epic_note_entity.rb'
+ - 'ee/app/serializers/epic_note_serializer.rb'
+ - 'ee/app/serializers/epic_serializer.rb'
+ - 'ee/app/serializers/file_lock_entity.rb'
+ - 'ee/app/serializers/geo_design_registry_entity.rb'
+ - 'ee/app/serializers/geo_design_registry_serializer.rb'
+ - 'ee/app/serializers/geo_node_serializer.rb'
+ - 'ee/app/serializers/geo_node_status_serializer.rb'
+ - 'ee/app/serializers/geo_project_registry_entity.rb'
+ - 'ee/app/serializers/geo_project_registry_serializer.rb'
+ - 'ee/app/serializers/group_analytics_serializer.rb'
+ - 'ee/app/serializers/group_issuable_autocomplete_entity.rb'
+ - 'ee/app/serializers/group_issuable_autocomplete_serializer.rb'
+ - 'ee/app/serializers/group_vulnerability_autocomplete_entity.rb'
+ - 'ee/app/serializers/group_vulnerability_autocomplete_serializer.rb'
+ - 'ee/app/serializers/invited_group_entity.rb'
+ - 'ee/app/serializers/invited_group_serializer.rb'
+ - 'ee/app/serializers/iteration_serializer.rb'
+ - 'ee/app/serializers/license_entity.rb'
+ - 'ee/app/serializers/license_scanning_reports_comparer_entity.rb'
+ - 'ee/app/serializers/license_scanning_reports_comparer_serializer.rb'
+ - 'ee/app/serializers/license_scanning_reports_serializer.rb'
+ - 'ee/app/serializers/licenses_list_entity.rb'
+ - 'ee/app/serializers/licenses_list_serializer.rb'
+ - 'ee/app/serializers/linked_epic_entity.rb'
+ - 'ee/app/serializers/linked_epic_issue_entity.rb'
+ - 'ee/app/serializers/linked_epic_issue_serializer.rb'
+ - 'ee/app/serializers/linked_epic_serializer.rb'
+ - 'ee/app/serializers/linked_feature_flag_issue_entity.rb'
+ - 'ee/app/serializers/linked_feature_flag_issue_serializer.rb'
+ - 'ee/app/serializers/managed_license_entity.rb'
+ - 'ee/app/serializers/managed_license_serializer.rb'
+ - 'ee/app/serializers/merge_request_compliance_entity.rb'
+ - 'ee/app/serializers/metrics_report_metric_entity.rb'
+ - 'ee/app/serializers/metrics_reports_comparer_entity.rb'
+ - 'ee/app/serializers/metrics_reports_comparer_serializer.rb'
+ - 'ee/app/serializers/milestone_serializer.rb'
+ - 'ee/app/serializers/namespace_entity.rb'
+ - 'ee/app/serializers/productivity_analytics_merge_request_entity.rb'
+ - 'ee/app/serializers/report_list_entity.rb'
+ - 'ee/app/serializers/scim_oauth_access_token_entity.rb'
+ - 'ee/app/serializers/storage_shard_entity.rb'
+ - 'ee/app/serializers/storage_shard_serializer.rb'
+ - 'ee/app/serializers/user_analytics_entity.rb'
+ - 'ee/app/serializers/vulnerability_entity.rb'
+ - 'ee/app/serializers/vulnerability_note_entity.rb'
+ - 'ee/app/serializers/vulnerability_note_serializer.rb'
+ - 'ee/app/serializers/vulnerability_serializer.rb'
+ - 'ee/app/services/clear_namespace_shared_runners_minutes_service.rb'
+ - 'ee/app/services/fetch_subscription_plans_service.rb'
+ - 'ee/app/services/ldap_group_reset_service.rb'
+ - 'ee/app/services/start_pull_mirroring_service.rb'
+ - 'ee/app/services/timebox_report_service.rb'
+ - 'ee/app/services/update_build_minutes_service.rb'
+ - 'ee/app/uploaders/issuable_metric_image_uploader.rb'
+ - 'ee/app/validators/host_validator.rb'
+ - 'ee/app/validators/ldap_filter_validator.rb'
+ - 'ee/app/workers/active_user_count_threshold_worker.rb'
+ - 'ee/app/workers/adjourned_group_deletion_worker.rb'
+ - 'ee/app/workers/adjourned_project_deletion_worker.rb'
+ - 'ee/app/workers/adjourned_projects_deletion_cron_worker.rb'
+ - 'ee/app/workers/admin_emails_worker.rb'
+ - 'ee/app/workers/clear_shared_runners_minutes_worker.rb'
+ - 'ee/app/workers/create_github_webhook_worker.rb'
+ - 'ee/app/workers/dast_site_validation_worker.rb'
+ - 'ee/app/workers/elastic_association_indexer_worker.rb'
+ - 'ee/app/workers/elastic_cluster_reindexing_cron_worker.rb'
+ - 'ee/app/workers/elastic_commit_indexer_worker.rb'
+ - 'ee/app/workers/elastic_delete_project_worker.rb'
+ - 'ee/app/workers/elastic_full_index_worker.rb'
+ - 'ee/app/workers/elastic_index_bulk_cron_worker.rb'
+ - 'ee/app/workers/elastic_index_initial_bulk_cron_worker.rb'
+ - 'ee/app/workers/elastic_indexer_worker.rb'
+ - 'ee/app/workers/elastic_indexing_control_worker.rb'
+ - 'ee/app/workers/elastic_namespace_indexer_worker.rb'
+ - 'ee/app/workers/elastic_namespace_rollout_worker.rb'
+ - 'ee/app/workers/elastic_remove_expired_namespace_subscriptions_from_index_cron_worker.rb'
+ - 'ee/app/workers/geo_repository_destroy_worker.rb'
+ - 'ee/app/workers/group_saml_group_sync_worker.rb'
+ - 'ee/app/workers/historical_data_worker.rb'
+ - 'ee/app/workers/import_software_licenses_worker.rb'
+ - 'ee/app/workers/ingress_modsecurity_counter_metrics_worker.rb'
+ - 'ee/app/workers/iterations_update_status_worker.rb'
+ - 'ee/app/workers/ldap_all_groups_sync_worker.rb'
+ - 'ee/app/workers/ldap_group_sync_worker.rb'
+ - 'ee/app/workers/ldap_sync_worker.rb'
+ - 'ee/app/workers/merge_request_reset_approvals_worker.rb'
+ - 'ee/app/workers/network_policy_metrics_worker.rb'
+ - 'ee/app/workers/new_epic_worker.rb'
+ - 'ee/app/workers/project_import_schedule_worker.rb'
+ - 'ee/app/workers/project_template_export_worker.rb'
+ - 'ee/app/workers/pseudonymizer_worker.rb'
+ - 'ee/app/workers/refresh_license_compliance_checks_worker.rb'
+ - 'ee/app/workers/repository_push_audit_event_worker.rb'
+ - 'ee/app/workers/repository_update_mirror_worker.rb'
+ - 'ee/app/workers/scan_security_report_secrets_worker.rb'
+ - 'ee/app/workers/set_user_status_based_on_user_cap_setting_worker.rb'
+ - 'ee/app/workers/store_security_reports_worker.rb'
+ - 'ee/app/workers/store_security_scans_worker.rb'
+ - 'ee/app/workers/sync_seat_link_request_worker.rb'
+ - 'ee/app/workers/sync_seat_link_worker.rb'
+ - 'ee/app/workers/sync_security_reports_to_report_approval_rules_worker.rb'
+ - 'ee/app/workers/update_all_mirrors_worker.rb'
+ - 'ee/app/workers/update_max_seats_used_for_gitlab_com_subscriptions_worker.rb'
+ - 'ee/lib/generators/rails/geo_migration_generator.rb'
+ - 'ee/lib/gitlab/path_locks_finder.rb'
+ - 'ee/spec/support/elastic_query_name_inspector.rb'
+ - 'ee/spec/support/ssh_keygen.rb'
+ - 'ee/spec/support/test_license.rb'
+ - 'lib/carrier_wave_string_file.rb'
+ - 'lib/csv_builder.rb'
+ - 'lib/event_filter.rb'
+ - 'lib/feature.rb'
+ - 'lib/feature/definition.rb'
+ - 'lib/feature/gitaly.rb'
+ - 'lib/feature/logger.rb'
+ - 'lib/feature/shared.rb'
+ - 'lib/file_size_validator.rb'
+ - 'lib/forever.rb'
+ - 'lib/gitlab_danger.rb'
+ - 'lib/learn_gitlab.rb'
+ - 'lib/tasks/gitlab/graphql.rake'
+ - 'lib/tasks/gitlab/seed/group_seed.rake'
+ - 'lib/tasks/import.rake'
+ - 'lib/tasks/tokens.rake'
+ - 'lib/uploaded_file.rb'
+ - 'lib/version_check.rb'
+ - 'qa/spec/specs/helpers/quarantine_spec.rb'
+ - 'spec/controllers/concerns/page_limiter_spec.rb'
+ - 'spec/lib/bitbucket/collection_spec.rb'
+ - 'spec/lib/gitlab/database/bulk_update_spec.rb'
+ - 'spec/lib/gitlab/multi_destination_logger_spec.rb'
+ - 'spec/lib/marginalia_spec.rb'
+ - 'spec/mailers/notify_spec.rb'
+ - 'spec/models/concerns/batch_destroy_dependent_associations_spec.rb'
+ - 'spec/models/concerns/bulk_insertable_associations_spec.rb'
+ - 'spec/models/concerns/triggerable_hooks_spec.rb'
+ - 'spec/support/helpers/bare_repo_operations.rb'
+ - 'spec/support/helpers/ci_artifact_metadata_generator.rb'
+ - 'spec/support/helpers/fake_migration_classes.rb'
+ - 'spec/support/helpers/fake_u2f_device.rb'
+ - 'spec/support/helpers/fake_webauthn_device.rb'
+ - 'spec/support/helpers/markdown_feature.rb'
+ - 'spec/support/helpers/redis_without_keys.rb'
+ - 'spec/support/helpers/require_migration.rb'
+ - 'spec/support/inspect_squelch.rb'
+ - 'spec/support/models/merge_request_without_merge_request_diff.rb'
+ - 'spec/support/renameable_upload.rb'
+ - 'spec/support/sidekiq_middleware.rb'
+ - 'spec/tasks/gitlab/task_helpers_spec.rb'
+ - 'spec/uploaders/object_storage_spec.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 334d4e7869b..7a148853798 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-c89fdf6bb2dc9f652f5c724caf13d3bde76e9d90
+6a06feda7fd01961bb332afce4d7f7b4ce4a5aad
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
index a3f76241bf2..dc9182574e6 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
@@ -1,6 +1,6 @@
<script>
/* eslint-disable vue/no-v-html */
-import { GlButton, GlFormGroup, GlFormInput, GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlButton, GlFormGroup, GlFormInput, GlIcon, GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
import { escape } from 'lodash';
import { mapState, mapActions } from 'vuex';
import { DEFAULT_REGION } from '../constants';
@@ -16,6 +16,7 @@ export default {
GlLink,
GlSprintf,
ClipboardButton,
+ GlAlert,
},
props: {
accountAndExternalIdsHelpPath: {
@@ -105,9 +106,14 @@ export default {
)
}}
</p>
- <div v-if="createRoleError" class="js-invalid-credentials bs-callout bs-callout-danger">
+ <gl-alert
+ v-if="createRoleError"
+ class="js-invalid-credentials gl-mb-5"
+ variant="danger"
+ :dismissible="false"
+ >
{{ createRoleError }}
- </div>
+ </gl-alert>
<div class="form-row">
<div class="form-group col-md-6">
<label for="gitlab-account-id">{{ __('Account ID') }}</label>
diff --git a/app/assets/javascripts/pages/groups/shared/group_details.js b/app/assets/javascripts/pages/groups/shared/group_details.js
index 8d956c694c0..90c5c71113b 100644
--- a/app/assets/javascripts/pages/groups/shared/group_details.js
+++ b/app/assets/javascripts/pages/groups/shared/group_details.js
@@ -8,6 +8,8 @@ import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GroupTabs from './group_tabs';
import initInviteMembersBanner from '~/groups/init_invite_members_banner';
+import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
+import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
export default function initGroupDetails(actionName = 'show') {
const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
@@ -24,4 +26,6 @@ export default function initGroupDetails(actionName = 'show') {
new ProjectsList();
initInviteMembersBanner();
+ initInviteMembersModal();
+ initInviteMembersTrigger();
}
diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb
index a643fea6d5a..889365e39de 100644
--- a/app/helpers/invite_members_helper.rb
+++ b/app/helpers/invite_members_helper.rb
@@ -3,10 +3,14 @@
module InviteMembersHelper
include Gitlab::Utils::StrongMemoize
- def invite_members_allowed?(group)
+ def can_invite_members_for_group?(group)
Feature.enabled?(:invite_members_group_modal, group) && can?(current_user, :admin_group_member, group)
end
+ def can_invite_members_for_project?(project)
+ Feature.enabled?(:invite_members_group_modal, project.group) && can_import_members?
+ end
+
def directly_invite_members?
strong_memoize(:directly_invite_members) do
experiment_enabled?(:invite_members_version_a) && can_import_members?
@@ -27,8 +31,8 @@ module InviteMembersHelper
link_to invite_members_url(form_model),
data: {
'track-event': 'click_link',
- 'track-label': tracking_label(current_user),
- 'track-property': experiment_tracking_category_and_group(:invite_members_new_dropdown, subject: current_user)
+ 'track-label': tracking_label,
+ 'track-property': experiment_tracking_category_and_group(:invite_members_new_dropdown)
} do
invite_member_link_content
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 4a579892e3f..a2a3d718fda 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -262,8 +262,6 @@ module Ci
end
after_transition any => any do |pipeline|
- next unless Feature.enabled?(:jira_sync_builds, pipeline.project)
-
pipeline.run_after_commit do
# Passing the seq-id ensures this is idempotent
seq_id = ::Atlassian::JiraConnect::Client.generate_update_sequence_id
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 1eb921da5fa..7bcf7c702f6 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -350,6 +350,13 @@ class Deployment < ApplicationRecord
File.join(environment.ref_path, 'deployments', iid.to_s)
end
+ def equal_to?(params)
+ ref == params[:ref] &&
+ tag == params[:tag] &&
+ sha == params[:sha] &&
+ status == params[:status]
+ end
+
private
def legacy_finished_at
diff --git a/app/services/deployments/create_service.rb b/app/services/deployments/create_service.rb
index 7355747d778..ebf2b077bca 100644
--- a/app/services/deployments/create_service.rb
+++ b/app/services/deployments/create_service.rb
@@ -11,6 +11,8 @@ module Deployments
end
def execute
+ return last_deployment if last_deployment&.equal_to?(params)
+
environment.deployments.build(deployment_attributes).tap do |deployment|
# Deployment#change_status already saves the model, so we only need to
# call #save ourselves if no status is provided.
@@ -36,5 +38,11 @@ module Deployments
on_stop: params[:on_stop]
}
end
+
+ private
+
+ def last_deployment
+ @environment.last_deployment
+ end
end
end
diff --git a/app/views/groups/_invite_members_modal.html.haml b/app/views/groups/_invite_members_modal.html.haml
index bd53f73230e..ba6dfcb70ff 100644
--- a/app/views/groups/_invite_members_modal.html.haml
+++ b/app/views/groups/_invite_members_modal.html.haml
@@ -1,4 +1,4 @@
-- if invite_members_allowed?(group)
+- if can_invite_members_for_group?(group)
.js-invite-members-modal{ data: { id: group.id,
name: group.name,
is_project: 'false',
diff --git a/app/views/groups/_invite_members_side_nav_link.html.haml b/app/views/groups/_invite_members_side_nav_link.html.haml
deleted file mode 100644
index 4f1c06d9fe3..00000000000
--- a/app/views/groups/_invite_members_side_nav_link.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- if invite_members_allowed?(group) && body_data_page == 'groups:show'
- %li
- .js-invite-members-trigger{ data: { icon: 'plus', display_text: _('Invite team members') } }
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index ab3998be009..abdb4553cdd 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -14,12 +14,12 @@
= _('Group members')
%p
= html_escape(_('You can invite a new member to %{strong_start}%{group_name}%{strong_end}.')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- - if invite_members_allowed?(@group)
+ - if can_invite_members_for_group?(@group)
.gl-w-half.gl-xs-w-full
.gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2.gl-mb-3
.js-invite-members-trigger.gl-px-2.gl-sm-w-auto.gl-w-full.gl-mb-4{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite members') } }
- = render_if_exists 'groups/invite_members_modal', group: @group
- - if can_manage_members && !invite_members_allowed?(@group)
+ = render 'groups/invite_members_modal', group: @group
+ - if can_manage_members && !can_invite_members_for_group?(@group)
%hr.gl-mt-4
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 109e7c3831e..d1787d36cd2 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -16,6 +16,11 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
+= content_for :invite_members_sidebar do
+ - if can_invite_members_for_group?(@group)
+ %li
+ .js-invite-members-trigger{ data: { icon: 'plus', classes: 'gl-text-decoration-none! gl-shadow-none!', display_text: _('Invite team members') } }
+
= render partial: 'flash_messages'
= render_if_exists 'trials/banner', namespace: @group
@@ -26,7 +31,7 @@
= render_if_exists 'groups/group_activity_analytics', group: @group
-= render_if_exists 'groups/invite_members_modal', group: @group
+= render 'groups/invite_members_modal', group: @group
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
.top-area.group-nav-container.justify-content-between
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 473a0d131b8..5569a69222f 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -141,7 +141,7 @@
%strong.fly-out-top-item-name
= _('Members')
- = render_if_exists 'groups/invite_members_side_nav_link', group: @group
+ = content_for :invite_members_sidebar
- if group_sidebar_link?(:settings)
= nav_link(path: group_settings_nav_link_paths) do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index e02b8333c60..d516484c4b8 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -383,7 +383,7 @@
%strong.fly-out-top-item-name
= _('Members')
- = render_if_exists 'projects/invite_members_side_nav_link', project: @project
+ = content_for :invite_members_sidebar
- if project_nav_tab? :settings
= nav_link(path: sidebar_settings_paths) do
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 3e1d08e646e..1f826567002 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -3,7 +3,7 @@
- max_project_topic_length = 15
- emails_disabled = @project.emails_disabled?
-= render_if_exists 'projects/invite_members_modal', project: @project
+= render 'projects/invite_members_modal', project: @project
.project-home-panel.js-show-on-project-root.gl-my-5{ class: [("empty-project" if empty_repo)] }
.row.gl-mb-3
diff --git a/app/views/projects/_invite_members_link.html.haml b/app/views/projects/_invite_members_link.html.haml
new file mode 100644
index 00000000000..95cfc75d955
--- /dev/null
+++ b/app/views/projects/_invite_members_link.html.haml
@@ -0,0 +1,4 @@
+- return unless can_invite_members_for_project?(@project)
+
+%li
+ .js-invite-members-trigger{ data: { icon: 'plus', classes: 'gl-text-decoration-none! gl-shadow-none!', display_text: _('Invite team members') } }
diff --git a/app/views/projects/_invite_members_modal.html.haml b/app/views/projects/_invite_members_modal.html.haml
index e8f61336882..b1bba5b59ca 100644
--- a/app/views/projects/_invite_members_modal.html.haml
+++ b/app/views/projects/_invite_members_modal.html.haml
@@ -1,4 +1,4 @@
-- if invite_members_allowed?(project.group)
+- if can_invite_members_for_project?(project)
.js-invite-members-modal{ data: { id: project.id,
name: project.name,
is_project: 'true',
diff --git a/app/views/projects/_invite_members_side_nav_link.html.haml b/app/views/projects/_invite_members_side_nav_link.html.haml
deleted file mode 100644
index 15e0b75cf57..00000000000
--- a/app/views/projects/_invite_members_side_nav_link.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- if invite_members_allowed?(project.group) && body_data_page == 'projects:show'
- %li
- .js-invite-members-trigger{ data: { icon: 'plus', display_text: _('Invite team members') } }
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 2936eff45df..2c245c1a914 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -2,6 +2,9 @@
- default_branch_name = @project.default_branch || "master"
- @skip_current_level_breadcrumb = true
+= content_for :invite_members_sidebar do
+ = render partial: 'projects/invite_members_link'
+
= render partial: 'flash_messages', locals: { project: @project }
= render "home_panel"
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index d7853c1b466..ea14e2d6ca5 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -12,7 +12,7 @@
#{ _('This means you can not push code until you create an empty repository or import existing one.') }
%hr
-= render_if_exists 'projects/invite_members_modal', project: @project
+= render 'projects/invite_members_modal', project: @project
.no-repo-actions
= link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index cf39ac4dd56..3da012d1335 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -4,7 +4,7 @@
.js-remove-member-modal
.row.gl-mt-3
.col-lg-12
- - if invite_members_allowed?(group)
+ - if can_invite_members_for_project?(@project)
.row
.col-md-12.col-lg-6.gl-display-flex
.gl-flex-direction-column.gl-flex-wrap.align-items-baseline
@@ -19,7 +19,7 @@
.col-md-12.col-lg-6
.gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2.gl-mb-3
.js-invite-members-trigger.gl-px-2.gl-sm-w-auto.gl-w-full.gl-mb-4{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite members') } }
- = render_if_exists 'projects/invite_members_modal', project: @project
+ = render 'projects/invite_members_modal', project: @project
- else
- if project_can_be_shared?
@@ -31,7 +31,7 @@
%p
= html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
- - if !invite_members_allowed?(group) && can_manage_project_members?(@project) && project_can_be_shared?
+ - if !can_invite_members_for_project?(@project) && can_manage_project_members?(@project) && project_can_be_shared?
- if !membership_locked? && @project.allowed_to_share_with_group?
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 40faf91eadf..e1774c955bc 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -6,6 +6,9 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
+= content_for :invite_members_sidebar do
+ = render partial: 'projects/invite_members_link'
+
= render partial: 'flash_messages', locals: { project: @project }
= render "projects/last_push"
diff --git a/app/views/shared/deploy_tokens/_table.html.haml b/app/views/shared/deploy_tokens/_table.html.haml
index 361471af0ad..ad3c53c4925 100644
--- a/app/views/shared/deploy_tokens/_table.html.haml
+++ b/app/views/shared/deploy_tokens/_table.html.haml
@@ -24,7 +24,7 @@
- else
%span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
- %td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger float-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
+ %td= link_to s_('DeployTokens|Revoke'), "#", class: "gl-button btn btn-danger float-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
= render 'shared/deploy_tokens/revoke_modal', token: token, group_or_project: group_or_project
- else
.settings-message.text-center
diff --git a/app/workers/jira_connect/sync_builds_worker.rb b/app/workers/jira_connect/sync_builds_worker.rb
index c1c749f6041..9cb5d5d247d 100644
--- a/app/workers/jira_connect/sync_builds_worker.rb
+++ b/app/workers/jira_connect/sync_builds_worker.rb
@@ -14,7 +14,6 @@ module JiraConnect
pipeline = Ci::Pipeline.find_by_id(pipeline_id)
return unless pipeline
- return unless Feature.enabled?(:jira_sync_builds, pipeline.project)
::JiraConnect::SyncService
.new(pipeline.project)
diff --git a/changelogs/unreleased/239172-add-entity-columns-to-vulnerability-occurrences.yml b/changelogs/unreleased/239172-add-entity-columns-to-vulnerability-occurrences.yml
new file mode 100644
index 00000000000..612d05809d7
--- /dev/null
+++ b/changelogs/unreleased/239172-add-entity-columns-to-vulnerability-occurrences.yml
@@ -0,0 +1,5 @@
+---
+title: Add entity columns to vulnerability occurrences
+merge_request: 51739
+author:
+type: changed
diff --git a/changelogs/unreleased/294004-enable-jira-sync-builds.yml b/changelogs/unreleased/294004-enable-jira-sync-builds.yml
new file mode 100644
index 00000000000..5a81425a89c
--- /dev/null
+++ b/changelogs/unreleased/294004-enable-jira-sync-builds.yml
@@ -0,0 +1,5 @@
+---
+title: Sync pipeline builds to Jira
+merge_request: 51627
+author:
+type: added
diff --git a/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsindeploytokens_table-html-haml.yml b/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsindeploytokens_table-html-haml.yml
new file mode 100644
index 00000000000..82e0ca5e42f
--- /dev/null
+++ b/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsindeploytokens_table-html-haml.yml
@@ -0,0 +1,5 @@
+---
+title: Adds GitLabUI button styles in deploy tokens _table.html.haml
+merge_request: 51082
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/zj-no-duplicate-deployments.yml b/changelogs/unreleased/zj-no-duplicate-deployments.yml
new file mode 100644
index 00000000000..13d9a13e8fe
--- /dev/null
+++ b/changelogs/unreleased/zj-no-duplicate-deployments.yml
@@ -0,0 +1,5 @@
+---
+title: Deployments::CreateService executions are idempotent for duplicate params
+merge_request: 47610
+author:
+type: added
diff --git a/config/feature_flags/development/jira_sync_builds.yml b/config/feature_flags/development/jira_sync_builds.yml
deleted file mode 100644
index 8cb054b848d..00000000000
--- a/config/feature_flags/development/jira_sync_builds.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: jira_sync_builds
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49348
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292013
-milestone: '13.7'
-type: development
-group: group::ecosystem
-default_enabled: false
diff --git a/config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml b/config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml
index 6fac63b381c..216726178f1 100644
--- a/config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml
+++ b/config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml
@@ -1,7 +1,7 @@
---
name: invite_members_new_dropdown_experiment_percentage
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50069
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/268129
+rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/291
milestone: '13.8'
type: experiment
group: group::expansion
diff --git a/db/migrate/20210105153342_add_entity_columns_to_vulnerability_occurrences.rb b/db/migrate/20210105153342_add_entity_columns_to_vulnerability_occurrences.rb
new file mode 100644
index 00000000000..3b5ffff7645
--- /dev/null
+++ b/db/migrate/20210105153342_add_entity_columns_to_vulnerability_occurrences.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddEntityColumnsToVulnerabilityOccurrences < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20200501000002_add_text_limit_to_sprints_extended_title
+ def change
+ add_column :vulnerability_occurrences, :description, :text
+ add_column :vulnerability_occurrences, :message, :text
+ add_column :vulnerability_occurrences, :solution, :text
+ add_column :vulnerability_occurrences, :cve, :text
+ add_column :vulnerability_occurrences, :location, :jsonb
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20210105154321_add_text_limit_to_vulnerability_occurrences_entity_columns.rb b/db/migrate/20210105154321_add_text_limit_to_vulnerability_occurrences_entity_columns.rb
new file mode 100644
index 00000000000..c2e138303d8
--- /dev/null
+++ b/db/migrate/20210105154321_add_text_limit_to_vulnerability_occurrences_entity_columns.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddTextLimitToVulnerabilityOccurrencesEntityColumns < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :vulnerability_occurrences, :description, 15000
+ add_text_limit :vulnerability_occurrences, :message, 3000
+ add_text_limit :vulnerability_occurrences, :solution, 7000
+ add_text_limit :vulnerability_occurrences, :cve, 48400
+ end
+
+ def down
+ remove_text_limit :vulnerability_occurrences, :description
+ remove_text_limit :vulnerability_occurrences, :message
+ remove_text_limit :vulnerability_occurrences, :solution
+ remove_text_limit :vulnerability_occurrences, :cve
+ end
+end
diff --git a/db/schema_migrations/20210105153342 b/db/schema_migrations/20210105153342
new file mode 100644
index 00000000000..cb970b9b3cc
--- /dev/null
+++ b/db/schema_migrations/20210105153342
@@ -0,0 +1 @@
+7a252c5d76c1e71421c3aa3e01584cdeeec6a5002ba6ef0824674c64f92e2764 \ No newline at end of file
diff --git a/db/schema_migrations/20210105154321 b/db/schema_migrations/20210105154321
new file mode 100644
index 00000000000..2f7f2477526
--- /dev/null
+++ b/db/schema_migrations/20210105154321
@@ -0,0 +1 @@
+9327676097c49bb1a221d79dd351ad8c57a434f19e32f49951c0d6d655c2fa4e \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index de4218ed405..38965f00aa6 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18009,7 +18009,16 @@ CREATE TABLE vulnerability_occurrences (
metadata_version character varying NOT NULL,
raw_metadata text NOT NULL,
vulnerability_id bigint,
- details jsonb DEFAULT '{}'::jsonb NOT NULL
+ details jsonb DEFAULT '{}'::jsonb NOT NULL,
+ description text,
+ message text,
+ solution text,
+ cve text,
+ location jsonb,
+ CONSTRAINT check_4a3a60f2ba CHECK ((char_length(solution) <= 7000)),
+ CONSTRAINT check_ade261da6b CHECK ((char_length(description) <= 15000)),
+ CONSTRAINT check_df6dd20219 CHECK ((char_length(message) <= 3000)),
+ CONSTRAINT check_f602da68dd CHECK ((char_length(cve) <= 48400))
);
CREATE SEQUENCE vulnerability_occurrences_id_seq
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index c43ac96a42f..6879b510ec8 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -884,6 +884,14 @@ Parameters:
"avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
"web_url": "https://gitlab.example.com/axel.block"
}],
+ "reviewers": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
@@ -1252,6 +1260,14 @@ Must include at least one non-required attribute from above.
"avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
"web_url": "https://gitlab.example.com/axel.block"
}],
+ "reviewers": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -1429,6 +1445,14 @@ Parameters:
"avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
"web_url": "https://gitlab.example.com/axel.block"
}],
+ "reviewers": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -1609,6 +1633,14 @@ Parameters:
"avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
"web_url": "https://gitlab.example.com/axel.block"
}],
+ "reviewers": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -1902,6 +1934,14 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
"web_url": "https://gitlab.example.com/axel.block"
}],
+ "reviewers": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -2053,6 +2093,14 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
"web_url": "https://gitlab.example.com/axel.block"
}],
+ "reviewers": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -2224,6 +2272,14 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
"web_url": "https://gitlab.example.com/axel.block"
}],
+ "reviewers": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 3,
"target_project_id": 3,
"labels": [],
diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md
index c4a9ec4c454..8fd23f96329 100644
--- a/doc/development/gemfile.md
+++ b/doc/development/gemfile.md
@@ -18,3 +18,16 @@ dependencies and build times.
## License compliance
Refer to [licensing guidelines](licensing.md) for ensuring license compliance.
+
+## Upgrade Rails
+
+When upgrading the Rails gem and its dependencies, you also should update the following:
+
+- The [Gemfile in the `qa` directory](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/Gemfile).
+- The [Gemfile in Gitaly Ruby](https://gitlab.com/gitlab-org/gitaly/-/blob/master/ruby/Gemfile),
+ to ensure that we ship only one version of these gems.
+
+You should also update NPM packages that follow the current version of Rails:
+
+- `@rails/ujs`
+- `@rails/actioncable`
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index ac5f1a47f9b..3979f6e195d 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -13,13 +13,13 @@ description: "GitLab development guidelines - testing best practices."
Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests
as we do the design of our features.
-When implementing a feature, we think about developing the right capabilities the right way, which helps us
+When implementing a feature, we think about developing the right capabilities the right way. This helps us
narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing
-the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to
+the right tests, but then cover _all_ the important ways the test may fail. This can quickly widen our scope to
a level that is difficult to manage.
Test heuristics can help solve this problem. They concisely address many of the common ways bugs
-manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform
+manifest themselves in our code. When designing our tests, take time to review known test heuristics to inform
our test design. We can find some helpful heuristics documented in the Handbook in the
[Test Engineering](https://about.gitlab.com/handbook/engineering/quality/test-engineering/#test-heuristics) section.
@@ -90,7 +90,7 @@ Obviously we should reduce test dependencies, and avoiding
capabilities also reduces the amount of set-up needed.
`:js` is particularly important to avoid. This must only be used if the feature
-test requires JavaScript reactivity in the browser, since using a headless
+test requires JavaScript reactivity in the browser. Using a headless
browser is much slower than parsing the HTML response from the app.
#### Optimize factory usage
@@ -108,8 +108,8 @@ To avoid creation, it is worth bearing in mind that:
- `instance_double` and `spy` are faster than `FactoryBot.build(...)`.
- `FactoryBot.build(...)` and `.build_stubbed` are faster than `.create`.
-- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
- `spy`, or `instance_double` will do. Database persistence is slow!
+- Don't `create` an object when you can use `build`, `build_stubbed`, `attributes_for`,
+ `spy`, or `instance_double`. Database persistence is slow!
Use [Factory Doctor](https://test-prof.evilmartians.io/#/profilers/factory_doctor) to find cases where database persistence is not needed in a given test.
@@ -171,14 +171,14 @@ RSpec.describe API::Search, factory_default: :keep do
let_it_be(:namespace) { create_default(:namespace) }
```
-Then every project we create will use this `namespace`, without us having to pass
+Then every project we create uses this `namespace`, without us having to pass
it as `namespace: namespace`. In order to make it work along with `let_it_be`, `factory_default: :keep`
-must be explicitly specified. That will keep the default factory for every example in a suite instead of
+must be explicitly specified. That keeps the default factory for every example in a suite instead of
recreating it for each example.
Maybe we don't need to create 208 different projects - we
can create one and reuse it. In addition, we can see that only about 1/3 of the
-projects we create are ones we ask for (76/208), so there is benefit in setting
+projects we create are ones we ask for (76/208). There is benefit in setting
a default value for projects as well:
```ruby
@@ -233,8 +233,8 @@ Finished in 2 minutes 19 seconds (files took 1 minute 4.42 seconds to load)
```
From this result, we can see the most expensive examples in our spec, giving us
-a place to start. The fact that the most expensive examples here are in
-shared examples means that any reductions are likely to have a larger impact as
+a place to start. The most expensive examples here are in
+shared examples; any reductions generally have a larger impact as
they are called in multiple places.
#### Avoid repeating expensive actions
@@ -287,7 +287,7 @@ results are available, and not just the first failure.
- Use `.method` to describe class methods and `#method` to describe instance
methods.
- Use `context` to test branching logic.
-- Try to match the ordering of tests to the ordering within the class.
+- Try to match the ordering of tests to the ordering in the class.
- Try to follow the [Four-Phase Test](https://thoughtbot.com/blog/four-phase-test) pattern, using newlines
to separate phases.
- Use `Gitlab.config.gitlab.host` rather than hard coding `'localhost'`
@@ -295,10 +295,10 @@ results are available, and not just the first failure.
[Gotchas](../gotchas.md#do-not-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
- Avoid using `expect_any_instance_of` or `allow_any_instance_of` (see
[Gotchas](../gotchas.md#do-not-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
-- Don't supply the `:each` argument to hooks since it's the default.
+- Don't supply the `:each` argument to hooks because it's the default.
- On `before` and `after` hooks, prefer it scoped to `:context` over `:all`
- When using `evaluate_script("$('.js-foo').testSomething()")` (or `execute_script`) which acts on a given element,
- use a Capybara matcher beforehand (e.g. `find('.js-foo')`) to ensure the element actually exists.
+ use a Capybara matcher beforehand (such as `find('.js-foo')`) to ensure the element actually exists.
- Use `focus: true` to isolate parts of the specs you want to run.
- Use [`:aggregate_failures`](https://relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures) when there is more than one expectation in a test.
- For [empty test description blocks](https://github.com/rubocop-hq/rspec-style-guide#it-and-specify), use `specify` rather than `it do` if the test is self-explanatory.
@@ -343,7 +343,7 @@ writing one](testing_levels.md#consider-not-writing-a-system-test)!
For instance, if you want to verify that a record was created, add
expectations that its attributes are displayed on the page, not that
`Model.count` increased by one.
-- It's ok to look for DOM elements but don't abuse it since it makes the tests
+- It's ok to look for DOM elements, but don't abuse it, because it makes the tests
more brittle
#### Debugging Capybara
@@ -353,7 +353,7 @@ Sometimes you may need to debug Capybara tests by observing browser behavior.
#### Live debug
You can pause Capybara and view the website on the browser by using the
-`live_debug` method in your spec. The current page will be automatically opened
+`live_debug` method in your spec. The current page is automatically opened
in your default browser.
You may need to sign in first (the current user's credentials are displayed in
the terminal).
@@ -381,13 +381,13 @@ Finished in 34.51 seconds (files took 0.76702 seconds to load)
#### Run `:js` spec in a visible browser
-Run the spec with `CHROME_HEADLESS=0`, e.g.:
+Run the spec with `CHROME_HEADLESS=0`, like this:
```shell
CHROME_HEADLESS=0 bin/rspec some_spec.rb
```
-The test will go by quickly, but this will give you an idea of what's happening.
+The test completes quickly, but this gives you an idea of what's happening.
Using `live_debug` with `CHROME_HEADLESS=0` pauses the open browser, and does not
open the page again. This can be used to debug and inspect elements.
@@ -416,20 +416,20 @@ There is a [small hack](https://gitlab.com/gitlab-org/gitlab-foss/snippets/17184
### Fast unit tests
-Some classes are well-isolated from Rails and you should be able to test them
+Some classes are well-isolated from Rails. You should be able to test them
without the overhead added by the Rails environment and Bundler's `:default`
group's gem loading. In these cases, you can `require 'fast_spec_helper'`
instead of `require 'spec_helper'` in your test file, and your test should run
-really fast since:
+really fast because:
-- Gems loading is skipped
+- Gem loading is skipped
- Rails app boot is skipped
- GitLab Shell and Gitaly setup are skipped
- Test repositories setup are skipped
`fast_spec_helper` also support autoloading classes that are located inside the
-`lib/` directory. It means that as long as your class / module is using only
-code from the `lib/` directory you will not need to explicitly load any
+`lib/` directory. If your class or module is using only
+code from the `lib/` directory, you don't need to explicitly load any
dependencies. `fast_spec_helper` also loads all ActiveSupport extensions,
including core extensions that are commonly used in the Rails environment.
@@ -439,9 +439,11 @@ in `lib/`.
For example, if you want to test your code that is calling the
`Gitlab::UntrustedRegexp` class, which under the hood uses `re2` library, you
-should either add `require_dependency 're2'` to files in your library that
-need `re2` gem, to make this requirement explicit, or you can add it to the
-spec itself, but the former is preferred.
+should either:
+
+- Add `require_dependency 're2'` to files in your library that need `re2` gem,
+ to make this requirement explicit. This approach is preferred.
+- Add it to the spec itself.
It takes around one second to load tests that are using `fast_spec_helper`
instead of 30+ seconds in case of a regular `spec_helper`.
@@ -465,7 +467,7 @@ so we need to set some guidelines for their use going forward:
- Don't define a `let` variable that's only used by the definition of another.
Use a helper method instead.
- `let!` variables should be used only in case if strict evaluation with defined
- order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't
+ order is required, otherwise `let` suffices. Remember that `let` is lazy and won't
be evaluated until it is referenced.
- Avoid referencing `subject` in examples. Use a named subject `subject(:name)`, or a `let` variable instead, so
the variable has a contextual name.
@@ -475,7 +477,7 @@ so we need to set some guidelines for their use going forward:
In some cases, there is no need to recreate the same object for tests
again for each example. For example, a project and a guest of that project
-is needed to test issues on the same project, one project and user will do for the entire file.
+are needed to test issues on the same project, so one project and user are enough for the entire file.
As much as possible, do not implement this using `before(:all)` or `before(:context)`. If you do,
you would need to manually clean up the data as those hooks run outside a database transaction.
@@ -494,9 +496,9 @@ before_all do
end
```
-This will result in only one `Project`, `User`, and `ProjectMember` created for this context.
+This results in only one `Project`, `User`, and `ProjectMember` created for this context.
-`let_it_be` and `before_all` are also available within nested contexts. Cleanup after the context
+`let_it_be` and `before_all` are also available in nested contexts. Cleanup after the context
is handled automatically using a transaction rollback.
Note that if you modify an object defined inside a `let_it_be` block,
@@ -519,6 +521,35 @@ let_it_be_with_refind(:project) { create(:project) }
let_it_be(:project, refind: true) { create(:project) }
```
+### License stubbing with `let_it_be`
+
+`let_it_be_with_refind` is also useful when using `stub_licensed_features` in your tests:
+
+```ruby
+let_it_be_with_refind(:project) { create(:project) }
+# Project#licensed_feature_available? is memoized, and so we need to refind
+# the project for license updates to be applied.
+# An alternative is `project.clear_memoization(:licensed_feature_available)`.
+
+subject { project.allows_multiple_assignees? }
+
+context 'with license multiple_issue_assignees disabled' do
+ before do
+ stub_licensed_features(multiple_issue_assignees: true)
+ end
+
+ it { is_expected.to eq(true) }
+end
+
+context 'with license multiple_issue_assignees disabled' do
+ before do
+ stub_licensed_features(multiple_issue_assignees: false)
+ end
+
+ it { is_expected.to eq(false) }
+end
+```
+
### Time-sensitive tests
[`ActiveSupport::Testing::TimeHelpers`](https://api.rubyonrails.org/v6.0.3.1/classes/ActiveSupport/Testing/TimeHelpers.html)
@@ -545,14 +576,14 @@ This section was moved to [developing with feature flags](../feature_flags/devel
The code exercised by a single GitLab test may access and modify many items of
data. Without careful preparation before a test runs, and cleanup afterward,
-data can be changed by a test in such a way that it affects the behavior of
+a test can change data in a way that affects the behavior of
following tests. This should be avoided at all costs! Fortunately, the existing
test framework handles most cases already.
When the test environment does get polluted, a common outcome is
-[flaky tests](flaky_tests.md). Pollution will often manifest as an order
-dependency: running spec A followed by spec B will reliably fail, but running
-spec B followed by spec A will reliably succeed. In these cases, you can use
+[flaky tests](flaky_tests.md). Pollution often manifests as an order
+dependency: running spec A followed by spec B reliably fails, but running
+spec B followed by spec A reliably succeeds. In these cases, you can use
`rspec --bisect` (or a manual pairwise bisect of spec files) to determine which
spec is at fault. Fixing the problem requires some understanding of how the test
suite ensures the environment is pristine. Read on to discover more about each
@@ -561,15 +592,15 @@ data store!
#### SQL database
This is managed for us by the `database_cleaner` gem. Each spec is surrounded in
-a transaction, which is rolled back once the test completes. Certain specs will
-instead issue `DELETE FROM` queries against every table after completion; this
+a transaction, which is rolled back after the test completes. Certain specs
+instead issue `DELETE FROM` queries against every table after completion. This
allows the created rows to be viewed from multiple database connections, which
is important for specs that run in a browser, or migration specs, among others.
One consequence of using these strategies, instead of the well-known
`TRUNCATE TABLES` approach, is that primary keys and other sequences are **not**
reset across specs. So if you create a project in spec A, then create a project
-in spec B, the first will have `id=1`, while the second will have `id=2`.
+in spec B, the first has `id=1`, while the second has `id=2`.
This means that specs should **never** rely on the value of an ID, or any other
sequence-generated column. To avoid accidental conflicts, specs should also
@@ -610,7 +641,7 @@ DNS requests are stubbed universally in the test suite
(as of [!22368](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22368)), as DNS can
cause issues depending on the developer's local network. There are RSpec labels
available in `spec/support/dns.rb` which you can apply to tests if you need to
-bypass the DNS stubbing, e.g.:
+bypass the DNS stubbing, like this:
```ruby
it "really connects to Prometheus", :permit_dns do
@@ -625,8 +656,8 @@ In the situations where you need to
[stub](https://relishapp.com/rspec/rspec-mocks/v/3-9/docs/basics/allowing-messages)
methods such as `File.read`, make sure to:
-1. Stub `File.read` for only the filepath you are interested in.
-1. Call the original implementation for other filepaths.
+1. Stub `File.read` for only the file path you are interested in.
+1. Call the original implementation for other file paths.
Otherwise `File.read` calls from other parts of the codebase get
stubbed incorrectly. You should use the `stub_file_read`, and
@@ -645,19 +676,19 @@ allow(File).to receive(:read).and_call_original
allow(File).to receive(:read).with(my_filepath)
```
-#### Filesystem
+#### File system
-Filesystem data can be roughly split into "repositories", and "everything else".
+File system data can be roughly split into "repositories", and "everything else".
Repositories are stored in `tmp/tests/repositories`. This directory is emptied
before a test run starts, and after the test run ends. It is not emptied between
-specs, so created repositories accumulate within this directory over the
+specs, so created repositories accumulate in this directory over the
lifetime of the process. Deleting them is expensive, but this could lead to
pollution unless carefully managed.
To avoid this, [hashed storage](../../administration/repository_storage_types.md)
is enabled in the test suite. This means that repositories are given a unique
-path that depends on their project's ID. Since the project IDs are not reset
-between specs, this guarantees that each spec gets its own repository on disk,
+path that depends on their project's ID. Because the project IDs are not reset
+between specs, each spec gets its own repository on disk,
and prevents changes from being visible between specs.
If a spec manually specifies a project ID, or inspects the state of the
@@ -671,9 +702,9 @@ written to disk in locations determined by ID, so conflicts should not occur.
Some specs disable hashed storage by passing the `:legacy_storage` trait to the
`projects` factory. Specs that do this must **never** override the `path` of the
-project, or any of its groups. The default path includes the project ID, so will
-not conflict; but if two specs create a `:legacy_storage` project with the same
-path, they will use the same repository on disk and lead to test environment
+project, or any of its groups. The default path includes the project ID, so it
+does not conflict. If two specs create a `:legacy_storage` project with the same
+path, they use the same repository on disk and lead to test environment
pollution.
Other files must be managed manually by the spec. If you run code that creates a
@@ -712,21 +743,20 @@ If you need to modify the contents of the `ENV` constant, you can use the
While most Ruby **instances** are not shared between specs, **classes**
and **modules** generally are. Class and module instance variables, accessors,
class variables, and other stateful idioms, should be treated in the same way as
-global variables - don't modify them unless you have to! In particular, prefer
+global variables. Don't modify them unless you have to! In particular, prefer
using expectations, or dependency injection along with stubs, to avoid the need
-for modifications. If you have no other choice, an `around` block similar to the
-example for global variables, above, can be used, but this should be avoided if
-at all possible.
+for modifications. If you have no other choice, an `around` block like the global
+variables example can be used, but avoid this if at all possible.
#### Test Snowplow events
WARNING:
Snowplow performs **runtime type checks** by using the [contracts gem](https://rubygems.org/gems/contracts).
-Since Snowplow is **by default disabled in tests and development**, it can be hard to
+Because Snowplow is **by default disabled in tests and development**, it can be hard to
**catch exceptions** when mocking `Gitlab::Tracking`.
-To catch runtime errors due to type checks, you can enable Snowplow in tests by marking the spec with
-`:snowplow` and use the `expect_snowplow_event` helper which will check for
+To catch runtime errors due to type checks, you can enable Snowplow in tests. Mark the spec with
+`:snowplow` and use the `expect_snowplow_event` helper, which checks for
calls to `Gitlab::Tracking#event`.
```ruby
@@ -794,7 +824,7 @@ end
WARNING:
Only use simple values as input in the `where` block. Using procs, stateful
-objects, FactoryBot-created objects etc. can lead to
+objects, FactoryBot-created objects, and similar items can lead to
[unexpected results](https://github.com/tomykaira/rspec-parameterized/issues/8).
### Prometheus tests
@@ -807,7 +837,7 @@ reset before each example, add the `:prometheus` tag to the RSpec test.
Custom matchers should be created to clarify the intent and/or hide the
complexity of RSpec expectations. They should be placed under
`spec/support/matchers/`. Matchers can be placed in subfolder if they apply to
-a certain type of specs only (e.g. features, requests etc.) but shouldn't be if
+a certain type of specs only (such as features or requests) but shouldn't be if
they apply to multiple type of specs.
#### `be_like_time`
@@ -881,13 +911,13 @@ expect(json_string).to be_valid_json.and match_schema(schema)
Testing query performance allows us to:
-- Assert that N+1 problems do not exist within a block of code.
-- Ensure that the number of queries within a block of code does not increase unnoticed.
+- Assert that N+1 problems do not exist in a block of code.
+- Ensure that the number of queries in a block of code does not increase unnoticed.
#### QueryRecorder
`QueryRecorder` allows profiling and testing of the number of database queries
-performed within a given block of code.
+performed in a given block of code.
See the [`QueryRecorder`](../query_recorder.md) section for more details.
@@ -905,9 +935,9 @@ Any shared contexts used by more than one spec file:
- Should be placed under `spec/support/shared_contexts/`.
- Can be placed in subfolder if they apply to a certain type of specs only
- (e.g. features, requests etc.) but shouldn't be if they apply to multiple type of specs.
+ (such as features or requests) but shouldn't be if they apply to multiple type of specs.
-Each file should include only one context and have a descriptive name, e.g.
+Each file should include only one context and have a descriptive name, such as
`spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb`.
### Shared examples
@@ -917,9 +947,9 @@ Any shared examples used by more than one spec file:
- Should be placed under `spec/support/shared_examples/`.
- Can be placed in subfolder if they apply to a certain type of specs only
- (e.g. features, requests etc.) but shouldn't be if they apply to multiple type of specs.
+ (such as features or requests) but shouldn't be if they apply to multiple type of specs.
-Each file should include only one context and have a descriptive name, e.g.
+Each file should include only one context and have a descriptive name, such as
`spec/support/shared_examples/controllers/githubish_import_controller_shared_example.rb`.
### Helpers
@@ -927,8 +957,8 @@ Each file should include only one context and have a descriptive name, e.g.
Helpers are usually modules that provide some methods to hide the complexity of
specific RSpec examples. You can define helpers in RSpec files if they're not
intended to be shared with other specs. Otherwise, they should be placed
-under `spec/support/helpers/`. Helpers can be placed in subfolder if they apply
-to a certain type of specs only (e.g. features, requests etc.) but shouldn't be
+under `spec/support/helpers/`. Helpers can be placed in a subfolder if they apply
+to a certain type of specs only (such as features or requests) but shouldn't be
if they apply to multiple type of specs.
Helpers should follow the Rails naming / namespacing convention. For instance
@@ -985,7 +1015,7 @@ All fixtures should be placed under `spec/fixtures/`.
### Repositories
-Testing some functionality, e.g., merging a merge request, requires a Git
+Testing some functionality, such as merging a merge request, requires a Git
repository with a certain state to be present in the test environment. GitLab
maintains the [`gitlab-test`](https://gitlab.com/gitlab-org/gitlab-test)
repository for certain common cases - you can ensure a copy of the repository is
@@ -996,7 +1026,7 @@ let(:project) { create(:project, :repository) }
```
Where you can, consider using the `:custom_repo` trait instead of `:repository`.
-This allows you to specify exactly what files will appear in the `master` branch
+This allows you to specify exactly what files appear in the `master` branch
of the project's repository. For example:
```ruby
@@ -1011,17 +1041,17 @@ let(:project) do
end
```
-This will create a repository containing two files, with default permissions and
+This creates a repository containing two files, with default permissions and
the specified content.
### Configuration
-RSpec configuration files are files that change the RSpec configuration (i.e.
+RSpec configuration files are files that change the RSpec configuration (like
`RSpec.configure do |config|` blocks). They should be placed under
`spec/support/`.
-Each file should be related to a specific domain, e.g.
-`spec/support/capybara.rb`, `spec/support/carrierwave.rb`, etc.
+Each file should be related to a specific domain, such as
+`spec/support/capybara.rb` or `spec/support/carrierwave.rb`.
If a helpers module applies only to a certain kind of specs, it should add
modifiers to the `config.include` call. For instance if
@@ -1047,7 +1077,7 @@ file which is used by the `spec/fast_spec_helper.rb` file. See
Services for the test environment are automatically configured and started when
tests are run, including Gitaly, Workhorse, Elasticsearch, and Capybara. When run in CI, or
-if the service needs to be installed, the test environment will log information
+if the service needs to be installed, the test environment logs information
about set-up time, producing log messages like the following:
```plaintext
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 8a01975f771..50fa897c7b4 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Enablement
+group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
@@ -100,7 +100,7 @@ the host, based on your installed version of GitLab:
- GitLab 12.1 and earlier:
```shell
- gitlab-rake gitlab:backup:create
+ docker exec -t <container name> gitlab-rake gitlab:backup:create
```
If you're using the [GitLab Helm chart](https://gitlab.com/gitlab-org/charts/gitlab)
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index 0c184aa0075..7afea743d74 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Enablement
+group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md
index bf67522c256..d99b7d5fd08 100644
--- a/doc/raketasks/features.md
+++ b/doc/raketasks/features.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Enablement
+group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 648cd784c1b..d4463eb3dae 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Enablement
+group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/raketasks/list_repos.md b/doc/raketasks/list_repos.md
index e2442df3418..440bb3d56a8 100644
--- a/doc/raketasks/list_repos.md
+++ b/doc/raketasks/list_repos.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Enablement
+group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/raketasks/spdx.md b/doc/raketasks/spdx.md
index fe7ac13c463..fb5f05affd0 100644
--- a/doc/raketasks/spdx.md
+++ b/doc/raketasks/spdx.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Secure
+group: Composition Analysis
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 6df978b2efd..92d01b5cef4 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Enablement
+group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index 1f40c60e23d..939ce936d7c 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Enablement
+group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 78be67a5196..51b2d09cc7c 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -157,16 +157,6 @@ as other environment [variables](../../ci/variables/README.md#priority-of-enviro
If the CI/CD variable is not set and the cluster setting is left blank, the instance-wide **Auto DevOps domain**
setting is used if set.
-NOTE:
-If you use the [GitLab managed app for Ingress](../../user/clusters/applications.md#ingress),
-the URL endpoint should be automatically configured for you. All you must do
-is use its value for the `KUBE_INGRESS_BASE_DOMAIN` variable.
-
-NOTE:
-`AUTO_DEVOPS_DOMAIN` was [deprecated in GitLab 11.8](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/52363)
-and replaced with `KUBE_INGRESS_BASE_DOMAIN`, and removed in
-[GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/56959).
-
Auto DevOps requires a wildcard DNS A record matching the base domain(s). For
a base domain of `example.com`, you'd need a DNS entry like:
diff --git a/doc/topics/autodevops/requirements.md b/doc/topics/autodevops/requirements.md
index 824874ed4d4..19e724ab850 100644
--- a/doc/topics/autodevops/requirements.md
+++ b/doc/topics/autodevops/requirements.md
@@ -46,12 +46,9 @@ To make full use of Auto DevOps with Kubernetes, you need:
- **Base domain** (for [Auto Review Apps](stages.md#auto-review-apps),
[Auto Deploy](stages.md#auto-deploy), and [Auto Monitoring](stages.md#auto-monitoring))
- You need a domain configured with wildcard DNS, which all of your Auto DevOps
- applications use. If you're using the
- [GitLab-managed app for Ingress](../../user/clusters/applications.md#ingress),
- the URL endpoint is automatically configured for you.
-
- You must also [specify the Auto DevOps base domain](index.md#auto-devops-base-domain).
+ You must [specify the Auto DevOps base domain](index.md#auto-devops-base-domain),
+ which all of your Auto DevOps applications use. This domain must be configured
+ with wildcard DNS.
- **GitLab Runner** (for all stages)
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index b03dfb79ae0..07bbeb415b2 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -1199,53 +1199,8 @@ determine the endpoint of your Ingress or Knative application, you can
#### Determining the external endpoint manually
-If the cluster is on GKE, click the **Google Kubernetes Engine** link in the
-**Advanced settings**, or go directly to the
-[Google Kubernetes Engine dashboard](https://console.cloud.google.com/kubernetes/)
-and select the proper project and cluster. Then click **Connect** and execute
-the `gcloud` command in a local terminal or using the **Cloud Shell**.
-
-If the cluster is not on GKE, follow the specific instructions for your
-Kubernetes provider to configure `kubectl` with the right credentials.
-The output of the following examples show the external endpoint of your
-cluster. This information can then be used to set up DNS entries and forwarding
-rules that allow external access to your deployed applications.
-
-- If you installed Ingress using the **Applications**, run the following
- command:
-
- ```shell
- kubectl get service --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
- ```
-
-- Some Kubernetes clusters return a hostname instead, like
- [Amazon EKS](https://aws.amazon.com/eks/). For these platforms, run:
-
- ```shell
- kubectl get service --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
- ```
-
- If EKS is used, an [Elastic Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/)
- is also created, which incurs additional AWS costs.
-
-- For Istio/Knative, the command is different:
-
- ```shell
- kubectl get svc --namespace=istio-system istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
- ```
-
-- Otherwise, you can list the IP addresses of all load balancers:
-
- ```shell
- kubectl get svc --all-namespaces -o jsonpath='{range.items[?(@.status.loadBalancer.ingress)]}{.status.loadBalancer.ingress[*].ip} '
- ```
-
-You may see a trailing `%` on some Kubernetes versions. Do not include it.
-
-The Ingress is now available at this address, and routes incoming requests to
-the proper service based on the DNS name in the request. To support this, create
-a wildcard DNS CNAME record for the desired domain name. For example,
-`*.myekscluster.com` would point to the Ingress hostname obtained earlier.
+See the [Base domain section](../project/clusters/index.md#base-domain) for a
+guide on how to determine the external endpoint manually.
#### Using a static IP
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 0ce92eac1a3..d72ab197388 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -418,6 +418,11 @@ This can be prevented by configuring the [NameID](#nameid) to return a consisten
Ensure that the user who is trying to link their GitLab account has been added as a user within the identity provider's SAML app.
+Alternatively, the SAML response may be missing the `InResponseTo` attribute in the
+`samlp:Response` tag, which is [expected by the SAML gem](https://github.com/onelogin/ruby-saml/blob/9f710c5028b069bfab4b9e2b66891e0549765af5/lib/onelogin/ruby-saml/response.rb#L307-L316).
+The [Identity Provider](#glossary) administrator should ensure that the login should be
+initiated by the Service Provider (typically GitLab) and not the Identity Provider.
+
### Stuck in a login "loop"
Ensure that the **GitLab single sign-on URL** has been configured as "Login URL" (or similarly named field) in the identity provider's SAML app.
@@ -446,3 +451,25 @@ However, self-managed GitLab instances use a configuration file that supports mo
Internally that uses the [`ruby-saml` library](https://github.com/onelogin/ruby-saml), so we sometimes check there to verify low level details of less commonly used options.
It can also help to compare the XML response from your provider with our [example XML used for internal testing](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/spec/fixtures/saml/response.xml).
+
+### Searching Rails log
+
+With access to the rails log or `production_json.log` (available only to GitLab team members for GitLab.com),
+you should be able to find the base64 encoded SAML response by searching with the following filters:
+
+- `json.meta.caller_id`: `Groups::OmniauthCallbacksController#group_saml`
+- `json.meta.user` or `json.username`: `username`
+- `json.method`: `POST`
+- `json.path`: `/groups/GROUP-PATH/-/saml/callback`
+
+In a relevant log entry, the `json.params` should provide a valid response with:
+
+- `"key": "SAMLResponse"` and the `"value": (full SAML response)`,
+- `"key": "RelayState"` with `"value": "/group-path"`, and
+- `"key": "group_id"` with `"value": "group-path"`.
+
+In some cases, if the SAML response is lengthy, you may receive a `"key": "truncated"` with `"value":"..."`.
+In these cases, please ask a group owner for a copy of the SAML response from when they select
+the "Verify SAML Configuration" button on the group SSO Settings page.
+
+Use a base64 decoder to see a human-readable version of the SAML response.
diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md
index 35172663cc1..1c90add1390 100644
--- a/doc/user/packages/nuget_repository/index.md
+++ b/doc/user/packages/nuget_repository/index.md
@@ -62,6 +62,8 @@ NuGet CLI.
## Use the GitLab endpoint for NuGet Packages
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36423) group-level endpoint in GitLab 13.8.
+
To use the GitLab endpoint for NuGet Packages, choose an option:
- **Project-level**: Use when you have few NuGet packages and they are not in
diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md
index 38ef01b7537..ae672d8414f 100644
--- a/doc/user/profile/notifications.md
+++ b/doc/user/profile/notifications.md
@@ -149,6 +149,7 @@ Users are notified of the following events:
| Password changed by administrator | User | Security email, always sent when an administrator changes the password of another user |
| Two-factor authentication disabled | User | Security email, always sent. |
| New user created | User | Sent on user creation, except for OmniAuth (LDAP)|
+| New SAML/SCIM user provisioned. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276018) in GitLab 13.8 | User | Sent when a user is provisioned through SAML/SCIM |
| User added to project | User | Sent when user is added to project |
| Project access level changed | User | Sent when user project access level is changed |
| User added to group | User | Sent when user is added to group |
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index a06846e33a6..7dc85f557ef 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -33,8 +33,10 @@ integrated at the [group level](../../group/clusters/index.md) or
To view your project level Kubernetes clusters, navigate to **Operations > Kubernetes**
from your project. On this page, you can [add a new cluster](#adding-and-removing-clusters)
-and view information about your existing clusters, such as nodes count and rough estimates
-of memory and CPU usage.
+and view information about your existing clusters, such as:
+
+- Nodes count.
+- Rough estimates of memory and CPU usage.
## Setting up
@@ -76,9 +78,8 @@ to:
You can associate more than one Kubernetes cluster to your
project. That way you can have different clusters for different environments,
-like dev, staging, production, and so on.
-
-Simply add another cluster, like you did the first time, and make sure to
+like development, staging, production, and so on.
+Add another cluster, like you did the first time, and make sure to
[set an environment scope](#setting-the-environment-scope) that
differentiates the new cluster from the rest.
@@ -165,7 +166,7 @@ details about the created resources.
If you choose to manage your own cluster, project-specific resources aren't created
automatically. If you are using [Auto DevOps](../../../topics/autodevops/index.md), you must
explicitly provide the `KUBE_NAMESPACE` [deployment variable](#deployment-variables)
-for your deployment jobs to use; otherwise a namespace is created for you.
+for your deployment jobs to use. Otherwise, a namespace is created for you.
#### Important notes
@@ -182,10 +183,10 @@ Note the following with GitLab and clusters:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31759) in GitLab 12.6.
-If you choose to allow GitLab to manage your cluster for you, GitLab stores a cached
+If you allow GitLab to manage your cluster, GitLab stores a cached
version of the namespaces and service accounts it creates for your projects. If you
modify these resources in your cluster manually, this cache can fall out of sync with
-your cluster, which can cause deployment jobs to fail.
+your cluster. This can cause deployment jobs to fail.
To clear the cache:
@@ -204,12 +205,61 @@ Specifying a base domain automatically sets `KUBE_INGRESS_BASE_DOMAIN` as an env
If you are using [Auto DevOps](../../../topics/autodevops/index.md), this domain is used for the different
stages. For example, Auto Review Apps and Auto Deploy.
-The domain should have a wildcard DNS configured to the Ingress IP address. After Ingress has been installed (see [Installing Applications](#installing-applications)),
+The domain should have a wildcard DNS configured to the Ingress IP address.
+After Ingress has been installed (see [Installing Applications](#installing-applications)),
you can either:
- Create an `A` record that points to the Ingress IP address with your domain provider.
- Enter a wildcard DNS address using a service such as nip.io or xip.io. For example, `192.168.1.1.xip.io`.
+To determine the external Ingress IP address, or external Ingress hostname:
+
+- *If the cluster is on GKE*:
+ 1. Click the **Google Kubernetes Engine** link in the **Advanced settings**,
+ or go directly to the [Google Kubernetes Engine dashboard](https://console.cloud.google.com/kubernetes/).
+ 1. Select the proper project and cluster.
+ 1. Click **Connect**
+ 1. Execute the `gcloud` command in a local terminal or using the **Cloud Shell**.
+
+- *If the cluster is not on GKE*: Follow the specific instructions for your
+ Kubernetes provider to configure `kubectl` with the right credentials.
+ The output of the following examples show the external endpoint of your
+ cluster. This information can then be used to set up DNS entries and forwarding
+ rules that allow external access to your deployed applications.
+
+Depending an your Ingress, the external IP address can be retrieved in various ways.
+This list provides a generic solution, and some GitLab-specific approaches:
+
+- In general, you can list the IP addresses of all load balancers by running:
+
+ ```shell
+ kubectl get svc --all-namespaces -o jsonpath='{range.items[?(@.status.loadBalancer.ingress)]}{.status.loadBalancer.ingress[*].ip} '
+ ```
+
+- If you installed Ingress using the **Applications**, run:
+
+ ```shell
+ kubectl get service --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
+ ```
+
+- Some Kubernetes clusters return a hostname instead, like
+ [Amazon EKS](https://aws.amazon.com/eks/). For these platforms, run:
+
+ ```shell
+ kubectl get service --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
+ ```
+
+ If you use EKS, an [Elastic Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/)
+ is also created, which incurs additional AWS costs.
+
+- Istio/Knative uses a different command. Run:
+
+ ```shell
+ kubectl get svc --namespace=istio-system istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
+ ```
+
+If you see a trailing `%` on some Kubernetes versions, do not include it.
+
## Installing applications
GitLab can install and manage some applications like Helm, GitLab Runner, Ingress,
@@ -224,10 +274,10 @@ Auto DevOps automatically detects, builds, tests, deploys, and monitors your
applications.
To make full use of Auto DevOps (Auto Deploy, Auto Review Apps, and
-Auto Monitoring) the Kubernetes project integration must be enabled, but
+Auto Monitoring) the Kubernetes project integration must be enabled. However,
Kubernetes clusters can be used without Auto DevOps.
-[Read more about Auto DevOps](../../../topics/autodevops/index.md)
+[Read more about Auto DevOps](../../../topics/autodevops/index.md).
## Deploying to a Kubernetes cluster
@@ -260,9 +310,9 @@ following command in your deployment job script, for Kubernetes to access the re
kubectl create secret docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_DEPLOY_USER" --docker-password="$CI_DEPLOY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL" -o yaml --dry-run | kubectl apply -f -
```
-The Kubernetes cluster integration exposes the following
+The Kubernetes cluster integration exposes these
[deployment variables](../../../ci/variables/README.md#deployment-environment-variables) in the
-GitLab CI/CD build environment to deployment jobs, which are jobs that have
+GitLab CI/CD build environment to deployment jobs. Deployment jobs have
[defined a target environment](../../../ci/environments/index.md#defining-environments).
| Variable | Description |
@@ -303,7 +353,7 @@ When you customize the namespace, existing environments remain linked to their c
namespaces until you [clear the cluster cache](#clearing-the-cluster-cache).
WARNING:
-By default, anyone who can create a deployment job can access any CI variable within
+By default, anyone who can create a deployment job can access any CI variable in
an environment's deployment job. This includes `KUBECONFIG`, which gives access to
any secret available to the associated service account in your cluster.
To keep your production credentials safe, consider using
@@ -327,8 +377,8 @@ the need to leave GitLab.
#### Deploy Boards
GitLab Deploy Boards offer a consolidated view of the current health and
-status of each CI [environment](../../../ci/environments/index.md) running on Kubernetes,
-displaying the status of the pods in the deployment. Developers and other
+status of each CI [environment](../../../ci/environments/index.md) running on Kubernetes.
+They display the status of the pods in the deployment. Developers and other
teammates can view the progress and status of a rollout, pod by pod, in the
workflow they already use without any need to access Kubernetes.
@@ -336,7 +386,7 @@ workflow they already use without any need to access Kubernetes.
#### Viewing pod logs
-GitLab makes it easy to view the logs of running pods in connected Kubernetes
+GitLab enables you to view the logs of running pods in connected Kubernetes
clusters. By displaying the logs directly in GitLab, developers can avoid having
to manage console tools or jump to a different interface.
@@ -349,7 +399,7 @@ to manage console tools or jump to a different interface.
When enabled, the Kubernetes integration adds [web terminal](../../../ci/environments/index.md#web-terminals)
support to your [environments](../../../ci/environments/index.md). This is based
on the `exec` functionality found in Docker and Kubernetes, so you get a new
-shell session within your existing containers. To use this integration, you
+shell session in your existing containers. To use this integration, you
should deploy to Kubernetes using the deployment variables above, ensuring any
deployments, replica sets, and pods are annotated with:
diff --git a/lib/atlassian/jira_connect/client.rb b/lib/atlassian/jira_connect/client.rb
index 89dc9b52b56..8b31c82510c 100644
--- a/lib/atlassian/jira_connect/client.rb
+++ b/lib/atlassian/jira_connect/client.rb
@@ -67,8 +67,6 @@ module Atlassian
end
def store_build_info(project:, pipelines:, update_sequence_id: nil)
- return unless Feature.enabled?(:jira_sync_builds, project)
-
builds = pipelines.map do |pipeline|
build = ::Atlassian::JiraConnect::Serializers::BuildEntity.represent(
pipeline,
diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb
index 196203211ed..6e5f0dc9a03 100644
--- a/lib/gitlab/experimentation.rb
+++ b/lib/gitlab/experimentation.rb
@@ -6,6 +6,7 @@
# Experiment options:
# - tracking_category (optional, used to set the category when tracking an experiment event)
# - use_backwards_compatible_subject_index (optional, set this to true if you need backwards compatibility -- you likely do not need this, see note in the next paragraph.)
+# - rollout_strategy: default is `:cookie` based rollout. We may also set it to `:user` based rollout
#
# Using the backwards-compatible subject index (use_backwards_compatible_subject_index option):
# This option was added when [the calculation of experimentation_subject_index was changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45733/diffs#41af4a6fa5a10c7068559ce21c5188483751d934_157_173). It is not intended to be used by new experiments, it exists merely for the segmentation integrity of in-flight experiments at the time the change was deployed. That is, we want users who were assigned to the "experimental" group or the "control" group before the change to still be in those same groups after the change. See [the original issue](https://gitlab.com/gitlab-org/gitlab/-/issues/270858) and [this related comment](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48110#note_458223745) for more information.
@@ -92,16 +93,19 @@ module Gitlab
tracking_category: 'Growth::Conversion::Experiment::TrialDuringSignup'
},
ci_syntax_templates: {
- tracking_category: 'Growth::Activation::Experiment::CiSyntaxTemplates'
+ tracking_category: 'Growth::Activation::Experiment::CiSyntaxTemplates',
+ rollout_strategy: :user
},
pipelines_empty_state: {
- tracking_category: 'Growth::Activation::Experiment::PipelinesEmptyState'
+ tracking_category: 'Growth::Activation::Experiment::PipelinesEmptyState',
+ rollout_strategy: :user
},
invite_members_new_dropdown: {
tracking_category: 'Growth::Expansion::Experiment::InviteMembersNewDropdown'
},
show_trial_status_in_sidebar: {
- tracking_category: 'Growth::Conversion::Experiment::ShowTrialStatusInSidebar'
+ tracking_category: 'Growth::Conversion::Experiment::ShowTrialStatusInSidebar',
+ rollout_strategy: :group
},
trial_onboarding_issues: {
tracking_category: 'Growth::Conversion::Experiment::TrialOnboardingIssues'
@@ -126,12 +130,44 @@ module Gitlab
return false if subject.blank?
return false unless active?(experiment_key)
+ log_invalid_rollout(experiment_key, subject)
+
experiment = get_experiment(experiment_key)
return false unless experiment
experiment.enabled_for_index?(index_for_subject(experiment, subject))
end
+ def rollout_strategy(experiment_key)
+ experiment = get_experiment(experiment_key)
+ return unless experiment
+
+ experiment.rollout_strategy
+ end
+
+ def log_invalid_rollout(experiment_key, subject)
+ return if valid_subject_for_rollout_strategy?(experiment_key, subject)
+
+ logger = Gitlab::ExperimentationLogger.build
+ logger.warn message: 'Subject must conform to the rollout strategy',
+ experiment_key: experiment_key,
+ subject: subject.class.to_s,
+ rollout_strategy: rollout_strategy(experiment_key)
+ end
+
+ def valid_subject_for_rollout_strategy?(experiment_key, subject)
+ case rollout_strategy(experiment_key)
+ when :user
+ subject.is_a?(User)
+ when :group
+ subject.is_a?(Group)
+ when :cookie
+ subject.nil? || subject.is_a?(String)
+ else
+ false
+ end
+ end
+
private
def index_for_subject(experiment, subject)
diff --git a/lib/gitlab/experimentation/controller_concern.rb b/lib/gitlab/experimentation/controller_concern.rb
index e43f3c8c007..4b1d3663095 100644
--- a/lib/gitlab/experimentation/controller_concern.rb
+++ b/lib/gitlab/experimentation/controller_concern.rb
@@ -40,6 +40,8 @@ module Gitlab
return true if forced_enabled?(experiment_key)
return false if dnt_enabled?
+ Experimentation.log_invalid_rollout(experiment_key, subject)
+
subject ||= fallback_experimentation_subject_index(experiment_key)
Experimentation.in_experiment_group?(experiment_key, subject: subject)
@@ -65,7 +67,9 @@ module Gitlab
return if dnt_enabled?
return unless Experimentation.active?(experiment_key) && current_user
- ::Experiment.add_user(experiment_key, tracking_group(experiment_key, nil, subject: current_user), current_user, context)
+ subject = Experimentation.rollout_strategy(experiment_key) == :cookie ? nil : current_user
+
+ ::Experiment.add_user(experiment_key, tracking_group(experiment_key, nil, subject: subject), current_user, context)
end
def record_experiment_conversion_event(experiment_key)
diff --git a/lib/gitlab/experimentation/experiment.rb b/lib/gitlab/experimentation/experiment.rb
index 36cd673a38f..17dda45f5b7 100644
--- a/lib/gitlab/experimentation/experiment.rb
+++ b/lib/gitlab/experimentation/experiment.rb
@@ -5,12 +5,13 @@ module Gitlab
class Experiment
FEATURE_FLAG_SUFFIX = "_experiment_percentage"
- attr_reader :key, :tracking_category, :use_backwards_compatible_subject_index
+ attr_reader :key, :tracking_category, :use_backwards_compatible_subject_index, :rollout_strategy
def initialize(key, **params)
@key = key
@tracking_category = params[:tracking_category]
@use_backwards_compatible_subject_index = params[:use_backwards_compatible_subject_index]
+ @rollout_strategy = params[:rollout_strategy] || :cookie
end
def active?
diff --git a/lib/gitlab/experimentation_logger.rb b/lib/gitlab/experimentation_logger.rb
new file mode 100644
index 00000000000..ba1b60d6b4c
--- /dev/null
+++ b/lib/gitlab/experimentation_logger.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class ExperimentationLogger < ::Gitlab::JsonLogger
+ def self.file_name_noext
+ 'experimentation_json'
+ end
+ end
+end
diff --git a/rubocop/cop/gitlab/namespaced_class.rb b/rubocop/cop/gitlab/namespaced_class.rb
new file mode 100644
index 00000000000..1f1fd280922
--- /dev/null
+++ b/rubocop/cop/gitlab/namespaced_class.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module Gitlab
+ # Cop that enforces use of namespaced classes in order to better identify
+ # high level domains within the codebase.
+
+ # @example
+ # # bad
+ # class MyClass
+ # end
+ #
+ # # good
+ # module MyDomain
+ # class MyClass
+ # end
+ # end
+
+ class NamespacedClass < RuboCop::Cop::Cop
+ MSG = 'Classes must be declared inside a module indicating a product domain namespace. For more info: https://gitlab.com/gitlab-org/gitlab/-/issues/212156'
+
+ def_node_matcher :compact_namespaced_class?, <<~PATTERN
+ (class (const (const ...) ...) ...)
+ PATTERN
+
+ def on_module(node)
+ @namespaced = true
+ end
+
+ def on_class(node)
+ return if @namespaced
+
+ add_offense(node) unless compact_namespaced_class?(node)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index ff4e592234b..46f2b2c4910 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -94,6 +94,7 @@ RSpec.describe "Admin::Projects" do
describe 'add admin himself to a project' do
before do
project.add_maintainer(user)
+ stub_feature_flags(invite_members_group_modal: false)
end
it 'adds admin a to a project as developer', :js do
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index a4c450c9a2c..7025874a4ff 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -87,12 +87,4 @@ RSpec.describe 'Group navbar' do
it_behaves_like 'verified navigation bar'
end
-
- context 'when invite team members is not available' do
- it 'does not display the js-invite-members-trigger' do
- visit group_path(group)
-
- expect(page).not_to have_selector('.js-invite-members-trigger')
- end
- end
end
diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index bb56ae348fb..07620929418 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -8,6 +8,10 @@ RSpec.describe 'Project > Members > Invite group', :js do
let(:maintainer) { create(:user) }
+ before do
+ stub_feature_flags(invite_members_group_modal: false)
+ end
+
describe 'Share with group lock' do
shared_examples 'the project can be shared with groups' do
it 'the "Invite group" tab exists' do
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index d69c3f2652c..e995af0d670 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
end
it 'expiration date is displayed in the members list' do
+ stub_feature_flags(invite_members_group_modal: false)
+
visit project_project_members_path(project)
page.within '.invite-users-form' do
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 25791b393bc..4ff3827b240 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -67,23 +67,4 @@ RSpec.describe 'Project navbar' do
it_behaves_like 'verified navigation bar'
end
-
- context 'when invite team members is not available' do
- it 'does not display the js-invite-members-trigger' do
- visit project_path(project)
-
- expect(page).not_to have_selector('.js-invite-members-trigger')
- end
- end
-
- context 'when invite team members is available' do
- it 'includes the div for js-invite-members-trigger' do
- stub_feature_flags(invite_members_group_modal: true)
- allow_any_instance_of(InviteMembersHelper).to receive(:invite_members_allowed?).and_return(true)
-
- visit project_path(project)
-
- expect(page).to have_selector('.js-invite-members-trigger')
- end
- end
end
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index 726b8fb6840..58855af4a4a 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -37,6 +37,8 @@ RSpec.describe 'Projects > Settings > User manages project members' do
end
it 'imports a team from another project' do
+ stub_feature_flags(invite_members_group_modal: false)
+
project2.add_maintainer(user)
project2.add_reporter(user_mike)
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index 1ea7fe1fbfe..747d46ca20b 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -17,13 +17,6 @@ jest.mock('~/smart_interval');
jest.mock('~/lib/utils/favicon');
-const returnPromise = (data) =>
- new Promise((resolve) => {
- resolve({
- data,
- });
- });
-
describe('MrWidgetOptions', () => {
let wrapper;
let mock;
@@ -281,7 +274,7 @@ describe('MrWidgetOptions', () => {
let isCbExecuted;
beforeEach(() => {
- jest.spyOn(wrapper.vm.service, 'checkStatus').mockReturnValue(returnPromise(mockData));
+ jest.spyOn(wrapper.vm.service, 'checkStatus').mockResolvedValue({ data: mockData });
jest.spyOn(wrapper.vm.mr, 'setData').mockImplementation(() => {});
jest.spyOn(wrapper.vm, 'handleNotification').mockImplementation(() => {});
@@ -331,7 +324,7 @@ describe('MrWidgetOptions', () => {
it('should fetch deployments', () => {
jest
.spyOn(wrapper.vm.service, 'fetchDeployments')
- .mockReturnValue(returnPromise([{ id: 1, status: SUCCESS }]));
+ .mockResolvedValue({ data: [{ id: 1, status: SUCCESS }] });
wrapper.vm.fetchPreMergeDeployments();
@@ -347,7 +340,7 @@ describe('MrWidgetOptions', () => {
it('should fetch content of Cherry Pick and Revert modals', () => {
jest
.spyOn(wrapper.vm.service, 'fetchMergeActionsContent')
- .mockReturnValue(returnPromise('hello world'));
+ .mockResolvedValue({ data: 'hello world' });
wrapper.vm.fetchActionsContent();
diff --git a/spec/helpers/invite_members_helper_spec.rb b/spec/helpers/invite_members_helper_spec.rb
index 914d0931476..ee7a30cd4cb 100644
--- a/spec/helpers/invite_members_helper_spec.rb
+++ b/spec/helpers/invite_members_helper_spec.rb
@@ -12,6 +12,40 @@ RSpec.describe InviteMembersHelper do
assign(:project, project)
end
+ describe "#can_invite_members_for_project?" do
+ context 'when the user can_import_members' do
+ before do
+ allow(helper).to receive(:can_import_members?).and_return(true)
+ end
+
+ it 'returns true' do
+ expect(helper.can_invite_members_for_project?(project)).to eq true
+ expect(helper).to have_received(:can_import_members?)
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(invite_members_group_modal: false)
+ end
+
+ it 'returns false' do
+ expect(helper.can_invite_members_for_project?(project)).to eq false
+ expect(helper).not_to have_received(:can_import_members?)
+ end
+ end
+ end
+
+ context 'when the user can not invite members' do
+ before do
+ expect(helper).to receive(:can_import_members?).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(helper.can_invite_members_for_project?(project)).to eq false
+ end
+ end
+ end
+
describe "#directly_invite_members?" do
context 'when the user is an owner' do
before do
@@ -80,6 +114,51 @@ RSpec.describe InviteMembersHelper do
context 'with group' do
let_it_be(:group) { create(:group) }
+ describe "#can_invite_members_for_group?" do
+ include Devise::Test::ControllerHelpers
+
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ allow(helper).to receive(:current_user) { user }
+ end
+
+ context 'when the user can_import_members' do
+ before do
+ allow(helper).to receive(:can?).with(user, :admin_group_member, group).and_return(true)
+ end
+
+ it 'returns true' do
+ expect(helper.can_invite_members_for_group?(group)).to eq true
+ expect(helper).to have_received(:can?).with(user, :admin_group_member, group)
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(invite_members_group_modal: false)
+ end
+
+ it 'returns false' do
+ stub_feature_flags(invite_members_group_modal: false)
+
+ expect(helper.can_invite_members_for_group?(group)).to eq false
+ expect(helper).not_to have_received(:can?)
+ end
+ end
+ end
+
+ context 'when the user can not invite members' do
+ before do
+ expect(helper).to receive(:can?).with(user, :admin_group_member, group).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(helper.can_invite_members_for_group?(group)).to eq false
+ end
+ end
+ end
+
describe "#invite_group_members?" do
context 'when the user is an owner' do
before do
@@ -123,7 +202,7 @@ RSpec.describe InviteMembersHelper do
before do
allow(helper).to receive(:experiment_tracking_category_and_group) { '_track_property_' }
- allow(helper).to receive(:tracking_label).with(owner)
+ allow(helper).to receive(:tracking_label)
allow(helper).to receive(:current_user) { owner }
end
@@ -132,8 +211,7 @@ RSpec.describe InviteMembersHelper do
helper.dropdown_invite_members_link(form_model)
- expect(helper).to have_received(:experiment_tracking_category_and_group)
- .with(:invite_members_new_dropdown, subject: owner)
+ expect(helper).to have_received(:experiment_tracking_category_and_group).with(:invite_members_new_dropdown)
end
context 'with experiment enabled' do
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index b920e2e5600..9af2811bba8 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -854,16 +854,36 @@ RSpec.describe ProjectsHelper do
end
describe '#can_import_members?' do
- let(:owner) { project.owner }
+ context 'when user is project owner' do
+ before do
+ allow(helper).to receive(:current_user) { project.owner }
+ end
- it 'returns false if user cannot admin_project_member' do
- allow(helper).to receive(:current_user) { user }
- expect(helper.can_import_members?).to eq false
+ it 'returns true for owner of project' do
+ expect(helper.can_import_members?).to eq true
+ end
end
- it 'returns true if user can admin_project_member' do
- allow(helper).to receive(:current_user) { owner }
- expect(helper.can_import_members?).to eq true
+ context 'when user is not a project owner' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_project_role, :can_import) do
+ :maintainer | true
+ :developer | false
+ :reporter | false
+ :guest | false
+ end
+
+ with_them do
+ before do
+ project.add_role(user, user_project_role)
+ allow(helper).to receive(:current_user) { user }
+ end
+
+ it 'resolves if the user can import members' do
+ expect(helper.can_import_members?).to eq can_import
+ end
+ end
end
end
diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb
index 257174aad4f..63f6573818b 100644
--- a/spec/lib/atlassian/jira_connect/client_spec.rb
+++ b/spec/lib/atlassian/jira_connect/client_spec.rb
@@ -368,24 +368,6 @@ RSpec.describe Atlassian::JiraConnect::Client do
subject.send(:store_build_info, project: project, pipelines: pipelines.take(1))
end
- it 'does not call the API if the feature flag is not enabled' do
- stub_feature_flags(jira_sync_builds: false)
-
- expect(subject).not_to receive(:post)
-
- subject.send(:store_build_info, project: project, pipelines: pipelines)
- end
-
- it 'does call the API if the feature flag enabled for the project' do
- stub_feature_flags(jira_sync_builds: project)
-
- expect(subject).to receive(:post)
- .with('/rest/builds/0.1/bulk', { builds: Array })
- .and_call_original
-
- subject.send(:store_build_info, project: project, pipelines: pipelines)
- end
-
context 'there are errors' do
let(:failures) do
[{ errors: [{ message: 'X' }, { message: 'Y' }] }, { errors: [{ message: 'Z' }] }]
diff --git a/spec/lib/gitlab/experimentation/controller_concern_spec.rb b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
index c47f71c207d..1cebe37bea5 100644
--- a/spec/lib/gitlab/experimentation/controller_concern_spec.rb
+++ b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
use_backwards_compatible_subject_index: true
},
test_experiment: {
+ tracking_category: 'Team',
+ rollout_strategy: rollout_strategy
+ },
+ my_experiment: {
tracking_category: 'Team'
}
}
@@ -20,6 +24,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end
let(:enabled_percentage) { 10 }
+ let(:rollout_strategy) { nil }
controller(ApplicationController) do
include Gitlab::Experimentation::ControllerConcern
@@ -117,6 +122,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end
context 'when subject is given' do
+ let(:rollout_strategy) { :user }
let(:user) { build(:user) }
it 'uses the subject' do
@@ -244,6 +250,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
it "provides the subject's hashed global_id as label" do
experiment_subject = double(:subject, to_global_id: 'abc')
+ allow(Gitlab::Experimentation).to receive(:valid_subject_for_rollout_strategy?).and_return(true)
controller.track_experiment_event(:test_experiment, 'start', 1, subject: experiment_subject)
@@ -420,6 +427,26 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
controller.record_experiment_user(:test_experiment, context)
end
+
+ context 'with a cookie based rollout strategy' do
+ it 'calls tracking_group with a nil subject' do
+ expect(controller).to receive(:tracking_group).with(:test_experiment, nil, subject: nil).and_return(:experimental)
+ allow(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user, context)
+
+ controller.record_experiment_user(:test_experiment, context)
+ end
+ end
+
+ context 'with a user based rollout strategy' do
+ let(:rollout_strategy) { :user }
+
+ it 'calls tracking_group with a user subject' do
+ expect(controller).to receive(:tracking_group).with(:test_experiment, nil, subject: user).and_return(:experimental)
+ allow(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user, context)
+
+ controller.record_experiment_user(:test_experiment, context)
+ end
+ end
end
context 'the user is part of the control group' do
diff --git a/spec/lib/gitlab/experimentation/experiment_spec.rb b/spec/lib/gitlab/experimentation/experiment_spec.rb
index 008e6699597..94dbf1d7e4b 100644
--- a/spec/lib/gitlab/experimentation/experiment_spec.rb
+++ b/spec/lib/gitlab/experimentation/experiment_spec.rb
@@ -9,7 +9,8 @@ RSpec.describe Gitlab::Experimentation::Experiment do
let(:params) do
{
tracking_category: 'Category1',
- use_backwards_compatible_subject_index: true
+ use_backwards_compatible_subject_index: true,
+ rollout_strategy: nil
}
end
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index b503960b8c7..726d2380446 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -27,6 +27,8 @@ RSpec.describe Gitlab::Experimentation::EXPERIMENTS do
end
RSpec.describe Gitlab::Experimentation do
+ using RSpec::Parameterized::TableSyntax
+
before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', {
backwards_compatible_test_experiment: {
@@ -35,6 +37,10 @@ RSpec.describe Gitlab::Experimentation do
},
test_experiment: {
tracking_category: 'Team'
+ },
+ tabular_experiment: {
+ tracking_category: 'Team',
+ rollout_strategy: rollout_strategy
}
})
@@ -46,6 +52,7 @@ RSpec.describe Gitlab::Experimentation do
end
let(:enabled_percentage) { 10 }
+ let(:rollout_strategy) { nil }
describe '.get_experiment' do
subject { described_class.get_experiment(:test_experiment) }
@@ -175,4 +182,59 @@ RSpec.describe Gitlab::Experimentation do
end
end
end
+
+ describe '.log_invalid_rollout' do
+ subject { described_class.log_invalid_rollout(:test_experiment, 1) }
+
+ before do
+ allow(described_class).to receive(:valid_subject_for_rollout_strategy?).and_return(valid)
+ end
+
+ context 'subject is not valid for experiment' do
+ let(:valid) { false }
+
+ it 'logs a warning message' do
+ expect_next_instance_of(Gitlab::ExperimentationLogger) do |logger|
+ expect(logger)
+ .to receive(:warn)
+ .with(
+ message: 'Subject must conform to the rollout strategy',
+ experiment_key: :test_experiment,
+ subject: 'Integer',
+ rollout_strategy: :cookie
+ )
+ end
+
+ subject
+ end
+ end
+
+ context 'subject is valid for experiment' do
+ let(:valid) { true }
+
+ it 'does not log a warning message' do
+ expect(Gitlab::ExperimentationLogger).not_to receive(:build)
+
+ subject
+ end
+ end
+ end
+
+ describe '.valid_subject_for_rollout_strategy?' do
+ subject { described_class.valid_subject_for_rollout_strategy?(:tabular_experiment, experiment_subject) }
+
+ where(:rollout_strategy, :experiment_subject, :result) do
+ :cookie | nil | true
+ nil | nil | true
+ :cookie | 'string' | true
+ nil | User.new | false
+ :user | User.new | true
+ :group | User.new | false
+ :group | Group.new | true
+ end
+
+ with_them do
+ it { is_expected.to be(result) }
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index f5e824bb066..e7943a99877 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1261,26 +1261,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
pipeline.send(event)
end
-
- context 'the feature is disabled' do
- it 'does not trigger a worker' do
- stub_feature_flags(jira_sync_builds: false)
-
- expect(worker).not_to receive(:perform_async)
-
- pipeline.send(event)
- end
- end
-
- context 'the feature is enabled for this project' do
- it 'does trigger a worker' do
- stub_feature_flags(jira_sync_builds: pipeline.project)
-
- expect(worker).to receive(:perform_async)
-
- pipeline.send(event)
- end
- end
end
end
end
diff --git a/spec/rubocop/cop/gitlab/namespaced_class_spec.rb b/spec/rubocop/cop/gitlab/namespaced_class_spec.rb
new file mode 100644
index 00000000000..d1f61aa5afb
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/namespaced_class_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/namespaced_class'
+
+RSpec.describe RuboCop::Cop::Gitlab::NamespacedClass do
+ subject(:cop) { described_class.new }
+
+ it 'flags a class definition without namespace' do
+ expect_offense(<<~SOURCE)
+ class MyClass
+ ^^^^^^^^^^^^^ #{described_class::MSG}
+ end
+ SOURCE
+ end
+
+ it 'flags a class definition with inheritance without namespace' do
+ expect_offense(<<~SOURCE)
+ class MyClass < ApplicationRecord
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
+ def some_method
+ true
+ end
+ end
+ SOURCE
+ end
+
+ it 'does not flag the class definition with namespace in separate lines' do
+ expect_no_offenses(<<~SOURCE)
+ module MyModule
+ class MyClass < ApplicationRecord
+ end
+
+ class MyOtherClass
+ def other_method
+ 1 + 1
+ end
+ end
+ end
+ SOURCE
+ end
+
+ it 'does not flag the class definition with nested namespace in separate lines' do
+ expect_no_offenses(<<~SOURCE)
+ module TopLevelModule
+ module NestedModule
+ class MyClass
+ end
+ end
+ end
+ SOURCE
+ end
+
+ it 'does not flag the class definition nested inside namespaced class' do
+ expect_no_offenses(<<~SOURCE)
+ module TopLevelModule
+ class TopLevelClass
+ class MyClass
+ end
+ end
+ end
+ SOURCE
+ end
+
+ it 'does not flag a compact namespaced class definition' do
+ expect_no_offenses(<<~SOURCE)
+ class MyModule::MyClass < ApplicationRecord
+ end
+ SOURCE
+ end
+end
diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb
index 2d157c9d114..0bb5949ddb1 100644
--- a/spec/services/deployments/create_service_spec.rb
+++ b/spec/services/deployments/create_service_spec.rb
@@ -41,6 +41,27 @@ RSpec.describe Deployments::CreateService do
expect(service.execute).to be_persisted
end
+
+ context 'when the last deployment has the same parameters' do
+ let(:params) do
+ {
+ sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
+ ref: 'master',
+ tag: false,
+ status: 'success'
+ }
+ end
+
+ it 'does not create a new deployment' do
+ described_class.new(environment, user, params).execute
+
+ expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
+ expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
+ expect(Deployments::ExecuteHooksWorker).not_to receive(:perform_async)
+
+ described_class.new(environment.reload, user, params).execute
+ end
+ end
end
describe '#deployment_attributes' do
diff --git a/spec/views/groups/show.html.haml_spec.rb b/spec/views/groups/show.html.haml_spec.rb
new file mode 100644
index 00000000000..a53aab43c18
--- /dev/null
+++ b/spec/views/groups/show.html.haml_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'groups/show.html.haml' do
+ let_it_be(:user) { build(:user) }
+ let_it_be(:group) { create(:group) }
+
+ before do
+ assign(:group, group)
+ end
+
+ context 'when rendering with the layout' do
+ subject(:render_page) { render template: 'groups/show.html.haml', layout: 'layouts/group' }
+
+ describe 'invite team members' do
+ before do
+ allow(view).to receive(:session).and_return({})
+ allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:experiment_enabled?).and_return(false)
+ allow(view).to receive(:group_path).and_return('')
+ allow(view).to receive(:group_shared_path).and_return('')
+ allow(view).to receive(:group_archived_path).and_return('')
+ end
+
+ context 'when invite team members is not available in sidebar' do
+ before do
+ allow(view).to receive(:can_invite_members_for_group?).and_return(false)
+ end
+
+ it 'does not display the js-invite-members-trigger' do
+ render_page
+
+ expect(rendered).not_to have_selector('.js-invite-members-trigger')
+ end
+ end
+
+ context 'when invite team members is available' do
+ before do
+ allow(view).to receive(:can_invite_members_for_group?).and_return(true)
+ end
+
+ it 'includes the div for js-invite-members-trigger' do
+ render_page
+
+ expect(rendered).to have_selector('.js-invite-members-trigger')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index 01892e72c97..80342cbdb41 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'layouts/header/_new_dropdown' do
before do
allow(Gitlab::Experimentation).to receive(:active?).and_return(true)
allow(view).to receive(:experiment_tracking_category_and_group)
- allow(view).to receive(:tracking_label).with(user)
+ allow(view).to receive(:tracking_label)
end
context 'with ability to invite members' do
@@ -20,8 +20,8 @@ RSpec.describe 'layouts/header/_new_dropdown' do
subject
expect(view).to have_received(:experiment_tracking_category_and_group)
- .with(:invite_members_new_dropdown, subject: user)
- expect(view).to have_received(:tracking_label).with(user)
+ .with(:invite_members_new_dropdown)
+ expect(view).to have_received(:tracking_label)
end
end
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index c5b56b15431..e34d8b91b38 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
describe 'Packages' do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let_it_be(:package_menu_name) { 'Packages & Registries' }
let_it_be(:package_entry_name) { 'Package Registry' }
diff --git a/spec/views/projects/empty.html.haml_spec.rb b/spec/views/projects/empty.html.haml_spec.rb
index de83722160e..6762dcd22d5 100644
--- a/spec/views/projects/empty.html.haml_spec.rb
+++ b/spec/views/projects/empty.html.haml_spec.rb
@@ -79,4 +79,41 @@ RSpec.describe 'projects/empty' do
it_behaves_like 'no invite member info'
end
end
+
+ context 'when rendering with the layout' do
+ subject(:render_page) { render template: 'projects/empty.html.haml', layout: 'layouts/project' }
+
+ describe 'invite team members' do
+ before do
+ allow(view).to receive(:session).and_return({})
+ allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:experiment_enabled?).and_return(false)
+ end
+
+ context 'when invite team members is not available in sidebar' do
+ before do
+ allow(view).to receive(:can_invite_members_for_project?).and_return(false)
+ end
+
+ it 'does not display the js-invite-members-trigger' do
+ render_page
+
+ expect(rendered).not_to have_selector('.js-invite-members-trigger')
+ end
+ end
+
+ context 'when invite team members is available' do
+ before do
+ allow(view).to receive(:can_invite_members_for_project?).and_return(true)
+ end
+
+ it 'includes the div for js-invite-members-trigger' do
+ render_page
+
+ expect(rendered).to have_selector('.js-invite-members-trigger')
+ end
+ end
+ end
+ end
end
diff --git a/spec/views/projects/show.html.haml_spec.rb b/spec/views/projects/show.html.haml_spec.rb
new file mode 100644
index 00000000000..995e31e83af
--- /dev/null
+++ b/spec/views/projects/show.html.haml_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'projects/show.html.haml' do
+ let_it_be(:user) { build(:user) }
+ let_it_be(:project) { ProjectPresenter.new(create(:project, :repository), current_user: user) }
+
+ before do
+ assign(:project, project)
+ end
+
+ context 'when rendering with the layout' do
+ subject(:render_page) { render template: 'projects/show.html.haml', layout: 'layouts/project' }
+
+ describe 'invite team members' do
+ before do
+ allow(view).to receive(:event_filter_link)
+ allow(view).to receive(:session).and_return({})
+ allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:experiment_enabled?).and_return(false)
+ allow(view).to receive(:add_page_startup_graphql_call)
+ end
+
+ context 'when invite team members is not available in sidebar' do
+ before do
+ allow(view).to receive(:can_invite_members_for_project?).and_return(false)
+ end
+
+ it 'does not display the js-invite-members-trigger' do
+ render_page
+
+ expect(rendered).not_to have_selector('.js-invite-members-trigger')
+ end
+ end
+
+ context 'when invite team members is available' do
+ before do
+ allow(view).to receive(:can_invite_members_for_project?).and_return(true)
+ end
+
+ it 'includes the div for js-invite-members-trigger' do
+ render_page
+
+ expect(rendered).to have_selector('.js-invite-members-trigger')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/workers/jira_connect/sync_builds_worker_spec.rb b/spec/workers/jira_connect/sync_builds_worker_spec.rb
index 7c58803d778..8fb8692fdf7 100644
--- a/spec/workers/jira_connect/sync_builds_worker_spec.rb
+++ b/spec/workers/jira_connect/sync_builds_worker_spec.rb
@@ -32,29 +32,5 @@ RSpec.describe ::JiraConnect::SyncBuildsWorker do
subject
end
end
-
- context 'when the feature flag is disabled' do
- before do
- stub_feature_flags(jira_sync_builds: false)
- end
-
- it 'does not call the sync service' do
- expect_next(::JiraConnect::SyncService).not_to receive(:execute)
-
- subject
- end
- end
-
- context 'when the feature flag is enabled for this project' do
- before do
- stub_feature_flags(jira_sync_builds: pipeline.project)
- end
-
- it 'calls the sync service' do
- expect_next(::JiraConnect::SyncService).to receive(:execute)
-
- subject
- end
- end
end
end
diff --git a/yarn.lock b/yarn.lock
index e21ccf83cc9..a075c24abb5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8424,7 +8424,7 @@ mkdirp@1.x, mkdirp@^1.0.4, mkdirp@~1.0.3:
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
version "0.5.1"
- resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"